From 265285e8928c8f7680caa96239847c13246b437a Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 4 Feb 2017 22:23:02 -0700 Subject: [PATCH] Future proof for java.xml.bind removal from Java 9 default classpath JEP-201 describes the new modularized Java Standard Library in Java 9. By default, java.xml.bind is no longer on the default classpath; it needs to be added explicitly with a JVM option `--add-modules java.xml.bind`, or with a dependency declaration in the module-info.java file if you package your own code up as a Jigsaw module. This commit traps the linkage error and (reflectively) uses the java.util.Base64, which is the recommended way to encode/decode since 1.8. --- .../src/main/scala/sbt/inc/Base64.scala | 61 +++++++++++++++++++ .../scala/sbt/inc/TextAnalysisFormat.scala | 5 +- 2 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 compile/persist/src/main/scala/sbt/inc/Base64.scala diff --git a/compile/persist/src/main/scala/sbt/inc/Base64.scala b/compile/persist/src/main/scala/sbt/inc/Base64.scala new file mode 100644 index 000000000..21001a2a4 --- /dev/null +++ b/compile/persist/src/main/scala/sbt/inc/Base64.scala @@ -0,0 +1,61 @@ +package sbt +package inc + +import java.lang.reflect.InvocationTargetException +import javax.xml.bind.DatatypeConverter + +private[sbt] trait Base64 { + def encode(bytes: Array[Byte]): String + + def decode(string: String): Array[Byte] +} + +private[sbt] object Base64 { + lazy val factory: () => Base64 = { + try { + new Java678Encoder().encode(Array[Byte]()) + () => new Java678Encoder() + } catch { + case _: LinkageError => + () => new Java89Encoder + } + } +} + +private[sbt] class Java678Encoder extends Base64 { + def encode(bytes: Array[Byte]): String = DatatypeConverter.printBase64Binary(bytes) + + def decode(string: String): Array[Byte] = DatatypeConverter.parseBase64Binary(string) +} + +private[sbt] object Java89Encoder { + + import scala.runtime.ScalaRunTime.ensureAccessible + + private val Base64_class = Class.forName("java.util.Base64") + private val Base64_getEncoder = ensureAccessible(Base64_class.getMethod("getEncoder")) + private val Base64_getDecoder = ensureAccessible(Base64_class.getMethod("getEncoder")) + private val Base64_Encoder_class = Class.forName("java.util.Base64$Encoder") + private val Base64_Decoder_class = Class.forName("java.util.Base64$Decoder") + private val Base64_Encoder_encodeToString = ensureAccessible(Base64_Encoder_class.getMethod("encodeToString", classOf[Array[Byte]])) + private val Base64_Decoder_decode = ensureAccessible(Base64_Decoder_class.getMethod("decode", classOf[String])) +} + +private[sbt] class Java89Encoder extends Base64 { + + import Java89Encoder._ + + def encode(bytes: Array[Byte]): String = try { + val encoder = Base64_getEncoder.invoke(null) + Base64_Encoder_encodeToString.invoke(encoder, bytes).asInstanceOf[String] + } catch { + case ex: InvocationTargetException => throw ex.getCause + } + + def decode(string: String): Array[Byte] = try { + val decoder = Base64_getDecoder.invoke(null) + Base64_Decoder_decode.invoke(decoder, string).asInstanceOf[Array[Byte]] + } catch { + case ex: InvocationTargetException => throw ex.getCause + } +} \ No newline at end of file diff --git a/compile/persist/src/main/scala/sbt/inc/TextAnalysisFormat.scala b/compile/persist/src/main/scala/sbt/inc/TextAnalysisFormat.scala index 74221a9c6..a03edcce4 100644 --- a/compile/persist/src/main/scala/sbt/inc/TextAnalysisFormat.scala +++ b/compile/persist/src/main/scala/sbt/inc/TextAnalysisFormat.scala @@ -5,7 +5,6 @@ import java.io._ import sbt.{ CompileSetup, Relation } import xsbti.api.{ Compilation, Source } import xsbti.compile.{ MultipleOutput, SingleOutput } -import javax.xml.bind.DatatypeConverter // Very simple timer for timing repeated code sections. // TODO: Temporary. Remove once we've milked all available performance gains. @@ -328,11 +327,11 @@ object TextAnalysisFormat { val out = new sbinary.JavaOutput(baos) FormatTimer.aggregate("sbinary write") { try { fmt.writes(out, o) } finally { baos.close() } } val bytes = FormatTimer.aggregate("byte copy") { baos.toByteArray } - FormatTimer.aggregate("bytes -> base64") { DatatypeConverter.printBase64Binary(bytes) } + FormatTimer.aggregate("bytes -> base64") { Base64.factory().encode(bytes) } } def stringToObj[T](s: String)(implicit fmt: sbinary.Format[T]) = { - val bytes = FormatTimer.aggregate("base64 -> bytes") { DatatypeConverter.parseBase64Binary(s) } + val bytes = FormatTimer.aggregate("base64 -> bytes") { Base64.factory().decode(s) } val in = new sbinary.JavaInput(new ByteArrayInputStream(bytes)) FormatTimer.aggregate("sbinary read") { fmt.reads(in) } }