
com.dslplatform.json.runtime.ScalaClassAnalyzer.scala Maven / Gradle / Ivy
The newest version!
package com.dslplatform.json
package runtime
import java.lang.reflect.{Constructor, Method, Modifier, ParameterizedType, Type => JavaType}
import scala.collection.mutable
import scala.reflect.runtime.universe
import scala.util.Try
object ScalaClassAnalyzer {
val Reader: DslJson.ConverterFactory[JsonReader.ReadObject[_]] = (manifest: JavaType, dslJson: DslJson[_]) => {
manifest match {
case cl: Class[_] => analyze(manifest, cl, dslJson, reading = true) match {
case Some(Left(fd)) => fd
case Some(Right(id)) => id
case _ => null
}
case pt: ParameterizedType =>
pt.getRawType match {
case rc: Class[_] =>
analyze(manifest, rc, dslJson, reading = true) match {
case Some(Left(fd)) => fd
case Some(Right(id)) => id
case _ => null
}
case _ => null
}
case _ => null
}
}
val Binder: DslJson.ConverterFactory[JsonReader.BindObject[_]] = (manifest: JavaType, dslJson: DslJson[_]) => {
manifest match {
case cl: Class[_] => analyze(manifest, cl, dslJson, reading = true) match {
case Some(Left(fd)) => fd
case _ => null
}
case pt: ParameterizedType =>
pt.getRawType match {
case rc: Class[_] => analyze(manifest, rc, dslJson, reading = false) match {
case Some(Left(fd)) => fd
case _ => null
}
case _ => null
}
case _ => null
}
}
val Writer: DslJson.ConverterFactory[JsonWriter.WriteObject[_]] = (manifest: JavaType, dslJson: DslJson[_]) => {
manifest match {
case cl: Class[_] => analyze(manifest, cl, dslJson, reading = false) match {
case Some(Left(fd)) => fd
case Some(Right(id)) => id
case _ => null
}
case pt: ParameterizedType =>
pt.getRawType match {
case rc: Class[_] => analyze(manifest, rc, dslJson, reading = false) match {
case Some(Left(fd)) => fd
case Some(Right(id)) => id
case _ => null
}
case _ => null
}
case _ => null
}
}
private class LazyImmutableDescription(json: DslJson[_], manifest: JavaType) extends JsonWriter.WriteObject[AnyRef] with JsonReader.ReadObject[AnyRef] {
private var encoder: Option[JsonWriter.WriteObject[AnyRef]] = None
private var decoder: Option[JsonReader.ReadObject[AnyRef]] = None
var resolved: Option[ImmutableDescription[AnyRef]] = None
private def checkSignatureNotFound() = {
var i = 0
while (i < 50) {
try {
Thread.sleep(100)
} catch {
case e: InterruptedException => throw new ConfigurationException(e)
}
if (resolved.nonEmpty) {
encoder = Some(resolved.get.asInstanceOf[JsonWriter.WriteObject[AnyRef]])
decoder = Some(resolved.get.asInstanceOf[JsonReader.ReadObject[AnyRef]])
i = 50
}
i += 1
}
resolved.isEmpty
}
override def read(reader: JsonReader[_]): AnyRef = {
if (decoder.isEmpty) {
if (checkSignatureNotFound()) {
val tmp = json.tryFindReader(manifest).asInstanceOf[JsonReader.ReadObject[AnyRef]]
if (tmp == null || (tmp eq this)) throw new ConfigurationException(s"Unable to find reader for $manifest")
decoder = Some(tmp)
}
}
decoder.get.read(reader)
}
override def write(writer: JsonWriter, value: AnyRef): Unit = {
if (encoder.isEmpty) {
if (checkSignatureNotFound()) {
val tmp = json.tryFindWriter(manifest).asInstanceOf[JsonWriter.WriteObject[AnyRef]]
if (tmp == null || (tmp eq this)) throw new ConfigurationException(s"Unable to find writer for $manifest")
encoder = Some(tmp)
}
}
encoder.get.write(writer, value)
}
}
private class LazyObjectDescription(json: DslJson[_], manifest: JavaType) extends JsonWriter.WriteObject[AnyRef] with JsonReader.ReadObject[AnyRef] with JsonReader.BindObject[AnyRef] {
private var encoder: Option[JsonWriter.WriteObject[AnyRef]] = None
private var decoder: Option[JsonReader.ReadObject[AnyRef]] = None
private var binder: Option[JsonReader.BindObject[AnyRef]] = None
var resolved: Option[ObjectFormatDescription[AnyRef, AnyRef]] = None
private def checkSignatureNotFound() = {
var i = 0
while (i < 50) {
try {
Thread.sleep(100)
} catch {
case e: InterruptedException => throw new ConfigurationException(e)
}
if (resolved.nonEmpty) {
encoder = Some(resolved.get.asInstanceOf[JsonWriter.WriteObject[AnyRef]])
decoder = Some(resolved.get.asInstanceOf[JsonReader.ReadObject[AnyRef]])
binder = Some(resolved.get.asInstanceOf[JsonReader.BindObject[AnyRef]])
i = 50
}
i += 1
}
resolved.isEmpty
}
override def read(reader: JsonReader[_]): AnyRef = {
if (decoder.isEmpty) {
if (checkSignatureNotFound()) {
val tmp = json.tryFindReader(manifest).asInstanceOf[JsonReader.ReadObject[AnyRef]]
if (tmp == null || (tmp eq this)) throw new ConfigurationException(s"Unable to find reader for $manifest")
decoder = Some(tmp)
}
}
decoder.get.read(reader)
}
override def bind(reader: JsonReader[_], instance: AnyRef): AnyRef = {
if (binder.isEmpty) {
if (checkSignatureNotFound()) {
val tmp = json.tryFindBinder(manifest).asInstanceOf[JsonReader.BindObject[AnyRef]]
if (tmp == null || (tmp eq this)) throw new ConfigurationException(s"Unable to find binder for $manifest")
binder = Some(tmp)
}
}
binder.get.bind(reader, instance)
}
override def write(writer: JsonWriter, value: AnyRef): Unit = {
if (encoder.isEmpty) {
if (checkSignatureNotFound()) {
val tmp = json.tryFindWriter(manifest).asInstanceOf[JsonWriter.WriteObject[AnyRef]]
if (tmp == null || (tmp eq this)) throw new ConfigurationException(s"Unable to find writer for $manifest")
encoder = Some(tmp)
}
}
encoder.get.write(writer, value)
}
}
private case class TypeInfo(
name: String,
rawType: JavaType,
isUnknown: Boolean,
concreteType: JavaType,
index: Int,
getDefault: Option[() => AnyRef])
private def analyze(
manifest: JavaType,
raw: Class[_],
json: DslJson[_],
reading: Boolean
) = {
if (isSupported(manifest, raw)) {
val sc = scala.reflect.runtime.currentMirror.staticClass(raw.getTypeName)
analyzeType(manifest, raw, json, reading, sc.info)
} else {
None
}
}
def isSupported(manifest: JavaType, raw: Class[_]): Boolean = {
!(classOf[scala.collection.Traversable[_]].isAssignableFrom(raw) ||
classOf[AnyRef] == manifest ||
(raw.getModifiers & Modifier.ABSTRACT) != 0 ||
raw.isInterface ||
(raw.getDeclaringClass != null && (raw.getModifiers & Modifier.STATIC) == 0) ||
(raw.getModifiers & Modifier.PUBLIC) == 0)
}
def analyzeType(
manifest: JavaType,
raw: Class[_],
json: DslJson[_],
reading: Boolean,
tpe: universe.TypeApi
): Option[Either[ObjectFormatDescription[AnyRef, AnyRef], ImmutableDescription[AnyRef]]] = {
val ctors = raw.getDeclaredConstructors.filter(it => (it.getModifiers & Modifier.PUBLIC) == 1)
tpe.members.find(_.name.toString == "") match {
case Some(init) if init.info.paramLists.size == 1 && ctors.exists(_.getParameterCount == 0) =>
val methods = tpe.members.flatMap { it =>
if (it.isPublic && it.isMethod) {
val eqName = it.name.toString + "_$eq"
val setter = tpe.members.find(m => m.isPublic && m.name.toString == eqName)
setter.map { s => it -> s }
} else None
}.toMap
if (methods.nonEmpty) analyzeEmptyCtor(manifest, raw, json, methods, reading)
else None
case Some(init) if init.info.paramLists.size == 1 && ctors.length == 1 =>
analyzeClassWithCtor(manifest, raw, json, ctors, tpe, init.info.paramLists.head, reading)
case _ =>
None
}
}
private def analyzeClassWithCtor(
manifest: JavaType,
raw: Class[_],
json: DslJson[_],
ctors: Array[Constructor[_]],
tpe: universe.TypeApi,
params: List[universe.Symbol],
reading: Boolean
) = {
import scala.collection.JavaConverters._
val isProduct = classOf[Product].isAssignableFrom(raw)
val genericMappings = Generics.analyze(manifest, raw)
val genericsByName = genericMappings.entrySet().asScala.map(kv => kv.getKey.getTypeName -> kv.getValue).toMap
val defaults = tpe.companion.members.filter(_.name.toString.startsWith("$lessinit$greater$default$"))
val types = params.map(_.typeSignature).toSet
val sameTypes = tpe.members.filter { it =>
!it.name.toString.contains("$") && types.contains(it.typeSignature)
}
lazy val names = Option(ImmutableAnalyzer.extractNames(ctors.head))
val arguments = params.zipWithIndex.flatMap { case (p, i) =>
val defMethod = defaults.find(_.name.toString.endsWith("$" + (i + 1))).flatMap { d =>
val name = d.name.toString
raw.getDeclaredMethods.find(_.getName == name).map { m =>
() => m.invoke(null, Array():_*)
}
}
val pName = if (p.name.toString.contains("$")) names.map(it => it(i)) else Some(p.name.toString)
Try(TypeAnalysis.convertType(p.typeSignature, genericsByName)).toOption.flatMap { rt =>
val concreteType = Generics.makeConcrete(rt, genericMappings)
val isUnknown = Generics.isUnknownType(rt)
val matchingTypeAndName = {
if (pName.isEmpty) None
else sameTypes.find(it => it.typeSignature == p.typeSignature && it.name.toString.trim == pName.get)
}
lazy val matchingTypeOnly = {
if (sameTypes.size != params.size) None
else sameTypes.find(it => it.typeSignature == p.typeSignature)
}
val name = matchingTypeAndName.orElse(matchingTypeOnly).map(_.name.toString.trim).orElse(pName)
if (name.isEmpty || name.get.contains("$")) None
Some(TypeInfo(name.get, rt, isUnknown, concreteType, i, defMethod))
}
}
if (arguments.size == params.size) {
val tmp = new LazyImmutableDescription(json, manifest)
val oldWriter = json.registerWriter(manifest, tmp)
val oldReader = json.registerReader(manifest, tmp)
val writeProps = if (isProduct) {
arguments.map { ti =>
Settings.createEncoder(
new GetProductIndex(ti.index),
ti.name,
json,
if (ti.isUnknown) null else ti.concreteType).asInstanceOf[JsonWriter.WriteObject[_]]
}.toArray
} else {
arguments.flatMap { ti =>
raw.getMethods.find(it => it.getName == ti.name && it.getParameterCount == 0).map { m =>
Settings.createEncoder(
new Reflection.ReadMethod(m),
ti.name,
json,
if (ti.isUnknown) null else ti.concreteType)
}
}.toArray
}
val ctor = ctors.head.asInstanceOf[Constructor[AnyRef]]
val readProps = arguments.flatMap { ti =>
if (ti.isUnknown || json.tryFindWriter(ti.concreteType) != null && json.tryFindReader(ti.concreteType) != null) {
val isNullable = ti.rawType.getTypeName.startsWith("scala.Option<")
Some(new DecodePropertyInfo[JsonReader.ReadObject[_]](ti.name, false, ti.getDefault.isEmpty, ti.index, !isNullable, new WriteCtor(json, ti.concreteType, ctor)))
} else None
}.toArray
if (params.size == writeProps.length && (!reading || params.size == readProps.length)) {
val defArgs = new Array[AnyRef](params.size)
arguments.zipWithIndex.foreach { case (a, i) =>
if (a.getDefault.isDefined) {
//TODO: it would be more correct to apply this on every invocation, but lets just use stable value instead
defArgs(i) = a.getDefault.get.apply()
}
}
val converter = new ImmutableDescription[AnyRef](
manifest,
defArgs,
new Settings.Function[Array[AnyRef], AnyRef] {
override def apply(args: Array[AnyRef]): AnyRef = ctor.newInstance(args:_*)
},
writeProps,
readProps,
!json.omitDefaults,
true)
tmp.resolved = Some(converter)
json.registerWriter(manifest, converter)
//TODO: since nested case classes have their type signatures broken allow encoding,
//TODO: but don't allow decoding if some types are erased
if (params.size == readProps.length) {
json.registerReader(manifest, converter)
} else {
json.registerReader(manifest, oldReader)
}
Some(Right(converter))
} else {
json.registerWriter(manifest, oldWriter)
json.registerReader(manifest, oldReader)
None
}
} else None
}
private class GetProductIndex(index: Int) extends Settings.Function[Product, Any] {
override def apply(t: Product): Any = t.productElement(index)
}
private def analyzeEmptyCtor(
manifest: JavaType,
raw: Class[_],
json: DslJson[_],
methods: Map[universe.Symbol, universe.Symbol],
reading: Boolean
) = {
import scala.collection.JavaConverters._
val tmp = new LazyObjectDescription(json, manifest)
val oldWriter = json.registerWriter(manifest, tmp)
val oldReader = json.registerReader(manifest, tmp)
val foundWrite = new mutable.LinkedHashMap[String, JsonWriter.WriteObject[_]]
val foundRead = new mutable.LinkedHashMap[String, DecodePropertyInfo[JsonReader.BindObject[_]]]
val genericMappings = Generics.analyze(manifest, raw)
val genericsByName = genericMappings.entrySet().asScala.map(kv => kv.getKey.getTypeName -> kv.getValue).toMap
val rawAny = raw.asInstanceOf[Class[AnyRef]]
val newInstance = new InstanceFactory[AnyRef] {
override def create(): AnyRef = rawAny.newInstance()
}
var index = 0
val rawMethods = raw.getMethods
methods.foreach { case (g, _) =>
val gName = g.name.toString
rawMethods.find(m => m.getParameterCount == 0 && m.getName.equals(gName)).foreach { jm =>
Try(TypeAnalysis.convertType(g.typeSignature.resultType, genericsByName)).foreach { t =>
if (analyzeMethods(jm, t, raw, json, foundWrite, foundRead, index, genericMappings)) {
index += 1
}
}
}
}
if (foundWrite.size == methods.size && (!reading || foundRead.size == methods.size)) {
val writeProps = foundWrite.values.toArray
val readProps = foundRead.values.toArray
val converter = ObjectFormatDescription.create(rawAny, newInstance, writeProps, readProps, json, true)
tmp.resolved = Some(converter)
json.registerWriter(manifest, converter)
if (foundRead.size == methods.size) {
json.registerReader(manifest, converter)
json.registerBinder(manifest, converter)
} else {
json.registerReader(manifest, oldReader)
}
Some(Left(converter))
} else {
json.registerWriter(manifest, oldWriter)
json.registerReader(manifest, oldReader)
None
}
}
private final class WriteCtor(json: DslJson[_], manifest: JavaType, ctor: Constructor[_]) extends JsonReader.ReadObject[Any] {
private var decoder: Option[JsonReader.ReadObject[Any]] = None
override def read(reader: JsonReader[_]): Any = {
if (decoder.isEmpty) {
Option(json.tryFindReader(manifest)) match {
case Some(f: JsonReader.ReadObject[Any @unchecked]) => decoder = Some(f)
case _ => throw new ConfigurationException(s"Unable to find reader for $manifest on $ctor")
}
}
decoder.get.read(reader)
}
}
private def analyzeMethods(
mget: Method,
actualType: JavaType,
manifest: Class[_],
json: DslJson[_],
foundWrite: mutable.LinkedHashMap[String, JsonWriter.WriteObject[_]],
foundRead: mutable.LinkedHashMap[String, DecodePropertyInfo[JsonReader.BindObject[_]]],
index: Int,
genericMappings: java.util.HashMap[JavaType, JavaType]
): Boolean = {
if (mget.getParameterTypes.length != 0) false
else {
val name = mget.getName
val setName = name + "_$eq"
manifest.getMethods.find(_.getName == setName) match {
case Some(mset) if !canRead(mget.getModifiers) || !canWrite(mset.getModifiers) =>
false
case Some(_) if foundRead.contains(name) && foundWrite.contains(name) =>
false
case Some(mset) =>
val concreteType = Generics.makeConcrete(actualType, genericMappings)
val isUnknown = Generics.isUnknownType(actualType)
if (isUnknown || json.tryFindWriter(concreteType) != null) {
foundWrite.put(
name,
Settings.createEncoder(
new Reflection.ReadMethod(mget),
name,
json,
if (isUnknown) null else concreteType))
if (isUnknown || json.tryFindReader(concreteType) != null) {
foundRead.put(
name,
Settings.createDecoder(
new Reflection.SetMethod(mset),
name,
json,
false,
false,
index,
!concreteType.getTypeName.startsWith("scala.Option<"),
concreteType).asInstanceOf[DecodePropertyInfo[JsonReader.BindObject[_]]]
)
}
true
} else {
false
}
case _ =>
false
}
}
}
private def canRead(modifiers: Int) =
(modifiers & Modifier.PUBLIC) != 0 &&
(modifiers & Modifier.TRANSIENT) == 0 &&
(modifiers & Modifier.NATIVE) == 0 &&
(modifiers & Modifier.STATIC) == 0
private def canWrite(modifiers: Int) =
(modifiers & Modifier.PUBLIC) != 0 &&
(modifiers & Modifier.TRANSIENT) == 0 &&
(modifiers & Modifier.NATIVE) == 0 &&
(modifiers & Modifier.FINAL) == 0 &&
(modifiers & Modifier.STATIC) == 0
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy