Skip to content

Commit

Permalink
Java Renderers (code generators for Java only sbt projects)
Browse files Browse the repository at this point in the history
  • Loading branch information
arixmkii committed Jan 17, 2021
1 parent 2f52495 commit 8f25db0
Show file tree
Hide file tree
Showing 10 changed files with 566 additions and 1 deletion.
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

0 comments on commit 8f25db0

Please sign in to comment.