All Downloads are FREE. Search and download functionalities are using the official Maven repository.

wvlet.airframe.surface.reflect.ReflectSurfaceFactory.scala Maven / Gradle / Ivy

The newest version!
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package wvlet.airframe.surface.reflect

import java.lang.reflect.InvocationTargetException
import java.util.concurrent.ConcurrentHashMap

import wvlet.log.LogSupport
import wvlet.airframe.surface._

import scala.collection.JavaConverters._
import scala.reflect.runtime.{universe => ru}

/**
  *
  */
object ReflectSurfaceFactory extends LogSupport {

  import ru._

  private type TypeName = String

  private[surface] val surfaceCache       = new ConcurrentHashMap[TypeName, Surface].asScala
  private[surface] val methodSurfaceCache = new ConcurrentHashMap[TypeName, Seq[MethodSurface]].asScala
  private[surface] val typeMap            = new ConcurrentHashMap[Surface, ru.Type].asScala

  private def belongsToScalaDefault(t: ru.Type) = {
    t match {
      case ru.TypeRef(prefix, _, _) =>
        val scalaDefaultPackages = Seq("scala.", "scala.Predef.", "scala.util.")
        scalaDefaultPackages.exists(p => prefix.dealias.typeSymbol.fullName.startsWith(p))
      case _ => false
    }
  }

  def of[A: ru.WeakTypeTag]: Surface = ofType(implicitly[ru.WeakTypeTag[A]].tpe)
  def ofType(tpe: ru.Type): Surface = {
    apply(tpe)
  }
  def findTypeOf(s: Surface): Option[ru.Type] = typeMap.get(s)

  def get(name: String): Surface = {
    surfaceCache.getOrElse(name, throw new IllegalArgumentException(s"Surface ${name} is not found in cache"))
  }

  private def typeNameOf(t: ru.Type): String = {
    t.dealias.typeSymbol.fullName
  }

  private def isTaggedType(t: ru.Type): Boolean = {
    typeNameOf(t).startsWith("wvlet.airframe.surface.tag.")
  }

  private def fullTypeNameOf(tpe: ru.Type): TypeName = {
    tpe match {
      case t if t.typeArgs.length == 2 && isTaggedType(t) =>
        s"${fullTypeNameOf(t.typeArgs(0))}@@${fullTypeNameOf(t.typeArgs(1))}"
      case alias @ TypeRef(prefix, symbol, args)
          if symbol.isType &&
            symbol.asType.isAliasType &&
            !belongsToScalaDefault(alias) =>
        val name     = symbol.asType.name.decodedName.toString
        val fullName = s"${prefix.typeSymbol.fullName}.${name}"
        fullName
      case TypeRef(prefix, typeSymbol, args) if args.isEmpty => typeSymbol.fullName
      case TypeRef(prefix, typeSymbol, args) if !args.isEmpty =>
        val typeArgs = args.map(fullTypeNameOf(_)).mkString(",")
        s"${typeSymbol.fullName}[${typeArgs}]"
      case _ => tpe.typeSymbol.fullName
    }
  }

  def apply(tpe: ru.Type): Surface = {
    surfaceCache.getOrElseUpdate(fullTypeNameOf(tpe), new SurfaceFinder().surfaceOf(tpe))
  }

  def methodsOf(s: Surface): Seq[MethodSurface] = {
    findTypeOf(s)
      .map { tpe =>
        methodsOfType(tpe)
      }
      .getOrElse(Seq.empty)
  }

  def methodsOf[A: ru.WeakTypeTag]: Seq[MethodSurface] = methodsOfType(implicitly[ru.WeakTypeTag[A]].tpe)

  def methodsOfType(tpe: ru.Type): Seq[MethodSurface] = {
    methodSurfaceCache.getOrElseUpdate(fullTypeNameOf(tpe), {
      new SurfaceFinder().createMethodSurfaceOf(tpe)
    })
  }

  private[surface] def mirror = ru.runtimeMirror(Thread.currentThread.getContextClassLoader)
  private def resolveClass(tpe: ru.Type): Class[_] = {
    try {
      mirror.runtimeClass(tpe)
    } catch {
      case e: Throwable => classOf[Any]
    }
  }

  def hasAbstractMethods(t: ru.Type): Boolean =
    t.members.exists(x => x.isMethod && x.isAbstract && !x.isAbstractOverride)

  private def isAbstract(t: ru.Type): Boolean = {
    t.typeSymbol.isAbstract && hasAbstractMethods(t)
  }

  private type SurfaceMatcher = PartialFunction[ru.Type, Surface]

  private class SurfaceFinder extends LogSupport {
    private val seen       = scala.collection.mutable.Set[ru.Type]()
    private val methodSeen = scala.collection.mutable.Set[ru.Type]()

    def localMethodsOf(t: ru.Type): Iterable[MethodSymbol] = {
      t.members
        .filter(x => x.isMethod && !x.isConstructor && !x.isImplementationArtifact).map(_.asMethod).filter(
          isTargetMethod(_, t))
    }

    def createMethodSurfaceOf(targetType: ru.Type): Seq[MethodSurface] = {
      val name = fullTypeNameOf(targetType)
      if (methodSurfaceCache.contains(name)) {
        methodSurfaceCache(name)
      } else if (methodSeen.contains(targetType)) {
        throw new IllegalArgumentException(s"recursive type in method: ${targetType.typeSymbol.fullName}")
      } else {
        methodSeen += targetType
        val methodSurfaces = targetType match {
          case t @ TypeRef(prefix, typeSymbol, typeArgs) =>
            val list = for (m <- localMethodsOf(t.dealias)) yield {
              val mod   = modifierBitMaskOf(m)
              val owner = surfaceOf(t)
              val name  = m.name.decodedName.toString
              val ret   = surfaceOf(m.returnType)
              val args  = methodParametersOf(t, m)
              ReflectMethodSurface(mod, owner, name, ret, args.toIndexedSeq)
            }
            list.toIndexedSeq
          case _ =>
            Seq.empty
        }
        methodSurfaceCache += name -> methodSurfaces
        methodSurfaces
      }
    }

    private def isTargetMethod(m: MethodSymbol, target: ru.Type): Boolean = {
      // synthetic is used for functions returning default values of method arguments (e.g., ping$default$1)
      val methodName = m.name.decodedName.toString
      m.isMethod &&
      !m.isImplicit &&
      !m.isSynthetic &&
      !m.isAccessor &&
      !methodName.startsWith("$") &&
      methodName != "" &&
      isOwnedByTargetClass(m, target)
    }
    private def isOwnedByTargetClass(m: MethodSymbol, t: ru.Type): Boolean = {
      m.owner == t.typeSymbol
    }

    def modifierBitMaskOf(m: MethodSymbol): Int = {
      var mod = 0
      if (m.isPublic) {
        mod |= MethodModifier.PUBLIC
      }
      if (m.isPrivate) {
        mod |= MethodModifier.PRIVATE
      }
      if (m.isProtected) {
        mod |= MethodModifier.PROTECTED
      }
      if (m.isStatic) {
        mod |= MethodModifier.STATIC
      }
      if (m.isFinal) {
        mod |= MethodModifier.FINAL
      }
      if (m.isAbstract) {
        mod |= MethodModifier.ABSTRACT
      }
      mod
    }

