From 39486b03e1e044fcf3256e1d9293dccd0871edf9 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Fri, 14 Sep 2012 21:31:21 +0200 Subject: SI-6356 reflection now supports Java annotations Except for one thingie: java enums are currently not understood by Scala reflection, hence they aren't yet supported in annotations. --- .../scala/tools/partest/nest/RunnerManager.scala | 1 + .../scala/reflect/internal/AnnotationInfos.scala | 16 +++++++---- .../scala/reflect/runtime/JavaMirrors.scala | 33 +++++++++++++++++++++- test/files/run/reflection-java-annotations.check | 20 +++++++++++++ .../reflection-java-annotations.jar.desired.sha1 | 1 + test/files/run/reflection-java-annotations.scala | 19 +++++++++++++ 6 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 test/files/run/reflection-java-annotations.check create mode 100644 test/files/run/reflection-java-annotations.jar.desired.sha1 create mode 100644 test/files/run/reflection-java-annotations.scala diff --git a/src/partest/scala/tools/partest/nest/RunnerManager.scala b/src/partest/scala/tools/partest/nest/RunnerManager.scala index 376e0e9bdb..4961424e1b 100644 --- a/src/partest/scala/tools/partest/nest/RunnerManager.scala +++ b/src/partest/scala/tools/partest/nest/RunnerManager.scala @@ -217,6 +217,7 @@ class RunnerManager(kind: String, val fileManager: FileManager, params: TestRunP "-Dpartest.output="+outDir.getAbsolutePath, "-Dpartest.lib="+LATEST_LIB, "-Dpartest.reflect="+LATEST_REFLECT, + "-Dpartest.comp="+LATEST_COMP, "-Dpartest.cwd="+outDir.getParent, "-Dpartest.test-path="+testFullPath, "-Dpartest.testname="+fileBase, diff --git a/src/reflect/scala/reflect/internal/AnnotationInfos.scala b/src/reflect/scala/reflect/internal/AnnotationInfos.scala index 8853b872c0..3bd7f4f4fa 100644 --- a/src/reflect/scala/reflect/internal/AnnotationInfos.scala +++ b/src/reflect/scala/reflect/internal/AnnotationInfos.scala @@ -65,6 +65,7 @@ trait AnnotationInfos extends api.Annotations { self: SymbolTable => */ abstract class ClassfileAnnotArg extends Product implicit val JavaArgumentTag = ClassTag[ClassfileAnnotArg](classOf[ClassfileAnnotArg]) + case object UnmappableAnnotArg extends ClassfileAnnotArg /** Represents a compile-time Constant (`Boolean`, `Byte`, `Short`, * `Char`, `Int`, `Long`, `Float`, `Double`, `String`, `java.lang.Class` or @@ -173,11 +174,14 @@ trait AnnotationInfos extends api.Annotations { self: SymbolTable => this } - override def toString = ( - atp + - (if (!args.isEmpty) args.mkString("(", ", ", ")") else "") + - (if (!assocs.isEmpty) (assocs map { case (x, y) => x+" = "+y } mkString ("(", ", ", ")")) else "") - ) + override def toString = completeAnnotationToString(this) + } + + private[scala] def completeAnnotationToString(annInfo: AnnotationInfo) = { + import annInfo._ + val s_args = if (!args.isEmpty) args.mkString("(", ", ", ")") else "" + val s_assocs = if (!assocs.isEmpty) (assocs map { case (x, y) => x+" = "+y } mkString ("(", ", ", ")")) else "" + s"${atp}${s_args}${s_assocs}" } /** Symbol annotations parsed in `Namer` (typeCompleter of @@ -215,7 +219,7 @@ trait AnnotationInfos extends api.Annotations { self: SymbolTable => * * `assocs` stores arguments to classfile annotations as name-value pairs. */ - sealed abstract class AnnotationInfo extends AnnotationApi { + abstract class AnnotationInfo extends AnnotationApi { def atp: Type def args: List[Tree] def assocs: List[(Name, ClassfileAnnotArg)] diff --git a/src/reflect/scala/reflect/runtime/JavaMirrors.scala b/src/reflect/scala/reflect/runtime/JavaMirrors.scala index be2661149a..6977b43d73 100644 --- a/src/reflect/scala/reflect/runtime/JavaMirrors.scala +++ b/src/reflect/scala/reflect/runtime/JavaMirrors.scala @@ -9,6 +9,7 @@ import java.lang.reflect.{ Method => jMethod, Constructor => jConstructor, Modifier => jModifier, Field => jField, Member => jMember, Type => jType, TypeVariable => jTypeVariable, Array => jArray, GenericDeclaration, GenericArrayType, ParameterizedType, WildcardType, AnnotatedElement } +import java.lang.annotation.{Annotation => jAnnotation} import java.io.IOException import internal.MissingRequirementError import internal.pickling.ByteCodecs @@ -573,7 +574,36 @@ trait JavaMirrors extends internal.SymbolTable with api.JavaUniverse { self: Sym * Note: If `sym` is a method or constructor, its parameter annotations are copied as well. */ private def copyAnnotations(sym: Symbol, jann: AnnotatedElement) { - // to do: implement + 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) + override def assocs: List[(Name, ClassfileAnnotArg)] = + // 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 + jann.annotationType.getDeclaredMethods.sortBy(_.getName).toList map (m => { + def enumToSymbol(enum: Enum[_]): Symbol = + classToScala(enum.getClass).typeSignature.declaration(newTermName(enum.name)) + + def toAnnotArg(value: Any, schema: jClass[_]): ClassfileAnnotArg = schema match { + case primitive if primitive.isPrimitive => LiteralAnnotArg(Constant(value)) + case string if string == classOf[String] => LiteralAnnotArg(Constant(value)) + case clazz if clazz == classOf[jClass[_]] => LiteralAnnotArg(Constant(classToScala(value.asInstanceOf[jClass[_]]).toType)) + case enum if enum.isEnum => LiteralAnnotArg(Constant(enumToSymbol(value.asInstanceOf[Enum[_]]))) + case array if array.isArray => ArrayAnnotArg(value.asInstanceOf[Array[_]] map (x => toAnnotArg(x, ScalaRunTime.arrayElementClass(array)))) + case ann if ann.isAnnotation => NestedAnnotArg(JavaAnnotationProxy(value.asInstanceOf[jAnnotation])) + case _ => UnmappableAnnotArg + } + + newTermName(m.getName) -> toAnnotArg(m.invoke(jann), m.getReturnType) + }) + } + + sym setAnnotations (jann.getAnnotations map JavaAnnotationProxy).toList } /** @@ -612,6 +642,7 @@ trait JavaMirrors extends internal.SymbolTable with api.JavaUniverse { self: Sym } override def complete(sym: Symbol): Unit = { + if (jclazz.isEnum) throw new ScalaReflectionException("implementation restriction: Java enums are not supported") load(sym) completeRest() } diff --git a/test/files/run/reflection-java-annotations.check b/test/files/run/reflection-java-annotations.check new file mode 100644 index 0000000000..9954c59d4a --- /dev/null +++ b/test/files/run/reflection-java-annotations.check @@ -0,0 +1,20 @@ +Type in expressions to have them evaluated. +Type :help for more information. + +scala> + +scala> import scala.reflect.runtime.universe._ +import scala.reflect.runtime.universe._ + +scala> val sym = typeOf[Foo].typeSymbol +sym: reflect.runtime.universe.Symbol = class Foo + +scala> sym.typeSignature +res0: reflect.runtime.universe.Type = java.lang.Object{def (): Foo} + +scala> println(sym.getAnnotations) +List(ComplexAnnotation(v1 = 1, v10 = "hello", v101 = [101, 101], v102 = [102, 102], v103 = ['g', 'g'], v104 = [104, 104], v105 = [105L, 105L], v106 = [106.0, 106.0], v107 = [107.0, 107.0], v108 = [false, true], v11 = classOf[Foo], v110 = ["hello", "world"], v111 = [classOf[SimpleAnnotation], classOf[ComplexAnnotation]], v113 = [SimpleAnnotation(v1 = 21, v10 = "world2", v11 = classOf[ComplexAnnotation], v2 = 22, v3 = '\027', v4 = 24, v5 = 25L, v6 = 26.0, v7 = 27.0, v8 = false)], v13 = SimpleAnnotation(v1 = 11, v10 = "world1", v11 = classOf[SimpleAnnotation], v2 = 12, v3 = '\r', v4 = 14, v5 = 15L, v6 = 16.0, v7 = 17.0, v8 = false), v2 = 2, v3 = '\03', v4 = 4, v5 = 5L, v6 = 6.0, v7 = 7.0, v8 = false)) + +scala> + +scala> diff --git a/test/files/run/reflection-java-annotations.jar.desired.sha1 b/test/files/run/reflection-java-annotations.jar.desired.sha1 new file mode 100644 index 0000000000..430e7626e6 --- /dev/null +++ b/test/files/run/reflection-java-annotations.jar.desired.sha1 @@ -0,0 +1 @@ +c35876a529c6be33bdda7b3f48ac8ae800d2f36a ?reflection-java-annotations.jar diff --git a/test/files/run/reflection-java-annotations.scala b/test/files/run/reflection-java-annotations.scala new file mode 100644 index 0000000000..96dbca86ad --- /dev/null +++ b/test/files/run/reflection-java-annotations.scala @@ -0,0 +1,19 @@ +import scala.tools.partest._ +import scala.tools.nsc.Settings + +object Test extends ReplTest { + def code = """ + import scala.reflect.runtime.universe._ + val sym = typeOf[Foo].typeSymbol + sym.typeSignature + println(sym.getAnnotations) + """ + + override def transformSettings(settings: Settings): Settings = { + val thisFile = testPath.jfile.getAbsolutePath + val javaCompiledAnnotationsJar = thisFile.substring(0, thisFile.length - ".scala".length) + ".jar" + val classpath = List(sys.props("partest.lib"), sys.props("partest.reflect"), sys.props("partest.comp"), javaCompiledAnnotationsJar) mkString sys.props("path.separator") + settings.processArguments(List("-cp", classpath), true) + settings + } +} \ No newline at end of file -- cgit v1.2.3