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.
This commit is contained in:
Jason Zaugg 2017-02-04 22:23:02 -07:00
parent 3ad3f87212
commit 265285e892
2 changed files with 63 additions and 3 deletions

View File

@ -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
}
}

View File

@ -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) }
}