Skip to content

Commit

Permalink
A bit cleaner schema extraction
Browse files Browse the repository at this point in the history
  • Loading branch information
johanandren committed Dec 11, 2024
1 parent 224f7c5 commit 1ca6276
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ private[impl] object ViewDescriptorFactory {
QueryStreamEffect[_]]}")

val inputType: Option[SpiType.QueryInput] =
method.getGenericParameterTypes.headOption.map(ViewSchema.apply).map {
method.getGenericParameterTypes.headOption.map(ViewSchema.apply(_)).map {
case validInput: SpiType.QueryInput => validInput
case other =>
// FIXME let's see if this flies
Expand Down
136 changes: 57 additions & 79 deletions akka-javasdk/src/main/scala/akka/javasdk/impl/view/ViewSchema.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,102 +8,80 @@ import akka.annotation.InternalApi
import akka.runtime.sdk.spi.views.SpiType
import akka.runtime.sdk.spi.views.SpiType.SpiBoolean
import akka.runtime.sdk.spi.views.SpiType.SpiByteString
import akka.runtime.sdk.spi.views.SpiType.SpiClass
import akka.runtime.sdk.spi.views.SpiType.SpiDouble
import akka.runtime.sdk.spi.views.SpiType.SpiFloat
import akka.runtime.sdk.spi.views.SpiType.SpiInteger
import akka.runtime.sdk.spi.views.SpiType.SpiList
import akka.runtime.sdk.spi.views.SpiType.SpiLong
import akka.runtime.sdk.spi.views.SpiType.SpiNestableType
import akka.runtime.sdk.spi.views.SpiType.SpiString
import akka.runtime.sdk.spi.views.SpiType.SpiTimestamp

import java.lang.reflect.AccessFlag
import java.lang.reflect.Field
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.time.Instant
import java.util.Optional
import scala.reflect.classTag

/**
* INTERNAL API
*/
@InternalApi
private[view] object ViewSchema {

def apply(javaType: Type): SpiType = {
if (javaType == classOf[String]) {
SpiString
} else if (javaType == classOf[java.lang.Long] || javaType.getTypeName == "long") {
SpiLong
} else if (javaType == classOf[java.lang.Integer] || javaType.getTypeName == "int"
|| javaType.getTypeName == "short"
|| javaType.getTypeName == "byte"
|| javaType.getTypeName == "char") {
SpiInteger
} else if (javaType == classOf[java.lang.Double] || javaType.getTypeName == "double") {
SpiDouble
} else if (javaType == classOf[java.lang.Float] || javaType.getTypeName == "float") {
SpiFloat
} else if (javaType == classOf[java.lang.Boolean] || javaType.getTypeName == "boolean") {
SpiBoolean
} else if (javaType == Array.emptyByteArray.getClass) {
SpiByteString
} else if (javaType.isInstanceOf[ParameterizedType] && classOf[java.util.Collection[_]]
.isAssignableFrom(javaType.asInstanceOf[ParameterizedType].getRawType.asInstanceOf[Class[_]])) {
val elementType = apply(javaType.asInstanceOf[ParameterizedType].getActualTypeArguments.head) match {
case spiNestableType: SpiNestableType => spiNestableType
case other => throw new IllegalArgumentException(s"Element type of list is $other, not supported")
}
new SpiList(elementType)
} else {
apply(javaType.asInstanceOf[Class[_]])
}
}
private final val typeNameMap = Map(
"short" -> SpiInteger,
"byte" -> SpiInteger,
"char" -> SpiInteger,
"int" -> SpiInteger,
"long" -> SpiLong,
"double" -> SpiDouble,
"float" -> SpiFloat,
"boolean" -> SpiBoolean)

def apply(clazz: Class[_]): SpiType = {
def firstTypeParam(field: Field): Class[_] = {
// FIXME: doesn't work for Scala Seqs of primitives (Int, Long, Float, Double, Boolean)
field.getGenericType.asInstanceOf[ParameterizedType].getActualTypeArguments.head.asInstanceOf[Class[_]]
}
def figureItOut(clazz: Class[_], field: Option[Field]): SpiType = {
if (clazz == classOf[String]) SpiType.SpiString
else if (clazz == classOf[Int]) SpiType.SpiInteger
else if (clazz == classOf[java.lang.Integer]) SpiType.SpiInteger
else if (clazz == classOf[Long]) SpiType.SpiLong
else if (clazz == classOf[java.lang.Long]) SpiType.SpiLong
else if (clazz == classOf[Float]) SpiType.SpiFloat
else if (clazz == classOf[java.lang.Float]) SpiType.SpiFloat
else if (clazz == classOf[Double]) SpiType.SpiDouble
else if (clazz == classOf[java.lang.Double]) SpiType.SpiDouble
else if (clazz == classOf[Boolean]) SpiType.SpiBoolean
else if (clazz == classOf[java.lang.Boolean]) SpiType.SpiBoolean
else if (clazz == classOf[Instant]) SpiType.SpiTimestamp
else if (clazz.isArray && clazz.componentType() == classOf[java.lang.Byte]) SpiType.SpiByteString
else if (clazz.isEnum) new SpiType.SpiEnum(clazz.getName)
else if (clazz == classOf[Optional[_]]) {
new SpiType.SpiOptional(figureItOut(firstTypeParam(field.get), field).asInstanceOf[SpiNestableType])
} else if (clazz == classOf[java.util.List[_]]) {
// FIXME support other types of collections?
new SpiType.SpiList(figureItOut(firstTypeParam(field.get), field).asInstanceOf[SpiNestableType])
} else {
new SpiType.SpiClass(
clazz.getName,
clazz.getDeclaredFields
.filterNot(f => f.accessFlags().contains(AccessFlag.STATIC))
// FIXME recursive classes with fields of their own type
.filterNot(_.getType == clazz)
.map(field => new SpiType.SpiField(field.getName, figureItOut(field.getType, Some(field))))
.toSeq)
}
}
private final val knownConcreteClasses = Map[Class[_], SpiType](
// wrapped types
classOf[java.lang.Boolean] -> SpiBoolean,
classOf[java.lang.Short] -> SpiInteger,
classOf[java.lang.Byte] -> SpiInteger,
classOf[java.lang.Character] -> SpiInteger,
classOf[java.lang.Integer] -> SpiInteger,
classOf[java.lang.Long] -> SpiLong,
classOf[java.lang.Double] -> SpiDouble,
classOf[java.lang.Float] -> SpiFloat,
// special classes
classOf[String] -> SpiString,
classOf[java.time.Instant] -> SpiTimestamp)

figureItOut(clazz, None) match {
case spiClass: SpiClass => spiClass
case _ =>
throw new IllegalArgumentException(
s"${classTag.runtimeClass} is not a class, only classes supported as top types")
def apply(javaType: Type): SpiType =
typeNameMap.get(javaType.getTypeName) match {
case Some(found) => found
case None =>
val clazz = javaType match {
case c: Class[_] => c
case p: ParameterizedType => p.getRawType.asInstanceOf[Class[_]]
}
knownConcreteClasses.get(clazz) match {
case Some(found) => found
case None =>
// trickier ones where we have to look at type parameters etc
if (clazz.isArray && clazz.componentType() == classOf[java.lang.Byte]) {
SpiByteString
} else if (clazz.isEnum) {
new SpiType.SpiEnum(clazz.getName)
} else {
javaType match {
case p: ParameterizedType if clazz == classOf[Optional[_]] =>
new SpiType.SpiOptional(apply(p.getActualTypeArguments.head).asInstanceOf[SpiNestableType])
case p: ParameterizedType if classOf[java.util.Collection[_]].isAssignableFrom(clazz) =>
new SpiType.SpiList(apply(p.getActualTypeArguments.head).asInstanceOf[SpiNestableType])
case _: Class[_] =>
new SpiType.SpiClass(
clazz.getName,
clazz.getDeclaredFields
.filterNot(f => f.accessFlags().contains(AccessFlag.STATIC))
// FIXME recursive classes with fields of their own type
.filterNot(_.getType == clazz)
.map(field => new SpiType.SpiField(field.getName, apply(field.getGenericType)))
.toSeq)
}
}
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ class ViewSchemaSpec extends AnyWordSpec with Matchers {
case _ => fail()
}
}

// FIXME self-referencing/recursive types
}

}

0 comments on commit 1ca6276

Please sign in to comment.