/* * Copyright (C) 2017 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 com.android.dx; import android.support.test.InstrumentationRegistry; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.File; import java.lang.annotation.*; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; import java.util.Map; import static com.android.dx.TypeId.*; import static java.lang.reflect.Modifier.PUBLIC; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public final class AnnotationIdTest { /** * Method Annotation definition for test */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @interface MethodAnnotation { boolean elementBoolean() default false; byte elementByte() default Byte.MIN_VALUE; char elementChar() default 'a'; double elementDouble() default Double.MIN_NORMAL; float elementFloat() default Float.MIN_NORMAL; int elementInt() default Integer.MIN_VALUE; long elementLong() default Long.MIN_VALUE; short elementShort() default Short.MIN_VALUE; String elementString() default "foo"; ElementEnum elementEnum() default ElementEnum.INSTANCE_0; Class elementClass() default Object.class; } enum ElementEnum { INSTANCE_0, INSTANCE_1, } private DexMaker dexMaker; private static TypeId GENERATED = TypeId.get("LGenerated;"); private static final Map, Class> TYPE_TO_PRIMITIVE = new HashMap<>(); static { TYPE_TO_PRIMITIVE.put(BOOLEAN, boolean.class); TYPE_TO_PRIMITIVE.put(BYTE, byte.class); TYPE_TO_PRIMITIVE.put(CHAR, char.class); TYPE_TO_PRIMITIVE.put(DOUBLE, double.class); TYPE_TO_PRIMITIVE.put(FLOAT, float.class); TYPE_TO_PRIMITIVE.put(INT, int.class); TYPE_TO_PRIMITIVE.put(LONG, long.class); TYPE_TO_PRIMITIVE.put(SHORT, short.class); TYPE_TO_PRIMITIVE.put(VOID, void.class); } @Before public void setUp() { init(); } /** * Test adding a method annotation with new value of Boolean element. */ @Test public void addMethodAnnotationWithBooleanElement() throws Exception { MethodId methodId = generateVoidMethod(TypeId.BOOLEAN); AnnotationId.Element element = new AnnotationId.Element("elementBoolean", true); addAnnotationToMethod(methodId, element); Annotation[] methodAnnotations = getMethodAnnotations(methodId); assertEquals(methodAnnotations.length, 1); Boolean elementBoolean = ((MethodAnnotation)methodAnnotations[0]).elementBoolean(); assertEquals(true, elementBoolean); } /** * Test adding a method annotation with new value of Byte element. */ @Test public void addMethodAnnotationWithByteElement() throws Exception { MethodId methodId = generateVoidMethod(TypeId.BYTE); AnnotationId.Element element = new AnnotationId.Element("elementByte", Byte.MAX_VALUE); addAnnotationToMethod(methodId, element); Annotation[] methodAnnotations = getMethodAnnotations(methodId); assertEquals(methodAnnotations.length, 1); byte elementByte = ((MethodAnnotation)methodAnnotations[0]).elementByte(); assertEquals(Byte.MAX_VALUE, elementByte); } /** * Test adding a method annotation with new value of Char element. */ @Test public void addMethodAnnotationWithCharElement() throws Exception { MethodId methodId = generateVoidMethod(TypeId.CHAR); AnnotationId.Element element = new AnnotationId.Element("elementChar", 'X'); addAnnotationToMethod(methodId, element); Annotation[] methodAnnotations = getMethodAnnotations(methodId); assertEquals(methodAnnotations.length, 1); char elementChar = ((MethodAnnotation)methodAnnotations[0]).elementChar(); assertEquals('X', elementChar); } /** * Test adding a method annotation with new value of Double element. */ @Test public void addMethodAnnotationWithDoubleElement() throws Exception { MethodId methodId = generateVoidMethod(TypeId.DOUBLE); AnnotationId.Element element = new AnnotationId.Element("elementDouble", Double.NaN); addAnnotationToMethod(methodId, element); Annotation[] methodAnnotations = getMethodAnnotations(methodId); assertEquals(methodAnnotations.length, 1); double elementDouble = ((MethodAnnotation)methodAnnotations[0]).elementDouble(); assertEquals(Double.NaN, elementDouble, 0); } /** * Test adding a method annotation with new value of Float element. */ @Test public void addMethodAnnotationWithFloatElement() throws Exception { MethodId methodId = generateVoidMethod(TypeId.FLOAT); AnnotationId.Element element = new AnnotationId.Element("elementFloat", Float.NaN); addAnnotationToMethod(methodId, element); Annotation[] methodAnnotations = getMethodAnnotations(methodId); assertEquals(methodAnnotations.length, 1); float elementFloat = ((MethodAnnotation)methodAnnotations[0]).elementFloat(); assertEquals(Float.NaN, elementFloat, 0); } /** * Test adding a method annotation with new value of Int element. */ @Test public void addMethodAnnotationWithIntElement() throws Exception { MethodId methodId = generateVoidMethod(TypeId.INT); AnnotationId.Element element = new AnnotationId.Element("elementInt", Integer.MAX_VALUE); addAnnotationToMethod(methodId, element); Annotation[] methodAnnotations = getMethodAnnotations(methodId); assertEquals(methodAnnotations.length, 1); int elementInt = ((MethodAnnotation)methodAnnotations[0]).elementInt(); assertEquals(Integer.MAX_VALUE, elementInt); } /** * Test adding a method annotation with new value of Long element. */ @Test public void addMethodAnnotationWithLongElement() throws Exception { MethodId methodId = generateVoidMethod(TypeId.LONG); AnnotationId.Element element = new AnnotationId.Element("elementLong", Long.MAX_VALUE); addAnnotationToMethod(methodId, element); Annotation[] methodAnnotations = getMethodAnnotations(methodId); assertEquals(methodAnnotations.length, 1); long elementLong = ((MethodAnnotation)methodAnnotations[0]).elementLong(); assertEquals(Long.MAX_VALUE, elementLong); } /** * Test adding a method annotation with new value of Short element. */ @Test public void addMethodAnnotationWithShortElement() throws Exception { MethodId methodId = generateVoidMethod(TypeId.SHORT); AnnotationId.Element element = new AnnotationId.Element("elementShort", Short.MAX_VALUE); addAnnotationToMethod(methodId, element); Annotation[] methodAnnotations = getMethodAnnotations(methodId); assertEquals(methodAnnotations.length, 1); short elementShort = ((MethodAnnotation)methodAnnotations[0]).elementShort(); assertEquals(Short.MAX_VALUE, elementShort); } /** * Test adding a method annotation with new value of String element. */ @Test public void addMethodAnnotationWithStingElement() throws Exception { MethodId methodId = generateVoidMethod(TypeId.STRING); AnnotationId.Element element = new AnnotationId.Element("elementString", "hello"); addAnnotationToMethod(methodId, element); Annotation[] methodAnnotations = getMethodAnnotations(methodId); assertEquals(methodAnnotations.length, 1); String elementString = ((MethodAnnotation)methodAnnotations[0]).elementString(); assertEquals("hello", elementString); } /** * Test adding a method annotation with new value of Enum element. */ @Test public void addMethodAnnotationWithEnumElement() throws Exception { MethodId methodId = generateVoidMethod(TypeId.get(Enum.class)); AnnotationId.Element element = new AnnotationId.Element("elementEnum", ElementEnum.INSTANCE_1); addAnnotationToMethod(methodId, element); Annotation[] methodAnnotations = getMethodAnnotations(methodId); assertEquals(methodAnnotations.length, 1); ElementEnum elementEnum = ((MethodAnnotation)methodAnnotations[0]).elementEnum(); assertEquals(ElementEnum.INSTANCE_1, elementEnum); } /** * Test adding a method annotation with new value of Class element. */ @Test public void addMethodAnnotationWithClassElement() throws Exception { MethodId methodId = generateVoidMethod(TypeId.get(AnnotationId.class)); AnnotationId.Element element = new AnnotationId.Element("elementClass", AnnotationId.class); addAnnotationToMethod(methodId, element); Annotation[] methodAnnotations = getMethodAnnotations(methodId); assertEquals(methodAnnotations.length, 1); Class elementClass = ((MethodAnnotation)methodAnnotations[0]).elementClass(); assertEquals(AnnotationId.class, elementClass); } /** * Test adding a method annotation with new multiple values of an element. */ @Test public void addMethodAnnotationWithMultiElements() throws Exception { MethodId methodId = generateVoidMethod(); AnnotationId.Element element1 = new AnnotationId.Element("elementClass", AnnotationId.class); AnnotationId.Element element2 = new AnnotationId.Element("elementEnum", ElementEnum.INSTANCE_1); AnnotationId.Element[] elements = {element1, element2}; addAnnotationToMethod(methodId, elements); Annotation[] methodAnnotations = getMethodAnnotations(methodId); assertEquals(methodAnnotations.length, 1); ElementEnum elementEnum = ((MethodAnnotation)methodAnnotations[0]).elementEnum(); assertEquals(ElementEnum.INSTANCE_1, elementEnum); Class elementClass = ((MethodAnnotation)methodAnnotations[0]).elementClass(); assertEquals(AnnotationId.class, elementClass); } /** * Test adding a method annotation with duplicate values of an element. The previous value will * be replaced by latter one. */ @Test public void addMethodAnnotationWithDuplicateElements() throws Exception { MethodId methodId = generateVoidMethod(); AnnotationId.Element element1 = new AnnotationId.Element("elementEnum", ElementEnum.INSTANCE_1); AnnotationId.Element element2 = new AnnotationId.Element("elementEnum", ElementEnum.INSTANCE_0); addAnnotationToMethod(methodId, element1, element2); Annotation[] methodAnnotations = getMethodAnnotations(methodId); assertEquals(methodAnnotations.length, 1); ElementEnum elementEnum = ((MethodAnnotation)methodAnnotations[0]).elementEnum(); assertEquals(ElementEnum.INSTANCE_0, elementEnum); } /** * Test adding a method annotation with new array value of an element. It's not supported yet. */ @Test public void addMethodAnnotationWithArrayElementValue() { try { MethodId methodId = generateVoidMethod(); int[] a = {1, 2}; AnnotationId.Element element = new AnnotationId.Element("elementInt", a); addAnnotationToMethod(methodId, element); fail(); } catch (UnsupportedOperationException e) { System.out.println(e); } } /** * Test adding a method annotation with new TypeId value of an element. It's not supported yet. */ @Test public void addMethodAnnotationWithTypeIdElementValue() { try { MethodId methodId = generateVoidMethod(); AnnotationId.Element element = new AnnotationId.Element("elementInt", INT); addAnnotationToMethod(methodId, element); fail(); } catch (UnsupportedOperationException e) { System.out.println(e); } } @After public void tearDown() { } /** * Internal methods */ private void init() { clearDataDirectory(); dexMaker = new DexMaker(); dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.OBJECT); } private void clearDataDirectory() { for (File f : getDataDirectory().listFiles()) { if (f.getName().endsWith(".jar") || f.getName().endsWith(".dex")) { f.delete(); } } } private static File getDataDirectory() { String dataDir = InstrumentationRegistry.getTargetContext().getApplicationInfo().dataDir; return new File(dataDir + "/cache" ); } private MethodId generateVoidMethod(TypeId... parameters) { MethodId methodId = GENERATED.getMethod(VOID, "call", parameters); Code code = dexMaker.declare(methodId, PUBLIC); code.returnVoid(); return methodId; } private void addAnnotationToMethod(MethodId methodId, AnnotationId.Element... elements) { TypeId annotationTypeId = TypeId.get(MethodAnnotation.class); AnnotationId annotationId = AnnotationId.get(GENERATED, annotationTypeId, ElementType.METHOD); for (AnnotationId.Element element : elements) { annotationId.set(element); } annotationId.addToMethod(dexMaker, methodId); } private Annotation[] getMethodAnnotations(MethodId methodId) throws Exception { Class generatedClass = generateAndLoad(); Class[] parameters = getMethodParameters(methodId); Method method = generatedClass.getMethod(methodId.getName(), parameters); return method.getAnnotations(); } private Class[] getMethodParameters(MethodId methodId) throws ClassNotFoundException { List> paras = methodId.getParameters(); Class[] p = null; if (paras.size() > 0) { p = new Class[paras.size()]; for (int i = 0; i < paras.size(); i++) { p[i] = TYPE_TO_PRIMITIVE.get(paras.get(i)); if (p[i] == null) { String name = paras.get(i).getName().replace('/', '.'); if (name.charAt(0) == 'L') { name = name.substring(1, name.length()-1); } p[i] = Class.forName(name); } } } return p; } private Class generateAndLoad() throws Exception { return dexMaker.generateAndLoad(getClass().getClassLoader(), getDataDirectory()) .loadClass("Generated"); } }