scala.reflect.runtime.JavaMirrors.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scala-reflect Show documentation
Show all versions of scala-reflect Show documentation
Reflection Library for the Scala Programming Language
/*
* Scala (https://www.scala-lang.org)
*
* Copyright EPFL and Lightbend, Inc.
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/
package scala
package reflect
package runtime
import java.io.IOException
import java.lang.{ Class => jClass, Package => jPackage }
import java.lang.annotation.{ Annotation => jAnnotation }
import java.lang.ref.{ WeakReference => jWeakReference }
import java.lang.reflect.{
Method => jMethod, Constructor => jConstructor, Field => jField,
Member => jMember, Type => jType, TypeVariable => jTypeVariable,
Parameter => jParameter, GenericDeclaration, GenericArrayType,
ParameterizedType, WildcardType, AnnotatedElement }
import java.nio.charset.StandardCharsets.UTF_8
import scala.annotation.nowarn
import scala.collection.immutable.ArraySeq
import scala.collection.mutable.{ListBuffer, WeakHashMap}
import scala.language.existentials
import scala.ref.WeakReference
import scala.reflect.api.TypeCreator
import scala.reflect.internal.{ JavaAccFlags, MissingRequirementError }
import scala.runtime.{BoxesRunTime, ClassValueCompat, ScalaRunTime}
import internal.Flags._
import internal.pickling.ByteCodecs
import internal.pickling.UnPickler
import internal.util.StringContextStripMarginOps
import ReflectionUtils._
private[scala] trait JavaMirrors extends internal.SymbolTable with api.JavaUniverse with TwoWayCaches { thisUniverse: SymbolTable =>
private lazy val mirrors = new WeakHashMap[ClassLoader, WeakReference[MirrorImpl]]()
private def createMirror(owner: Symbol, cl: ClassLoader): Mirror = {
val jm = new MirrorImpl(owner, cl)
mirrors(cl) = new WeakReference[MirrorImpl](jm)
jm.init()
jm
}
override type Mirror = MirrorImpl
implicit val MirrorTag: ClassTag[Mirror] = ClassTag[Mirror](classOf[MirrorImpl])
override lazy val rootMirror: Mirror = createMirror(NoSymbol, rootClassLoader)
// overridden by ReflectGlobal
def rootClassLoader: ClassLoader = this.getClass.getClassLoader
trait JavaClassCompleter
def runtimeMirror(cl: ClassLoader): Mirror = gilSynchronized {
mirrors get cl match {
case Some(WeakReference(m)) => m
case _ => createMirror(rootMirror.RootClass, cl)
}
}
/** The API of a mirror for a reflective universe */
@nowarn("""cat=deprecation&origin=scala\.reflect\.runtime\.JavaMirrors\.JavaMirror""")
final type MirrorImpl = JavaMirror
/**
* The API of a mirror for a reflective universe.
*
* @deprecated this class's name shadows another; use [[MirrorImpl]] instead
*/
@nowarn("msg=shadowing a nested class of a parent is deprecated")
@deprecated("use MirrorImpl instead", since = "2.13.4")
class JavaMirror(owner: Symbol,
/* Class loader that is a mastermind behind the reflexive mirror */
val classLoader: ClassLoader
) extends Roots(owner)
with super.JavaMirror { thisMirror =>
val universe: thisUniverse.type = thisUniverse
import definitions._
private[reflect] lazy val runDefinitions = new definitions.RunDefinitions // only one "run" in the reflection universe
import runDefinitions._
override lazy val RootPackage = (new RootPackage with SynchronizedTermSymbol).markFlagsCompleted(mask = AllFlags)
override lazy val RootClass = (new RootClass with SynchronizedModuleClassSymbol).markFlagsCompleted(mask = AllFlags)
override lazy val EmptyPackage = (new EmptyPackage with SynchronizedTermSymbol).markFlagsCompleted(mask = AllFlags)
override lazy val EmptyPackageClass = (new EmptyPackageClass with SynchronizedModuleClassSymbol).markFlagsCompleted(mask = AllFlags)
/** The lazy type for root.
*/
override lazy val rootLoader = new LazyType with FlagAgnosticCompleter {
override def complete(sym: Symbol) = sym setInfo new LazyPackageType
}
// reflective mirrors can't know the exhaustive list of available packages
// (that's because compiler mirrors are based on directories and reflective mirrors are based on classloaders,
// and unlike directories classloaders might make up stuff on the fly)
// hence we need to be optimistic and create packages out of thin air
// the same thing is done by the `missingHook` below
override def staticPackage(fullname: String): ModuleSymbol =
try super.staticPackage(fullname)
catch { case _: ScalaReflectionException => makeScalaPackage(fullname) }
// ----------- Caching ------------------------------------------------------------------
private[this] val classCache = new TwoWayCache[jClass[_], ClassSymbol]
private[this] val packageCache = new TwoWayCache[Package, ModuleSymbol]
private[this] val methodCache = new TwoWayCache[jMethod, MethodSymbol]
private[this] val constructorCache = new TwoWayCache[jConstructor[_], MethodSymbol]
private[this] val fieldCache = new TwoWayCache[jField, TermSymbol]
private[this] val tparamCache = new TwoWayCache[jTypeVariable[_ <: GenericDeclaration], TypeSymbol]
private[this] object typeTagCache extends ClassValueCompat[jWeakReference[TypeTag[_]]]() {
val typeCreator = new ThreadLocal[TypeCreator]()
override protected def computeValue(cls: jClass[_]): jWeakReference[TypeTag[_]] = {
val creator = typeCreator.get()
assert(creator.getClass == cls, (creator, cls))
new jWeakReference(TypeTagImpl[AnyRef](thisMirror.asInstanceOf[Mirror], creator))
}
}
final def typeTag(typeCreator: TypeCreator): TypeTag[_] = {
typeTagCache.typeCreator.set(typeCreator)
try {
val ref = typeTagCache.get(typeCreator.getClass)
var tag = ref.get
if (tag == null) {
typeTagCache.remove(typeCreator.getClass)
tag = TypeTagImpl[AnyRef](thisMirror.asInstanceOf[Mirror], typeCreator)
}
tag
} finally {
typeTagCache.typeCreator.remove()
}
}
private[runtime] def toScala[J: HasJavaClass, S](cache: TwoWayCache[J, S], key: J)(body: (MirrorImpl, J) => S): S =
cache.toScala(key){
val jclazz = implicitly[HasJavaClass[J]] getClazz key
body(mirrorDefining(jclazz), key)
}
private implicit val classHasJavaClass: HasJavaClass[jClass[_]] = new HasJavaClass(identity)
private implicit val methHasJavaClass: HasJavaClass[jMethod] = new HasJavaClass(_.getDeclaringClass)
private implicit val fieldHasJavaClass: HasJavaClass[jField] = new HasJavaClass(_.getDeclaringClass)
private implicit val constrHasJavaClass: HasJavaClass[jConstructor[_]] = new HasJavaClass(_.getDeclaringClass)
private implicit val tparamHasJavaClass: HasJavaClass[jTypeVariable[_ <: GenericDeclaration]] =
new HasJavaClass ( (tparam: jTypeVariable[_ <: GenericDeclaration]) => {
tparam.getGenericDeclaration match {
case jclazz: jClass[_] => jclazz
case jmeth: jMethod => jmeth.getDeclaringClass
case jconstr: jConstructor[_] => jconstr.getDeclaringClass
case x => throw new MatchError(x)
}
})
// ----------- Implementations of mirror operations and classes -------------------
private def abort(msg: String) = throw new ScalaReflectionException(msg)
private def ErrorInnerClass(sym: Symbol) = abort(s"$sym is an inner class, use reflectClass on an InstanceMirror to obtain its ClassMirror")
private def ErrorInnerModule(sym: Symbol) = abort(s"$sym is an inner module, use reflectModule on an InstanceMirror to obtain its ModuleMirror")
private def ErrorStaticClass(sym: Symbol) = abort(s"$sym is a static class, use reflectClass on a RuntimeMirror to obtain its ClassMirror")
private def ErrorStaticModule(sym: Symbol) = abort(s"$sym is a static module, use reflectModule on a RuntimeMirror to obtain its ModuleMirror")
private def ErrorNotMember(sym: Symbol, owner: Symbol) = abort(s"expected a member of $owner, you provided ${sym.kindString} ${sym.fullName}")
private def ErrorNotField(sym: Symbol) = abort(s"expected a field or an accessor method symbol, you provided $sym")
private def ErrorNotConstructor(sym: Symbol, owner: Symbol) = abort(s"expected a constructor of $owner, you provided $sym")
private def ErrorArrayConstructor(sym: Symbol, owner: Symbol) = abort(s"Cannot instantiate arrays with mirrors. Consider using `scala.reflect.ClassTag().newArray()` instead")
private def ErrorFree(member: Symbol, freeType: Symbol) = abort(s"cannot reflect ${member.kindString} ${member.name}, because it's a member of a weak type ${freeType.name}")
private def ErrorNonExistentField(sym: Symbol) = abort(
sm"""Scala field ${sym.name} of ${sym.owner} isn't represented as a Java field, nor does it have a
|Java accessor method. One common reason for this is that it may be a private class parameter
|not used outside the primary constructor.""")
/** Helper functions for extracting typed values from a (Class[_], Any)
* representing an annotation argument.
*/
private object toAnnotArg {
val StringClass = classOf[String]
val ClassClass = classOf[jClass[_]]
object PrimitiveClass { def unapply(x: jClass[_]) = x.isPrimitive }
object EnumClass { def unapply(x: jClass[_]) = x.isEnum }
object ArrayClass { def unapply(x: jClass[_]) = x.isArray }
object AnnotationClass { def unapply(x: jClass[_]) = x.isAnnotation }
object ConstantArg {
def enumToSymbol(`enum`: Enum[_]): Symbol = {
val staticPartOfEnum = classToScala(`enum`.getClass).companionSymbol
staticPartOfEnum.info.declaration(TermName(`enum`.name))
}
def unapply(schemaAndValue: (jClass[_], Any)): Option[Any] = schemaAndValue match {
case (StringClass | PrimitiveClass(), value) => Some(value)
case (ClassClass, value: jClass[_]) => Some(classToScala(value).toType)
case (EnumClass(), value: Enum[_]) => Some(enumToSymbol(value))
case _ => None
}
}
def apply(schemaAndValue: (jClass[_], Any)): ClassfileAnnotArg = schemaAndValue match {
case ConstantArg(value) => LiteralAnnotArg(Constant(value))
case (clazz @ ArrayClass(), value: Array[_]) => ArrayAnnotArg(value map (x => apply(clazz.getComponentType -> x)))
case (AnnotationClass(), value: jAnnotation) => NestedAnnotArg(JavaAnnotationProxy(value))
case _ => UnmappableAnnotArg
}
}
private case class JavaAnnotationProxy(jann: jAnnotation) extends AnnotationInfo {
override val atp: Type = classToScala(jann.annotationType).toType
override val args: List[Tree] = Nil
override def original: Tree = EmptyTree
override def setOriginal(t: Tree): this.type = throw new Exception("setOriginal inapplicable for " + this)
override def pos: Position = NoPosition
override def setPos(pos: Position): this.type = throw new Exception("setPos inapplicable for " + this)
override def toString = completeAnnotationToString(this)
// todo. find out the exact order of assocs as they are written in the class file
// currently I'm simply sorting the methods to guarantee stability of the output
override lazy val assocs: List[(Name, ClassfileAnnotArg)] = (
jann.annotationType.getDeclaredMethods.sortBy(_.getName).toList map (m =>
TermName(m.getName) -> toAnnotArg(m.getReturnType -> m.invoke(jann))
)
)
override def transformArgs(f: List[Tree] => List[Tree]) = this
}
def reflect[T: ClassTag](obj: T): InstanceMirror = new JavaInstanceMirror(obj)
def reflectClass(cls: ClassSymbol): ClassMirror = {
if (!cls.isStatic) ErrorInnerClass(cls)
new JavaClassMirror(null, cls)
}
def reflectModule(mod: ModuleSymbol): ModuleMirror = {
if (!mod.isStatic) ErrorInnerModule(mod)
new JavaModuleMirror(null, mod)
}
def runtimeClass(tpe: Type): RuntimeClass = typeToJavaClass(tpe)
def runtimeClass(cls: ClassSymbol): RuntimeClass = classToJava(cls)
def classSymbol(rtcls: RuntimeClass): ClassSymbol = classToScala(rtcls)
def moduleSymbol(rtcls: RuntimeClass): ModuleSymbol = classToScala(rtcls).companionModule.asModule
private def ensuringNotFree(sym: Symbol)(body: => Any): Unit = {
val freeType = sym.ownerChain find (_.isFreeType)
freeType match {
case Some(freeType) => ErrorFree(sym, freeType)
case _ => body
}
}
private def checkMemberOf(sym: Symbol, owner: ClassSymbol): Unit = {
if (sym.owner == AnyClass || sym.owner == AnyRefClass || sym.owner == ObjectClass) {
// do nothing
} else if (sym.owner == AnyValClass) {
if (!owner.isPrimitiveValueClass && !owner.isDerivedValueClass) ErrorNotMember(sym, owner)
} else {
ensuringNotFree(sym) {
if (!(owner.info.baseClasses contains sym.owner)) ErrorNotMember(sym, owner)
}
}
}
private def checkConstructorOf(sym: Symbol, owner: ClassSymbol): Unit = {
if (!sym.isClassConstructor) ErrorNotConstructor(sym, owner)
if (owner == ArrayClass) ErrorArrayConstructor(sym, owner)
ensuringNotFree(sym) {
if (!owner.info.decls.toList.contains(sym)) ErrorNotConstructor(sym, owner)
}
}
private def preciseClass[T: ClassTag](instance: T) = {
val staticClazz = classTag[T].runtimeClass
val dynamicClazz = instance.getClass
if (staticClazz.isPrimitive) staticClazz else dynamicClazz
}
private class JavaInstanceMirror[T: ClassTag](val instance: T) extends InstanceMirror {
def symbol = thisMirror.classSymbol(preciseClass(instance))
def reflectField(field: TermSymbol): FieldMirror = {
checkMemberOf(field, symbol)
if ((field.isMethod && !field.isAccessor) || field.isModule) ErrorNotField(field)
val name = if (field.isAccessor) field.localName else field.name
val field1 = (field.owner.info decl name).asTerm
try fieldToJava(field1)
catch {
case _: NoSuchFieldException => ErrorNonExistentField(field1)
}
new JavaFieldMirror(instance, field1)
}
def reflectMethod(method: MethodSymbol): MethodMirror = {
checkMemberOf(method, symbol)
mkMethodMirror(instance, method)
}
def reflectClass(cls: ClassSymbol): ClassMirror = {
if (cls.isStatic) ErrorStaticClass(cls)
checkMemberOf(cls, symbol)
new JavaClassMirror(instance.asInstanceOf[AnyRef], cls)
}
def reflectModule(mod: ModuleSymbol): ModuleMirror = {
if (mod.isStatic) ErrorStaticModule(mod)
checkMemberOf(mod, symbol)
new JavaModuleMirror(instance.asInstanceOf[AnyRef], mod)
}
override def toString = s"instance mirror for $instance"
}
// caches value class metadata, so that we minimize the work that needs to be done during Mirror.apply
private class DerivedValueClassMetadata(info: Type) {
val symbol = info.typeSymbol
val isDerivedValueClass = symbol.isDerivedValueClass
lazy val boxer = runtimeClass(symbol.toType).getDeclaredConstructors().head
lazy val unboxer = {
val fields @ (field :: _) = symbol.toType.decls.collect{ case ts: TermSymbol if ts.isParamAccessor && ts.isMethod => ts }.toList: @unchecked
assert(fields.lengthIs == 1, s"$symbol: $fields")
runtimeClass(symbol.asClass).getDeclaredMethod(field.name.toString)
}
}
private class JavaFieldMirror(val receiver: Any, val symbol: TermSymbol, metadata: DerivedValueClassMetadata)
extends FieldMirror {
def this(receiver: Any, symbol: TermSymbol) = this(receiver, symbol, new DerivedValueClassMetadata(symbol.info))
def bind(newReceiver: Any) = new JavaFieldMirror(newReceiver, symbol, metadata)
import metadata._
lazy val jfield = ensureAccessible(fieldToJava(symbol))
def get = {
val value = jfield get receiver
if (isDerivedValueClass) boxer.newInstance(value) else value
}
def set(value: Any) = {
// it appears useful to be able to set values of vals, therefore I'm disabling this check
// if (!symbol.isMutable) ErrorSetImmutableField(symbol)
jfield.set(receiver, if (isDerivedValueClass) unboxer.invoke(value) else value)
}
override def toString = s"field mirror for ${showDecl(symbol)} (bound to $receiver)"
}
// the "symbol == Any_getClass || symbol == Object_getClass" test doesn't cut it
// because both AnyVal and its primitive descendants define their own getClass methods
private def isGetClass(meth: MethodSymbol) = (meth.name string_== "getClass") && meth.paramss.flatten.isEmpty
private def isStringConcat(meth: MethodSymbol) = meth == String_+ || (meth.owner.isPrimitiveValueClass && meth.returnType =:= StringClass.toType)
lazy val bytecodelessMethodOwners =
Set[Symbol](AnyClass, AnyValClass, AnyRefClass, ObjectClass, ArrayClass) ++ ScalaPrimitiveValueClasses
lazy val bytecodefulObjectMethods =
Set[Symbol](Object_clone, Object_equals, Object_finalize, Object_hashCode, Object_toString, Object_notify, Object_notifyAll) ++ Object_wait.alternatives
private def isBytecodelessMethod(meth: MethodSymbol): Boolean = {
if (isGetClass(meth) || isStringConcat(meth) || meth.owner.isPrimitiveValueClass || meth == runDefinitions.Predef_classOf || meth.isMacro) return true
bytecodelessMethodOwners(meth.owner) && !bytecodefulObjectMethods(meth)
}
private def isByNameParam(p: Type) = isByNameParamType(p)
private def isValueClassParam(p: Type) = p.typeSymbol.isDerivedValueClass
// unlike other mirrors, method mirrors are created by a factory
// that's because we want to have decent performance
// therefore we move special cases into separate subclasses
// rather than have them on a hot path them in a unified implementation of the `apply` method
private def mkMethodMirror[T: ClassTag](receiver: T, symbol: MethodSymbol): MethodMirror =
if (isBytecodelessMethod(symbol))
new BytecodelessMethodMirror(receiver, symbol)
else if (mexists(symbol.paramss)(p => isByNameParam(p.info) || isValueClassParam(p.info)))
new JavaTransformingMethodMirror(receiver, symbol)
else
sumSize(symbol.paramss, 0) match {
case 0 => new JavaVanillaMethodMirror0(receiver, symbol)
case 1 => new JavaVanillaMethodMirror1(receiver, symbol)
case 2 => new JavaVanillaMethodMirror2(receiver, symbol)
case 3 => new JavaVanillaMethodMirror3(receiver, symbol)
case 4 => new JavaVanillaMethodMirror4(receiver, symbol)
case _ => new JavaVanillaMethodMirror(receiver, symbol)
}
private abstract class JavaMethodMirror(val symbol: MethodSymbol, protected val ret: DerivedValueClassMetadata) extends MethodMirror {
lazy val jmeth = ensureAccessible(methodToJava(symbol))
lazy val jconstr = ensureAccessible(constructorToJava(symbol))
def jinvokeraw(args: Seq[Any]) =
if (!symbol.isConstructor) jmeth.invoke(receiver, args.asInstanceOf[Seq[AnyRef]]: _*)
else if (receiver == null) jconstr.newInstance(args.asInstanceOf[Seq[AnyRef]]: _*)
else jconstr.newInstance((receiver +: args).asInstanceOf[Seq[AnyRef]]: _*)
def jinvoke(args: Seq[Any]): Any = {
val result = jinvokeraw(args)
if (!symbol.isConstructor && jmeth.getReturnType == java.lang.Void.TYPE) ()
else if (!symbol.isConstructor && ret.isDerivedValueClass) ret.boxer.newInstance(result.asInstanceOf[AnyRef])
else result
}
override def toString = {
val what = if (symbol.isConstructor) "constructor mirror" else "method mirror"
s"$what for ${showDecl(symbol)} (bound to $receiver)"
}
}
private class JavaVanillaMethodMirror(val receiver: Any, symbol: MethodSymbol, ret: DerivedValueClassMetadata)
extends JavaMethodMirror(symbol, ret) {
def this(receiver: Any, symbol: MethodSymbol) = this(receiver, symbol, new DerivedValueClassMetadata(symbol.returnType))
def bind(newReceiver: Any) = new JavaVanillaMethodMirror(newReceiver, symbol, ret)
def apply(args: Any*): Any = jinvoke(args)
}
private class JavaVanillaMethodMirror0(receiver: Any, symbol: MethodSymbol, ret: DerivedValueClassMetadata)
extends JavaVanillaMethodMirror(receiver, symbol, ret) {
def this(receiver: Any, symbol: MethodSymbol) = this(receiver, symbol, new DerivedValueClassMetadata(symbol.returnType))
override def bind(newReceiver: Any) = new JavaVanillaMethodMirror0(newReceiver, symbol, ret)
override def jinvokeraw(args: Seq[Any]) =
if (!symbol.isConstructor) jmeth.invoke(receiver)
else if (receiver == null) jconstr.newInstance()
else jconstr.newInstance(receiver.asInstanceOf[AnyRef])
}
private class JavaVanillaMethodMirror1(receiver: Any, symbol: MethodSymbol, ret: DerivedValueClassMetadata)
extends JavaVanillaMethodMirror(receiver, symbol, ret) {
def this(receiver: Any, symbol: MethodSymbol) = this(receiver, symbol, new DerivedValueClassMetadata(symbol.returnType))
override def bind(newReceiver: Any) = new JavaVanillaMethodMirror1(newReceiver, symbol, ret)
override def jinvokeraw(args: Seq[Any]) =
if (!symbol.isConstructor) jmeth.invoke(receiver, args(0).asInstanceOf[AnyRef])
else if (receiver == null) jconstr.newInstance(args(0).asInstanceOf[AnyRef])
else jconstr.newInstance(receiver.asInstanceOf[AnyRef], args(0).asInstanceOf[AnyRef])
}
private class JavaVanillaMethodMirror2(receiver: Any, symbol: MethodSymbol, ret: DerivedValueClassMetadata)
extends JavaVanillaMethodMirror(receiver, symbol, ret) {
def this(receiver: Any, symbol: MethodSymbol) = this(receiver, symbol, new DerivedValueClassMetadata(symbol.returnType))
override def bind(newReceiver: Any) = new JavaVanillaMethodMirror2(newReceiver, symbol, ret)
override def jinvokeraw(args: Seq[Any]) =
if (!symbol.isConstructor) jmeth.invoke(receiver, args(0).asInstanceOf[AnyRef], args(1).asInstanceOf[AnyRef])
else if (receiver == null) jconstr.newInstance(args(0).asInstanceOf[AnyRef], args(1).asInstanceOf[AnyRef])
else jconstr.newInstance(receiver.asInstanceOf[AnyRef], args(0).asInstanceOf[AnyRef], args(1).asInstanceOf[AnyRef])
}
private class JavaVanillaMethodMirror3(receiver: Any, symbol: MethodSymbol, ret: DerivedValueClassMetadata)
extends JavaVanillaMethodMirror(receiver, symbol, ret) {
def this(receiver: Any, symbol: MethodSymbol) = this(receiver, symbol, new DerivedValueClassMetadata(symbol.returnType))
override def bind(newReceiver: Any) = new JavaVanillaMethodMirror3(newReceiver, symbol, ret)
override def jinvokeraw(args: Seq[Any]) =
if (!symbol.isConstructor) jmeth.invoke(receiver, args(0).asInstanceOf[AnyRef], args(1).asInstanceOf[AnyRef], args(2).asInstanceOf[AnyRef])
else if (receiver == null) jconstr.newInstance(args(0).asInstanceOf[AnyRef], args(1).asInstanceOf[AnyRef], args(2).asInstanceOf[AnyRef])
else jconstr.newInstance(receiver.asInstanceOf[AnyRef], args(0).asInstanceOf[AnyRef], args(1).asInstanceOf[AnyRef], args(2).asInstanceOf[AnyRef])
}
private class JavaVanillaMethodMirror4(receiver: Any, symbol: MethodSymbol, ret: DerivedValueClassMetadata)
extends JavaVanillaMethodMirror(receiver, symbol, ret) {
def this(receiver: Any, symbol: MethodSymbol) = this(receiver, symbol, new DerivedValueClassMetadata(symbol.returnType))
override def bind(newReceiver: Any) = new JavaVanillaMethodMirror4(newReceiver, symbol, ret)
override def jinvokeraw(args: Seq[Any]) =
if (!symbol.isConstructor) jmeth.invoke(receiver, args(0).asInstanceOf[AnyRef], args(1).asInstanceOf[AnyRef], args(2).asInstanceOf[AnyRef], args(3).asInstanceOf[AnyRef])
else if (receiver == null) jconstr.newInstance(args(0).asInstanceOf[AnyRef], args(1).asInstanceOf[AnyRef], args(2).asInstanceOf[AnyRef], args(3).asInstanceOf[AnyRef])
else jconstr.newInstance(receiver.asInstanceOf[AnyRef], args(0).asInstanceOf[AnyRef], args(1).asInstanceOf[AnyRef], args(2).asInstanceOf[AnyRef], args(3).asInstanceOf[AnyRef])
}
// caches MethodSymbol metadata, so that we minimize the work that needs to be done during Mirror.apply
// TODO: vararg is only supported in the last parameter list (scala/bug#6182), so we don't need to worry about the rest for now
private class MethodMetadata(symbol: MethodSymbol) {
private[this] val params = symbol.paramss.flatten.toArray
private[this] val vcMetadata = params.map(p => new DerivedValueClassMetadata(p.info))
val isByName = params.map(p => isByNameParam(p.info))
def isDerivedValueClass(i: Int) = vcMetadata(i).isDerivedValueClass
def paramUnboxers(i: Int) = vcMetadata(i).unboxer
val paramCount = params.length
val ret = new DerivedValueClassMetadata(symbol.returnType)
}
private class JavaTransformingMethodMirror(val receiver: Any, symbol: MethodSymbol, metadata: MethodMetadata)
extends JavaMethodMirror(symbol, metadata.ret) {
def this(receiver: Any, symbol: MethodSymbol) = this(receiver, symbol, new MethodMetadata(symbol))
override def bind(newReceiver: Any) = new JavaTransformingMethodMirror(newReceiver, symbol, metadata)
import metadata._
def apply(args: Any*): Any = {
val args1 = new Array[Any](args.length)
var i = 0
while (i < args1.length) {
val arg = args(i)
args1(i) = (
if (i >= paramCount) arg // don't transform varargs
else if (isByName(i)) () => arg // don't transform by-name value class params
else if (isDerivedValueClass(i)) paramUnboxers(i).invoke(arg) // do get the underlying value
else arg // don't molest anything else
)
i += 1
}
jinvoke(ArraySeq.unsafeWrapArray(args1))
}
}
private class BytecodelessMethodMirror[T: ClassTag](val receiver: T, val symbol: MethodSymbol)
extends MethodMirror {
def bind(newReceiver: Any) = new BytecodelessMethodMirror(newReceiver.asInstanceOf[T], symbol)
override def toString = s"bytecodeless method mirror for ${showDecl(symbol)} (bound to $receiver)"
def apply(args: Any*): Any = {
// checking type conformance is too much of a hassle, so we don't do it here
// actually it's not even necessary, because we manually dispatch arguments below
val params = symbol.paramss.flatten
val perfectMatch = args.length == params.length
// todo. this doesn't account for multiple vararg parameter lists
// however those aren't supported by the mirror API: https://github.com/scala/bug/issues/6182
// hence I leave this code as is, to be fixed when the corresponding bug is fixed
val varargMatch = args.length >= params.length - 1 && isVarArgsList(params)
if (!perfectMatch && !varargMatch) {
val n_arguments = if (isVarArgsList(params)) s"${params.length - 1} or more" else s"${params.length}"
val s_arguments = if (params.lengthIs == 1 && !isVarArgsList(params)) "argument" else "arguments"
abort(s"${showDecl(symbol)} takes $n_arguments $s_arguments")
}
def objReceiver = receiver.asInstanceOf[AnyRef]
def objArg0 = args(0).asInstanceOf[AnyRef]
def objArgs = args.asInstanceOf[Seq[AnyRef]]
def fail(msg: String) = abort(msg + ", it cannot be invoked with mirrors")
def invokePrimitiveMethod = {
val jmeths = classOf[BoxesRunTime].getDeclaredMethods.filter(_.getName == nme.primitiveMethodName(symbol.name).toString)
assert(jmeths.length == 1, jmeths.toList)
val jmeth = jmeths.head
val result = jmeth.invoke(null, (objReceiver +: objArgs).asInstanceOf[Seq[AnyRef]]: _*)
if (jmeth.getReturnType == java.lang.Void.TYPE) ()
else result
}
symbol match {
case Any_== | Object_== => objReceiver == objArg0
case Any_!= | Object_!= => objReceiver != objArg0
case Any_## | Object_## => objReceiver.##
case Any_equals => receiver.equals(objArg0)
case Any_hashCode => receiver.hashCode
case Any_toString => receiver.toString
case Object_eq => objReceiver eq objArg0
case Object_ne => objReceiver ne objArg0
case Object_synchronized => objReceiver.synchronized(objArg0)
case sym if isGetClass(sym) => preciseClass(receiver)
case Any_asInstanceOf => fail("Any.asInstanceOf requires a type argument")
case Any_isInstanceOf => fail("Any.isInstanceOf requires a type argument")
case Object_asInstanceOf => fail("AnyRef.%s is an internal method" format symbol.name)
case Object_isInstanceOf => fail("AnyRef.%s is an internal method" format symbol.name)
case Array_length => ScalaRunTime.array_length(objReceiver)
case Array_apply => ScalaRunTime.array_apply(objReceiver, args(0).asInstanceOf[Int])
case Array_update => ScalaRunTime.array_update(objReceiver, args(0).asInstanceOf[Int], args(1))
case Array_clone => ScalaRunTime.array_clone(objReceiver)
case sym if isStringConcat(sym) => receiver.toString + objArg0
case sym if sym.owner.isPrimitiveValueClass => invokePrimitiveMethod
case sym if sym == Predef_classOf => fail("Predef.classOf is a compile-time function")
case sym if sym.isMacro => fail(s"${symbol.fullName} is a macro, i.e. a compile-time function")
case _ => abort(s"unsupported symbol $symbol when invoking $this")
}
}
}
private abstract class JavaTemplateMirror
extends TemplateMirror {
def outer: AnyRef
def erasure: ClassSymbol
}
private class JavaClassMirror(val outer: AnyRef, val symbol: ClassSymbol)
extends JavaTemplateMirror with ClassMirror {
def erasure = symbol
def isStatic = false
def reflectConstructor(constructor: MethodSymbol) = {
checkConstructorOf(constructor, symbol)
mkMethodMirror(outer, constructor)
}
override def toString = s"class mirror for ${symbol.fullName} (bound to $outer)"
}
private class JavaModuleMirror(val outer: AnyRef, val symbol: ModuleSymbol)
extends JavaTemplateMirror with ModuleMirror {
def erasure = symbol.moduleClass.asClass
def isStatic = true
def instance = {
if (symbol.isTopLevel)
staticSingletonInstance(classLoader, symbol.fullName)
else
if (outer == null) staticSingletonInstance(classToJava(symbol.moduleClass.asClass))
else innerSingletonInstance(outer, symbol.name.toString)
}
override def toString = s"module mirror for ${symbol.fullName} (bound to $outer)"
}
// -------------------- Java to Scala -----------------------------------
/** Does method `meth` erase to Java method `jmeth`?
* This is true if the Java method type is the same as the Scala method type after performing
* all Scala-specific transformations in InfoTransformers. (to be done)
*/
private def erasesTo(meth: Symbol, jmeth: jMethod): Boolean = {
val mtpe = transformedType(meth)
(mtpe.paramTypes map runtimeClass) == jmeth.getParameterTypes.toList &&
runtimeClass(mtpe.resultType) == jmeth.getReturnType
}
private def erasesTo(meth: Symbol, jconstr: jConstructor[_]): Boolean = {
val mtpe = transformedType(meth)
(mtpe.paramTypes map runtimeClass) == jconstr.getParameterTypes.toList &&
runtimeClass(mtpe.resultType) == jconstr.getDeclaringClass
}
def javaClass(path: String): jClass[_] =
jClass.forName(path, false, classLoader)
/** Does `path` correspond to a Java class with that fully qualified name in the current class loader? */
def tryJavaClass(path: String): Option[jClass[_]] = (
try Some(javaClass(path))
catch { case ex @ (_: LinkageError | _: ClassNotFoundException) => None } // TODO - log
)
/** The mirror that corresponds to the classloader that original defined the given Java class */
def mirrorDefining(jclazz: jClass[_]): MirrorImpl = {
val cl = jclazz.getClassLoader
if (cl == this.classLoader) this else runtimeMirror(cl)
}
private object unpickler extends UnPickler {
val symbolTable: thisUniverse.type = thisUniverse
}
/** how connected????
* Generate types for top-level Scala root class and root companion object
* from the pickled information stored in a corresponding Java class
* @param clazz The top-level Scala class for which info is unpickled
* @param module The top-level Scala companion object for which info is unpickled
* @param jclazz The Java class which contains the unpickled information in a
* ScalaSignature or ScalaLongSignature annotation.
*/
def unpickleClass(clazz: ClassSymbol, module: ModuleSymbol, jclazz: jClass[_]): Unit = {
def markAbsent(tpe: Type) = setAllInfos(clazz, module, tpe)
def handleError(ex: Exception) = {
markAbsent(ErrorType)
if (settings.isDebug) ex.printStackTrace()
val msg = ex.getMessage()
MissingRequirementError.signal(
(if (msg eq null) "reflection error while loading " + clazz.name
else "error while loading " + clazz.name) + ", " + msg)
}
// don't use classOf[scala.reflect.ScalaSignature] here, because it will use getClass.getClassLoader, not mirror's classLoader
// don't use asInstanceOf either because of the same reason (lol, I cannot believe I fell for it)
// don't use structural types to simplify reflective invocations because of the same reason
// TODO scala/bug#9296 duplicated code, refactor
def loadAnnotation(name: String): Option[java.lang.annotation.Annotation] =
tryJavaClass(name) flatMap { annotClass =>
val anns = jclazz.getAnnotations
val result = anns find (_.annotationType == annotClass)
if (result.isEmpty && (anns exists (_.annotationType.getName == name)))
throw new ClassNotFoundException(
sm"""Mirror classloader mismatch: $jclazz (loaded by ${ReflectionUtils.show(jclazz.getClassLoader)})
|is unrelated to the mirror's classloader: (${ReflectionUtils.show(classLoader)})""")
result
}
def loadBytes[T: ClassTag](name: String): Option[T] =
loadAnnotation(name) map { ssig =>
val bytesMethod = ssig.annotationType.getMethod("bytes")
bytesMethod.invoke(ssig).asInstanceOf[T]
}
try {
markAbsent(NoType)
loadBytes[String]("scala.reflect.ScalaSignature") match {
case Some(ssig) =>
info(s"unpickling Scala $clazz and $module, owner = ${clazz.owner}")
val bytes = ssig.getBytes(UTF_8)
val len = ByteCodecs.decode(bytes)
assignAssociatedFile(clazz, module, jclazz)
unpickler.unpickle(bytes take len, 0, clazz, module, jclazz.getName)
markAllCompleted(clazz, module)
case None =>
loadBytes[Array[String]]("scala.reflect.ScalaLongSignature") match {
case Some(slsig) =>
info(s"unpickling Scala $clazz and $module with long Scala signature")
val encoded = slsig.flatMap(_.getBytes(UTF_8))
val len = ByteCodecs.decode(encoded)
val decoded = encoded.take(len)
assignAssociatedFile(clazz, module, jclazz)
unpickler.unpickle(decoded, 0, clazz, module, jclazz.getName)
markAllCompleted(clazz, module)
case None =>
// class does not have a Scala signature; it's a Java class
info("translating reflection info for Java " + jclazz) //debug
initClassAndModule(clazz, module, new FromJavaClassCompleter(clazz, module, jclazz))
}
}
} catch {
case ex: MissingRequirementError =>
handleError(ex)
case ex: IOException =>
handleError(ex)
}
}
/**
* A fresh Scala type parameter that corresponds to a Java type variable.
* The association between Scala type parameter and Java type variable is entered in the cache.
* @param jtvar The Java type variable
*/
private def createTypeParameter(jtvar: jTypeVariable[_ <: GenericDeclaration]): TypeSymbol = {
val tparam = sOwner(jtvar).newTypeParameter(newTypeName(jtvar.getName))
.setInfo(new TypeParamCompleter(jtvar))
markFlagsCompleted(tparam)(mask = AllFlags)
tparamCache.enter(jtvar, tparam)
tparam
}
/**
* A completer that fills in the type of a Scala type parameter from the bounds of a Java type variable.
* @param jtvar The Java type variable
*/
private class TypeParamCompleter(jtvar: jTypeVariable[_ <: GenericDeclaration]) extends LazyType with FlagAgnosticCompleter {
override def load(sym: Symbol) = complete(sym)
override def complete(sym: Symbol) = {
sym setInfo TypeBounds.upper(glb(jtvar.getBounds.toList map typeToScala))
markAllCompleted(sym)
}
}
private def assignAssociatedFile(clazz: Symbol, module: Symbol, jclazz: jClass[_]): Unit = {
val associatedFile = ReflectionUtils.associatedFile(jclazz)
clazz.associatedFile = associatedFile
if (module != NoSymbol) module.associatedFile = associatedFile
}
/**
* Copy all annotations of Java annotated element `jann` over to Scala symbol `sym`.
* Also creates `@throws`, `@transient`, `@native`, and `@volatile` annotations if necessary.
* Pre: `sym` is already initialized with a concrete type.
* Note: If `sym` is a method or constructor, its parameter annotations are copied as well.
*/
private def copyAnnotations(sym: Symbol, jann: AnnotatedElement): Unit = {
sym setAnnotations (jann.getAnnotations map JavaAnnotationProxy).toList
// scala/bug#7065: we're not using getGenericExceptionTypes here to be consistent with ClassfileParser
val jexTpes = jann match {
case jm: jMethod => jm.getExceptionTypes.toList
case jconstr: jConstructor[_] => jconstr.getExceptionTypes.toList
case _ => Nil
}
jexTpes foreach (jexTpe => sym.addThrowsAnnotation(classSymbol(jexTpe)))
jann match {
case mem: jMember =>
mem.javaFlags.toScalaAnnotations(thisUniverse) foreach (ann => sym.addAnnotation(ann))
case _ =>
}
}
private implicit class jClassOps(val clazz: jClass[_]) {
def javaFlags: JavaAccFlags = JavaAccFlags(clazz)
def scalaFlags: Long = javaFlags.toScalaFlags
}
private implicit class jMemberOps(val member: jMember) {
def javaFlags: JavaAccFlags = JavaAccFlags(member)
def scalaFlags: Long = javaFlags.toScalaFlags
}
/**
* A completer that fills in the types of a Scala class and its companion object
* by copying corresponding type info from a Java class. This completer is used
* to reflect classes in Scala that do not have a Scala pickle info, be it
* because they are local classes or have been compiled from Java sources.
* @param clazz The Scala class for which info is copied
* @param module The Scala companion object for which info is copied
* @param jclazz The Java class
*/
private class FromJavaClassCompleter(clazz: Symbol, module: Symbol, jclazz: jClass[_]) extends LazyType with JavaClassCompleter with FlagAgnosticCompleter {
// one doesn't need to do non-trivial computations to assign flags for Java-based reflection artifacts
// therefore I'm moving flag-assigning logic from completion to construction
val flags = jclazz.scalaFlags
clazz setFlag (flags | JAVA)
if (module != NoSymbol) {
module setFlag (flags & PRIVATE | JAVA)
module.moduleClass setFlag (flags & PRIVATE | JAVA)
}
markFlagsCompleted(clazz, module)(mask = AllFlags)
/** used to avoid cycles while initializing classes */
private[this] var parentsLevel = 0
private[this] var pendingLoadActions: List[() => Unit] = Nil
private[this] val relatedSymbols = clazz +: (if (module != NoSymbol) List(module, module.moduleClass) else Nil)
override def load(sym: Symbol): Unit = {
debugInfo("completing from Java " + sym + "/" + clazz.fullName)//debug
assert(sym == clazz || (module != NoSymbol && (sym == module || sym == module.moduleClass)), sym)
assignAssociatedFile(clazz, module, jclazz)
propagatePackageBoundary(jclazz, relatedSymbols: _*)
copyAnnotations(clazz, jclazz)
// to do: annotations to set also for module?
clazz setInfo new LazyPolyType(jclazz.getTypeParameters.toList map createTypeParameter)
if (module != NoSymbol) {
module setInfo module.moduleClass.tpe
module.moduleClass setInfo new LazyPolyType(List())
}
}
override def complete(sym: Symbol): Unit = {
load(sym)
completeRest()
markAllCompleted(clazz, module)
}
def completeRest(): Unit = gilSynchronized {
val tparams = clazz.rawInfo.typeParams
val parents = try {
parentsLevel += 1
val jsuperclazz = jclazz.getGenericSuperclass
val ifaces = jclazz.getGenericInterfaces.toList map typeToScala
if (jclazz.isInterface) ObjectTpe :: ifaces // interfaces have Object as superclass in the classfile (see jvm spec), but getGenericSuperclass seems to return null
else (if (jsuperclazz == null) AnyTpe else typeToScala(jsuperclazz)) :: ifaces
} finally {
parentsLevel -= 1
}
clazz setInfo GenPolyType(tparams, new ClassInfoType(parents, newScope, clazz))
if (module != NoSymbol) {
module.moduleClass setInfo new ClassInfoType(List(), newScope, module.moduleClass)
}
def enter(sym: Symbol, mods: JavaAccFlags) = followStatic(clazz, module, mods).info.decls enter sym
def enterEmptyCtorIfNecessary(): Unit = {
if (jclazz.getConstructors.isEmpty)
clazz.info.decls.enter(clazz.newClassConstructor(NoPosition))
}
for (jinner <- jclazz.getDeclaredClasses) {
jclassAsScala(jinner) // inner class is entered as a side-effect
// no need to call enter explicitly
}
pendingLoadActions ::= { () =>
jclazz.getDeclaredFields foreach (f => enter(jfieldAsScala(f), f.javaFlags))
jclazz.getDeclaredMethods foreach (m => enter(jmethodAsScala(m), m.javaFlags))
jclazz.getConstructors foreach (c => enter(jconstrAsScala(c), c.javaFlags))
enterEmptyCtorIfNecessary()
}
if (parentsLevel == 0) {
while (pendingLoadActions.nonEmpty) {
val item = pendingLoadActions.head
pendingLoadActions = pendingLoadActions.tail
item()
}
}
}
class LazyPolyType(override val typeParams: List[Symbol]) extends LazyType with FlagAgnosticCompleter {
override def complete(sym: Symbol): Unit = {
completeRest()
markAllCompleted(clazz, module)
}
}
}
/**
* If Java modifiers `mods` contain STATIC, return the module class
* of the companion module of `clazz`, otherwise the class `clazz` itself.
*/
private def followStatic(clazz: Symbol, mods: JavaAccFlags): Symbol = followStatic(clazz, clazz.companionModule, mods)
private def followStatic(clazz: Symbol, module: Symbol, mods: JavaAccFlags): Symbol =
// scala/bug#8196 `orElse(clazz)` needed for implementation details of the backend, such as the static
// field containing the cache for structural calls.
if (mods.isStatic) module.moduleClass.orElse(clazz) else clazz
/**
* Certain method of the Java reflection api cannot be used on classfiles created by Scala.
* See the comment in test/files/jvm/javaReflection/Test.scala. The methods are
*
* public String getSimpleName()
* public boolean isAnonymousClass()
* public boolean isLocalClass()
* public String getCanonicalName()
* public boolean isSynthetic()
*
* TODO - find all such calls and wrap them.
* TODO - create mechanism to avoid the recurrence of unwrapped calls.
*/
implicit class RichClass(jclazz: jClass[_]) {
// As explained in the javaReflection test, Class.isLocalClass is true for all non-member
// nested classes in Scala. This is fine per se, however the implementation may throw an
// InternalError. We therefore re-implement it here.
// TODO: this method should be renamed to `isLocalOrAnonymousClass`.
// due to bin compat that's only possible in 2.12, we cannot introduce a new alias in 2.11.
def isLocalClass0: Boolean = jclazz.getEnclosingClass != null && !jclazz.isMemberClass
}
/**
* The Scala owner of the Scala class corresponding to the Java class `jclazz`
*/
// @eb: a weird classloader might return a null package for something with a non-empty package name
// for example, https://groups.google.com/group/scala-internals/browse_thread/thread/7be09ff8f67a1e5c
// in that case we could invoke packageNameToScala(jPackageName) and, probably, be okay
// however, I think, it's better to blow up, since weirdness of the class loader might bite us elsewhere
// [martin] I think it's better to be forgiving here. Restoring packageNameToScala.
private def sOwner(jclazz: jClass[_]): Symbol = jclazz match {
case PrimitiveOrArray() => ScalaPackageClass
case EnclosedInMethod(jowner) => methodToScala(jowner)
case EnclosedInConstructor(jowner) => constructorToScala(jowner)
case EnclosedInClass(jowner) => followStatic(classToScala(jowner), jclazz.javaFlags)
case EnclosedInPackage(jowner) => packageToScala(jowner).moduleClass
case _ => packageNameToScala(jclazz.getName take jclazz.getName.lastIndexOf('.')).moduleClass
}
/**
* The Scala owner of the Scala symbol corresponding to the Java member `jmember`
*/
private def sOwner(jmember: jMember): Symbol = {
followStatic(classToScala(jmember.getDeclaringClass), jmember.javaFlags)
}
/**
* The Scala owner of the Scala type parameter corresponding to the Java type variable `jtvar`
*/
private def sOwner(jtvar: jTypeVariable[_ <: GenericDeclaration]): Symbol =
genericDeclarationToScala(jtvar.getGenericDeclaration)
/**
* Find declarations or definition in class `clazz` that maps to a Java
* entity with name `jname`. Because of name-mangling, this is more difficult
* than a simple name-based lookup via `decl`. If `decl` fails, members
* that start with the given name are searched instead.
*/
private def lookup(clazz: Symbol, jname: String): Symbol = {
def approximateMatch(sym: Symbol, jstr: String): Boolean = (
(sym.name string_== jstr)
|| sym.isPrivate && (nme.expandedName(sym.name.toTermName, sym.owner) string_== jstr)
)
clazz.info.decl(newTermName(jname)) orElse {
(clazz.info.decls.iterator filter (approximateMatch(_, jname))).toList match {
case List() => NoSymbol
case List(sym) => sym
case alts => clazz.newOverloaded(alts.head.tpe.prefix, alts)
}
}
}
/**
* The Scala method corresponding to given Java method.
* @param jmeth The Java method
* @return A Scala method object that corresponds to `jmeth`.
*/
def methodToScala(jmeth: jMethod): MethodSymbol =
toScala(methodCache, jmeth)(_ methodToScala1 _)
private def methodToScala1(jmeth: jMethod): MethodSymbol = {
val jOwner = jmeth.getDeclaringClass
val preOwner = classToScala(jOwner)
val owner = followStatic(preOwner, jmeth.javaFlags)
(lookup(owner, jmeth.getName) suchThat (erasesTo(_, jmeth)) orElse jmethodAsScala(jmeth))
.asMethod
}
/**
* The Scala constructor corresponding to given Java constructor.
* @param jconstr The Java constructor
* @return A Scala method object that corresponds to `jconstr`.
*/
def constructorToScala(jconstr: jConstructor[_]): MethodSymbol =
toScala(constructorCache, jconstr)(_ constructorToScala1 _)
private def constructorToScala1(jconstr: jConstructor[_]): MethodSymbol = {
val owner = followStatic(classToScala(jconstr.getDeclaringClass), jconstr.javaFlags)
(lookup(owner, jconstr.getName) suchThat (erasesTo(_, jconstr)) orElse jconstrAsScala(jconstr))
.asMethod
}
/**
* The Scala package corresponding to given Java package
*/
def packageToScala(jpkg: jPackage): ModuleSymbol = packageCache.toScala(jpkg) {
makeScalaPackage(jpkg.getName)
}
/**
* The Scala package with given fully qualified name.
*/
def packageNameToScala(fullname: String): ModuleSymbol = {
if (fullname == "") EmptyPackage
else {
val jpkg = jPackage.getPackage(fullname)
if (jpkg != null) packageToScala(jpkg) else makeScalaPackage(fullname)
}
}
/**
* The Scala package with given fully qualified name. Unlike `packageNameToScala`,
* this one bypasses the cache.
*/
private[JavaMirrors] def makeScalaPackage(fullname: String): ModuleSymbol = if (fullname == "") EmptyPackage else gilSynchronized {
val split = fullname lastIndexOf '.'
val ownerModule: ModuleSymbol =
if (split > 0) packageNameToScala(fullname take split) else this.RootPackage
val owner = ownerModule.moduleClass
val name = TermName(fullname) drop split + 1
// Be tolerant of clashes between, e.g. a subpackage and a package object member. These could arise
// under separate compilation.
val opkg = owner.info.decl(name).filter(_.hasPackageFlag)
if (opkg != NoSymbol)
opkg.asModule
else {
val pkg = owner.newPackage(name)
pkg.moduleClass setInfo new LazyPackageType
pkg setInfoAndEnter pkg.moduleClass.tpe
markFlagsCompleted(pkg)(mask = AllFlags)
info("made Scala "+pkg)
pkg
}
}
private def scalaSimpleName(jclazz: jClass[_]): TypeName = {
val owner = sOwner(jclazz)
val enclosingClass = jclazz.getEnclosingClass
var prefix = if (enclosingClass != null) enclosingClass.getName else ""
val isObject = owner.isModuleClass && !owner.isPackageClass
if (isObject && !prefix.endsWith(nme.MODULE_SUFFIX_STRING)) prefix += nme.MODULE_SUFFIX_STRING
assert(jclazz.getName.startsWith(prefix), s"Class name ${jclazz.getName} missing prefix $prefix")
var name = jclazz.getName.substring(prefix.length)
name = name.substring(name.lastIndexOf(".") + 1)
newTypeName(name)
}
/**
* The Scala class that corresponds to a given Java class.
* @param jclazz The Java class
* @return A Scala class symbol that reflects all elements of the Java class,
* in the form they appear in the Scala pickling info, or, if that is
* not available, wrapped from the Java reflection info.
*/
def classToScala(jclazz: jClass[_]): ClassSymbol =
toScala(classCache, jclazz)(_ classToScala1 _)
private def classToScala1(jclazz: jClass[_]): ClassSymbol = {
val jname = newTypeName(jclazz.getName)
if (jname == fulltpnme.RuntimeNothing) NothingClass
else if (jname == fulltpnme.RuntimeNull) NullClass
else {
val owner = sOwner(jclazz)
val simpleName = scalaSimpleName(jclazz)
def lookupClass = {
def coreLookup(name: Name): Symbol =
owner.info.decl(name) orElse {
if (name.startsWith(nme.NAME_JOIN_STRING)) coreLookup(name drop 1) else NoSymbol
}
if (nme.isModuleName(simpleName))
coreLookup(simpleName.dropModule.toTermName) map (_.moduleClass)
else
coreLookup(simpleName)
}
val cls =
if (jclazz.isMemberClass)
lookupClass
else if (jclazz.isLocalClass0)
// local classes and implementation classes not preserved by unpickling - treat as Java
//
// upd. but only if they cannot be loaded as top-level classes
// otherwise we may mistake mangled symbolic names for mangled nested names
//
// in case when a Java binary name can be treated both as a top-level class and as a nested class
// (as described in https://groups.google.com/group/scala-internals/browse_thread/thread/10855403bbf04298)
// we check for a top-level class first
// this is totally correct, because a top-level class and a nested class with the same name cannot coexist
// so it's either one or another, but not both - therefore we always load $-bearing classes correctly
lookupClass orElse jclassAsScala(jclazz)
else if (jclazz.isArray)
ArrayClass
else
javaTypeToValueClass(jclazz) orElse lookupClass
assert (cls.isType,
(if (cls != NoSymbol) s"not a type: symbol $cls" else "no symbol could be") +
s" loaded from $jclazz in $owner with name $simpleName and classloader $classLoader")
cls.asClass
}
}
/**
* The Scala type parameter that corresponds to a given Java type parameter.
* @param jparam The Java type parameter
* @return A Scala type parameter symbol that has the same owner and name as the Java type parameter
*/
def typeParamToScala(jparam: jTypeVariable[_ <: GenericDeclaration]): TypeSymbol =
toScala(tparamCache, jparam)(_ typeParamToScala1 _)
private def typeParamToScala1(jparam: jTypeVariable[_ <: GenericDeclaration]): TypeSymbol = {
val owner = genericDeclarationToScala(jparam.getGenericDeclaration)
owner.info match {
case PolyType(tparams, _) => tparams.find(_.name string_== jparam.getName).get.asType
case x => throw new MatchError(x)
}
}
/**
* The Scala symbol that corresponds to a given Java generic declaration (class, method, or constructor)
*/
def genericDeclarationToScala(jdecl: GenericDeclaration): Symbol = jdecl match {
case jclazz: jClass[_] => classToScala(jclazz)
case jmeth: jMethod => methodToScala(jmeth)
case jconstr: jConstructor[_] => constructorToScala(jconstr)
case x => throw new MatchError(x)
}
def reflectMemberToScala(m: jMember): Symbol = m match {
case x: GenericDeclaration => genericDeclarationToScala(x)
case x: jField => jfieldAsScala(x)
case x => throw new MatchError(x)
}
/**
* Given some Java type arguments, a corresponding list of Scala types, plus potentially
* some existentially bound type variables that represent wildcard arguments.
*/
private def targsToScala(owner: Symbol, args: List[jType]): (List[Type], List[TypeSymbol]) = {
val tparams = new ListBuffer[TypeSymbol]
def targToScala(arg: jType): Type = arg match {
case jwild: WildcardType =>
val tparam = owner.newExistential(newTypeName("T$" + tparams.length))
.setInfo(TypeBounds(
lub(jwild.getLowerBounds.toList map typeToScala),
glb(jwild.getUpperBounds.toList map typeToScala)))
tparams += tparam
typeRef(NoPrefix, tparam, List())
case _ =>
typeToScala(arg)
}
(args map targToScala, tparams.toList)
}
/**
* The Scala type that corresponds to given Java type
*/
def typeToScala(jtpe: jType): Type = jtpe match {
case jclazz: jClass[_] =>
if (jclazz.isArray)
arrayType(typeToScala(jclazz.getComponentType))
else {
val clazz = classToScala(jclazz)
rawToExistential(typeRef(clazz.owner.thisType, clazz, List())) match {
case ObjectTpe => ObjectTpeJava
case tp => tp
}
}
case japplied: ParameterizedType =>
// https://stackoverflow.com/questions/5767122/parameterizedtype-getrawtype-returns-j-l-r-type-not-class
val jcls = japplied.getRawType.asInstanceOf[jClass[_]]
val sym = classToScala(jcls)
val isStatic = java.lang.reflect.Modifier.isStatic(jcls.getModifiers)
val pre = if (!isStatic && (japplied.getOwnerType ne null)) typeToScala(japplied.getOwnerType) else sym.owner.thisType
val args0 = japplied.getActualTypeArguments
val (args, bounds) = targsToScala(pre.typeSymbol, args0.toList)
newExistentialType(bounds, typeRef(pre, sym, args))
case jarr: GenericArrayType =>
var elemtp = typeToScala(jarr.getGenericComponentType)
if (elemtp.typeSymbol.isAbstractType && elemtp.upperBound =:= ObjectTpe) {
elemtp = intersectionType(List(elemtp, ObjectTpe))
}
arrayType(elemtp)
case jtvar: jTypeVariable[_] =>
val tparam = typeParamToScala(jtvar)
typeRef(NoPrefix, tparam, List())
case x => throw new MatchError(x)
}
/**
* The Scala class that corresponds to given Java class without taking
* Scala pickling info into account.
* @param jclazz The Java class
* @return A Scala class symbol that wraps all reflection info of `jclazz`
*/
private def jclassAsScala(jclazz: jClass[_]): ClassSymbol =
toScala(classCache, jclazz)(_ jclassAsScala1 _)
private def jclassAsScala1(jclazz: jClass[_]): ClassSymbol = {
val owner = sOwner(jclazz)
val name = scalaSimpleName(jclazz)
val completer = (clazz: Symbol, module: Symbol) => new FromJavaClassCompleter(clazz, module, jclazz)
initAndEnterClassAndModule(owner, name, completer)._1
}
/**
* The Scala field that corresponds to given Java field without taking
* Scala pickling info into account.
* @param jfield The Java field
* @return A Scala value symbol that wraps all reflection info of `jfield`
*/
private def jfieldAsScala(jfield: jField): TermSymbol =
toScala(fieldCache, jfield)(_ jfieldAsScala1 _)
private def jfieldAsScala1(jfield: jField): TermSymbol = {
val field = sOwner(jfield)
.newValue(newTermName(jfield.getName), NoPosition, jfield.scalaFlags)
.setInfo(typeToScala(jfield.getGenericType))
fieldCache.enter(jfield, field)
propagatePackageBoundary(jfield, field)
copyAnnotations(field, jfield)
markAllCompleted(field)
field
}
private def setMethType(meth: Symbol, tparams: List[Symbol], params: List[Symbol], restpe: Type) = {
meth setInfo GenPolyType(tparams, MethodType(params, restpe))
}
/**
* The Scala method that corresponds to given Java method without taking
* Scala pickling info into account.
* @param jmeth The Java method
* @return A Scala method symbol that wraps all reflection info of `jmethod`
*/
private def jmethodAsScala(jmeth: jMethod): MethodSymbol =
toScala(methodCache, jmeth)(_ jmethodAsScala1 _)
private def jmethodAsScala1(jmeth: jMethod): MethodSymbol = {
val clazz = sOwner(jmeth)
val meth = clazz.newMethod(newTermName(jmeth.getName), NoPosition, jmeth.scalaFlags)
methodCache.enter(jmeth, meth)
val tparams = jmeth.getTypeParameters.toList map createTypeParameter
val params = jparamsAsScala(meth, jmeth.getParameters.toList)
val resulttpe = typeToScala(jmeth.getGenericReturnType)
setMethType(meth, tparams, params, resulttpe)
propagatePackageBoundary(jmeth.javaFlags, meth)
copyAnnotations(meth, jmeth)
if (jmeth.javaFlags.isVarargs) meth modifyInfo arrayToRepeated
if (jmeth.getDefaultValue != null) meth.addAnnotation(AnnotationDefaultAttr)
markAllCompleted(meth)
meth
}
/**
* The Scala constructor that corresponds to given Java constructor without taking
* Scala pickling info into account.
* @param jconstr The Java constructor
* @return A Scala constructor symbol that wraps all reflection info of `jconstr`
*/
private def jconstrAsScala(jconstr: jConstructor[_]): MethodSymbol =
toScala(constructorCache, jconstr)(_ jconstrAsScala1 _)
private def jconstrAsScala1(jconstr: jConstructor[_]): MethodSymbol = {
// [Martin] Note: I know there's a lot of duplication wrt jmethodAsScala, but don't think it's worth it to factor this out.
val clazz = sOwner(jconstr)
val constr = clazz.newConstructor(NoPosition, jconstr.scalaFlags)
constructorCache.enter(jconstr, constr)
val tparams = jconstr.getTypeParameters.toList map createTypeParameter
val params = jparamsAsScala(constr, jconstr.getParameters.toList)
setMethType(constr, tparams, params, clazz.tpe)
propagatePackageBoundary(jconstr.javaFlags, constr)
copyAnnotations(constr, jconstr)
if (jconstr.javaFlags.isVarargs) constr modifyInfo arrayToRepeated
markAllCompleted(constr)
constr
}
/** Transform Java parameters `params` into a list of value parameters
* for `meth`.
*/
private def jparamsAsScala(meth: MethodSymbol, params: List[jParameter]): List[Symbol] = {
params.zipWithIndex.map {
case (param, ix) =>
val name =
if (param.isNamePresent) TermName(param.getName)
else nme.syntheticParamName(ix + 1)
meth.owner.newValueParameter(name, meth.pos)
.setInfo(typeToScala(param.getParameterizedType))
.setFlag(if (param.isNamePresent) 0 else SYNTHETIC)
}
}
// -------------------- Scala to Java -----------------------------------
/** The Java class corresponding to given Scala class.
* Note: This only works for
* - top-level classes
* - Scala classes that were generated via jclassToScala
* - classes that have a class owner that has a corresponding Java class
* @throws ClassNotFoundException for all Scala classes not in one of these categories.
*/
@throws(classOf[ClassNotFoundException])
def classToJava(clazz: ClassSymbol): jClass[_] = classCache.toJava(clazz) {
def noClass = throw new ClassNotFoundException("no Java class corresponding to "+clazz+" found")
//println("classToJava "+clazz+" "+clazz.owner+" "+clazz.owner.isPackageClass)//debug
if (clazz.isPrimitiveValueClass)
valueClassToJavaType(clazz)
else if (clazz == ArrayClass)
noClass
else if (clazz.isTopLevel)
javaClass(clazz.javaClassName)
else if (clazz.owner.isClass) {
val childOfClass = !clazz.owner.isModuleClass
val childOfTopLevel = clazz.owner.isTopLevel
val childOfTopLevelObject = clazz.owner.isModuleClass && childOfTopLevel
// suggested in https://github.com/scala/bug/issues/4023#issuecomment-292387855
var ownerClazz = classToJava(clazz.owner.asClass)
if (childOfTopLevelObject)
ownerClazz = jClass.forName(ownerClazz.getName stripSuffix "$", false, ownerClazz.getClassLoader)
val ownerChildren = ownerClazz.getDeclaredClasses
var fullNameOfJavaClass = ownerClazz.getName
if (childOfClass || childOfTopLevel) fullNameOfJavaClass += "$"
fullNameOfJavaClass += clazz.name
// compactify (see scala/bug#7779)
fullNameOfJavaClass = fullNameOfJavaClass match {
case PackageAndClassPattern(pack, clazzName) =>
// in a package
pack + compactifyName(clazzName)
case _ =>
// in the empty package
compactifyName(fullNameOfJavaClass)
}
if (clazz.isModuleClass) fullNameOfJavaClass += "$"
// println(s"ownerChildren = ${ownerChildren.toList}")
// println(s"fullNameOfJavaClass = $fullNameOfJavaClass")
ownerChildren.find(_.getName == fullNameOfJavaClass).getOrElse(noClass)
} else
noClass
}
private[this] val PackageAndClassPattern = """(.*\.)(.*)$""".r
private def expandedName(sym: Symbol): String =
if (sym.isPrivate) nme.expandedName(sym.name.toTermName, sym.owner).toString
else sym.name.toString
/** The Java field corresponding to a given Scala field.
* @param fld The Scala field.
*/
def fieldToJava(fld: TermSymbol): jField = fieldCache.toJava(fld) {
val jclazz = classToJava(fld.owner.asClass)
val jname = fld.name.dropLocal.toString
try jclazz getDeclaredField jname
catch {
case ex: NoSuchFieldException => jclazz getDeclaredField expandedName(fld)
}
}
/** The Java method corresponding to a given Scala method.
* @param meth The Scala method
*/
def methodToJava(meth: MethodSymbol): jMethod = methodCache.toJava(meth) {
val jclazz = classToJava(meth.owner.asClass)
val paramClasses = transformedType(meth).paramTypes map typeToJavaClass
val jname = meth.name.dropLocal.toString
try jclazz.getDeclaredMethod(jname, paramClasses: _*)
catch {
case ex: NoSuchMethodException =>
jclazz.getDeclaredMethod(expandedName(meth), paramClasses: _*)
}
}
/** The Java constructor corresponding to a given Scala constructor.
* @param constr The Scala constructor
*/
def constructorToJava(constr: MethodSymbol): jConstructor[_] = constructorCache.toJava(constr) {
val jclazz = classToJava(constr.owner.asClass)
val paramClasses = transformedType(constr).paramTypes map typeToJavaClass
val effectiveParamClasses =
if (!constr.owner.owner.isStaticOwner) jclazz.getEnclosingClass +: paramClasses
else paramClasses
jclazz getDeclaredConstructor (effectiveParamClasses: _*)
}
/** The Java class that corresponds to given Scala type.
* Pre: Scala type is already transformed to Java level.
*/
def typeToJavaClass(tpe: Type): jClass[_] = tpe match {
case ExistentialType(_, rtpe) => typeToJavaClass(rtpe)
case TypeRef(_, ArrayClass, List(elemtpe)) => ScalaRunTime.arrayClass(typeToJavaClass(elemtpe))
case TypeRef(_, sym: ClassSymbol, _) => classToJava(sym.asClass)
case tpe @ TypeRef(_, sym: AliasTypeSymbol, _) => typeToJavaClass(tpe.dealias)
case SingleType(_, sym: ModuleSymbol) => classToJava(sym.moduleClass.asClass)
case _ => throw new NoClassDefFoundError("no Java class corresponding to "+tpe+" found")
}
}
/** Assert that packages have package scopes */
override def validateClassInfo(tp: ClassInfoType): Unit =
assert(!tp.typeSymbol.isPackageClass || tp.decls.isInstanceOf[PackageScope], s"$tp is package class or scope")
override def newPackageScope(pkgClass: Symbol) = new PackageScope(pkgClass)
override def scopeTransform(owner: Symbol)(op: => Scope): Scope =
if (owner.isPackageClass) owner.info.decls else op
override def mirrorThatLoaded(sym: Symbol): Mirror = sym.enclosingRootClass match {
case root: RootSymbol => root.mirror
case _ => abort(s"${sym}.enclosingRootClass = ${sym.enclosingRootClass}, which is not a RootSymbol")
}
/** 1. If `owner` is a package class (but not the empty package) and `name` is a term name, make a new package
* ., otherwise return NoSymbol.
* Exception: If owner is root and a java class with given name exists, create symbol in empty package instead
* 2. If `owner` is the scala package and `name` designates a phantom class, return
* the corresponding class symbol and enter it into this mirror's ScalaPackage.
*/
override def missingHook(owner: Symbol, name: Name): Symbol = {
if (owner.hasPackageFlag) {
val mirror = mirrorThatLoaded(owner)
if (owner.isRootSymbol && mirror.tryJavaClass(name.toString).isDefined)
return mirror.EmptyPackageClass.info decl name
if (name.isTermName && !owner.isEmptyPackageClass)
return mirror.makeScalaPackage(
if (owner.isRootSymbol) name.toString else owner.fullName+"."+name)
if (name == tpnme.AnyRef && owner.owner.isRoot && owner.name == tpnme.scala_)
// when we synthesize the scala.AnyRef symbol, we need to add it to the scope of the scala package
// the problem is that adding to the scope implies doing something like `owner.info.decls enter anyRef`
// which entails running a completer for the scala package
// which will try to unpickle the stuff in scala/package.class
// which will transitively load scala.AnyRef
// which doesn't exist yet, because it hasn't been added to the scope yet
// this missing hook ties the knot without introducing synchronization problems like before
return definitions.AnyRefClass
}
info("*** missing: "+name+"/"+name.isTermName+"/"+owner+"/"+owner.hasPackageFlag+"/"+owner.info.decls.getClass)
super.missingHook(owner, name)
}
}
private[reflect] class ReflectError(msg: String) extends java.lang.Error(msg)
private[reflect] class HasJavaClass[J](val getClazz: J => java.lang.Class[_])