    def surfaceOf(tpe: ru.Type): Surface = {
      try {
        val fullName = fullTypeNameOf(tpe)
        if (surfaceCache.contains(fullName)) {
          surfaceCache(fullName)
        } else if (seen.contains(tpe)) {
          // Recursive type
          LazySurface(resolveClass(tpe), fullName, typeArgsOf(tpe).map(x => surfaceOf(x)))
        } else {
          seen += tpe
          val m = surfaceFactories.orElse[ru.Type, Surface] {
            case _ =>
              trace(f"Resolving the unknown type $tpe into AnyRef")
              new GenericSurface(resolveClass(tpe))
          }
          val surface = m(tpe)
          // Cache if not yet cached
          surfaceCache.getOrElseUpdate(fullName, surface)
          typeMap.getOrElseUpdate(surface, tpe)
          surface
        }
      } catch {
        case e: Throwable =>
          error(s"Failed to build Surface.of[${tpe}]", e)
          throw e
      }
    }

    private val surfaceFactories: SurfaceMatcher =
      taggedTypeFactory orElse
        aliasFactory orElse
        higherKindedTypeFactory orElse
        primitiveTypeFactory orElse
        arrayFactory orElse
        optionFactory orElse
        tupleFactory orElse
        javaUtilFactory orElse
        enumFactory orElse
        genericSurfaceWithConstructorFactory orElse
        existentialTypeFactory orElse
        genericSurfaceFactory

    private def primitiveTypeFactory: SurfaceMatcher = {
      case t if t =:= typeOf[String]  => Primitive.String
      case t if t =:= typeOf[Boolean] => Primitive.Boolean
      case t if t =:= typeOf[Int]     => Primitive.Int
      case t if t =:= typeOf[Long]    => Primitive.Long
      case t if t =:= typeOf[Float]   => Primitive.Float
      case t if t =:= typeOf[Double]  => Primitive.Double
      case t if t =:= typeOf[Short]   => Primitive.Short
      case t if t =:= typeOf[Byte]    => Primitive.Byte
      case t if t =:= typeOf[Char]    => Primitive.Char
      case t if t =:= typeOf[Unit]    => Primitive.Unit
    }

    private def typeArgsOf(t: ru.Type): List[ru.Type] = t match {
      case TypeRef(prefix, symbol, args) =>
        args
      case ru.ExistentialType(quantified, underlying) =>
        typeArgsOf(underlying)
      case other =>
        List.empty
    }

    private def elementTypeOf(t: ru.Type): Surface = {
      typeArgsOf(t).map(surfaceOf(_)).head
    }

    private def higherKindedTypeFactory: SurfaceMatcher = {
      case t @ TypeRef(prefix, symbol, args) if t.typeArgs.isEmpty && t.takesTypeArgs =>
        // When higher-kinded types (e.g., Option[X], Future[X]) is passed as Option, Future without type arguments
        val inner    = surfaceOf(t.erasure)
        val name     = symbol.asType.name.decodedName.toString
        val fullName = s"${prefix.typeSymbol.fullName}.${name}"
        HigherKindedTypeSurface(name, fullName, inner)
    }

    private def taggedTypeFactory: SurfaceMatcher = {
      case t if t.typeArgs.length == 2 && typeNameOf(t).startsWith("wvlet.airframe.surface.tag.") =>
        TaggedSurface(surfaceOf(t.typeArgs(0)), surfaceOf(t.typeArgs(1)))
    }

    private def aliasFactory: SurfaceMatcher = {
      case alias @ TypeRef(prefix, symbol, args)
          if symbol.isType &&
            symbol.asType.isAliasType &&
            !belongsToScalaDefault(alias) =>
        val dealiased = alias.dealias
        val inner = if (alias != dealiased) {
          surfaceOf(dealiased)
        } else {
          // When higher kind types are aliased (e.g., type M[A] = Future[A]),
          // alias.dealias will not return the aliased type (Future[A]),
          // So we need to find the resulting type by applying type erasure.
          surfaceOf(alias.erasure)
        }

        val name     = symbol.asType.name.decodedName.toString
        val fullName = s"${prefix.typeSymbol.fullName}.${name}"
        val a        = Alias(name, fullName, inner)
        a
    }

    private def arrayFactory: SurfaceMatcher = {
      case t if typeNameOf(t) == "scala.Array" =>
        ArraySurface(resolveClass(t), elementTypeOf(t))
    }

