1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.test.anno;
18 
19 import java.lang.annotation.Annotation;
20 import java.lang.annotation.AnnotationFormatError;
21 import java.lang.reflect.Constructor;
22 import java.lang.reflect.Field;
23 import java.lang.reflect.Method;
24 import java.lang.reflect.Proxy;
25 import java.util.TreeMap;
26 
27 public class TestAnnotations {
28     /**
29      * Print the annotations in sorted order, so as to avoid
30      * any (legitimate) non-determinism with regard to the iteration order.
31      */
printAnnotationArray(String prefix, Annotation[] arr)32     static private void printAnnotationArray(String prefix, Annotation[] arr) {
33         TreeMap<String, Annotation> sorted =
34             new TreeMap<String, Annotation>();
35 
36         for (Annotation a : arr) {
37             sorted.put(a.annotationType().getName(), a);
38         }
39 
40         for (Annotation a : sorted.values()) {
41             System.out.println(prefix + "  " + a);
42             System.out.println(prefix + "    " + a.annotationType());
43         }
44     }
45 
printAnnotations(Class<?> clazz)46     static void printAnnotations(Class<?> clazz) {
47         Annotation[] annos;
48         Annotation[][] parAnnos;
49 
50         annos = clazz.getAnnotations();
51         System.out.println("annotations on TYPE " + clazz +
52             "(" + annos.length + "):");
53         printAnnotationArray("", annos);
54         System.out.println();
55 
56         for (Constructor<?> c: clazz.getDeclaredConstructors()) {
57             annos = c.getDeclaredAnnotations();
58             System.out.println("  annotations on CTOR " + c + ":");
59             printAnnotationArray("  ", annos);
60 
61             System.out.println("    constructor parameter annotations:");
62             for (Annotation[] pannos: c.getParameterAnnotations()) {
63                 printAnnotationArray("    ", pannos);
64             }
65         }
66 
67         for (Method m: clazz.getDeclaredMethods()) {
68             annos = m.getDeclaredAnnotations();
69             System.out.println("  annotations on METH " + m + ":");
70             printAnnotationArray("  ", annos);
71 
72             System.out.println("    method parameter annotations:");
73             for (Annotation[] pannos: m.getParameterAnnotations()) {
74                 printAnnotationArray("    ", pannos);
75             }
76         }
77 
78         for (Field f: clazz.getDeclaredFields()) {
79             annos = f.getDeclaredAnnotations();
80             System.out.println("  annotations on FIELD " + f + ":");
81             printAnnotationArray("  ", annos);
82 
83             AnnoFancyField aff;
84             aff = (AnnoFancyField) f.getAnnotation(AnnoFancyField.class);
85             if (aff != null) {
86                 System.out.println("    aff: " + aff + " / " + Proxy.isProxyClass(aff.getClass()));
87                 System.out.println("    --> nombre is '" + aff.nombre() + "'");
88             }
89         }
90         System.out.println();
91     }
92 
93 
94     @ExportedProperty(mapping = {
95         @IntToString(from = 0, to = "NORMAL_FOCUS"),
96         @IntToString(from = 2, to = "WEAK_FOCUS")
97     })
getFocusType()98     public int getFocusType() {
99         return 2;
100     }
101 
102 
103     @AnnoArrayField
104     String thing1;
105 
106     @AnnoArrayField(
107             zz = {true,false,true},
108             bb = {-1,0,1},
109             cc = {'Q'},
110             ss = {12,13,14,15,16,17},
111             ii = {1,2,3,4},
112             ff = {1.1f,1.2f,1.3f},
113             jj = {-5,0,5},
114             dd = {0.3,0.6,0.9},
115             str = {"hickory","dickory","dock"}
116             )
117     String thing2;
118 
testArrays()119     public static void testArrays() {
120         TestAnnotations ta = new TestAnnotations();
121         Field field;
122         Annotation[] annotations;
123 
124         try {
125             field = TestAnnotations.class.getDeclaredField("thing1");
126             annotations = field.getAnnotations();
127             System.out.println(field + ": " + annotations[0].toString());
128 
129             field = TestAnnotations.class.getDeclaredField("thing2");
130             annotations = field.getAnnotations();
131             System.out.println(field + ": " + annotations[0].toString());
132         } catch (NoSuchFieldException nsfe) {
133             throw new RuntimeException(nsfe);
134         }
135     }
136 
testArrayProblem()137     public static void testArrayProblem() {
138         Method meth;
139         ExportedProperty property;
140         final IntToString[] mapping;
141 
142         try {
143             meth = TestAnnotations.class.getMethod("getFocusType");
144         } catch (NoSuchMethodException nsme) {
145             throw new RuntimeException(nsme);
146         }
147         property = meth.getAnnotation(ExportedProperty.class);
148         mapping = property.mapping();
149 
150         System.out.println("mapping is " + mapping.getClass() +
151             "\n  0='" + mapping[0] + "'\n  1='" + mapping[1] + "'");
152 
153         /* while we're here, check isAnnotationPresent on Method */
154         System.out.println("present(getFocusType, ExportedProperty): " +
155             meth.isAnnotationPresent(ExportedProperty.class));
156         System.out.println("present(getFocusType, AnnoSimpleType): " +
157             meth.isAnnotationPresent(AnnoSimpleType.class));
158 
159         System.out.println("");
160     }
161 
testVisibilityCompatibility()162     public static void testVisibilityCompatibility() throws Exception {
163         if (!VMRuntime.isAndroid()) {
164             return;
165         }
166         Object runtime = VMRuntime.getRuntime();
167         int currentSdkVersion = VMRuntime.getTargetSdkVersion(runtime);
168         // SDK version 23 is M.
169         int oldSdkVersion = 23;
170         VMRuntime.setTargetSdkVersion(runtime, oldSdkVersion);
171         // This annotation has CLASS retention, but is visible to the runtime in M and earlier.
172         Annotation anno = SimplyNoted.class.getAnnotation(AnnoSimpleTypeInvis.class);
173         if (anno == null) {
174             System.out.println("testVisibilityCompatibility failed: " +
175                     "SimplyNoted.get(AnnoSimpleTypeInvis) should not be null");
176         }
177         VMRuntime.setTargetSdkVersion(runtime, currentSdkVersion);
178     }
179 
main(String[] args)180     public static void main(String[] args) {
181         System.out.println("TestAnnotations...");
182 
183         testArrays();
184         testArrayProblem();
185 
186         System.out.println(
187             "AnnoSimpleField " + AnnoSimpleField.class.isAnnotation() +
188             ", SimplyNoted " + SimplyNoted.class.isAnnotation());
189 
190         printAnnotations(SimplyNoted.class);
191         printAnnotations(INoted.class);
192         printAnnotations(SubNoted.class);
193         printAnnotations(FullyNoted.class);
194 
195         try {
196             ClassWithInnerAnnotationClass.class.getDeclaredClasses();
197             throw new AssertionError();
198         } catch (NoClassDefFoundError expected) {
199         }
200 
201         // this is expected to be non-null
202         Annotation anno = SimplyNoted.class.getAnnotation(AnnoSimpleType.class);
203         System.out.println("SimplyNoted.get(AnnoSimpleType) = " + anno);
204         // this is expected to be null
205         anno = SimplyNoted.class.getAnnotation(AnnoSimpleTypeInvis.class);
206         System.out.println("SimplyNoted.get(AnnoSimpleTypeInvis) = " + anno);
207         // this is non-null if the @Inherited tag is present
208         anno = SubNoted.class.getAnnotation(AnnoSimpleType.class);
209         System.out.println("SubNoted.get(AnnoSimpleType) = " + anno);
210 
211         System.out.println();
212 
213         // Package annotations aren't inherited, so getAnnotations and getDeclaredAnnotations are
214         // the same.
215         System.out.println("Package annotations:");
216         printAnnotationArray("    ", TestAnnotations.class.getPackage().getAnnotations());
217         System.out.println("Package declared annotations:");
218         printAnnotationArray("    ", TestAnnotations.class.getPackage().getDeclaredAnnotations());
219 
220         System.out.println();
221 
222         // Test inner classes.
223         System.out.println("Inner Classes:");
224         new ClassWithInnerClasses().print();
225 
226         System.out.println();
227 
228         // Test TypeNotPresentException.
229         try {
230             AnnoMissingClass missingAnno =
231                 ClassWithMissingAnnotation.class.getAnnotation(AnnoMissingClass.class);
232             System.out.println("Get annotation with missing class should not throw");
233             System.out.println(missingAnno.value());
234             System.out.println("Getting value of missing annotaton should have thrown");
235         } catch (TypeNotPresentException expected) {
236             System.out.println("Got expected TypeNotPresentException");
237         }
238 
239         // Test renamed enums.
240         try {
241             for (Method m: RenamedNoted.class.getDeclaredMethods()) {
242                 Annotation[] annos = m.getDeclaredAnnotations();
243                 System.out.println("  annotations on METH " + m + ":");
244             }
245         } catch (Error expected) {
246             System.out.println("Got expected Error for renamed enum");
247         }
248 
249         // Test if annotations marked VISIBILITY_BUILD are visible to runtime in M and earlier.
250         try {
251             testVisibilityCompatibility();
252         } catch (Exception e) {
253             System.out.println("testVisibilityCompatibility failed: " + e);
254         }
255     }
256 
257     private static class VMRuntime {
258         private static Class<?> vmRuntimeClass;
259         private static Method getRuntimeMethod;
260         private static Method getTargetSdkVersionMethod;
261         private static Method setTargetSdkVersionMethod;
262         static {
init()263             init();
264         }
265 
init()266         private static void init() {
267             try {
268                 vmRuntimeClass = Class.forName("dalvik.system.VMRuntime");
269             } catch (Exception e) {
270                 return;
271             }
272             try {
273                 getRuntimeMethod = vmRuntimeClass.getDeclaredMethod("getRuntime");
274                 getTargetSdkVersionMethod =
275                         vmRuntimeClass.getDeclaredMethod("getTargetSdkVersion");
276                 setTargetSdkVersionMethod =
277                         vmRuntimeClass.getDeclaredMethod("setTargetSdkVersion", Integer.TYPE);
278             } catch (Exception e) {
279                 throw new RuntimeException(e);
280             }
281         }
282 
isAndroid()283         public static boolean isAndroid() {
284             return vmRuntimeClass != null;
285         }
286 
getRuntime()287         public static Object getRuntime() throws Exception {
288             return getRuntimeMethod.invoke(null);
289         }
290 
getTargetSdkVersion(Object runtime)291         public static int getTargetSdkVersion(Object runtime) throws Exception {
292             return (int) getTargetSdkVersionMethod.invoke(runtime);
293         }
294 
setTargetSdkVersion(Object runtime, int version)295         public static void setTargetSdkVersion(Object runtime, int version) throws Exception {
296             setTargetSdkVersionMethod.invoke(runtime, version);
297         }
298     }
299 }
300