All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sbtbuildinfo.JavaRenderer.scala Maven / Gradle / Ivy

package sbtbuildinfo

import PluginCompat.TypeExpression

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.manifest)
      .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.URI(urlString).toURL();
      |    } catch (Exception e) {
      |      return null;
      |    }
      |  }
      |""".stripMargin

  protected val buildMapLines: String =
    """  @SuppressWarnings({"unchecked", "rawtypes"})
      |  private static  java.util.Map internalAsMap(java.util.Map.Entry... entries) {
      |    java.util.Map 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.manifest).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 ${methodPrefix}Map() {",
        "    java.util.Map m = new java.util.HashMap<>();"
      ) ++
        results
          .filter(v => getJavaType(v.manifest).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) value)
           |          .stream().map($cl::toJsonValue).collect(java.util.stream.Collectors.joining(",", "[", "]"));
           |    } else if (value instanceof java.util.Optional) {
           |      return ((java.util.Optional) value).map($cl::toJsonValue).orElse("null");
           |    } else if (value instanceof java.util.Map) {
           |      return ((java.util.Map) 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(m: PluginCompat.Manifest[?]): Option[String] = {
    def tpeToReturnType(tpe: PluginCompat.Manifest[?]): Option[String] =
      tpe match {
        case TypeExpression("Any", Nil)                       => None
        case TypeExpression("Short" | "scala.Short", Nil)     => Some("Short")
        case TypeExpression("Int" | "scala.Int", Nil)         => Some("Integer")
        case TypeExpression("Long" | "scala.Long", Nil)       => Some("Long")
        case TypeExpression("Double" | "scala.Double", Nil)   => Some("Double")
        case TypeExpression("Float" | "scala.Float", Nil)     => Some("Float")
        case TypeExpression("Boolean" | "scala.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("xsbti.HashedVirtualFileRef", Nil) => Some("String")
        case TypeExpression("xsbti.VirtualFileRef", Nil) => Some("String")
        case TypeExpression("xsbti.VirtualFile", 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("sbt.internal.util.Attributed", Seq(TypeExpression("xsbti.HashedVirtualFileRef", Nil))) => Some("String")

        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 TypeExpression("java.time.LocalDate", Nil) => Some("java.time.LocalDate")
        case TypeExpression("java.time.Instant", Nil) => Some("java.time.Instant")

        case _ =>
          // println(s"java other: $tpe")
          None
      }
    tpeToReturnType(m)
  }

  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 date: java.time.LocalDate  => "java.time.LocalDate.parse(%s)" format quote(date.toString)
    case instant: java.time.Instant => "java.time.Instant.parse(%s)" format quote(instant.toString)
    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("\"", "\\\"")

}