ProxyBuilder.java revision ff314e1f06b974be78de3356a71072dbf0a450cd
1/* 2 * Copyright (C) 2011 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 17package com.google.dexmaker.stock; 18 19import com.google.dexmaker.Code; 20import com.google.dexmaker.Comparison; 21import com.google.dexmaker.DexMaker; 22import com.google.dexmaker.FieldId; 23import com.google.dexmaker.Label; 24import com.google.dexmaker.Local; 25import com.google.dexmaker.MethodId; 26import com.google.dexmaker.TypeId; 27import java.io.File; 28import java.io.IOException; 29import java.lang.reflect.Constructor; 30import java.lang.reflect.Field; 31import java.lang.reflect.InvocationHandler; 32import java.lang.reflect.InvocationTargetException; 33import java.lang.reflect.Method; 34import java.lang.reflect.Modifier; 35import static java.lang.reflect.Modifier.PRIVATE; 36import static java.lang.reflect.Modifier.PUBLIC; 37import static java.lang.reflect.Modifier.STATIC; 38import java.lang.reflect.UndeclaredThrowableException; 39import java.util.Arrays; 40import java.util.HashMap; 41import java.util.HashSet; 42import java.util.Map; 43import java.util.Set; 44 45/** 46 * Creates dynamic proxies of concrete classes. 47 * <p> 48 * This is similar to the {@code java.lang.reflect.Proxy} class, but works for classes instead of 49 * interfaces. 50 * <h3>Example</h3> 51 * The following example demonstrates the creation of a dynamic proxy for {@code java.util.Random} 52 * which will always return 4 when asked for integers, and which logs method calls to every method. 53 * <pre> 54 * InvocationHandler handler = new InvocationHandler() { 55 * @Override 56 * public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 57 * if (method.getName().equals("nextInt")) { 58 * // Chosen by fair dice roll, guaranteed to be random. 59 * return 4; 60 * } 61 * Object result = ProxyBuilder.callSuper(proxy, method, args); 62 * System.out.println("Method: " + method.getName() + " args: " 63 * + Arrays.toString(args) + " result: " + result); 64 * return result; 65 * } 66 * }; 67 * Random debugRandom = ProxyBuilder.forClass(Random.class) 68 * .dexCache(getInstrumentation().getTargetContext().getDir("dx", Context.MODE_PRIVATE)) 69 * .handler(handler) 70 * .build(); 71 * assertEquals(4, debugRandom.nextInt()); 72 * debugRandom.setSeed(0); 73 * assertTrue(debugRandom.nextBoolean()); 74 * </pre> 75 * <h3>Usage</h3> 76 * Call {@link #forClass(Class)} for the Class you wish to proxy. Call 77 * {@link #handler(InvocationHandler)} passing in an {@link InvocationHandler}, and then call 78 * {@link #build()}. The returned instance will be a dynamically generated subclass where all method 79 * calls will be delegated to the invocation handler, except as noted below. 80 * <p> 81 * The static method {@link #callSuper(Object, Method, Object...)} allows you to access the original 82 * super method for a given proxy. This allows the invocation handler to selectively override some 83 * methods but not others. 84 * <p> 85 * By default, the {@link #build()} method will call the no-arg constructor belonging to the class 86 * being proxied. If you wish to call a different constructor, you must provide arguments for both 87 * {@link #constructorArgTypes(Class[])} and {@link #constructorArgValues(Object[])}. 88 * <p> 89 * This process works only for classes with public and protected level of visibility. 90 * <p> 91 * You may proxy abstract classes. You may not proxy final classes. 92 * <p> 93 * Only non-private, non-final, non-static methods will be dispatched to the invocation handler. 94 * Private, static or final methods will always call through to the superclass as normal. 95 * <p> 96 * The {@link #finalize()} method on {@code Object} will not be proxied. 97 * <p> 98 * You must provide a dex cache directory via the {@link #dexCache(File)} method. You should take 99 * care not to make this a world-writable directory, so that third parties cannot inject code into 100 * your application. A suitable parameter for these output directories would be something like 101 * this: 102 * <pre>{@code 103 * getApplicationContext().getDir("dx", Context.MODE_PRIVATE); 104 * }</pre> 105 * <p> 106 * If the base class to be proxied leaks the {@code this} pointer in the constructor (bad practice), 107 * that is to say calls a non-private non-final method from the constructor, the invocation handler 108 * will not be invoked. As a simple concrete example, when proxying Random we discover that it 109 * inernally calls setSeed during the constructor. The proxy will not intercept this call during 110 * proxy construction, but will intercept as normal afterwards. This behaviour may be subject to 111 * change in future releases. 112 * <p> 113 * This class is <b>not thread safe</b>. 114 */ 115public final class ProxyBuilder<T> { 116 private static final String FIELD_NAME_HANDLER = "$__handler"; 117 private static final String FIELD_NAME_METHODS = "$__methodArray"; 118 119 private final Class<T> baseClass; 120 // TODO: make DexMaker do the defaulting here 121 private ClassLoader parentClassLoader = ProxyBuilder.class.getClassLoader(); 122 private InvocationHandler handler; 123 private File dexCache; 124 private Class<?>[] constructorArgTypes = new Class[0]; 125 private Object[] constructorArgValues = new Object[0]; 126 127 private ProxyBuilder(Class<T> clazz) { 128 baseClass = clazz; 129 } 130 131 public static <T> ProxyBuilder<T> forClass(Class<T> clazz) { 132 return new ProxyBuilder<T>(clazz); 133 } 134 135 /** 136 * Specifies the parent ClassLoader to use when creating the proxy. 137 * 138 * <p>If null, {@code ProxyBuilder.class.getClassLoader()} will be used. 139 */ 140 public ProxyBuilder<T> parentClassLoader(ClassLoader parent) { 141 parentClassLoader = parent; 142 return this; 143 } 144 145 public ProxyBuilder<T> handler(InvocationHandler handler) { 146 this.handler = handler; 147 return this; 148 } 149 150 public ProxyBuilder<T> dexCache(File dexCache) { 151 this.dexCache = dexCache; 152 return this; 153 } 154 155 public ProxyBuilder<T> constructorArgValues(Object... constructorArgValues) { 156 this.constructorArgValues = constructorArgValues; 157 return this; 158 } 159 160 public ProxyBuilder<T> constructorArgTypes(Class<?>... constructorArgTypes) { 161 this.constructorArgTypes = constructorArgTypes; 162 return this; 163 } 164 165 /** 166 * Create a new instance of the class to proxy. 167 * 168 * @throws UnsupportedOperationException if the class we are trying to create a proxy for is 169 * not accessible. 170 * @throws IOException if an exception occurred writing to the {@code dexCache} directory. 171 * @throws UndeclaredThrowableException if the constructor for the base class to proxy throws 172 * a declared exception during construction. 173 * @throws IllegalArgumentException if the handler is null, if the constructor argument types 174 * do not match the constructor argument values, or if no such constructor exists. 175 */ 176 public T build() throws IOException { 177 check(handler != null, "handler == null"); 178 check(constructorArgTypes.length == constructorArgValues.length, 179 "constructorArgValues.length != constructorArgTypes.length"); 180 DexMaker dexMaker = new DexMaker(); 181 String generatedName = getMethodNameForProxyOf(baseClass); 182 TypeId<? extends T> generatedType = TypeId.get("L" + generatedName + ";"); 183 TypeId<T> superType = TypeId.get(baseClass); 184 generateConstructorsAndFields(dexMaker, generatedType, superType, baseClass); 185 Method[] methodsToProxy = getMethodsToProxy(baseClass); 186 generateCodeForAllMethods(dexMaker, generatedType, methodsToProxy, superType); 187 dexMaker.declare(generatedType, generatedName + ".generated", PUBLIC, superType); 188 ClassLoader classLoader = dexMaker.generateAndLoad(parentClassLoader, dexCache); 189 Class<? extends T> proxyClass; 190 try { 191 proxyClass = loadClass(classLoader, generatedName); 192 } catch (IllegalAccessError e) { 193 // Thrown when the base class is not accessible. 194 throw new UnsupportedOperationException("cannot proxy inaccessible classes", e); 195 } catch (ClassNotFoundException e) { 196 // Should not be thrown, we're sure to have generated this class. 197 throw new AssertionError(e); 198 } 199 setMethodsStaticField(proxyClass, methodsToProxy); 200 Constructor<? extends T> constructor; 201 try { 202 constructor = proxyClass.getConstructor(constructorArgTypes); 203 } catch (NoSuchMethodException e) { 204 // Thrown when the ctor to be called does not exist. 205 throw new IllegalArgumentException("could not find matching constructor", e); 206 } 207 T result; 208 try { 209 result = constructor.newInstance(constructorArgValues); 210 } catch (InstantiationException e) { 211 // Should not be thrown, generated class is not abstract. 212 throw new AssertionError(e); 213 } catch (IllegalAccessException e) { 214 // Should not be thrown, the generated constructor is accessible. 215 throw new AssertionError(e); 216 } catch (InvocationTargetException e) { 217 // Thrown when the base class ctor throws an exception. 218 throw launderCause(e); 219 } 220 setHandlerInstanceField(result, handler); 221 return result; 222 } 223 224 // The type cast is safe: the generated type will extend the base class type. 225 @SuppressWarnings("unchecked") 226 private Class<? extends T> loadClass(ClassLoader classLoader, String generatedName) 227 throws ClassNotFoundException { 228 return (Class<? extends T>) classLoader.loadClass(generatedName); 229 } 230 231 private static RuntimeException launderCause(InvocationTargetException e) { 232 Throwable cause = e.getCause(); 233 // Errors should be thrown as they are. 234 if (cause instanceof Error) { 235 throw (Error) cause; 236 } 237 // RuntimeException can be thrown as-is. 238 if (cause instanceof RuntimeException) { 239 throw (RuntimeException) cause; 240 } 241 // Declared exceptions will have to be wrapped. 242 throw new UndeclaredThrowableException(cause); 243 } 244 245 private static void setHandlerInstanceField(Object instance, InvocationHandler handler) { 246 try { 247 Field handlerField = instance.getClass().getDeclaredField(FIELD_NAME_HANDLER); 248 handlerField.setAccessible(true); 249 handlerField.set(instance, handler); 250 } catch (NoSuchFieldException e) { 251 // Should not be thrown, generated proxy class has been generated with this field. 252 throw new AssertionError(e); 253 } catch (IllegalAccessException e) { 254 // Should not be thrown, we just set the field to accessible. 255 throw new AssertionError(e); 256 } 257 } 258 259 private static void setMethodsStaticField(Class<?> proxyClass, Method[] methodsToProxy) { 260 try { 261 Field methodArrayField = proxyClass.getDeclaredField(FIELD_NAME_METHODS); 262 methodArrayField.setAccessible(true); 263 methodArrayField.set(null, methodsToProxy); 264 } catch (NoSuchFieldException e) { 265 // Should not be thrown, generated proxy class has been generated with this field. 266 throw new AssertionError(e); 267 } catch (IllegalAccessException e) { 268 // Should not be thrown, we just set the field to accessible. 269 throw new AssertionError(e); 270 } 271 } 272 273 /** 274 * Returns the proxy's {@link InvocationHandler}. 275 * 276 * @throws IllegalArgumentException if the object supplied is not a proxy created by this class. 277 */ 278 public static InvocationHandler getInvocationHandler(Object instance) { 279 try { 280 Field field = instance.getClass().getDeclaredField(FIELD_NAME_HANDLER); 281 field.setAccessible(true); 282 return (InvocationHandler) field.get(instance); 283 } catch (NoSuchFieldException e) { 284 throw new IllegalArgumentException("Not a valid proxy instance", e); 285 } catch (IllegalAccessException e) { 286 // Should not be thrown, we just set the field to accessible. 287 throw new AssertionError(e); 288 } 289 } 290 291 private static <T, G extends T> void generateCodeForAllMethods(DexMaker dexMaker, 292 TypeId<G> generatedType, Method[] methodsToProxy, TypeId<T> superclassType) { 293 TypeId<InvocationHandler> handlerType = TypeId.get(InvocationHandler.class); 294 TypeId<Method[]> methodArrayType = TypeId.get(Method[].class); 295 FieldId<G, InvocationHandler> handlerField = 296 generatedType.getField(handlerType, FIELD_NAME_HANDLER); 297 FieldId<G, Method[]> allMethods = 298 generatedType.getField(methodArrayType, FIELD_NAME_METHODS); 299 TypeId<Method> methodType = TypeId.get(Method.class); 300 TypeId<Object[]> objectArrayType = TypeId.get(Object[].class); 301 MethodId<InvocationHandler, Object> methodInvoke = handlerType.getMethod(TypeId.OBJECT, 302 "invoke", TypeId.OBJECT, methodType, objectArrayType); 303 for (int m = 0; m < methodsToProxy.length; ++m) { 304 /* 305 * If the 5th method on the superclass Example that can be overridden were to look like 306 * this: 307 * 308 * public int doSomething(Bar param0, int param1) { 309 * ... 310 * } 311 * 312 * Then the following code will generate a method on the proxy that looks something 313 * like this: 314 * 315 * public int doSomething(Bar param0, int param1) { 316 * int methodIndex = 4; 317 * Method[] allMethods = Example_Proxy.$__methodArray; 318 * Method thisMethod = allMethods[methodIndex]; 319 * int argsLength = 2; 320 * Object[] args = new Object[argsLength]; 321 * InvocationHandler localHandler = this.$__handler; 322 * // for-loop begins 323 * int p = 0; 324 * Bar parameter0 = param0; 325 * args[p] = parameter0; 326 * p = 1; 327 * int parameter1 = param1; 328 * Integer boxed1 = Integer.valueOf(parameter1); 329 * args[p] = boxed1; 330 * // for-loop ends 331 * Object result = localHandler.invoke(this, thisMethod, args); 332 * Integer castResult = (Integer) result; 333 * int unboxedResult = castResult.intValue(); 334 * return unboxedResult; 335 * } 336 * 337 * Or, in more idiomatic Java: 338 * 339 * public int doSomething(Bar param0, int param1) { 340 * if ($__handler == null) { 341 * return super.doSomething(param0, param1); 342 * } 343 * return __handler.invoke(this, __methodArray[4], 344 * new Object[] { param0, Integer.valueOf(param1) }); 345 * } 346 */ 347 Method method = methodsToProxy[m]; 348 String name = method.getName(); 349 Class<?>[] argClasses = method.getParameterTypes(); 350 TypeId<?>[] argTypes = new TypeId<?>[argClasses.length]; 351 for (int i = 0; i < argTypes.length; ++i) { 352 argTypes[i] = TypeId.get(argClasses[i]); 353 } 354 Class<?> returnType = method.getReturnType(); 355 TypeId<?> resultType = TypeId.get(returnType); 356 MethodId<T, ?> superMethod = superclassType.getMethod(resultType, name, argTypes); 357 MethodId<?, ?> methodId = generatedType.getMethod(resultType, name, argTypes); 358 Code code = dexMaker.declare(methodId, PUBLIC); 359 Local<G> localThis = code.getThis(generatedType); 360 Local<InvocationHandler> localHandler = code.newLocal(handlerType); 361 Local<Object> invokeResult = code.newLocal(TypeId.OBJECT); 362 Local<Integer> intValue = code.newLocal(TypeId.INT); 363 Local<Object[]> args = code.newLocal(objectArrayType); 364 Local<Integer> argsLength = code.newLocal(TypeId.INT); 365 Local<Object> temp = code.newLocal(TypeId.OBJECT); 366 Local<?> resultHolder = code.newLocal(resultType); 367 Local<Method[]> methodArray = code.newLocal(methodArrayType); 368 Local<Method> thisMethod = code.newLocal(methodType); 369 Local<Integer> methodIndex = code.newLocal(TypeId.INT); 370 Class<?> aBoxedClass = PRIMITIVE_TO_BOXED.get(returnType); 371 Local<?> aBoxedResult = null; 372 if (aBoxedClass != null) { 373 aBoxedResult = code.newLocal(TypeId.get(aBoxedClass)); 374 } 375 Local<?>[] superArgs2 = new Local<?>[argClasses.length]; 376 Local<?> superResult2 = code.newLocal(resultType); 377 Local<InvocationHandler> nullHandler = code.newLocal(handlerType); 378 379 code.loadConstant(methodIndex, m); 380 code.sget(allMethods, methodArray); 381 code.aget(thisMethod, methodArray, methodIndex); 382 code.loadConstant(argsLength, argTypes.length); 383 code.newArray(args, argsLength); 384 code.iget(handlerField, localHandler, localThis); 385 386 // if (proxy == null) 387 code.loadConstant(nullHandler, null); 388 Label handlerNullCase = code.newLabel(); 389 code.compare(Comparison.EQ, handlerNullCase, nullHandler, localHandler); 390 391 // This code is what we execute when we have a valid proxy: delegate to invocation 392 // handler. 393 for (int p = 0; p < argTypes.length; ++p) { 394 code.loadConstant(intValue, p); 395 Local<?> parameter = code.getParameter(p, argTypes[p]); 396 Local<?> unboxedIfNecessary = boxIfRequired(code, parameter, temp); 397 code.aput(args, intValue, unboxedIfNecessary); 398 } 399 code.invokeInterface(methodInvoke, invokeResult, localHandler, 400 localThis, thisMethod, args); 401 generateCodeForReturnStatement(code, returnType, invokeResult, resultHolder, 402 aBoxedResult); 403 404 // This code is executed if proxy is null: call the original super method. 405 // This is required to handle the case of construction of an object which leaks the 406 // "this" pointer. 407 code.mark(handlerNullCase); 408 for (int i = 0; i < superArgs2.length; ++i) { 409 superArgs2[i] = code.getParameter(i, argTypes[i]); 410 } 411 if (void.class.equals(returnType)) { 412 code.invokeSuper(superMethod, null, localThis, superArgs2); 413 code.returnVoid(); 414 } else { 415 invokeSuper(superMethod, code, localThis, superArgs2, superResult2); 416 code.returnValue(superResult2); 417 } 418 419 /* 420 * And to allow calling the original super method, the following is also generated: 421 * 422 * public int super_doSomething(Bar param0, int param1) { 423 * int result = super.doSomething(param0, param1); 424 * return result; 425 * } 426 */ 427 String superName = "super_" + name; 428 MethodId<G, ?> callsSuperMethod = generatedType.getMethod( 429 resultType, superName, argTypes); 430 Code superCode = dexMaker.declare(callsSuperMethod, PUBLIC); 431 Local<G> superThis = superCode.getThis(generatedType); 432 Local<?>[] superArgs = new Local<?>[argClasses.length]; 433 for (int i = 0; i < superArgs.length; ++i) { 434 superArgs[i] = superCode.getParameter(i, argTypes[i]); 435 } 436 if (void.class.equals(returnType)) { 437 superCode.invokeSuper(superMethod, null, superThis, superArgs); 438 superCode.returnVoid(); 439 } else { 440 Local<?> superResult = superCode.newLocal(resultType); 441 invokeSuper(superMethod, superCode, superThis, superArgs, superResult); 442 superCode.returnValue(superResult); 443 } 444 } 445 } 446 447 @SuppressWarnings({"unchecked", "rawtypes"}) 448 private static void invokeSuper(MethodId superMethod, Code superCode, 449 Local superThis, Local[] superArgs, Local superResult) { 450 superCode.invokeSuper(superMethod, superResult, superThis, superArgs); 451 } 452 453 private static Local<?> boxIfRequired(Code code, Local<?> parameter, Local<Object> temp) { 454 MethodId<?, ?> unboxMethod = PRIMITIVE_TYPE_TO_UNBOX_METHOD.get(parameter.getType()); 455 if (unboxMethod == null) { 456 return parameter; 457 } 458 code.invokeStatic(unboxMethod, temp, parameter); 459 return temp; 460 } 461 462 public static Object callSuper(Object proxy, Method method, Object... args) 463 throws SecurityException, IllegalAccessException, 464 InvocationTargetException, NoSuchMethodException { 465 return proxy.getClass() 466 .getMethod("super_" + method.getName(), method.getParameterTypes()) 467 .invoke(proxy, args); 468 } 469 470 private static void check(boolean condition, String message) { 471 if (!condition) { 472 throw new IllegalArgumentException(message); 473 } 474 } 475 476 private static <T, G extends T> void generateConstructorsAndFields(DexMaker dexMaker, 477 TypeId<G> generatedType, TypeId<T> superType, Class<T> superClass) { 478 TypeId<InvocationHandler> handlerType = TypeId.get(InvocationHandler.class); 479 TypeId<Method[]> methodArrayType = TypeId.get(Method[].class); 480 FieldId<G, InvocationHandler> handlerField = generatedType.getField( 481 handlerType, FIELD_NAME_HANDLER); 482 dexMaker.declare(handlerField, PRIVATE, null); 483 FieldId<G, Method[]> allMethods = generatedType.getField( 484 methodArrayType, FIELD_NAME_METHODS); 485 dexMaker.declare(allMethods, PRIVATE | STATIC, null); 486 for (Constructor<T> constructor : getConstructorsToOverwrite(superClass)) { 487 if (constructor.getModifiers() == Modifier.FINAL) { 488 continue; 489 } 490 TypeId<?>[] types = classArrayToTypeArray(constructor.getParameterTypes()); 491 MethodId<?, ?> method = generatedType.getConstructor(types); 492 Code constructorCode = dexMaker.declareConstructor(method, PUBLIC); 493 Local<G> thisRef = constructorCode.getThis(generatedType); 494 Local<?>[] params = new Local[types.length]; 495 for (int i = 0; i < params.length; ++i) { 496 params[i] = constructorCode.getParameter(i, types[i]); 497 } 498 MethodId<T, ?> superConstructor = superType.getConstructor(types); 499 constructorCode.invokeDirect(superConstructor, null, thisRef, params); 500 constructorCode.returnVoid(); 501 } 502 } 503 504 // The type parameter on Constructor is the class in which the constructor is declared. 505 // The getDeclaredConstructors() method gets constructors declared only in the given class, 506 // hence this cast is safe. 507 @SuppressWarnings("unchecked") 508 private static <T> Constructor<T>[] getConstructorsToOverwrite(Class<T> clazz) { 509 return (Constructor<T>[]) clazz.getDeclaredConstructors(); 510 } 511 512 /** 513 * Gets all {@link Method} objects we can proxy in the hierarchy of the supplied class. 514 */ 515 private static <T> Method[] getMethodsToProxy(Class<T> clazz) { 516 Set<MethodSetEntry> methodsToProxy = new HashSet<MethodSetEntry>(); 517 for (Class<?> current = clazz; current != null; current = current.getSuperclass()) { 518 for (Method method : current.getDeclaredMethods()) { 519 if ((method.getModifiers() & Modifier.FINAL) != 0) { 520 // Skip final methods, we can't override them. 521 continue; 522 } 523 if ((method.getModifiers() & STATIC) != 0) { 524 // Skip static methods, overriding them has no effect. 525 continue; 526 } 527 if (method.getName().equals("finalize") && method.getParameterTypes().length == 0) { 528 // Skip finalize method, it's likely important that it execute as normal. 529 continue; 530 } 531 methodsToProxy.add(new MethodSetEntry(method)); 532 } 533 } 534 Method[] results = new Method[methodsToProxy.size()]; 535 int i = 0; 536 for (MethodSetEntry entry : methodsToProxy) { 537 results[i++] = entry.originalMethod; 538 } 539 return results; 540 } 541 542 private static <T> String getMethodNameForProxyOf(Class<T> clazz) { 543 return clazz.getSimpleName() + "_Proxy"; 544 } 545 546 private static TypeId<?>[] classArrayToTypeArray(Class<?>[] input) { 547 TypeId<?>[] result = new TypeId[input.length]; 548 for (int i = 0; i < input.length; ++i) { 549 result[i] = TypeId.get(input[i]); 550 } 551 return result; 552 } 553 554 /** 555 * Calculates the correct return statement code for a method. 556 * <p> 557 * A void method will not return anything. A method that returns a primitive will need to 558 * unbox the boxed result. Otherwise we will cast the result. 559 */ 560 // This one is tricky to fix, I gave up. 561 @SuppressWarnings({ "rawtypes", "unchecked" }) 562 private static void generateCodeForReturnStatement(Code code, Class methodReturnType, 563 Local localForResultOfInvoke, Local localOfMethodReturnType, Local aBoxedResult) { 564 if (PRIMITIVE_TO_UNBOX_METHOD.containsKey(methodReturnType)) { 565 code.typeCast(aBoxedResult, localForResultOfInvoke); 566 MethodId unboxingMethodFor = getUnboxMethodForPrimitive(methodReturnType); 567 code.invokeVirtual(unboxingMethodFor, localOfMethodReturnType, aBoxedResult); 568 code.returnValue(localOfMethodReturnType); 569 } else if (void.class.equals(methodReturnType)) { 570 code.returnVoid(); 571 } else { 572 code.typeCast(localOfMethodReturnType, localForResultOfInvoke); 573 code.returnValue(localOfMethodReturnType); 574 } 575 } 576 577 private static MethodId<?, ?> getUnboxMethodForPrimitive(Class<?> methodReturnType) { 578 return PRIMITIVE_TO_UNBOX_METHOD.get(methodReturnType); 579 } 580 581 private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_BOXED; 582 static { 583 PRIMITIVE_TO_BOXED = new HashMap<Class<?>, Class<?>>(); 584 PRIMITIVE_TO_BOXED.put(boolean.class, Boolean.class); 585 PRIMITIVE_TO_BOXED.put(int.class, Integer.class); 586 PRIMITIVE_TO_BOXED.put(byte.class, Byte.class); 587 PRIMITIVE_TO_BOXED.put(long.class, Long.class); 588 PRIMITIVE_TO_BOXED.put(short.class, Short.class); 589 PRIMITIVE_TO_BOXED.put(float.class, Float.class); 590 PRIMITIVE_TO_BOXED.put(double.class, Double.class); 591 PRIMITIVE_TO_BOXED.put(char.class, Character.class); 592 } 593 594 private static final Map<TypeId<?>, MethodId<?, ?>> PRIMITIVE_TYPE_TO_UNBOX_METHOD; 595 static { 596 PRIMITIVE_TYPE_TO_UNBOX_METHOD = new HashMap<TypeId<?>, MethodId<?, ?>>(); 597 for (Map.Entry<Class<?>, Class<?>> entry : PRIMITIVE_TO_BOXED.entrySet()) { 598 TypeId<?> primitiveType = TypeId.get(entry.getKey()); 599 TypeId<?> boxedType = TypeId.get(entry.getValue()); 600 MethodId<?, ?> valueOfMethod = boxedType.getMethod(boxedType, "valueOf", primitiveType); 601 PRIMITIVE_TYPE_TO_UNBOX_METHOD.put(primitiveType, valueOfMethod); 602 } 603 } 604 605 /** 606 * Map from primitive type to method used to unbox a boxed version of the primitive. 607 * <p> 608 * This is required for methods whose return type is primitive, since the 609 * {@link InvocationHandler} will return us a boxed result, and we'll need to convert it back to 610 * primitive value. 611 */ 612 private static final Map<Class<?>, MethodId<?, ?>> PRIMITIVE_TO_UNBOX_METHOD; 613 static { 614 Map<Class<?>, MethodId<?, ?>> map = new HashMap<Class<?>, MethodId<?, ?>>(); 615 map.put(boolean.class, TypeId.get(Boolean.class).getMethod(TypeId.BOOLEAN, "booleanValue")); 616 map.put(int.class, TypeId.get(Integer.class).getMethod(TypeId.INT, "intValue")); 617 map.put(byte.class, TypeId.get(Byte.class).getMethod(TypeId.BYTE, "byteValue")); 618 map.put(long.class, TypeId.get(Long.class).getMethod(TypeId.LONG, "longValue")); 619 map.put(short.class, TypeId.get(Short.class).getMethod(TypeId.SHORT, "shortValue")); 620 map.put(float.class, TypeId.get(Float.class).getMethod(TypeId.FLOAT, "floatValue")); 621 map.put(double.class, TypeId.get(Double.class).getMethod(TypeId.DOUBLE, "doubleValue")); 622 map.put(char.class, TypeId.get(Character.class).getMethod(TypeId.CHAR, "charValue")); 623 PRIMITIVE_TO_UNBOX_METHOD = map; 624 } 625 626 /** 627 * Wrapper class to let us disambiguate {@link Method} objects. 628 * <p> 629 * The purpose of this class is to override the {@link #equals(Object)} and {@link #hashCode()} 630 * methods so we can use a {@link Set} to remove duplicate methods that are overrides of one 631 * another. For these purposes, we consider two methods to be equal if they have the same 632 * name, return type, and parameter types. 633 */ 634 private static class MethodSetEntry { 635 private final String name; 636 private final Class<?>[] paramTypes; 637 private final Class<?> returnType; 638 private final Method originalMethod; 639 640 public MethodSetEntry(Method method) { 641 originalMethod = method; 642 name = method.getName(); 643 paramTypes = method.getParameterTypes(); 644 returnType = method.getReturnType(); 645 } 646 647 @Override 648 public boolean equals(Object o) { 649 if (o instanceof MethodSetEntry) { 650 MethodSetEntry other = (MethodSetEntry) o; 651 return name.equals(other.name) 652 && returnType.equals(other.returnType) 653 && Arrays.equals(paramTypes, other.paramTypes); 654 } 655 return false; 656 } 657 658 @Override 659 public int hashCode() { 660 int result = 17; 661 result += 31 * result + name.hashCode(); 662 result += 31 * result + returnType.hashCode(); 663 result += 31 * result + Arrays.hashCode(paramTypes); 664 return result; 665 } 666 } 667} 668