package com.google.gwt.reflect.test; import org.junit.Test; import xapi.gwt.junit.api.JUnitExecution; import static com.google.gwt.reflect.shared.GwtReflect.magicClass; import static com.google.gwt.reflect.test.annotations.AbstractAnnotation.MemberType.Annotation; import static com.google.gwt.reflect.test.annotations.AbstractAnnotation.MemberType.Annotation_Array; import static com.google.gwt.reflect.test.annotations.AbstractAnnotation.MemberType.Boolean; import static com.google.gwt.reflect.test.annotations.AbstractAnnotation.MemberType.Boolean_Array; import static com.google.gwt.reflect.test.annotations.AbstractAnnotation.MemberType.Class; import static com.google.gwt.reflect.test.annotations.AbstractAnnotation.MemberType.Class_Array; import static com.google.gwt.reflect.test.annotations.AbstractAnnotation.MemberType.Enum; import static com.google.gwt.reflect.test.annotations.AbstractAnnotation.MemberType.Enum_Array; import static com.google.gwt.reflect.test.annotations.AbstractAnnotation.MemberType.Int; import static com.google.gwt.reflect.test.annotations.AbstractAnnotation.MemberType.Int_Array; import static com.google.gwt.reflect.test.annotations.AbstractAnnotation.MemberType.Long; import static com.google.gwt.reflect.test.annotations.AbstractAnnotation.MemberType.Long_Array; import static com.google.gwt.reflect.test.annotations.AbstractAnnotation.MemberType.String; import static com.google.gwt.reflect.test.annotations.AbstractAnnotation.MemberType.String_Array; import com.google.gwt.core.client.UnsafeNativeLong; import com.google.gwt.core.shared.GWT; import com.google.gwt.reflect.client.strategy.GwtRetention; import com.google.gwt.reflect.client.strategy.ReflectionStrategy; import com.google.gwt.reflect.test.annotations.AbstractAnnotation; import com.google.gwt.reflect.test.annotations.CompileRetention; import com.google.gwt.reflect.test.annotations.ComplexAnnotation; import com.google.gwt.reflect.test.annotations.InheritedAnnotation; import com.google.gwt.reflect.test.annotations.RuntimeRetention; import com.google.gwt.reflect.test.annotations.SimpleAnnotation; import com.google.gwt.reflect.test.annotations.UninheritedAnnotation; import com.google.gwt.reflect.test.cases.ReflectionCaseHasAllAnnos; import com.google.gwt.reflect.test.cases.ReflectionCaseSimple; import com.google.gwt.reflect.test.cases.ReflectionCaseSubclass; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; @ComplexAnnotation @SuppressWarnings("all") @ReflectionStrategy( // Keep all annotations on this type annotationRetention=ReflectionStrategy.ALL_ANNOTATIONS, // Keep all annotations on this type's members memberRetention=@GwtRetention( annotationRetention=ReflectionStrategy.ALL_ANNOTATIONS ) ) public class AnnotationTests extends AbstractReflectionTest{ public JUnitExecution exe; static class ValueImpl extends AbstractAnnotation implements SimpleAnnotation { public ValueImpl() { this("1"); } public ValueImpl(final String value) { setValue("value", value); } @Override public Class<? extends Annotation> annotationType() { return SimpleAnnotation.class; } @Override public String value() { return getValue("value"); } @Override protected String[] members() { return new String[] {"value"}; } @Override protected MemberType[] memberTypes() { return new MemberType[] {String}; } } static class TestCaseImpl extends AbstractAnnotation implements ComplexAnnotation { private static final String[] members = new String[] { "singleBool", "singleInt", "singleLong", "singleString", "singleEnum", "singleAnnotation", "singleClass", "multiBool", "multiInt", "multiLong", "multiString", "multiEnum", "multiAnnotation", "multiClass" }; private static final MemberType[] types = new MemberType[] { Boolean, Int, Long, String, Enum, Annotation, Class, Boolean_Array, Int_Array, Long_Array, String_Array, Enum_Array, Annotation_Array, Class_Array }; public TestCaseImpl() { set( true, 1, 2, "3", ElementType.ANNOTATION_TYPE, new ValueImpl(), ElementType.class, new boolean[]{true, false, true}, new int[] {1, 3, 2}, new long[] {2, 4, 3}, new String[] {"3", "0", "a"}, new ElementType[] {ElementType.CONSTRUCTOR, ElementType.ANNOTATION_TYPE}, new SimpleAnnotation[] {new ValueImpl(), new ValueImpl("2")}, new Class<?>[] {ElementType.class, SimpleAnnotation.class} ); } @UnsafeNativeLong public native void set( boolean singleBool, int singleInt, long singleLong, String singleString, Enum<?> singleEnum, SimpleAnnotation singleAnnotation, Class<?> singleClass, boolean[] multiBool, int[] multiInt, long[] multiLong, String[] multiString, Enum<?>[] multiEnum, Annotation[] multiAnnotation, Class<?>[] multiClass ) /*-{ var m = this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap; m['singleBool'] = singleBool; m['singleInt'] = singleInt; m['singleLong'] = singleLong; m['singleString'] = singleString; m['singleEnum'] = singleEnum; m['singleAnnotation'] = singleAnnotation; m['singleClass'] = singleClass; m['multiBool'] = multiBool; m['multiInt'] = multiInt; m['multiLong'] = multiLong; m['multiString'] = multiString; m['multiEnum'] = multiEnum; m['multiAnnotation'] = multiAnnotation; m['multiClass'] = multiClass; }-*/; @Override public native boolean singleBool() /*-{ return this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap['singleBool']; }-*/; @Override public native int singleInt() /*-{ return this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap['singleInt']; }-*/; @Override @UnsafeNativeLong public native long singleLong() /*-{ return this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap['singleLong']; }-*/; @Override public native String singleString() /*-{ return this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap['singleString']; }-*/; @Override public native ElementType singleEnum() /*-{ return this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap['singleEnum']; }-*/; @Override public native SimpleAnnotation singleAnnotation() /*-{ return this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap['singleAnnotation']; }-*/; @Override public native Class<?> singleClass() /*-{ return this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap['singleClass']; }-*/; @Override public native boolean[] multiBool() /*-{ return this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap['multiBool']; }-*/; @Override public native int[] multiInt() /*-{ return this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap['multiInt']; }-*/; @Override @UnsafeNativeLong public native long[] multiLong() /*-{ return this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap['multiLong']; }-*/; @Override public native String[] multiString() /*-{ return this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap['multiString']; }-*/; @Override public native ElementType[] multiEnum() /*-{ return this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap['multiEnum']; }-*/; @Override public native SimpleAnnotation[] multiAnnotation() /*-{ return this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap['multiAnnotation']; }-*/; @Override public native Class<?>[] multiClass() /*-{ return this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap['multiClass']; }-*/; @Override public Class<? extends Annotation> annotationType() { return ComplexAnnotation.class; } @Override protected String[] members() { return members; } @Override protected MemberType[] memberTypes() { return types; } } @SimpleAnnotation Object field; @SimpleAnnotation void method(){} @Test public void testAnnotationMethods() { if (!GWT.isClient()) { return;// Don't let jvms try to load jsni; this @Test is gwt only } final TestCaseImpl impl1 = new TestCaseImpl(); final TestCaseImpl impl2 = new TestCaseImpl(); assertEquals(impl1, impl2); assertEquals(impl1.toString(), impl2.toString()); assertEquals(impl1.hashCode(), impl2.hashCode()); } @Test public void testSimpleReflection() throws Exception { final Class<ReflectionCaseSimple> c = ReflectionCaseSimple.class; final ReflectionCaseSimple inst = testNewInstance(magicClass(c)); final ReflectionCaseSimple anon = new ReflectionCaseSimple() {}; testAssignable(inst, anon); testHasNoArgDeclaredMethods(c, "privatePrimitive", "privateObject", "publicPrimitive", "publicObject"); testHasNoArgPublicMethods(c, "publicPrimitive", "publicObject", "hashCode", "toString"); testCantAccessNonPublicMethods(c, "privatePrimitive", "privateObject"); testCantAccessNonDeclaredMethods(c, "hashCode", "toString"); } @Test public void testSelfEquals() { final ComplexAnnotation self = AnnotationTests.class.getAnnotation(ComplexAnnotation.class); assertEquals(self, self); } @Test @ComplexAnnotation public void testOtherEquals() throws Throwable { final ComplexAnnotation onType = AnnotationTests.class.getAnnotation(ComplexAnnotation.class); final Method method = AnnotationTests.class.getMethod("testOtherEquals"); final ComplexAnnotation onMethod = method.getAnnotation(ComplexAnnotation.class); assertTrue(onType.equals(onMethod)); log(onType.toString()); } /** * @param onType */ private native void log(String onType) /*-{ $wnd.console.log(onType); }-*/; @Test public void testAnnotationsKeepAll() throws Exception { final Class<?> testCase = magicClass(ReflectionCaseHasAllAnnos.class); final Field field = testCase.getDeclaredField("field"); final Method method = testCase.getDeclaredMethod("method", Long.class); final Constructor<?> ctor = testCase.getDeclaredConstructor(long.class); Annotation[] annos = testCase.getAnnotations(); assertHasAnno(testCase, annos, RuntimeRetention.class); if (GWT.isScript()) { // Gwt Dev can only access runtime level retention annotations assertHasAnno(testCase, annos, CompileRetention.class); } annos = field.getAnnotations(); assertHasAnno(testCase, annos, RuntimeRetention.class); if (GWT.isScript()) { // Gwt Dev can only access runtime level retention annotations assertHasAnno(testCase, annos, CompileRetention.class); } annos = method.getAnnotations(); assertHasAnno(testCase, annos, RuntimeRetention.class); if (GWT.isScript()) { // Gwt Dev can only access runtime level retention annotations assertHasAnno(testCase, annos, CompileRetention.class); } annos = ctor.getAnnotations(); assertHasAnno(testCase, annos, RuntimeRetention.class); if (GWT.isScript()) { // Gwt Dev can only access runtime level retention annotations assertHasAnno(testCase, annos, CompileRetention.class); } } @Test public void testAnnotationOnField() throws Throwable { final Field f = AnnotationTests.class.getDeclaredField("field"); final SimpleAnnotation anno = f.getAnnotation(SimpleAnnotation.class); assertNotNull(anno); } @Test public void testInheritsAnnotations() throws Throwable { final InheritedAnnotation inherited = ReflectionCaseHasAllAnnos.class.getAnnotation(InheritedAnnotation.class); final UninheritedAnnotation uninherited = ReflectionCaseHasAllAnnos.class.getAnnotation(UninheritedAnnotation.class); assertNotNull(inherited); assertNull(uninherited); } @Test public void testInheritsAnnotationsFilter() throws Throwable { // In order to use a class reference here and bypass the injector, which will ignore any sort // of reflection strategy restrictions, we must ensure that our class reference is not tracable // to a constant literal expression. Thus, we obscure it with an opaque conditional guaranteed to be false final Class<ReflectionCaseSubclass> cls = Math.random() == -1 ? null : ReflectionCaseSubclass.class; final InheritedAnnotation inherited = cls.getAnnotation(InheritedAnnotation.class); final UninheritedAnnotation uninherited = cls.getAnnotation(UninheritedAnnotation.class); assertNull(inherited); assertNull(uninherited); } private void assertHasAnno(final Class<?> cls, final Annotation[] annos, final Class<? extends Annotation> annoClass) { for (final Annotation anno : annos) { if (anno.annotationType() == annoClass) { return; } } fail(cls.getName()+" did not have required annotation "+annoClass); } private void testCantAccessNonPublicMethods(final Class<?> c, final String ... methods) { for (final String method : methods) { try { c.getMethod(method); fail("Could erroneously access non-public method "+method+" in "+c.getName()); } catch (final NoSuchMethodException e) {} } } private void testCantAccessNonDeclaredMethods(final Class<?> c, final String ... methods) { for (final String method : methods) { try { c.getDeclaredMethod(method); fail("Could erroneously access non-declared method "+method+" in "+c.getName()); } catch (final NoSuchMethodException e) {} } } private void testHasNoArgDeclaredMethods(final Class<?> c, final String ... methods) throws Exception{ for (final String method : methods) { assertNotNull(c.getDeclaredMethod(method)); } } private void testHasNoArgPublicMethods(final Class<?> c, final String ... methods) throws Exception{ for (final String method : methods) { assertNotNull(c.getMethod(method)); } } private void testAssignable(final Object inst, final Object anon) { assertTrue(inst.getClass().isAssignableFrom(anon.getClass())); assertFalse(anon.getClass().isAssignableFrom(inst.getClass())); } private <T> T testNewInstance(final Class<T> c) throws Exception { final T newInst = c.newInstance(); assertNotNull(c.getName()+" returned null instead of a new instance", newInst); assertTrue(c.isAssignableFrom(newInst.getClass())); return newInst; } }