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