# HG changeset patch # User Jaroslav Tulach # Date 1458379873 -3600 # Node ID e1953d8b83387bd8c9bd5a3d4d1dba72652fd55c # Parent d13786d6c185589a38e635e0666f170f533ed946 Support for default attributes of annotations diff -r d13786d6c185 -r e1953d8b8338 rt/emul/compacttest/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionAnnotationTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compacttest/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionAnnotationTest.java Sat Mar 19 10:31:13 2016 +0100 @@ -0,0 +1,117 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012-2015 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.bck2brwsr.tck; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class ReflectionAnnotationTest { + @Retention(RetentionPolicy.RUNTIME) + @interface Ann { + int integer() default 33; + double dbl() default 33.3; + String string() default "Ahoj"; + E enums() default E.B; + Class type() default String.class; + } + + @interface AnnAnn { + Ann ann() default @Ann( + dbl = 44.4, + enums = E.A, + string = "Hi", + integer = 44 + ); + } + + @Ann + @AnnAnn + class D { + } + + @Ann(type = String.class) + @AnnAnn(ann = @Ann(string = "Ciao")) + enum E { + A, B + }; + + + @Compare public String annoClass() throws Exception { + Retention r = Ann.class.getAnnotation(Retention.class); + assert r != null : "Annotation is present"; + assert r.value() == RetentionPolicy.RUNTIME : "Policy value is OK: " + r.value(); + return r.annotationType().getName(); + } + + @Compare public boolean isAnnotation() { + return Ann.class.isAnnotation(); + } + @Compare public boolean isNotAnnotation() { + return String.class.isAnnotation(); + } + @Compare public boolean isNotAnnotationEnum() { + return E.class.isAnnotation(); + } + + @Compare public int intDefaultAttrIsRead() { + return D.class.getAnnotation(Ann.class).integer(); + } + + @Compare public double doubleDefaultAttrIsRead() { + return D.class.getAnnotation(Ann.class).dbl(); + } + + @Compare public String stringDefaultAttrIsRead() { + return D.class.getAnnotation(Ann.class).string(); + } + + @Compare public String enumDefaultAttrIsRead() { + return D.class.getAnnotation(Ann.class).enums().toString(); + } + + @Compare public String classDefaultAttrIsRead() { + return D.class.getAnnotation(Ann.class).type().getName(); + } + + @Compare public String classAttrIsRead() { + return E.class.getAnnotation(Ann.class).type().getName(); + } + +// @Compare public String defaultAnnotationAttrIsRead() { +// final Ann ann = D.class.getAnnotation(AnnAnn.class).ann(); +// return ann.string() + ann.dbl() + ann.enums() + ann.integer() + ann.type(); +// } +// +// @Compare public String annotationAttrIsRead() { +// final Ann ann = E.class.getAnnotation(AnnAnn.class).ann(); +// return ann.string(); +// } + + @Factory + public static Object[] create() { + return VMTest.create(ReflectionAnnotationTest.class); + } + +} diff -r d13786d6c185 -r e1953d8b8338 rt/emul/mini/src/main/java/org/apidesign/bck2brwsr/emul/reflect/AnnotationImpl.java --- a/rt/emul/mini/src/main/java/org/apidesign/bck2brwsr/emul/reflect/AnnotationImpl.java Sat Mar 19 10:28:03 2016 +0100 +++ b/rt/emul/mini/src/main/java/org/apidesign/bck2brwsr/emul/reflect/AnnotationImpl.java Sat Mar 19 10:31:13 2016 +0100 @@ -38,11 +38,20 @@ } @JavaScriptBody(args = { "a", "n", "arr", "values" }, body = "" - + "function f(val, prop, clazz) {\n" + + "function r(anno, val, prop, m) {\n" + + " var v = val[prop];\n" + + " if (typeof v === 'undefined') {\n" + + " var cls = anno.fld_org_apidesign_bck2brwsr_emul_reflect_AnnotationImpl_type.cnstr;\n" + + " try { throw 'x'; } catch (errr) {};\n" + + " v = cls.prototype[m]();\n" + + " }\n" + + " return v;\n" + + "}\n" + + "function f(val, prop, clazz, m) {\n" + " return function() {\n" - + " if (clazz == null) return val[prop];\n" + + " if (clazz == null) return r(this, val, prop, m);\n" + " if (clazz.isArray__Z()) {\n" - + " var valarr = val[prop];\n" + + " var valarr = r(this, val, prop, m);\n" + " var cmp = clazz.getComponentType__Ljava_lang_Class_2();\n" + " var retarr = vm.java_lang_reflect_Array(false).newInstance__Ljava_lang_Object_2Ljava_lang_Class_2I(cmp, valarr.length);\n" + " for (var i = 0; i < valarr.length; i++) {\n" @@ -50,14 +59,14 @@ + " }\n" + " return retarr;\n" + " }\n" - + " return CLS.prototype.c__Ljava_lang_Object_2Ljava_lang_Class_2Ljava_lang_Object_2(clazz, val[prop]);\n" + + " return CLS.prototype.c__Ljava_lang_Object_2Ljava_lang_Class_2Ljava_lang_Object_2(clazz, r(this, val, prop, m));\n" + " };\n" + "}\n" + "for (var i = 0; i < arr.length; i += 3) {\n" + " var m = arr[i];\n" + " var p = arr[i + 1];\n" + " var c = arr[i + 2];\n" - + " a[m] = f(values, p, c);\n" + + " a[m] = f(values, p, c, m);\n" + "}\n" + "a['$instOf_' + n] = true;\n" + "return a;" diff -r d13786d6c185 -r e1953d8b8338 rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeParser.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeParser.java Sat Mar 19 10:28:03 2016 +0100 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeParser.java Sat Mar 19 10:31:13 2016 +0100 @@ -78,6 +78,7 @@ public static final int ACC_STRICT = 0x00000800; public static final int ACC_EXPLICIT = 0x00001000; public static final int ACC_SYNTHETIC = 0x00010000; // actually, this is an attribute + private static final int ACC_ANNOTATION = 0x00020000; /* Type codes for StackMap attribute */ public static final int ITEM_Bogus =0; // an unknown or uninitialized value @@ -356,14 +357,22 @@ } protected void visitAttr( - String annoType, String attr, String attrType, String value) throws IOException { + String annoType, String attr, String attrType, String value + ) throws IOException { } protected void visitEnumAttr( - String annoType, String attr, String attrType, String value) throws IOException { + String annoType, String attr, String attrType, String value + ) throws IOException { visitAttr(annoType, attr, attrType, value); } + protected void visitClassAttr( + String annoType, String attr, String className + ) throws IOException { + visitAttr(annoType, attr, className, className); + } + /** * Initialize the parsing with constant pool from * cd. @@ -404,6 +413,16 @@ } } + public void parseDefault(byte[] defaultAttribute, ClassData cd) throws IOException { + ByteArrayInputStream is = new ByteArrayInputStream(defaultAttribute); + DataInputStream dis = new DataInputStream(is); + try { + readValue(dis, cd, null, null); + } finally { + is.close(); + } + } + private void readValue( DataInputStream dis, ClassData cd, String typeName, String attrName) throws IOException { char type = (char) dis.readByte(); @@ -425,6 +444,8 @@ visitAttr(typeName, attrName, attrType, val); } else if (type == 'c') { int cls = dis.readUnsignedShort(); + String attrType = cd.stringValue(cls, textual); + visitClassAttr(typeName, attrName, attrType); } else if (type == '[') { int cnt = dis.readUnsignedShort(); for (int i = 0; i < cnt; i++) { @@ -875,6 +896,10 @@ return false; } + public boolean isAnnotation() { + return (access & ACC_ANNOTATION) != 0; + } + /** * Returns true if this member is public, false otherwise. */ @@ -1687,6 +1712,7 @@ int max_stack, max_locals; boolean isSynthetic = false; boolean isDeprecated = false; + private AttrData annotationDefault; public MethodData(ClassData cls) { this.cls = cls; @@ -1737,6 +1763,12 @@ attr.read(attr_name_index); attrs.addElement(attr); break readAttr; + } else if (attr_name.equals("AnnotationDefault")) { + AttrData attr = new AttrData(cls); + attr.read(attr_name_index, in); + attrs.addElement(attr); + annotationDefault = attr; + break readAttr; } } AttrData attr = new AttrData(cls); @@ -1994,6 +2026,10 @@ return code_attrs; } + byte[] getDefaultAttribute() { + return annotationDefault == null ? null : annotationDefault.getData(); + } + /** * Return true if method id synthetic. */ diff -r d13786d6c185 -r e1953d8b8338 rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java Sat Mar 19 10:28:03 2016 +0100 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java Sat Mar 19 10:31:13 2016 +0100 @@ -288,7 +288,8 @@ byte[] runAnno = m.findAnnotationData(false); if (runAnno != null) { append("\n m.anno = {"); - generateAnno(jc, runAnno); + AnnotationParser ap = new GenerateAnno(true, false); + ap.parse(runAnno, jc); append("\n };"); } append("\n m.access = " + m.getAccess()).append(";"); @@ -343,7 +344,8 @@ byte[] classAnno = jc.findAnnotationData(false); if (classAnno != null) { append("\n CLS.$class.anno = {"); - generateAnno(jc, classAnno); + AnnotationParser ap = new GenerateAnno(true, false); + ap.parse(classAnno, jc); append("\n };"); } for (String init : toInitilize.toArray()) { @@ -453,10 +455,18 @@ final byte[] byteCodes = m.getCode(); if (byteCodes == null) { - if (debug(" throw 'no code found for ")) { - this - .append(jc.getClassName()).append('.') - .append(m.getName()).append("';\n"); + byte[] defaultAttr = m.getDefaultAttribute(); + if (defaultAttr != null) { + append(" return "); + AnnotationParser ap = new GenerateAnno(true, false); + ap.parseDefault(defaultAttr, jc); + append(";\n"); + } else { + if (debug(" throw 'no code found for ")) { + this + .append(jc.getClassName()).append('.') + .append(m.getName()).append("';\n"); + } } if (defineProp) { append("}});"); @@ -2156,81 +2166,6 @@ return " = null;"; } - private void generateAnno(ClassData cd, byte[] data) throws IOException { - AnnotationParser ap = new AnnotationParser(true, false) { - int[] cnt = new int[32]; - int depth; - - @Override - protected void visitAnnotationStart(String attrType, boolean top) throws IOException { - final String slashType = attrType.substring(1, attrType.length() - 1); - requireReference(slashType); - - if (cnt[depth]++ > 0) { - append(","); - } - if (top) { - append('"').append(attrType).append("\" : "); - } - append("{\n"); - cnt[++depth] = 0; - } - - @Override - protected void visitAnnotationEnd(String type, boolean top) throws IOException { - append("\n}\n"); - depth--; - } - - @Override - protected void visitValueStart(String attrName, char type) throws IOException { - if (cnt[depth]++ > 0) { - append(",\n"); - } - cnt[++depth] = 0; - if (attrName != null) { - append('"').append(attrName).append("\" : "); - } - if (type == '[') { - append("["); - } - } - - @Override - protected void visitValueEnd(String attrName, char type) throws IOException { - if (type == '[') { - append("]"); - } - depth--; - } - - @Override - protected void visitAttr(String type, String attr, String attrType, String value) - throws IOException { - if (attr == null && value == null) { - return; - } - append(value); - } - - @Override - protected void visitEnumAttr(String type, String attr, String attrType, String value) - throws IOException { - final String slashType = attrType.substring(1, attrType.length() - 1); - requireReference(slashType); - - final String cn = mangleClassName(slashType); - append(accessClassFalse(cn)) - .append("['valueOf__L"). - append(cn). - append("_2Ljava_lang_String_2']('"). - append(value). - append("')"); - } - }; - ap.parse(data, cd); - } - private static String outputArg(Appendable out, String[] args, int indx) throws IOException { final String name = args[indx]; if (name == null) { @@ -2505,4 +2440,88 @@ private static void println(String msg) { System.err.println(msg); } + + private class GenerateAnno extends AnnotationParser { + public GenerateAnno(boolean textual, boolean iterateArray) { + super(textual, iterateArray); + } + int[] cnt = new int[32]; + int depth; + + @Override + protected void visitAnnotationStart(String attrType, boolean top) throws IOException { + final String slashType = attrType.substring(1, attrType.length() - 1); + requireReference(slashType); + + if (cnt[depth]++ > 0) { + append(","); + } + if (top) { + append('"').append(attrType).append("\" : "); + } + append("{\n"); + cnt[++depth] = 0; + } + + @Override + protected void visitAnnotationEnd(String type, boolean top) throws IOException { + append("\n}\n"); + depth--; + } + + @Override + protected void visitValueStart(String attrName, char type) throws IOException { + if (cnt[depth]++ > 0) { + append(",\n"); + } + cnt[++depth] = 0; + if (attrName != null) { + append('"').append(attrName).append("\" : "); + } + if (type == '[') { + append("["); + } + } + + @Override + protected void visitValueEnd(String attrName, char type) throws IOException { + if (type == '[') { + append("]"); + } + depth--; + } + + @Override + protected void visitAttr(String type, String attr, String attrType, String value) + throws IOException { + if (attr == null && value == null) { + return; + } + append(value); + } + + @Override + protected void visitEnumAttr(String type, String attr, String attrType, String value) + throws IOException { + final String slashType = attrType.substring(1, attrType.length() - 1); + requireReference(slashType); + + final String cn = mangleClassName(slashType); + append(accessClassFalse(cn)) + .append("['valueOf__L"). + append(cn). + append("_2Ljava_lang_String_2']('"). + append(value). + append("')"); + } + + @Override + protected void visitClassAttr(String annoType, String attr, String className) throws IOException { + final String slashType = className.substring(1, className.length() - 1); + requireReference(slashType); + + final String cn = mangleClassName(slashType); + append(accessClassFalse(cn)).append(".constructor.$class"); + } + } } diff -r d13786d6c185 -r e1953d8b8338 rt/vm/src/test/java/org/apidesign/vm4brwsr/ClassTest.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/ClassTest.java Sat Mar 19 10:28:03 2016 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/ClassTest.java Sat Mar 19 10:31:13 2016 +0100 @@ -87,12 +87,18 @@ @Test public void jsAnnotation() throws Exception { assertExec("Check class annotation", Classes.class, "getMarker__I", Double.valueOf(10)); } + @Test public void jsAnnotationDefaultValue() throws Exception { + assertExec("Check class annotation", Classes.class, "getMarkerDefault__I", Double.valueOf(42)); + } @Test public void jsArrayAnnotation() throws Exception { assertExec("Check array annotation", Classes.class, "getMarkerNicknames__Ljava_lang_String_2", Classes.getMarkerNicknames()); } @Test public void jsEnumAnnotation() throws Exception { assertExec("Check enum annotation", Classes.class, "getMarkerE__Ljava_lang_String_2", Classes.getMarkerE()); } + @Test public void jsEnumAnnotationDefault() throws Exception { + assertExec("Check enum annotation", Classes.class, "getMarkerED__Ljava_lang_String_2", Classes.getMarkerED()); + } @Test public void jsRetentionAnnotation() throws Exception { assertExec("Check enum annotation", Classes.class, "getRetention__Ljava_lang_String_2", Classes.getRetention()); } @@ -108,6 +114,12 @@ @Test public void jsInnerAnnotationFromArray() throws Exception { assertExec("Check inner annotation", Classes.class, "getInnerNamers__I", Double.valueOf(Classes.getInnerNamers())); } + @Test public void jsAnnotationClassAttr() throws Exception { + assertExec("Check annotation with class attribute", Classes.class, "self__I", 1); + } + @Test public void jsAnnotationDefaultClassAttr() throws Exception { + assertExec("Check annotation with class attribute", Classes.class, "defaultSelf__I", 1); + } @Test public void javaInvokeMethod() throws Exception { assertEquals(Classes.reflectiveMethodCall(true, "name"), "java.io.IOException", "Calls the name() method via reflection"); } diff -r d13786d6c185 -r e1953d8b8338 rt/vm/src/test/java/org/apidesign/vm4brwsr/Classes.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/Classes.java Sat Mar 19 10:28:03 2016 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/Classes.java Sat Mar 19 10:31:13 2016 +0100 @@ -67,6 +67,33 @@ public static String name() { return IOException.class.getName().toString(); } + + @ClassesMarker(self = Self.class, number = 42, nicknames = {}) + public static class Self { + } + + @ClassesMarker(number = 42, nicknames = {}) + public static class DefaultSelf { + } + + public static int self() { + ClassesMarker cm = Self.class.getAnnotation(ClassesMarker.class); + if (cm.self() == Self.class) { + return 1; + } else { + return 0; + } + } + + public static int defaultSelf() { + ClassesMarker cm = DefaultSelf.class.getAnnotation(ClassesMarker.class); + if (cm.self() == Object.class) { + return 1; + } else { + throw new IllegalStateException("" + cm.self()); + } + } + public static String simpleName() { return IOException.class.getSimpleName(); } @@ -103,6 +130,11 @@ assert !((Object)cm instanceof Class) : "Is not Class " + cm; return cm == null ? -1 : cm.number(); } + public static int getMarkerDefault() { + try { throw new IllegalStateException(); } catch (Exception e) {} + ClassesMarker cm = CD.class.getAnnotation(ClassesMarker.class); + return cm == null ? -1 : cm.number(); + } public static String getMarkerNicknames() { ClassesMarker cm = Classes.class.getAnnotation(ClassesMarker.class); if (cm == null) { @@ -149,6 +181,17 @@ } return cm.count().name(); } + + @ClassesMarker(nicknames = {}) + class CD { + } + public static String getMarkerED() { + ClassesMarker cm = CD.class.getAnnotation(ClassesMarker.class); + if (cm == null) { + return null; + } + return cm.count().name(); + } public static String getNamer(boolean direct) { if (direct) { ClassesNamer cm = Classes.class.getAnnotation(ClassesNamer.class); diff -r d13786d6c185 -r e1953d8b8338 rt/vm/src/test/java/org/apidesign/vm4brwsr/ClassesMarker.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/ClassesMarker.java Sat Mar 19 10:28:03 2016 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/ClassesMarker.java Sat Mar 19 10:31:13 2016 +0100 @@ -26,9 +26,10 @@ */ @Retention(RetentionPolicy.RUNTIME) public @interface ClassesMarker { - int number(); + int number() default 42; String[] nicknames(); E count() default E.ONE; + Class self() default Object.class; enum E { ONE, TWO;