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