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