/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.test.anno; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.TreeMap; public class TestAnnotations { /** * Print the annotations in sorted order, so as to avoid * any (legitimate) non-determinism with regard to the iteration order. */ static private void printAnnotationArray(String prefix, Annotation[] arr) { TreeMap sorted = new TreeMap(); for (Annotation a : arr) { sorted.put(a.annotationType().getName(), a); } for (Annotation a : sorted.values()) { System.out.println(prefix + " " + a); System.out.println(prefix + " " + a.annotationType()); } } static void printAnnotations(Class clazz) { Annotation[] annos; Annotation[][] parAnnos; annos = clazz.getAnnotations(); System.out.println("annotations on TYPE " + clazz + "(" + annos.length + "):"); printAnnotationArray("", annos); System.out.println(); for (Constructor c: clazz.getDeclaredConstructors()) { annos = c.getDeclaredAnnotations(); System.out.println(" annotations on CTOR " + c + ":"); printAnnotationArray(" ", annos); System.out.println(" constructor parameter annotations:"); for (Annotation[] pannos: c.getParameterAnnotations()) { printAnnotationArray(" ", pannos); } } for (Method m: clazz.getDeclaredMethods()) { annos = m.getDeclaredAnnotations(); System.out.println(" annotations on METH " + m + ":"); printAnnotationArray(" ", annos); System.out.println(" method parameter annotations:"); for (Annotation[] pannos: m.getParameterAnnotations()) { printAnnotationArray(" ", pannos); } } for (Field f: clazz.getDeclaredFields()) { annos = f.getDeclaredAnnotations(); System.out.println(" annotations on FIELD " + f + ":"); printAnnotationArray(" ", annos); AnnoFancyField aff; aff = (AnnoFancyField) f.getAnnotation(AnnoFancyField.class); if (aff != null) { System.out.println(" aff: " + aff + " / " + Proxy.isProxyClass(aff.getClass())); System.out.println(" --> nombre is '" + aff.nombre() + "'"); } } System.out.println(); } @ExportedProperty(mapping = { @IntToString(from = 0, to = "NORMAL_FOCUS"), @IntToString(from = 2, to = "WEAK_FOCUS") }) public int getFocusType() { return 2; } @AnnoArrayField String thing1; @AnnoArrayField( zz = {true,false,true}, bb = {-1,0,1}, cc = {'Q'}, ss = {12,13,14,15,16,17}, ii = {1,2,3,4}, ff = {1.1f,1.2f,1.3f}, jj = {-5,0,5}, dd = {0.3,0.6,0.9}, str = {"hickory","dickory","dock"} ) String thing2; public static void testArrays() { TestAnnotations ta = new TestAnnotations(); Field field; Annotation[] annotations; try { field = TestAnnotations.class.getDeclaredField("thing1"); annotations = field.getAnnotations(); System.out.println(field + ": " + annotations[0].toString()); field = TestAnnotations.class.getDeclaredField("thing2"); annotations = field.getAnnotations(); System.out.println(field + ": " + annotations[0].toString()); } catch (NoSuchFieldException nsfe) { throw new RuntimeException(nsfe); } } public static void testArrayProblem() { Method meth; ExportedProperty property; final IntToString[] mapping; try { meth = TestAnnotations.class.getMethod("getFocusType", (Class[])null); } catch (NoSuchMethodException nsme) { throw new RuntimeException(nsme); } property = meth.getAnnotation(ExportedProperty.class); mapping = property.mapping(); System.out.println("mapping is " + mapping.getClass() + "\n 0='" + mapping[0] + "'\n 1='" + mapping[1] + "'"); /* while we're here, check isAnnotationPresent on Method */ System.out.println("present(getFocusType, ExportedProperty): " + meth.isAnnotationPresent(ExportedProperty.class)); System.out.println("present(getFocusType, AnnoSimpleType): " + meth.isAnnotationPresent(AnnoSimpleType.class)); System.out.println(""); } public static void testVisibilityCompatibility() throws Exception { if (!VMRuntime.isAndroid()) { return; } Object runtime = VMRuntime.getRuntime(); int currentSdkVersion = VMRuntime.getTargetSdkVersion(runtime); // SDK version 23 is M. int oldSdkVersion = 23; VMRuntime.setTargetSdkVersion(runtime, oldSdkVersion); // This annotation has CLASS retention, but is visible to the runtime in M and earlier. Annotation anno = SimplyNoted.class.getAnnotation(AnnoSimpleTypeInvis.class); if (anno == null) { System.out.println("testVisibilityCompatibility failed: " + "SimplyNoted.get(AnnoSimpleTypeInvis) should not be null"); } VMRuntime.setTargetSdkVersion(runtime, currentSdkVersion); } public static void main(String[] args) { System.out.println("TestAnnotations..."); testArrays(); testArrayProblem(); System.out.println( "AnnoSimpleField " + AnnoSimpleField.class.isAnnotation() + ", SimplyNoted " + SimplyNoted.class.isAnnotation()); printAnnotations(SimplyNoted.class); printAnnotations(INoted.class); printAnnotations(SubNoted.class); printAnnotations(FullyNoted.class); try { ClassWithInnerAnnotationClass.class.getDeclaredClasses(); throw new AssertionError(); } catch (NoClassDefFoundError expected) { } // this is expected to be non-null Annotation anno = SimplyNoted.class.getAnnotation(AnnoSimpleType.class); System.out.println("SimplyNoted.get(AnnoSimpleType) = " + anno); // this is expected to be null anno = SimplyNoted.class.getAnnotation(AnnoSimpleTypeInvis.class); System.out.println("SimplyNoted.get(AnnoSimpleTypeInvis) = " + anno); // this is non-null if the @Inherited tag is present anno = SubNoted.class.getAnnotation(AnnoSimpleType.class); System.out.println("SubNoted.get(AnnoSimpleType) = " + anno); System.out.println(); // Package annotations aren't inherited, so getAnnotations and getDeclaredAnnotations are // the same. System.out.println("Package annotations:"); printAnnotationArray(" ", TestAnnotations.class.getPackage().getAnnotations()); System.out.println("Package declared annotations:"); printAnnotationArray(" ", TestAnnotations.class.getPackage().getDeclaredAnnotations()); System.out.println(); // Test inner classes. System.out.println("Inner Classes:"); new ClassWithInnerClasses().print(); System.out.println(); // Test TypeNotPresentException. try { AnnoMissingClass missingAnno = ClassWithMissingAnnotation.class.getAnnotation(AnnoMissingClass.class); System.out.println("Get annotation with missing class should not throw"); System.out.println(missingAnno.value()); System.out.println("Getting value of missing annotaton should have thrown"); } catch (TypeNotPresentException expected) { System.out.println("Got expected TypeNotPresentException"); } // Test renamed enums. try { for (Method m: RenamedNoted.class.getDeclaredMethods()) { Annotation[] annos = m.getDeclaredAnnotations(); System.out.println(" annotations on METH " + m + ":"); } } catch (NoSuchFieldError expected) { System.out.println("Got expected NoSuchFieldError"); } // Test if annotations marked VISIBILITY_BUILD are visible to runtime in M and earlier. try { testVisibilityCompatibility(); } catch (Exception e) { System.out.println("testVisibilityCompatibility failed: " + e); } } private static class VMRuntime { private static Class vmRuntimeClass; private static Method getRuntimeMethod; private static Method getTargetSdkVersionMethod; private static Method setTargetSdkVersionMethod; static { init(); } private static void init() { try { vmRuntimeClass = Class.forName("dalvik.system.VMRuntime"); } catch (Exception e) { return; } try { getRuntimeMethod = vmRuntimeClass.getDeclaredMethod("getRuntime"); getTargetSdkVersionMethod = vmRuntimeClass.getDeclaredMethod("getTargetSdkVersion"); setTargetSdkVersionMethod = vmRuntimeClass.getDeclaredMethod("setTargetSdkVersion", Integer.TYPE); } catch (Exception e) { throw new RuntimeException(e); } } public static boolean isAndroid() { return vmRuntimeClass != null; } public static Object getRuntime() throws Exception { return getRuntimeMethod.invoke(null); } public static int getTargetSdkVersion(Object runtime) throws Exception { return (int) getTargetSdkVersionMethod.invoke(runtime); } public static void setTargetSdkVersion(Object runtime, int version) throws Exception { setTargetSdkVersionMethod.invoke(runtime, version); } } }