• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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 package com.android.dx;
17 
18 import static com.android.dx.TypeId.*;
19 
20 import static org.junit.Assert.assertEquals;
21 import static org.junit.Assert.fail;
22 import static org.junit.Assume.assumeTrue;
23 
24 import android.os.Build;
25 
26 import androidx.test.InstrumentationRegistry;
27 
28 import org.junit.After;
29 import org.junit.Before;
30 import org.junit.Test;
31 
32 import static java.lang.reflect.Modifier.PUBLIC;
33 
34 import java.io.File;
35 import java.lang.annotation.*;
36 import java.lang.reflect.Method;
37 import java.util.HashMap;
38 import java.util.List;
39 import java.util.Map;
40 
41 public final class AnnotationIdTest {
42 
43     /**
44      *  Method Annotation definition for test
45      */
46     @Retention(RetentionPolicy.RUNTIME)
47     @Target({ElementType.METHOD})
48     @interface MethodAnnotation {
elementBoolean()49         boolean elementBoolean() default false;
elementByte()50         byte elementByte() default Byte.MIN_VALUE;
elementChar()51         char elementChar() default 'a';
elementDouble()52         double elementDouble() default Double.MIN_NORMAL;
elementFloat()53         float elementFloat() default Float.MIN_NORMAL;
elementInt()54         int elementInt() default Integer.MIN_VALUE;
elementLong()55         long elementLong() default Long.MIN_VALUE;
elementShort()56         short elementShort() default Short.MIN_VALUE;
elementString()57         String elementString() default "foo";
elementEnum()58         ElementEnum elementEnum() default ElementEnum.INSTANCE_0;
elementClass()59         Class<?> elementClass() default Object.class;
60     }
61 
62     enum ElementEnum {
63         INSTANCE_0,
64         INSTANCE_1,
65     }
66 
67     private DexMaker dexMaker;
68     private static TypeId<?> GENERATED = TypeId.get("LGenerated;");
69     private static final Map<TypeId<?>, Class<?>> TYPE_TO_PRIMITIVE = new HashMap<>();
70     static {
TYPE_TO_PRIMITIVE.put(BOOLEAN, boolean.class)71         TYPE_TO_PRIMITIVE.put(BOOLEAN, boolean.class);
TYPE_TO_PRIMITIVE.put(BYTE, byte.class)72         TYPE_TO_PRIMITIVE.put(BYTE, byte.class);
TYPE_TO_PRIMITIVE.put(CHAR, char.class)73         TYPE_TO_PRIMITIVE.put(CHAR, char.class);
TYPE_TO_PRIMITIVE.put(DOUBLE, double.class)74         TYPE_TO_PRIMITIVE.put(DOUBLE, double.class);
TYPE_TO_PRIMITIVE.put(FLOAT, float.class)75         TYPE_TO_PRIMITIVE.put(FLOAT, float.class);
TYPE_TO_PRIMITIVE.put(INT, int.class)76         TYPE_TO_PRIMITIVE.put(INT, int.class);
TYPE_TO_PRIMITIVE.put(LONG, long.class)77         TYPE_TO_PRIMITIVE.put(LONG, long.class);
TYPE_TO_PRIMITIVE.put(SHORT, short.class)78         TYPE_TO_PRIMITIVE.put(SHORT, short.class);
TYPE_TO_PRIMITIVE.put(VOID, void.class)79         TYPE_TO_PRIMITIVE.put(VOID, void.class);
80     }
81 
82     @Before
setUp()83     public void setUp() {
84         init();
85     }
86 
87     /**
88      *  Test adding a method annotation with new value of Boolean element.
89      */
90     @Test
addMethodAnnotationWithBooleanElement()91     public void addMethodAnnotationWithBooleanElement() throws Exception {
92         MethodId<?, Void> methodId = generateVoidMethod(TypeId.BOOLEAN);
93         AnnotationId.Element element = new AnnotationId.Element("elementBoolean", true);
94         addAnnotationToMethod(methodId, element);
95 
96         Annotation[] methodAnnotations = getMethodAnnotations(methodId);
97         assertEquals(methodAnnotations.length, 1);
98 
99         Boolean elementBoolean = ((MethodAnnotation)methodAnnotations[0]).elementBoolean();
100         assertEquals(true, elementBoolean);
101     }
102 
103     /**
104      *  Test adding a method annotation with new value of Byte element.
105      */
106     @Test
addMethodAnnotationWithByteElement()107     public void addMethodAnnotationWithByteElement() throws Exception {
108         MethodId<?, Void> methodId = generateVoidMethod(TypeId.BYTE);
109         AnnotationId.Element element = new AnnotationId.Element("elementByte", Byte.MAX_VALUE);
110         addAnnotationToMethod(methodId, element);
111 
112         Annotation[] methodAnnotations = getMethodAnnotations(methodId);
113         assertEquals(methodAnnotations.length, 1);
114 
115         byte elementByte = ((MethodAnnotation)methodAnnotations[0]).elementByte();
116         assertEquals(Byte.MAX_VALUE, elementByte);
117     }
118 
119     /**
120      *  Test adding a method annotation with new value of Char element.
121      */
122     @Test
addMethodAnnotationWithCharElement()123     public void addMethodAnnotationWithCharElement() throws Exception {
124         MethodId<?, Void> methodId = generateVoidMethod(TypeId.CHAR);
125         AnnotationId.Element element = new AnnotationId.Element("elementChar", 'X');
126         addAnnotationToMethod(methodId, element);
127 
128         Annotation[] methodAnnotations = getMethodAnnotations(methodId);
129         assertEquals(methodAnnotations.length, 1);
130 
131         char elementChar = ((MethodAnnotation)methodAnnotations[0]).elementChar();
132         assertEquals('X', elementChar);
133     }
134 
135     /**
136      *  Test adding a method annotation with new value of Double element.
137      */
138     @Test
addMethodAnnotationWithDoubleElement()139     public void addMethodAnnotationWithDoubleElement() throws Exception {
140         MethodId<?, Void> methodId = generateVoidMethod(TypeId.DOUBLE);
141         AnnotationId.Element element = new AnnotationId.Element("elementDouble", Double.NaN);
142         addAnnotationToMethod(methodId, element);
143 
144         Annotation[] methodAnnotations = getMethodAnnotations(methodId);
145         assertEquals(methodAnnotations.length, 1);
146 
147         double elementDouble = ((MethodAnnotation)methodAnnotations[0]).elementDouble();
148         assertEquals(Double.NaN, elementDouble, 0);
149     }
150 
151     /**
152      *  Test adding a method annotation with new value of Float element.
153      */
154     @Test
addMethodAnnotationWithFloatElement()155     public void addMethodAnnotationWithFloatElement() throws Exception {
156         MethodId<?, Void> methodId = generateVoidMethod(TypeId.FLOAT);
157         AnnotationId.Element element = new AnnotationId.Element("elementFloat", Float.NaN);
158         addAnnotationToMethod(methodId, element);
159 
160         Annotation[] methodAnnotations = getMethodAnnotations(methodId);
161         assertEquals(methodAnnotations.length, 1);
162 
163         float elementFloat = ((MethodAnnotation)methodAnnotations[0]).elementFloat();
164         assertEquals(Float.NaN, elementFloat, 0);
165     }
166 
167     /**
168      *  Test adding a method annotation with new value of Int element.
169      */
170     @Test
addMethodAnnotationWithIntElement()171     public void addMethodAnnotationWithIntElement() throws Exception {
172         MethodId<?, Void> methodId = generateVoidMethod(TypeId.INT);
173         AnnotationId.Element element = new AnnotationId.Element("elementInt", Integer.MAX_VALUE);
174         addAnnotationToMethod(methodId, element);
175 
176         Annotation[] methodAnnotations = getMethodAnnotations(methodId);
177         assertEquals(methodAnnotations.length, 1);
178 
179         int elementInt = ((MethodAnnotation)methodAnnotations[0]).elementInt();
180         assertEquals(Integer.MAX_VALUE, elementInt);
181     }
182 
183     /**
184      *  Test adding a method annotation with new value of Long element.
185      */
186     @Test
addMethodAnnotationWithLongElement()187     public void addMethodAnnotationWithLongElement() throws Exception {
188         MethodId<?, Void> methodId = generateVoidMethod(TypeId.LONG);
189         AnnotationId.Element element = new AnnotationId.Element("elementLong", Long.MAX_VALUE);
190         addAnnotationToMethod(methodId, element);
191 
192         Annotation[] methodAnnotations = getMethodAnnotations(methodId);
193         assertEquals(methodAnnotations.length, 1);
194 
195         long elementLong = ((MethodAnnotation)methodAnnotations[0]).elementLong();
196         assertEquals(Long.MAX_VALUE, elementLong);
197     }
198 
199     /**
200      *  Test adding a method annotation with new value of Short element.
201      */
202     @Test
addMethodAnnotationWithShortElement()203     public void addMethodAnnotationWithShortElement() throws Exception {
204         MethodId<?, Void> methodId = generateVoidMethod(TypeId.SHORT);
205         AnnotationId.Element element = new AnnotationId.Element("elementShort", Short.MAX_VALUE);
206         addAnnotationToMethod(methodId, element);
207 
208         Annotation[] methodAnnotations = getMethodAnnotations(methodId);
209         assertEquals(methodAnnotations.length, 1);
210 
211         short elementShort = ((MethodAnnotation)methodAnnotations[0]).elementShort();
212         assertEquals(Short.MAX_VALUE, elementShort);
213     }
214 
215     /**
216      *  Test adding a method annotation with new value of String element.
217      */
218     @Test
addMethodAnnotationWithStingElement()219     public void addMethodAnnotationWithStingElement() throws Exception {
220         MethodId<?, Void> methodId = generateVoidMethod(TypeId.STRING);
221         AnnotationId.Element element = new AnnotationId.Element("elementString", "hello");
222         addAnnotationToMethod(methodId, element);
223 
224         Annotation[] methodAnnotations = getMethodAnnotations(methodId);
225         assertEquals(methodAnnotations.length, 1);
226 
227         String elementString = ((MethodAnnotation)methodAnnotations[0]).elementString();
228         assertEquals("hello", elementString);
229     }
230 
231     /**
232      *  Test adding a method annotation with new value of Enum element.
233      */
234     @Test
addMethodAnnotationWithEnumElement()235     public void addMethodAnnotationWithEnumElement() throws Exception {
236         assumeTrue(Build.VERSION.SDK_INT >= 21);
237 
238         MethodId<?, Void> methodId = generateVoidMethod(TypeId.get(Enum.class));
239         AnnotationId.Element element = new AnnotationId.Element("elementEnum", ElementEnum.INSTANCE_1);
240         addAnnotationToMethod(methodId, element);
241 
242         Annotation[] methodAnnotations = getMethodAnnotations(methodId);
243         assertEquals(methodAnnotations.length, 1);
244 
245         ElementEnum elementEnum = ((MethodAnnotation)methodAnnotations[0]).elementEnum();
246         assertEquals(ElementEnum.INSTANCE_1, elementEnum);
247     }
248 
249     /**
250      *  Test adding a method annotation with new value of Class element.
251      */
252     @Test
addMethodAnnotationWithClassElement()253     public void addMethodAnnotationWithClassElement() throws Exception {
254         MethodId<?, Void> methodId = generateVoidMethod(TypeId.get(AnnotationId.class));
255         AnnotationId.Element element = new AnnotationId.Element("elementClass", AnnotationId.class);
256         addAnnotationToMethod(methodId, element);
257 
258         Annotation[] methodAnnotations = getMethodAnnotations(methodId);
259         assertEquals(methodAnnotations.length, 1);
260 
261         Class<?> elementClass = ((MethodAnnotation)methodAnnotations[0]).elementClass();
262         assertEquals(AnnotationId.class, elementClass);
263     }
264 
265     /**
266      *  Test adding a method annotation with new multiple values of an element.
267      */
268     @Test
addMethodAnnotationWithMultiElements()269     public void addMethodAnnotationWithMultiElements() throws Exception {
270         assumeTrue(Build.VERSION.SDK_INT >= 21);
271 
272         MethodId<?, Void> methodId = generateVoidMethod();
273         AnnotationId.Element element1 = new AnnotationId.Element("elementClass", AnnotationId.class);
274         AnnotationId.Element element2 = new AnnotationId.Element("elementEnum", ElementEnum.INSTANCE_1);
275         AnnotationId.Element[] elements = {element1, element2};
276         addAnnotationToMethod(methodId, elements);
277 
278         Annotation[] methodAnnotations = getMethodAnnotations(methodId);
279         assertEquals(methodAnnotations.length, 1);
280 
281         ElementEnum elementEnum = ((MethodAnnotation)methodAnnotations[0]).elementEnum();
282         assertEquals(ElementEnum.INSTANCE_1, elementEnum);
283         Class<?> elementClass = ((MethodAnnotation)methodAnnotations[0]).elementClass();
284         assertEquals(AnnotationId.class, elementClass);
285     }
286 
287     /**
288      *  Test adding a method annotation with duplicate values of an element. The previous value will
289      *  be replaced by latter one.
290      */
291     @Test
addMethodAnnotationWithDuplicateElements()292     public void addMethodAnnotationWithDuplicateElements() throws Exception {
293         assumeTrue(Build.VERSION.SDK_INT >= 21);
294 
295         MethodId<?, Void> methodId = generateVoidMethod();
296         AnnotationId.Element element1 = new AnnotationId.Element("elementEnum", ElementEnum.INSTANCE_1);
297         AnnotationId.Element element2 = new AnnotationId.Element("elementEnum", ElementEnum.INSTANCE_0);
298         addAnnotationToMethod(methodId, element1, element2);
299 
300         Annotation[] methodAnnotations = getMethodAnnotations(methodId);
301         assertEquals(methodAnnotations.length, 1);
302 
303         ElementEnum elementEnum = ((MethodAnnotation)methodAnnotations[0]).elementEnum();
304         assertEquals(ElementEnum.INSTANCE_0, elementEnum);
305     }
306 
307 
308     /**
309      *  Test adding a method annotation with new array value of an element. It's not supported yet.
310      */
311     @Test
addMethodAnnotationWithArrayElementValue()312     public void addMethodAnnotationWithArrayElementValue() {
313         try {
314             MethodId<?, Void> methodId = generateVoidMethod();
315             int[] a = {1, 2};
316             AnnotationId.Element element = new AnnotationId.Element("elementInt", a);
317             addAnnotationToMethod(methodId, element);
318             fail();
319         } catch (UnsupportedOperationException e) {
320             System.out.println(e);
321         }
322     }
323 
324     /**
325      *  Test adding a method annotation with new TypeId value of an element. It's not supported yet.
326      */
327     @Test
addMethodAnnotationWithTypeIdElementValue()328     public void addMethodAnnotationWithTypeIdElementValue() {
329         try {
330             MethodId<?, Void> methodId = generateVoidMethod();
331             AnnotationId.Element element = new AnnotationId.Element("elementInt", INT);
332             addAnnotationToMethod(methodId, element);
333             fail();
334         } catch (UnsupportedOperationException e) {
335             System.out.println(e);
336         }
337     }
338 
339     @After
tearDown()340     public void tearDown() {
341     }
342 
343     /**
344      *  Internal methods
345      */
init()346     private void init() {
347         clearDataDirectory();
348 
349         dexMaker = new DexMaker();
350         dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.OBJECT);
351     }
352 
clearDataDirectory()353     private void clearDataDirectory() {
354         for (File f : getDataDirectory().listFiles()) {
355             if (f.getName().endsWith(".jar") || f.getName().endsWith(".dex")) {
356                 f.delete();
357             }
358         }
359     }
360 
getDataDirectory()361     private static File getDataDirectory() {
362         String dataDir = InstrumentationRegistry.getTargetContext().getApplicationInfo().dataDir;
363         return new File(dataDir + "/cache" );
364     }
365 
generateVoidMethod(TypeId<?>.... parameters)366     private MethodId<?, Void> generateVoidMethod(TypeId<?>... parameters) {
367         MethodId<?, Void> methodId = GENERATED.getMethod(VOID, "call", parameters);
368         Code code = dexMaker.declare(methodId, PUBLIC);
369         code.returnVoid();
370         return methodId;
371     }
372 
addAnnotationToMethod(MethodId<?, Void> methodId, AnnotationId.Element... elements)373     private void addAnnotationToMethod(MethodId<?, Void> methodId, AnnotationId.Element... elements) {
374         TypeId<MethodAnnotation> annotationTypeId = TypeId.get(MethodAnnotation.class);
375         AnnotationId<?, MethodAnnotation> annotationId = AnnotationId.get(GENERATED, annotationTypeId, ElementType.METHOD);
376         for (AnnotationId.Element element : elements) {
377             annotationId.set(element);
378         }
379         annotationId.addToMethod(dexMaker, methodId);
380     }
381 
getMethodAnnotations(MethodId<?, Void> methodId)382     private Annotation[] getMethodAnnotations(MethodId<?, Void> methodId) throws Exception {
383         Class<?> generatedClass = generateAndLoad();
384         Class<?>[] parameters = getMethodParameters(methodId);
385         Method method = generatedClass.getMethod(methodId.getName(), parameters);
386         return method.getAnnotations();
387     }
388 
getMethodParameters(MethodId<?, Void> methodId)389     private Class<?>[] getMethodParameters(MethodId<?, Void> methodId) throws ClassNotFoundException {
390         List<TypeId<?>> paras = methodId.getParameters();
391         Class<?>[] p = null;
392         if (paras.size() > 0) {
393             p = new Class<?>[paras.size()];
394             for (int i = 0; i < paras.size(); i++) {
395                 p[i] = TYPE_TO_PRIMITIVE.get(paras.get(i));
396                 if (p[i] == null) {
397                     String name = paras.get(i).getName().replace('/', '.');
398                     if (name.charAt(0) == 'L') {
399                         name = name.substring(1, name.length()-1);
400                     }
401                     p[i] = Class.forName(name);
402                 }
403             }
404         }
405         return p;
406     }
407 
generateAndLoad()408     private Class<?> generateAndLoad() throws Exception {
409         return dexMaker.generateAndLoad(getClass().getClassLoader(), getDataDirectory())
410                 .loadClass("Generated");
411     }
412 }
413