    private def optionFactory: SurfaceMatcher = {
      case t if typeNameOf(t) == "scala.Option" =>
        OptionSurface(resolveClass(t), elementTypeOf(t))
    }

    private def tupleFactory: SurfaceMatcher = {
      case t if t <:< typeOf[Product] && t.typeSymbol.fullName.startsWith("scala.Tuple") =>
        val paramType = typeArgsOf(t).map(x => surfaceOf(x))
        TupleSurface(resolveClass(t), paramType.toIndexedSeq)
    }

    private def javaUtilFactory: SurfaceMatcher = {
      case t
          if t =:= typeOf[java.io.File] ||
            t =:= typeOf[java.util.Date] ||
            t =:= typeOf[java.time.temporal.Temporal] ||
            t =:= typeOf[Throwable] ||
            t =:= typeOf[Exception] ||
            t =:= typeOf[Error] =>
        new GenericSurface(resolveClass(t))
    }

    private def isEnum(t: ru.Type): Boolean = {
      t.baseClasses.exists { x =>
        if (x.isJava && x.isType) {
          x.asType.fullName.toString.startsWith("java.lang.Enum")
        } else {
          false
        }
      }
    }

    private def enumFactory: SurfaceMatcher = {
      case t if isEnum(t) =>
        EnumSurface(resolveClass(t))
    }

    def hasAbstractMethods(t: ru.Type): Boolean =
      t.members.exists(x => x.isMethod && x.isAbstract && !x.isAbstractOverride)

    private def isAbstract(t: ru.Type): Boolean = {
      t.typeSymbol.isAbstract && hasAbstractMethods(t)
    }

    private def isPhantomConstructor(constructor: Symbol): Boolean =
      constructor.asMethod.fullName.endsWith("$init$")

    def publicConstructorsOf(t: ru.Type): Iterable[MethodSymbol] = {
      t.members
        .filter(m => m.isMethod && m.asMethod.isConstructor && m.isPublic).filterNot(isPhantomConstructor).map(
          _.asMethod)
    }

    def findPrimaryConstructorOf(t: ru.Type): Option[MethodSymbol] = {
      publicConstructorsOf(t).find(x => x.isPrimaryConstructor)
    }

    case class MethodArg(paramName: Symbol, tpe: ru.Type) {
      def name: String         = paramName.name.decodedName.toString
      def typeSurface: Surface = surfaceOf(tpe)
    }

    private def findMethod(m: ru.Type, name: String): Option[MethodSymbol] = {
      m.member(ru.TermName(name)) match {
        case ru.NoSymbol => None
        case other       => Some(other.asMethod)
      }
    }

    private def methodArgsOf(targetType: ru.Type, constructor: MethodSymbol): List[List[MethodArg]] = {
      val classTypeParams = targetType.typeSymbol.asClass.typeParams

      val companion = targetType.companion match {
        case NoType => None
        case comp   => Some(comp)
      }

      for (params <- constructor.paramLists) yield {
        val concreteArgTypes = params.map(_.typeSignature.substituteTypes(classTypeParams, targetType.typeArgs))
        var index            = 1
        for ((p, t) <- params.zip(concreteArgTypes)) yield {
          index += 1
          MethodArg(p, t)
        }
      }
    }

    def methodParametersOf(targetType: ru.Type, method: MethodSymbol): Seq[RuntimeMethodParameter] = {
      val args = methodArgsOf(targetType, method).flatten
      val argTypes = args.map { x: MethodArg =>
        resolveClass(x.tpe)
      }.toSeq
      val ref = MethodRef(resolveClass(targetType), method.name.decodedName.toString, argTypes, method.isConstructor)

      var index = 0
      val surfaceParams = args.map { arg =>
        val t = arg.name
        //accessor = { x : Any => x.asInstanceOf[${target.tpe}].${arg.paramName} }
        val expr = RuntimeMethodParameter(
          method = ref,
          index = index,
          name = arg.name,
          surface = arg.typeSurface
        )
        index += 1
        expr
      }
      // Using IndexedSeq is necessary for Serialization
      surfaceParams.toIndexedSeq
    }

