diff --git a/smt-cache/src/main/scala/org/bitlap/cache/Cache.scala b/smt-cache/src/main/scala/org/bitlap/cache/Cache.scala index 1dedfaeee8aa3840f817142411d056c44bc70c93..45ff81353f61ced74ebfcad03c89c670c21153f8 100644 --- a/smt-cache/src/main/scala/org/bitlap/cache/Cache.scala +++ b/smt-cache/src/main/scala/org/bitlap/cache/Cache.scala @@ -61,7 +61,9 @@ object Cache { override def getTField(key: String, field: CaseClassField)(implicit keyBuilder: CacheKeyBuilder[String] ): Future[Option[field.Field]] = - getT(key).map(opt => opt.flatMap(t => CaseClassExtractor.getFieldValueUnSafely[cache.Out](t, field))) + getT(key).map(opt => + opt.flatMap(t => CaseClassExtractor.ofValue[cache.Out](t, field).asInstanceOf[Option[field.Field]]) + ) override def clear(): Future[Unit] = cache.clear() } @@ -91,7 +93,7 @@ object Cache { override def getTField(key: String, field: CaseClassField)(implicit keyBuilder: CacheKeyBuilder[String] ): Identity[Option[field.Field]] = - getT(key).flatMap(t => CaseClassExtractor.getFieldValueUnSafely[cache.Out](t, field)) + getT(key).flatMap(t => CaseClassExtractor.ofValue[cache.Out](t, field).asInstanceOf[Option[field.Field]]) override def clear(): Identity[Unit] = cache.clear() } diff --git a/smt-common/src/main/scala/org/bitlap/common/CaseClassExtractor.scala b/smt-common/src/main/scala/org/bitlap/common/CaseClassExtractor.scala index 5cfa3c00aece3d41115e1502dbeaaf10c6bbc0d7..53ede01d41a365a0fa403f12a3da07faeb2d3632 100644 --- a/smt-common/src/main/scala/org/bitlap/common/CaseClassExtractor.scala +++ b/smt-common/src/main/scala/org/bitlap/common/CaseClassExtractor.scala @@ -35,42 +35,25 @@ import scala.util.{ Failure, Success } */ object CaseClassExtractor { - def getFieldValueUnSafely[T: ru.TypeTag](obj: T, field: CaseClassField)(implicit - classTag: ClassTag[T] - ): Option[field.Field] = { - val mirror = ru.runtimeMirror(getClass.getClassLoader) - val fieldOption = scala.util.Try( - getMethods[T] - .filter(_.name.toTermName.decodedName.toString == field.stringify) - .map(m => mirror.reflect(obj).reflectField(m).get) - .headOption - .map(_.asInstanceOf[field.Field]) - ) - fieldOption match { - case Success(value) => value - case Failure(exception) => exception.printStackTrace(); None - } - } - - def getMethods[T: ru.TypeTag]: List[ru.MethodSymbol] = typeOf[T].members.collect { - case m: MethodSymbol if m.isCaseAccessor => m - }.toList - - def getFieldValueSafely[T <: Product, Field](t: T, name: String): Option[Field] = macro macroImpl[T, Field] + /** Using the characteristics of the product type to get the field value should force the conversion externally + * (safely). + */ + def ofValue[T <: Product](t: T, field: CaseClassField): Option[Any] = macro macroImpl[T] - def macroImpl[T: c.WeakTypeTag, Field: c.WeakTypeTag]( + def macroImpl[T: c.WeakTypeTag]( c: whitebox.Context - )(t: c.Expr[T], name: c.Expr[String]): c.Expr[Option[Field]] = { + )(t: c.Expr[T], field: c.Expr[CaseClassField]): c.Expr[Option[Any]] = { import c.universe._ - val typ = TypeName(c.weakTypeOf[Field].typeSymbol.name.decodedName.toString) + // scalafmt: { maxColumn = 400 } val tree = q""" if ($t == null) None else { - val idx = $t.productElementNames.indexOf($name) - Option($t.productElement(idx).asInstanceOf[$typ]) + val _field = $field + _field.${TermName(CaseClassField.fieldNamesTermName)}.find(kv => kv._2 == _field.${TermName(CaseClassField.stringifyTermName)}) + .map(kv => $t.productElement(kv._1)) } """ - exprPrintTree[Option[Field]](c)(tree) + exprPrintTree[Option[Any]](c)(tree) } @@ -83,4 +66,28 @@ object CaseClassExtractor { ) c.Expr[Field](resTree) } + + /** Using scala reflect to get the field value (safely). + */ + @deprecated + def reflectValue[T: ru.TypeTag](obj: T, field: CaseClassField)(implicit + classTag: ClassTag[T] + ): Option[field.Field] = { + val mirror = ru.runtimeMirror(getClass.getClassLoader) + val fieldOption = scala.util.Try( + getMethods[T] + .filter(_.name.toTermName.decodedName.toString == field.stringify) + .map(m => mirror.reflect(obj).reflectField(m).get) + .headOption + .map(_.asInstanceOf[field.Field]) + ) + fieldOption match { + case Success(value) => value + case Failure(exception) => exception.printStackTrace(); None + } + } + + def getMethods[T: ru.TypeTag]: List[ru.MethodSymbol] = typeOf[T].members.collect { + case m: MethodSymbol if m.isCaseAccessor => m + }.toList } diff --git a/smt-common/src/main/scala/org/bitlap/common/CaseClassField.scala b/smt-common/src/main/scala/org/bitlap/common/CaseClassField.scala index de7a8e9a6174938cfe0f2a43dfbe5e88b5c4ec23..511159fdab8eca36e7327e8991015a436cc917b9 100644 --- a/smt-common/src/main/scala/org/bitlap/common/CaseClassField.scala +++ b/smt-common/src/main/scala/org/bitlap/common/CaseClassField.scala @@ -31,13 +31,18 @@ trait CaseClassField { def stringify: String type Field + + /** product index -> name, scala2.11 2.12 not `productElementNames` + */ + val fieldIndexNames: Map[Int, String] } object CaseClassField { - final val classNameTermName = "CaseClassField" - final val stringifyTermName = "stringify" - final val fieldTermName = "Field" + final val classNameTermName = "CaseClassField" + final val stringifyTermName = "stringify" + final val fieldTermName = "Field" + final val fieldNamesTermName = "fieldIndexNames" def apply[T <: Product](field: T => Any): CaseClassField = macro selectFieldMacroImpl[T] @@ -74,17 +79,20 @@ object CaseClassField { } val fieldNameTypeName = TermName(s"${CaseClassField.classNameTermName}$$$fieldName") - val res = q""" + val res = + q""" case object $fieldNameTypeName extends $packageName.${TypeName(CaseClassField.classNameTermName)} { override def ${TermName(CaseClassField.stringifyTermName)}: String = $fieldName override type ${TypeName(CaseClassField.fieldTermName)} = $genericType + override val ${TermName(CaseClassField.fieldNamesTermName)} = + (${caseClassParams.indices.toList} zip ${caseClassParams.map(_.name.decodedName.toString)}).toMap } $fieldNameTypeName """ exprPrintTree[CaseClassField](c)(res) } - private def getCaseClassParams[T: c.WeakTypeTag](c: whitebox.Context): List[c.Symbol] = { + def getCaseClassParams[T: c.WeakTypeTag](c: whitebox.Context): List[c.Symbol] = { import c.universe._ val parameters = c.weakTypeOf[T].resultType.member(TermName("")).typeSignature.paramLists if (parameters.size > 1) { diff --git a/smt-common/src/test/scala/org/bitlap/common/CaseClassExtractorTest.scala b/smt-common/src/test/scala/org/bitlap/common/CaseClassExtractorTest.scala index 4c61b10a298153af17d833e94db1f010ebb1ed06..a46a200f90c164fc53667c964f0a9d8c26f4ab5f 100644 --- a/smt-common/src/test/scala/org/bitlap/common/CaseClassExtractorTest.scala +++ b/smt-common/src/test/scala/org/bitlap/common/CaseClassExtractorTest.scala @@ -31,10 +31,9 @@ import org.scalatest.matchers.should.Matchers class CaseClassExtractorTest extends AnyFlatSpec with Matchers { "CaseClassExtractorTest1" should "safe" in { - val obj = TestEntity("name", "id", "key", Some(1)) - val keyField = CaseClassField[TestEntity](_.key) - val key = - CaseClassExtractor.getFieldValueSafely[TestEntity, keyField.Field](obj, keyField.stringify) + val obj = TestEntity("name", "id", "key", Some(1)) + val key = CaseClassExtractor.ofValue(obj, CaseClassField[TestEntity](_.key)) + println(key) assert(key == Option("key")) } @@ -47,9 +46,8 @@ class CaseClassExtractorTest extends AnyFlatSpec with Matchers { } "CaseClassExtractorTest3" should "unsafe" in { - val obj = TestEntity("name", "id", "key", Some(1)) - val key: Option[CaseClassField#Field] = - CaseClassExtractor.getFieldValueUnSafely(obj, CaseClassField[TestEntity](_.key)) + val obj = TestEntity("name", "id", "key", Some(1)) + val key: Option[CaseClassField#Field] = CaseClassExtractor.reflectValue(obj, CaseClassField[TestEntity](_.key)) assert(key == Option("key")) }