From 1960081d1ac7d8449fa0a0f8d1c251bb1efbe70e Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Nov 2013 15:02:43 -0600 Subject: [PATCH 1/9] Compile the annotation tests with the annotations in the class path Earlier, if the annotations were already up-to-date (but Annotations.class not), the compilation would fail. Signed-off-by: Johannes Schindelin --- makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/makefile b/makefile index 113593478b..c5989e178b 100755 --- a/makefile +++ b/makefile @@ -1566,7 +1566,7 @@ $(test-dep): $(test-sources) $(test-library) @mkdir -p $(test-build) files="$(shell $(MAKE) -s --no-print-directory build=$(build) $(test-classes))"; \ if test -n "$${files}"; then \ - $(javac) -d $(test-build) -bootclasspath $(boot-classpath) $${files}; \ + $(javac) -classpath $(test-build) -d $(test-build) -bootclasspath $(boot-classpath) $${files}; \ fi $(javac) -source 1.2 -target 1.1 -XDjsrlimit=0 -d $(test-build) \ -bootclasspath $(boot-classpath) test/Subroutine.java From db0422dcdec1769a6d7782303bb9c20151681ef6 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 25 Nov 2013 09:54:40 -0600 Subject: [PATCH 2/9] Proxy: make all methods public Proxies implement interfaces whose methods *must* be public, as per the specification of the Java language. Signed-off-by: Johannes Schindelin --- classpath/java/lang/reflect/Proxy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classpath/java/lang/reflect/Proxy.java b/classpath/java/lang/reflect/Proxy.java index c4ed436dfb..16187e6f55 100644 --- a/classpath/java/lang/reflect/Proxy.java +++ b/classpath/java/lang/reflect/Proxy.java @@ -368,7 +368,7 @@ public class Proxy { { int i = 0; for (avian.VMMethod m: virtualMap.values()) { methodTable[i] = new MethodData - (0, + (Modifier.PUBLIC, ConstantPool.addUtf8(pool, Classes.toString(m.name)), ConstantPool.addUtf8(pool, Classes.toString(m.spec)), makeInvokeCode(pool, name, m.spec, m.parameterCount, From ff323d46a394b4f9602cd87bef7ac7c8ad9789c2 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Nov 2013 14:46:09 -0600 Subject: [PATCH 3/9] AnnotationInvocationHandler: avoid calling Method#getName too often This is only a cosmetic change, but we should not call getName() over and over again ;-) Signed-off-by: Johannes Schindelin --- classpath/avian/AnnotationInvocationHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/classpath/avian/AnnotationInvocationHandler.java b/classpath/avian/AnnotationInvocationHandler.java index 47d7428863..2f859fa86f 100644 --- a/classpath/avian/AnnotationInvocationHandler.java +++ b/classpath/avian/AnnotationInvocationHandler.java @@ -21,8 +21,9 @@ public class AnnotationInvocationHandler implements InvocationHandler { } public Object invoke(Object proxy, Method method, Object[] arguments) { + String name = method.getName(); for (int i = 2; i < data.length; i += 2) { - if (method.getName().equals(data[i])) { + if (name.equals(data[i])) { return data[i + 1]; } } From 58ec623d7ad2be38b886c690d28845a6d63c899e Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 31 Oct 2013 00:28:01 -0500 Subject: [PATCH 4/9] Implement Method#getDefaultValue() Signed-off-by: Johannes Schindelin --- classpath/avian/Classes.java | 19 +++++++++++++++++++ classpath/java/lang/reflect/Method.java | 5 +++++ 2 files changed, 24 insertions(+) diff --git a/classpath/avian/Classes.java b/classpath/avian/Classes.java index 86fba20710..932360da0e 100644 --- a/classpath/avian/Classes.java +++ b/classpath/avian/Classes.java @@ -450,6 +450,25 @@ public class Classes { return (Annotation) a[0]; } + public static Object getAnnotationDefaultValue(ClassLoader loader, + MethodAddendum addendum) { + if (addendum == null) { + return null; + } + byte[] annotationDefault = (byte[]) addendum.annotationDefault; + if (annotationDefault == null) { + return null; + } + try { + return parseAnnotationValue(loader, addendum.pool, + new ByteArrayInputStream(annotationDefault)); + } catch (IOException e) { + AssertionError error = new AssertionError(); + error.initCause(e); + throw error; + } + } + public static native Method makeMethod(Class c, int slot); private static native void acquireClassLock(); diff --git a/classpath/java/lang/reflect/Method.java b/classpath/java/lang/reflect/Method.java index 51a64c905c..55c7744c13 100644 --- a/classpath/java/lang/reflect/Method.java +++ b/classpath/java/lang/reflect/Method.java @@ -142,4 +142,9 @@ public class Method extends AccessibleObject implements Member { public boolean isVarArgs() { return (getModifiers() & 0x80) != 0; } + + public Object getDefaultValue() { + ClassLoader loader = getDeclaringClass().getClassLoader(); + return Classes.getAnnotationDefaultValue(loader, vmMethod.addendum); + } } From 6f5bcb00b9e52f556e1c37d8bc68117df1b3163c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 31 Oct 2013 01:44:03 -0500 Subject: [PATCH 5/9] Use the default value in annotations' proxies Signed-off-by: Johannes Schindelin --- classpath/avian/AnnotationInvocationHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classpath/avian/AnnotationInvocationHandler.java b/classpath/avian/AnnotationInvocationHandler.java index 2f859fa86f..c49f56f13c 100644 --- a/classpath/avian/AnnotationInvocationHandler.java +++ b/classpath/avian/AnnotationInvocationHandler.java @@ -27,6 +27,6 @@ public class AnnotationInvocationHandler implements InvocationHandler { return data[i + 1]; } } - throw new IllegalArgumentException(); + return method.getDefaultValue(); } } From 0a179355f4302d4a0cd642d6eafe75889c11fdc8 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 26 Nov 2013 15:03:53 -0600 Subject: [PATCH 6/9] Pass the correct Method instance to the InvocationHandlers We should pass the method of the original interface to the InvocationHandler, not the method of the interface. That way, proxy instances of annotations will have easy access to the default values. This happens to be compatible with the way Oracle Java does it, too. To accomplish our goal, we keep a global map between proxy classes and Method references and assign the appropriate list to a field of the Proxy subclass. This means that we now have to call the super-class constructor in the generated constructor (which is the correct thing to do anyway... ;-)). Signed-off-by: Johannes Schindelin --- classpath/java/lang/reflect/Proxy.java | 79 +++++++++++++++----------- 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/classpath/java/lang/reflect/Proxy.java b/classpath/java/lang/reflect/Proxy.java index 16187e6f55..4160c6bdf3 100644 --- a/classpath/java/lang/reflect/Proxy.java +++ b/classpath/java/lang/reflect/Proxy.java @@ -29,6 +29,8 @@ import java.util.List; import java.util.ArrayList; import java.util.Map; import java.util.HashMap; +import java.util.Set; +import java.util.HashSet; import java.io.OutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -37,6 +39,13 @@ public class Proxy { private static int nextNumber; protected InvocationHandler h; + protected final static Map methodRefsMap = + new HashMap(); + protected final Method[] methodRefs; + + public Proxy() { + methodRefs = methodRefsMap.get(getClass()); + } public static Class getProxyClass(ClassLoader loader, Class ... interfaces) @@ -90,15 +99,14 @@ public class Proxy { write1(out, aload_0); - write1(out, ldc_w); - write2(out, ConstantPool.addClass(pool, className) + 1); + write1(out, aload_0); + write1(out, getfield); + write2(out, ConstantPool.addFieldRef + (pool, className, + "methodRefs", "[Ljava/lang/reflect/Method;") + 1); write1(out, ldc_w); write2(out, ConstantPool.addInteger(pool, index) + 1); - write1(out, invokestatic); - write2(out, ConstantPool.addMethodRef - (pool, "avian/Classes", - "makeMethod", "(Ljava/lang/Class;I)Ljava/lang/reflect/Method;") - + 1); + write1(out, aaload); write1(out, ldc_w); write2(out, ConstantPool.addInteger(pool, parameterCount) + 1); @@ -324,7 +332,13 @@ public class Proxy { ByteArrayOutputStream out = new ByteArrayOutputStream(); write2(out, 2); // max stack write2(out, 2); // max locals - write4(out, 6); // length + write4(out, 10); // length + + write1(out, aload_0); + write1(out, invokespecial); + write2(out, ConstantPool.addMethodRef + (pool, "java/lang/reflect/Proxy", + "", "()V") + 1); write1(out, aload_0); write1(out, aload_1); @@ -353,47 +367,48 @@ public class Proxy { (pool, interfaces[i].getName().replace('.', '/')); } - Map virtualMap = new HashMap(); + Set specs = new HashSet(); + List methodTable = new ArrayList(); + List refs = new ArrayList(); for (Class c: interfaces) { avian.VMMethod[] ivtable = SystemClassLoader.vmClass(c).virtualTable; if (ivtable != null) { for (avian.VMMethod m: ivtable) { - virtualMap.put - (Classes.toString(m.name) + Classes.toString(m.spec), m); + String spec = Classes.toString(m.name) + Classes.toString(m.spec); + if (specs.contains(spec)) { + continue; + } + methodTable.add(new MethodData + (Modifier.PUBLIC, + ConstantPool.addUtf8(pool, Classes.toString(m.name)), + ConstantPool.addUtf8(pool, Classes.toString(m.spec)), + makeInvokeCode(pool, name, m.spec, m.parameterCount, + m.parameterFootprint, methodTable.size()))); + refs.add(new Method(m)); } } } - MethodData[] methodTable = new MethodData[virtualMap.size() + 1]; - { int i = 0; - for (avian.VMMethod m: virtualMap.values()) { - methodTable[i] = new MethodData - (Modifier.PUBLIC, - ConstantPool.addUtf8(pool, Classes.toString(m.name)), - ConstantPool.addUtf8(pool, Classes.toString(m.spec)), - makeInvokeCode(pool, name, m.spec, m.parameterCount, - m.parameterFootprint, i)); - ++ i; - } - - methodTable[i++] = new MethodData - (Modifier.PUBLIC, - ConstantPool.addUtf8(pool, ""), - ConstantPool.addUtf8 - (pool, "(Ljava/lang/reflect/InvocationHandler;)V"), - makeConstructorCode(pool)); - } + methodTable.add(new MethodData + (Modifier.PUBLIC, + ConstantPool.addUtf8(pool, ""), + ConstantPool.addUtf8 + (pool, "(Ljava/lang/reflect/InvocationHandler;)V"), + makeConstructorCode(pool))); int nameIndex = ConstantPool.addClass(pool, name); int superIndex = ConstantPool.addClass(pool, "java/lang/reflect/Proxy"); ByteArrayOutputStream out = new ByteArrayOutputStream(); Assembler.writeClass - (out, pool, nameIndex, superIndex, interfaceIndexes, methodTable); + (out, pool, nameIndex, superIndex, interfaceIndexes, + methodTable.toArray(new MethodData[methodTable.size()])); byte[] classData = out.toByteArray(); - return avian.SystemClassLoader.getClass + Class result = avian.SystemClassLoader.getClass (avian.Classes.defineVMClass(loader, classData, 0, classData.length)); + methodRefsMap.put(result, refs.toArray(new Method[refs.size()])); + return result; } public static Object newProxyInstance(ClassLoader loader, From 6c57bd9174ddb954e18157e126da38dc5647d7a2 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Nov 2013 14:52:08 -0600 Subject: [PATCH 7/9] Verify that Proxy instances have access to annotations' default values For quick access, the sezpoz library stores lists in META-INF/annotations/ of classes that have been annotated in a special way. To support the use case where the annotations actually changed since sezpoz stored said lists, sezpoz then creates proxy instances for the annotations to provide some backwards compatibility: as long as there are default values for any newly-introduced annotation values, everything is groovy. Therefore, let's make sure that proxy instances inherit the annotations' default values. Signed-off-by: Johannes Schindelin --- test/Annotations.java | 15 +++++++++++++++ test/avian/testing/annotations/Test.java | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/test/Annotations.java b/test/Annotations.java index c0234d21c0..7674e5d6f4 100644 --- a/test/Annotations.java +++ b/test/Annotations.java @@ -1,4 +1,6 @@ +import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import avian.testing.annotations.Color; import avian.testing.annotations.Test; @@ -27,6 +29,7 @@ public class Annotations { Method noAnno = Annotations.class.getMethod("noAnnotation"); expect(noAnno.getAnnotation(Test.class) == null); expect(noAnno.getAnnotations().length == 0); + testProxyDefaultValue(); } @Test("couscous") @@ -39,4 +42,16 @@ public class Annotations { public static void noAnnotation() { } + + private static void testProxyDefaultValue() { + ClassLoader loader = Annotations.class.getClassLoader(); + InvocationHandler handler = new InvocationHandler() { + public Object invoke(Object proxy, Method method, Object... args) { + return method.getDefaultValue(); + } + }; + Test test = (Test) + Proxy.newProxyInstance(loader, new Class[] { Test.class }, handler); + expect("Hello, world!".equals(test.value())); + } } diff --git a/test/avian/testing/annotations/Test.java b/test/avian/testing/annotations/Test.java index f9ba919647..24b8229480 100644 --- a/test/avian/testing/annotations/Test.java +++ b/test/avian/testing/annotations/Test.java @@ -5,5 +5,5 @@ import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface Test { - public String value(); + public String value() default "Hello, world!"; } From e1d91f153b74cfd06a7fc34faa474c63354f98d5 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 26 Nov 2013 14:15:16 -0600 Subject: [PATCH 8/9] Fix parsing of recursive annotations Signed-off-by: Johannes Schindelin --- classpath/avian/Classes.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classpath/avian/Classes.java b/classpath/avian/Classes.java index 932360da0e..8b14fd233f 100644 --- a/classpath/avian/Classes.java +++ b/classpath/avian/Classes.java @@ -117,7 +117,7 @@ public class Classes { } case '@': - return parseAnnotation(loader, pool, in); + return getAnnotation(loader, parseAnnotation(loader, pool, in)); case '[': { Object[] array = new Object[read2(in)]; From 0681531dc0ac706de137ad29cfd719ba12d83101 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 26 Nov 2013 15:25:39 -0600 Subject: [PATCH 9/9] Test complicated annotation constructs Signed-off-by: Johannes Schindelin --- test/Annotations.java | 32 +++++++++++++++++++ .../testing/annotations/TestComplex.java | 14 ++++++++ 2 files changed, 46 insertions(+) create mode 100644 test/avian/testing/annotations/TestComplex.java diff --git a/test/Annotations.java b/test/Annotations.java index 7674e5d6f4..f09b0251eb 100644 --- a/test/Annotations.java +++ b/test/Annotations.java @@ -4,6 +4,7 @@ import java.lang.reflect.Proxy; import avian.testing.annotations.Color; import avian.testing.annotations.Test; +import avian.testing.annotations.TestComplex; import avian.testing.annotations.TestEnum; import avian.testing.annotations.TestInteger; @@ -30,6 +31,7 @@ public class Annotations { expect(noAnno.getAnnotation(Test.class) == null); expect(noAnno.getAnnotations().length == 0); testProxyDefaultValue(); + testComplexAnnotation(); } @Test("couscous") @@ -54,4 +56,34 @@ public class Annotations { Proxy.newProxyInstance(loader, new Class[] { Test.class }, handler); expect("Hello, world!".equals(test.value())); } + + private interface World { + @TestComplex(arrayValue = { @Test, @Test(value = "7/9") }, + stringValue = "adjunct element", charValue = '7', doubleValue = 0.7778, + classValue = TestInteger.class) + int hello(); + } + + private static void testComplexAnnotation(TestComplex annotation) + throws Exception + { + expect(2 == annotation.arrayValue().length); + expect("Hello, world!".equals(annotation.arrayValue()[0].value())); + expect("7/9".equals(annotation.arrayValue()[1].value())); + expect("adjunct element".equals(annotation.stringValue())); + expect('7' == annotation.charValue()); + expect(0.7778 == annotation.doubleValue()); + expect(TestInteger.class == annotation.classValue()); + } + + public static void testComplexAnnotation() throws Exception { + ClassLoader loader = Annotations.class.getClassLoader(); + TestComplex annotation = (TestComplex) + World.class.getMethod("hello").getAnnotation(TestComplex.class); + testComplexAnnotation(annotation); + Class clazz = Proxy.getProxyClass(loader, new Class[] { World.class }); + annotation = (TestComplex) + clazz.getMethod("hello").getAnnotation(TestComplex.class); + expect(annotation == null); + } } diff --git a/test/avian/testing/annotations/TestComplex.java b/test/avian/testing/annotations/TestComplex.java new file mode 100644 index 0000000000..4e271cc9d9 --- /dev/null +++ b/test/avian/testing/annotations/TestComplex.java @@ -0,0 +1,14 @@ +package avian.testing.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface TestComplex { + public Test[] arrayValue(); + public Class classValue(); + public String stringValue(); + public char charValue(); + public double doubleValue(); +} +