    private def genericSurfaceWithConstructorFactory: SurfaceMatcher = new SurfaceMatcher with LogSupport {
      override def isDefinedAt(t: ru.Type): Boolean = {
        !isAbstract(t) && findPrimaryConstructorOf(t).exists(!_.paramLists.isEmpty)
      }
      override def apply(t: ru.Type): Surface = {
        val primaryConstructor = findPrimaryConstructorOf(t).get
        val typeArgs           = typeArgsOf(t).map(surfaceOf(_)).toIndexedSeq
        val s = new RuntimeGenericSurface(
          resolveClass(t),
          typeArgs,
          params = methodParametersOf(t, primaryConstructor)
        )
        s
      }
    }

    private def existentialTypeFactory: SurfaceMatcher = {
      case t @ ru.ExistentialType(quantified, underlying) =>
        surfaceOf(underlying)
    }

    private def genericSurfaceFactory: SurfaceMatcher = {
      case t @ TypeRef(prefix, symbol, args) if !args.isEmpty =>
        val typeArgs = typeArgsOf(t).map(surfaceOf(_)).toIndexedSeq
        new GenericSurface(resolveClass(t), typeArgs = typeArgs)
      case t @ TypeRef(NoPrefix, symbol, args) if !t.typeSymbol.isClass =>
        wvlet.airframe.surface.ExistentialType
      case t @ TypeRef(prefix, symbol, args) if resolveClass(t) == classOf[AnyRef] && !(t =:= typeOf[AnyRef]) =>
        // For example, trait MyTag, which has no implementation will be just an java.lang.Object
        val name     = t.typeSymbol.name.decodedName.toString
        val fullName = s"${prefix.typeSymbol.fullName}.${name}"
        new Alias(name, fullName, AnyRefSurface)
      case t =>
        new GenericSurface(resolveClass(t))
    }
  }

  /**
    * Used when we can use reflection to instantiate objects of this surface
    * @param rawType
    * @param typeArgs
    * @param params
    */
  class RuntimeGenericSurface(override val rawType: Class[_],
                              override val typeArgs: Seq[Surface] = Seq.empty,
                              override val params: Seq[Parameter] = Seq.empty)
      extends GenericSurface(rawType, typeArgs, params, None)
      with LogSupport { self =>
    override val objectFactory: Option[ObjectFactory] = {
      if (rawType.getConstructors.isEmpty) {
        None
      } else {
        Some(new ObjectFactory {
          // Create instance with Reflection
          override def newInstance(args: Seq[Any]): Any = {
            try {
              // We should find the primary constructor here to avoid including java.lang.reflect.Constructor, which is non-serializable, within Surface instance
              val cc = rawType.getConstructors()
              assert(cc.length > 0)
              val primaryConstructor = cc(0)
              val obj = if (args.isEmpty) {
                primaryConstructor.newInstance()
              } else {
                val a = args.map(_.asInstanceOf[AnyRef])
                logger.trace(s"build ${rawType.getName} with args: ${a.mkString(", ")}")
                primaryConstructor.newInstance(a: _*)
              }
              obj.asInstanceOf[Any]
            } catch {
              case e: InvocationTargetException =>
                logger.warn(
                  s"Failed to instantiate ${self}: [${e.getTargetException.getClass.getName}] ${e.getTargetException.getMessage}\nargs:[${args
                    .mkString(", ")}]")
                throw e.getTargetException
              case e: Throwable =>
                logger.warn(
                  s"Failed to instantiate ${self}: [${e.getClass.getName}] ${e.getMessage}\nargs:[${args.mkString(", ")}]")
                throw e
            }
          }
        })
      }
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy