Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Java Renderers (code generators for Java only sbt projects) #167

Merged
merged 2 commits into from
Jan 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ lazy val root = (project in file("."))
description := "sbt plugin to generate build info",
homepage := Some(url("https://github.com/sbt/sbt-buildinfo")),
licenses := Seq("MIT License" -> url("https://github.com/sbt/sbt-buildinfo/blob/master/LICENSE")),
scriptedLaunchOpts ++= Seq("-Xmx1024M", "-Dplugin.version=" + version.value),
scriptedLaunchOpts ++= Seq("-Xmx1024M", "-Xss4M", "-Dplugin.version=" + version.value),
scriptedBufferLog := false,
publishTo := (bintray / publishTo).value,
publishMavenStyle := false,
Expand Down
208 changes: 208 additions & 0 deletions src/main/scala/sbtbuildinfo/JavaRenderer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package sbtbuildinfo

abstract class JavaRenderer(pkg: String, cl: String, makeStatic: Boolean) extends BuildInfoRenderer {

override def fileType = BuildInfoType.Source
override def extension = "java"

def header = List(
"// $COVERAGE-OFF$",
s"package $pkg;",
"",
s"/** This file was generated by sbt-buildinfo. */",
s"public final class $cl {",
s" private $cl() {}",
""
)

protected def footer = List("}", "// $COVERAGE-ON$")

protected def line(result: BuildInfoResult): Seq[String] = {
import result._
val mod = if (makeStatic) " static" else ""
getJavaType(result.typeExpr)
.map(typeDecl =>
List(
s" /** The value is ${quote(value)}. */",
s" public$mod final $typeDecl $identifier = ${quote(value)};"
)
)
.getOrElse(List.empty)
}

protected val buildUrlLines: String =
""" private static java.net.URL internalAsUrl(String urlString) {
| try {
| return new java.net.URL(urlString);
| } catch (Exception e) {
| return null;
| }
| }
|""".stripMargin

protected val buildMapLines: String =
""" @SuppressWarnings({"unchecked", "rawtypes"})
| private static <K, V> java.util.Map<K, V> internalAsMap(java.util.Map.Entry... entries) {
| java.util.Map<K, V> m = new java.util.HashMap<>();
| for (java.util.Map.Entry e : entries) {
| m.put((K) e.getKey(), (V) e.getValue());
| }
| return java.util.Collections.unmodifiableMap(m);
| }
|""".stripMargin

protected def toStringLines(results: Seq[BuildInfoResult]): String = {
val mod = if (makeStatic) " static" else ""
val methodPrefix = if (makeStatic) "make" else "to"
val idents = results.filter(v => getJavaType(v.typeExpr).isDefined).map(_.identifier)
val fmt = idents.map("%s: %%s" format _).mkString(", ")
val vars = idents.mkString(", ")
s"""
| public$mod String ${methodPrefix}String() {
| return String.format("$fmt",
| $vars
| );
| }
|""".stripMargin
}

protected def toMapLines(results: Seq[BuildInfoResult]): Seq[String] =
if (options.contains(BuildInfoOption.ToMap) || options.contains(BuildInfoOption.ToJson)) {
val mod = if (makeStatic) " static" else ""
val methodPrefix = if (makeStatic) "make" else "to"
List(
s" public$mod java.util.Map<String, Object> ${methodPrefix}Map() {",
" java.util.Map<String, Object> m = new java.util.HashMap<>();"
) ++
results
.filter(v => getJavaType(v.typeExpr).isDefined)
.map(result => " m.put(\"%s\", %s);".format(result.identifier, result.identifier)) ++
List(
" return java.util.Collections.unmodifiableMap(m);",
" }",
""
)
} else Nil

protected def buildJsonLines: Seq[String] =
if (options contains BuildInfoOption.ToJson)
List(
s""" private static String quote(Object x) {
| return "\\"" + x + "\\"";
| }
|
| @SuppressWarnings({"unchecked"})
| private static String toJsonValue(Object value) {
| if (value instanceof java.util.Collection) {
| return ((java.util.Collection<Object>) value)
| .stream().map($cl::toJsonValue).collect(java.util.stream.Collectors.joining(",", "[", "]"));
| } else if (value instanceof java.util.Optional) {
| return ((java.util.Optional<Object>) value).map($cl::toJsonValue).orElse("null");
| } else if (value instanceof java.util.Map) {
| return ((java.util.Map<Object, Object>) value)
| .entrySet().stream()
| .map(e -> toJsonValue(e.getKey().toString()) + ":" + toJsonValue(e.getValue()))
| .collect(java.util.stream.Collectors.joining(", ", "{", "}"));
| } else if (value instanceof Double) {
| return value.toString();
| } else if (value instanceof Float) {
| return value.toString();
| } else if (value instanceof Long) {
| return value.toString();
| } else if (value instanceof Integer) {
| return value.toString();
| } else if (value instanceof Short) {
| return value.toString();
| } else if (value instanceof Boolean) {
| return value.toString();
| } else if (value instanceof String) {
| return quote(value);
| } else {
| return quote(value.toString());
| }
| }
|""".stripMargin
)
else
Nil

protected def toJsonLines: Seq[String] =
if (options contains BuildInfoOption.ToJson) {
val mod = if (makeStatic) " static" else ""
val methodPrefix = if (makeStatic) "make" else "to"
List(
s""" public$mod final String ${methodPrefix}Json = toJsonValue(${methodPrefix}Map());""".stripMargin
)
} else
Nil

protected def getJavaType(typeExpr: TypeExpression): Option[String] = {
def tpeToReturnType(tpe: TypeExpression): Option[String] =
tpe match {
case TypeExpression("Any", Nil) => None
case TypeExpression("Short", Nil) => Some("Short")
case TypeExpression("Int", Nil) => Some("Integer")
case TypeExpression("Long", Nil) => Some("Long")
case TypeExpression("Double", Nil) => Some("Double")
case TypeExpression("Float", Nil) => Some("Float")
case TypeExpression("Boolean", Nil) => Some("Boolean")
case TypeExpression("scala.Symbol", Nil) => Some("String")
case TypeExpression("java.lang.String", Nil) => Some("String")
case TypeExpression("java.net.URL", Nil) => Some("java.net.URL")
case TypeExpression("sbt.URL", Nil) => Some("java.net.URL")
case TypeExpression("java.io.File", Nil) => Some("java.io.File")
case TypeExpression("sbt.File", Nil) => Some("java.io.File")
case TypeExpression("scala.xml.NodeSeq", Nil) => None

case TypeExpression("sbt.ModuleID", Nil) => Some("String")
case TypeExpression("sbt.Resolver", Nil) => Some("String")

case TypeExpression("sbt.librarymanagement.ModuleID", Nil) => Some("String")
case TypeExpression("sbt.librarymanagement.Resolver", Nil) => Some("String")

case TypeExpression("sbt.internal.util.Attributed", Seq(TypeExpression("java.io.File", Nil))) =>
Some("java.io.File")

case TypeExpression("scala.Option", Seq(arg)) =>
tpeToReturnType(arg) map { x => s"java.util.Optional<$x>" }
case TypeExpression("scala.collection.Seq" | "scala.collection.immutable.Seq", Seq(arg)) =>
tpeToReturnType(arg) map { x => s"java.util.Collection<$x>" }
case TypeExpression("scala.collection.immutable.Map", Seq(arg0, arg1)) =>
for {
x0 <- tpeToReturnType(arg0)
x1 <- tpeToReturnType(arg1)
} yield s"java.util.Map<$x0, $x1>"
case TypeExpression("scala.Tuple2", Seq(arg0, arg1)) =>
for {
x0 <- tpeToReturnType(arg0)
x1 <- tpeToReturnType(arg1)
} yield s"java.util.Map.Entry<$x0, $x1>"
case _ => None
}
tpeToReturnType(typeExpr)
}

protected def quote(v: Any): String = v match {
case x @ (_: Int | _: Short | _: Double | _: Boolean) => x.toString
case x: Float => x.toString + "f"
case x: Symbol => s"""("${x.name}").intern()"""
case x: Long => x.toString + "L"
case node: scala.xml.NodeSeq if node.toString().trim.nonEmpty => node.toString()
case node: scala.xml.NodeSeq => scala.xml.NodeSeq.Empty.toString()
case (k, _v) => "new java.util.AbstractMap.SimpleImmutableEntry<>(%s, %s)" format (quote(k), quote(_v))
case mp: Map[_, _] => mp.toList.map(quote(_)).mkString("internalAsMap(", ", ", ")")
case seq: collection.Seq[_] =>
seq.map(quote).mkString("java.util.Collections.unmodifiableList(java.util.Arrays.asList(", ", ", "))")
case op: Option[_] =>
op map { x => "java.util.Optional.of(" + quote(x) + ")" } getOrElse { "java.util.Optional.empty()" }
case url: java.net.URL => "internalAsUrl(%s)" format quote(url.toString)
case file: java.io.File => "new java.io.File(%s)" format quote(file.toString)
case attr: sbt.Attributed[_] => quote(attr.data)
case s => "\"%s\"" format encodeStringLiteral(s.toString)
}

protected def encodeStringLiteral(str: String): String =
str.replace("\\", "\\\\").replace("\n", "\\n").replace("\b", "\\b").replace("\r", "\\r")
.replace("\t", "\\t").replace("\'", "\\'").replace("\f", "\\f").replace("\"", "\\\"")

}
22 changes: 22 additions & 0 deletions src/main/scala/sbtbuildinfo/JavaSingletonRenderer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package sbtbuildinfo

