| /* |
| * 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<TypeId<?>, 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<?, Void> 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<?, Void> 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<?, Void> 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<?, Void> 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<?, Void> 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<?, Void> 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<?, Void> 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<?, Void> 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<?, Void> 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<?, Void> 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<?, Void> 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<?, Void> 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<?, Void> 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<?, Void> 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<?, Void> 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<?, Void> generateVoidMethod(TypeId<?>... parameters) { |
| MethodId<?, Void> methodId = GENERATED.getMethod(VOID, "call", parameters); |
| Code code = dexMaker.declare(methodId, PUBLIC); |
| code.returnVoid(); |
| return methodId; |
| } |
| |
| private void addAnnotationToMethod(MethodId<?, Void> methodId, AnnotationId.Element... elements) { |
| TypeId<MethodAnnotation> annotationTypeId = TypeId.get(MethodAnnotation.class); |
| AnnotationId<?, MethodAnnotation> annotationId = AnnotationId.get(GENERATED, annotationTypeId, ElementType.METHOD); |
| for (AnnotationId.Element element : elements) { |
| annotationId.set(element); |
| } |
| annotationId.addToMethod(dexMaker, methodId); |
| } |
| |
| private Annotation[] getMethodAnnotations(MethodId<?, Void> methodId) throws Exception { |
| Class<?> generatedClass = generateAndLoad(); |
| Class<?>[] parameters = getMethodParameters(methodId); |
| Method method = generatedClass.getMethod(methodId.getName(), parameters); |
| return method.getAnnotations(); |
| } |
| |
| private Class<?>[] getMethodParameters(MethodId<?, Void> methodId) throws ClassNotFoundException { |
| List<TypeId<?>> 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"); |
| } |
| } |