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
17package android.test.anno;
18
19import java.lang.annotation.Annotation;
20import java.lang.reflect.Constructor;
21import java.lang.reflect.Field;
22import java.lang.reflect.Method;
23import java.lang.reflect.Proxy;
24import java.util.TreeMap;
25
26public class TestAnnotations {
27    /**
28     * Print the annotations in sorted order, so as to avoid
29     * any (legitimate) non-determinism with regard to the iteration order.
30     */
31    static private void printAnnotationArray(String prefix, Annotation[] arr) {
32        TreeMap<String, Annotation> sorted =
33            new TreeMap<String, Annotation>();
34
35        for (Annotation a : arr) {
36            sorted.put(a.annotationType().getName(), a);
37        }
38
39        for (Annotation a : sorted.values()) {
40            System.out.println(prefix + "  " + a);
41            System.out.println(prefix + "    " + a.annotationType());
42        }
43    }
44
45    static void printAnnotations(Class clazz) {
46        Annotation[] annos;
47        Annotation[][] parAnnos;
48
49        annos = clazz.getAnnotations();
50        System.out.println("annotations on TYPE " + clazz +
51            "(" + annos.length + "):");
52        printAnnotationArray("", annos);
53        System.out.println();
54
55        for (Constructor c: clazz.getDeclaredConstructors()) {
56            annos = c.getDeclaredAnnotations();
57            System.out.println("  annotations on CTOR " + c + ":");
58            printAnnotationArray("  ", annos);
59
60            System.out.println("    constructor parameter annotations:");
61            for (Annotation[] pannos: c.getParameterAnnotations()) {
62                printAnnotationArray("    ", pannos);
63            }
64        }
65
66        for (Method m: clazz.getDeclaredMethods()) {
67            annos = m.getDeclaredAnnotations();
68            System.out.println("  annotations on METH " + m + ":");
69            printAnnotationArray("  ", annos);
70
71            System.out.println("    method parameter annotations:");
72            for (Annotation[] pannos: m.getParameterAnnotations()) {
73                printAnnotationArray("    ", pannos);
74            }
75        }
76
77        for (Field f: clazz.getDeclaredFields()) {
78            annos = f.getDeclaredAnnotations();
79            System.out.println("  annotations on FIELD " + f + ":");
80            printAnnotationArray("  ", annos);
81
82            AnnoFancyField aff;
83            aff = (AnnoFancyField) f.getAnnotation(AnnoFancyField.class);
84            if (aff != null) {
85                System.out.println("    aff: " + aff + " / " + Proxy.isProxyClass(aff.getClass()));
86                System.out.println("    --> nombre is '" + aff.nombre() + "'");
87            }
88        }
89        System.out.println();
90    }
91
92
93    @ExportedProperty(mapping = {
94        @IntToString(from = 0, to = "NORMAL_FOCUS"),
95        @IntToString(from = 2, to = "WEAK_FOCUS")
96    })
97    public int getFocusType() {
98        return 2;
99    }
100
101
102    @AnnoArrayField
103    String thing1;
104
105    @AnnoArrayField(
106            zz = {true,false,true},
107            bb = {-1,0,1},
108            cc = {'Q'},
109            ss = {12,13,14,15,16,17},
110            ii = {1,2,3,4},
111            ff = {1.1f,1.2f,1.3f},
112            jj = {-5,0,5},
113            dd = {0.3,0.6,0.9},
114            str = {"hickory","dickory","dock"}
115            )
116    String thing2;
117
118    public static void testArrays() {
119        TestAnnotations ta = new TestAnnotations();
120        Field field;
121        Annotation[] annotations;
122
123        try {
124            field = TestAnnotations.class.getDeclaredField("thing1");
125            annotations = field.getAnnotations();
126            System.out.println(field + ": " + annotations[0].toString());
127
128            field = TestAnnotations.class.getDeclaredField("thing2");
129            annotations = field.getAnnotations();
130            System.out.println(field + ": " + annotations[0].toString());
131        } catch (NoSuchFieldException nsfe) {
132            throw new RuntimeException(nsfe);
133        }
134    }
135
136    public static void testArrayProblem() {
137        Method meth;
138        ExportedProperty property;
139        final IntToString[] mapping;
140
141        try {
142            meth = TestAnnotations.class.getMethod("getFocusType",
143                    (Class[])null);
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
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
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 (NoSuchFieldError expected) {
246            System.out.println("Got expected NoSuchFieldError");
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 {
263            init();
264        }
265
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
283        public static boolean isAndroid() {
284            return vmRuntimeClass != null;
285        }
286
287        public static Object getRuntime() throws Exception {
288            return getRuntimeMethod.invoke(null);
289        }
290
291        public static int getTargetSdkVersion(Object runtime) throws Exception {
292            return (int) getTargetSdkVersionMethod.invoke(runtime);
293        }
294
295        public static void setTargetSdkVersion(Object runtime, int version) throws Exception {
296            setTargetSdkVersionMethod.invoke(runtime, version);
297        }
298    }
299}
300