case class JavaSingletonRenderer(options: Seq[BuildInfoOption], pkg: String, cl: String) extends JavaRenderer(pkg, cl, false) {

override def renderKeys(buildInfoResults: Seq[BuildInfoResult]): Seq[String] =
header ++
instanceLine ++
buildInfoResults.flatMap(line) ++
Seq(toStringLines(buildInfoResults)) ++
toMapLines(buildInfoResults) ++
Seq(buildUrlLines, buildMapLines) ++
buildJsonLines ++
toJsonLines ++
footer

private def instanceLine: Seq[String] =
List(
s" public static final $cl instance = new $cl();",
""
)

}
15 changes: 15 additions & 0 deletions src/main/scala/sbtbuildinfo/JavaStaticFieldsRenderer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package sbtbuildinfo

case class JavaStaticFieldsRenderer(options: Seq[BuildInfoOption], pkg: String, cl: String) extends JavaRenderer(pkg, cl, true) {

override def renderKeys(buildInfoResults: Seq[BuildInfoResult]): Seq[String] =
header ++
buildInfoResults.flatMap(line) ++
Seq(toStringLines(buildInfoResults)) ++
toMapLines(buildInfoResults) ++
Seq(buildUrlLines, buildMapLines) ++
buildJsonLines ++
toJsonLines ++
footer

}
2 changes: 1 addition & 1 deletion src/main/scala/sbtbuildinfo/ScalaCaseObjectRenderer.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package sbtbuildinfo

private[sbtbuildinfo] case class ScalaCaseObjectRenderer(options: Seq[BuildInfoOption], pkg: String, obj: String) extends ScalaRenderer {
case class ScalaCaseObjectRenderer(options: Seq[BuildInfoOption], pkg: String, obj: String) extends ScalaRenderer {

override def fileType = BuildInfoType.Source
override def extension = "scala"
Expand Down
Loading