ProxyBuilder.java revision 1977585657cb304a9e1ffa8a2320fa8053a7383c
1c52f43e72dfcea03037729649da84c23b3beb04aHoward Hinnant/* 2c52f43e72dfcea03037729649da84c23b3beb04aHoward Hinnant * Copyright (C) 2011 The Android Open Source Project 3c52f43e72dfcea03037729649da84c23b3beb04aHoward Hinnant * 4c52f43e72dfcea03037729649da84c23b3beb04aHoward Hinnant * Licensed under the Apache License, Version 2.0 (the "License"); 5b64f8b07c104c6cc986570ac8ee0ed16a9f23976Howard Hinnant * you may not use this file except in compliance with the License. 6b64f8b07c104c6cc986570ac8ee0ed16a9f23976Howard Hinnant * You may obtain a copy of the License at 7c52f43e72dfcea03037729649da84c23b3beb04aHoward Hinnant * 8c52f43e72dfcea03037729649da84c23b3beb04aHoward Hinnant * http://www.apache.org/licenses/LICENSE-2.0 9c52f43e72dfcea03037729649da84c23b3beb04aHoward Hinnant * 10c52f43e72dfcea03037729649da84c23b3beb04aHoward Hinnant * Unless required by applicable law or agreed to in writing, software 11c52f43e72dfcea03037729649da84c23b3beb04aHoward Hinnant * distributed under the License is distributed on an "AS IS" BASIS, 12c52f43e72dfcea03037729649da84c23b3beb04aHoward Hinnant * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13c52f43e72dfcea03037729649da84c23b3beb04aHoward Hinnant * See the License for the specific language governing permissions and 14c52f43e72dfcea03037729649da84c23b3beb04aHoward Hinnant * limitations under the License. 15c52f43e72dfcea03037729649da84c23b3beb04aHoward Hinnant */ 16c52f43e72dfcea03037729649da84c23b3beb04aHoward Hinnant 17c52f43e72dfcea03037729649da84c23b3beb04aHoward Hinnantpackage com.google.dexmaker.stock; 18c52f43e72dfcea03037729649da84c23b3beb04aHoward Hinnant 19c52f43e72dfcea03037729649da84c23b3beb04aHoward Hinnantimport com.google.dexmaker.Code; 20c52f43e72dfcea03037729649da84c23b3beb04aHoward Hinnantimport com.google.dexmaker.Comparison; 21c52f43e72dfcea03037729649da84c23b3beb04aHoward Hinnantimport com.google.dexmaker.DexGenerator; 22c52f43e72dfcea03037729649da84c23b3beb04aHoward Hinnantimport com.google.dexmaker.FieldId; 23c52f43e72dfcea03037729649da84c23b3beb04aHoward Hinnantimport com.google.dexmaker.Label; 24import com.google.dexmaker.Local; 25import com.google.dexmaker.MethodId; 26import com.google.dexmaker.Type; 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 private ClassLoader parentClassLoader = ProxyBuilder.class.getClassLoader(); 121 private InvocationHandler handler; 122 private File dexCache; 123 private Class<?>[] constructorArgTypes = new Class[0]; 124 private Object[] constructorArgValues = new Object[0]; 125 126 private ProxyBuilder(Class<T> clazz) { 127 baseClass = clazz; 128 } 129 130 public static <T> ProxyBuilder<T> forClass(Class<T> clazz) { 131 return new ProxyBuilder<T>(clazz); 132 } 133 134 /** 135 * Specifies the parent ClassLoader to use when creating the proxy. 136 * 137 * <p>If null, {@code ProxyBuilder.class.getClassLoader()} will be used. 138 */ 139 public ProxyBuilder<T> parentClassLoader(ClassLoader parent) { 140 parentClassLoader = parent; 141 return this; 142 } 143 144 public ProxyBuilder<T> handler(InvocationHandler handler) { 145 this.handler = handler; 146 return this; 147 } 148 149 public ProxyBuilder<T> dexCache(File dexCache) { 150 this.dexCache = dexCache; 151 return this; 152 } 153 154 public ProxyBuilder<T> constructorArgValues(Object... constructorArgValues) { 155 this.constructorArgValues = constructorArgValues; 156 return this; 157 } 158 159 public ProxyBuilder<T> constructorArgTypes(Class<?>... constructorArgTypes) { 160 this.constructorArgTypes = constructorArgTypes; 161 return this; 162 } 163 164 /** 165 * Create a new instance of the class to proxy. 166 * 167 * @throws UnsupportedOperationException if the class we are trying to create a proxy for is 168 * not accessible. 169 * @throws IOException if an exception occurred writing to the {@code dexCache} directory. 170 * @throws UndeclaredThrowableException if the constructor for the base class to proxy throws 171 * a declared exception during construction. 172 * @throws IllegalArgumentException if the handler is null, if the constructor argument types 173 * do not match the constructor argument values, or if no such constructor exists. 174 */ 175 public T build() throws IOException { 176 check(handler != null, "handler == null"); 177 check(constructorArgTypes.length == constructorArgValues.length, 178 "constructorArgValues.length != constructorArgTypes.length"); 179 DexGenerator generator = new DexGenerator(); 180 String generatedName = getMethodNameForProxyOf(baseClass); 181 Type<? extends T> generatedType = Type.get("L" + generatedName + ";"); 182 Type<T> superType = Type.get(baseClass); 183 generateConstructorsAndFields(generator, generatedType, superType, baseClass); 184 Method[] methodsToProxy = getMethodsToProxy(baseClass); 185 generateCodeForAllMethods(generator, generatedType, methodsToProxy, superType); 186 generator.declare(generatedType, generatedName + ".generated", PUBLIC, superType); 187 ClassLoader classLoader = generator.load(parentClassLoader, dexCache, dexCache); 188 Class<? extends T> proxyClass; 189 try { 190 proxyClass = loadClass(classLoader, generatedName); 191 } catch (IllegalAccessError e) { 192 // Thrown when the base class is not accessible. 193 throw new UnsupportedOperationException("cannot proxy inaccessible classes", e); 194 } catch (ClassNotFoundException e) { 195 // Should not be thrown, we're sure to have generated this class. 196 throw new AssertionError(e); 197 } 198 setMethodsStaticField(proxyClass, methodsToProxy); 199 Constructor<? extends T> constructor; 200 try { 201 constructor = proxyClass.getConstructor(constructorArgTypes); 202 } catch (NoSuchMethodException e) { 203 // Thrown when the ctor to be called does not exist. 204 throw new IllegalArgumentException("could not find matching constructor", e); 205 } 206 T result; 207 try { 208 result = constructor.newInstance(constructorArgValues); 209 } catch (InstantiationException e) { 210 // Should not be thrown, generated class is not abstract. 211 throw new AssertionError(e); 212 } catch (IllegalAccessException e) { 213 // Should not be thrown, the generated constructor is accessible. 214 throw new AssertionError(e); 215 } catch (InvocationTargetException e) { 216 // Thrown when the base class ctor throws an exception. 217 throw launderCause(e); 218 } 219 setHandlerInstanceField(result, handler); 220 return result; 221 } 222 223 // The type cast is safe: the generated type will extend the base class type. 224 @SuppressWarnings("unchecked") 225 private Class<? extends T> loadClass(ClassLoader classLoader, String generatedName) 226 throws ClassNotFoundException { 227 return (Class<? extends T>) classLoader.loadClass(generatedName); 228 } 229 230 private static RuntimeException launderCause(InvocationTargetException e) { 231 Throwable cause = e.getCause(); 232 // Errors should be thrown as they are. 233 if (cause instanceof Error) { 234 throw (Error) cause; 235 } 236 // RuntimeException can be thrown as-is. 237 if (cause instanceof RuntimeException) { 238 throw (RuntimeException) cause; 239 } 240 // Declared exceptions will have to be wrapped. 241 throw new UndeclaredThrowableException(cause); 242 } 243 244 private static void setHandlerInstanceField(Object instance, InvocationHandler handler) { 245 try { 246 Field handlerField = instance.getClass().getDeclaredField(FIELD_NAME_HANDLER); 247 handlerField.setAccessible(true); 248 handlerField.set(instance, handler); 249 } catch (NoSuchFieldException e) { 250 // Should not be thrown, generated proxy class has been generated with this field. 251 throw new AssertionError(e); 252 } catch (IllegalAccessException e) { 253 // Should not be thrown, we just set the field to accessible. 254 throw new AssertionError(e); 255 } 256 } 257 258 private static void setMethodsStaticField(Class<?> proxyClass, Method[] methodsToProxy) { 259 try { 260 Field methodArrayField = proxyClass.getDeclaredField(FIELD_NAME_METHODS); 261 methodArrayField.setAccessible(true); 262 methodArrayField.set(null, methodsToProxy); 263 } catch (NoSuchFieldException e) { 264 // Should not be thrown, generated proxy class has been generated with this field. 265 throw new AssertionError(e); 266 } catch (IllegalAccessException e) { 267 // Should not be thrown, we just set the field to accessible. 268 throw new AssertionError(e); 269 } 270 } 271 272 /** 273 * Returns the proxy's {@link InvocationHandler}. 274 * 275 * @throws IllegalArgumentException if the object supplied is not a proxy created by this class. 276 */ 277 public static InvocationHandler getInvocationHandler(Object instance) { 278 try { 279 Field field = instance.getClass().getDeclaredField(FIELD_NAME_HANDLER); 280 field.setAccessible(true); 281 return (InvocationHandler) field.get(instance); 282 } catch (NoSuchFieldException e) { 283 throw new IllegalArgumentException("Not a valid proxy instance", e); 284 } catch (IllegalAccessException e) { 285 // Should not be thrown, we just set the field to accessible. 286 throw new AssertionError(e); 287 } 288 } 289 290 private static <T, G extends T> void generateCodeForAllMethods(DexGenerator generator, 291 Type<G> generatedType, Method[] methodsToProxy, Type<T> superclassType) { 292 Type<InvocationHandler> handlerType = Type.get(InvocationHandler.class); 293 Type<Method[]> methodArrayType = Type.get(Method[].class); 294 FieldId<G, InvocationHandler> handlerField = 295 generatedType.getField(handlerType, FIELD_NAME_HANDLER); 296 FieldId<G, Method[]> allMethods = 297 generatedType.getField(methodArrayType, FIELD_NAME_METHODS); 298 Type<Method> methodType = Type.get(Method.class); 299 Type<Object[]> objectArrayType = Type.get(Object[].class); 300 MethodId<InvocationHandler, Object> methodInvoke = handlerType.getMethod(Type.OBJECT, 301 "invoke", Type.OBJECT, methodType, objectArrayType); 302 for (int m = 0; m < methodsToProxy.length; ++m) { 303 /* 304 * If the 5th method on the superclass Example that can be overridden were to look like 305 * this: 306 * 307 * public int doSomething(Bar param0, int param1) { 308 * ... 309 * } 310 * 311 * Then the following code will generate a method on the proxy that looks something 312 * like this: 313 * 314 * public int doSomething(Bar param0, int param1) { 315 * int methodIndex = 4; 316 * Method[] allMethods = Example_Proxy.$__methodArray; 317 * Method thisMethod = allMethods[methodIndex]; 318 * int argsLength = 2; 319 * Object[] args = new Object[argsLength]; 320 * InvocationHandler localHandler = this.$__handler; 321 * // for-loop begins 322 * int p = 0; 323 * Bar parameter0 = param0; 324 * args[p] = parameter0; 325 * p = 1; 326 * int parameter1 = param1; 327 * Integer boxed1 = Integer.valueOf(parameter1); 328 * args[p] = boxed1; 329 * // for-loop ends 330 * Object result = localHandler.invoke(this, thisMethod, args); 331 * Integer castResult = (Integer) result; 332 * int unboxedResult = castResult.intValue(); 333 * return unboxedResult; 334 * } 335 * 336 * Or, in more idiomatic Java: 337 * 338 * public int doSomething(Bar param0, int param1) { 339 * if ($__handler == null) { 340 * return super.doSomething(param0, param1); 341 * } 342 * return __handler.invoke(this, __methodArray[4], 343 * new Object[] { param0, Integer.valueOf(param1) }); 344 * } 345 */ 346 Method method = methodsToProxy[m]; 347 String name = method.getName(); 348 Class<?>[] argClasses = method.getParameterTypes(); 349 Type<?>[] argTypes = new Type<?>[argClasses.length]; 350 for (int i = 0; i < argTypes.length; ++i) { 351 argTypes[i] = Type.get(argClasses[i]); 352 } 353 Class<?> returnType = method.getReturnType(); 354 Type<?> resultType = Type.get(returnType); 355 MethodId<T, ?> superMethod = superclassType.getMethod(resultType, name, argTypes); 356 MethodId<?, ?> methodId = generatedType.getMethod(resultType, name, argTypes); 357 Code code = generator.declare(methodId, PUBLIC); 358 Local<G> localThis = code.getThis(generatedType); 359 Local<InvocationHandler> localHandler = code.newLocal(handlerType); 360 Local<Object> invokeResult = code.newLocal(Type.OBJECT); 361 Local<Integer> intValue = code.newLocal(Type.INT); 362 Local<Object[]> args = code.newLocal(objectArrayType); 363 Local<Integer> argsLength = code.newLocal(Type.INT); 364 Local<Object> temp = code.newLocal(Type.OBJECT); 365 Local<?> resultHolder = code.newLocal(resultType); 366 Local<Method[]> methodArray = code.newLocal(methodArrayType); 367 Local<Method> thisMethod = code.newLocal(methodType); 368 Local<Integer> methodIndex = code.newLocal(Type.INT); 369 Class<?> aBoxedClass = PRIMITIVE_TO_BOXED.get(returnType); 370 Local<?> aBoxedResult = null; 371 if (aBoxedClass != null) { 372 aBoxedResult = code.newLocal(Type.get(aBoxedClass)); 373 } 374 Local<?>[] superArgs2 = new Local<?>[argClasses.length]; 375 Local<?> superResult2 = code.newLocal(resultType); 376 Local<InvocationHandler> nullHandler = code.newLocal(handlerType); 377 378 code.loadConstant(methodIndex, m); 379 code.sget(allMethods, methodArray); 380 code.aget(methodArray, methodIndex, thisMethod); 381 code.loadConstant(argsLength, argTypes.length); 382 code.newArray(argsLength, args); 383 code.iget(handlerField, localThis, localHandler); 384 385 // if (proxy == null) 386 code.loadConstant(nullHandler, null); 387 Label handlerNullCase = code.newLabel(); 388 code.compare(Comparison.EQ, nullHandler, localHandler, handlerNullCase); 389 390 // This code is what we execute when we have a valid proxy: delegate to invocation 391 // handler. 392 for (int p = 0; p < argTypes.length; ++p) { 393 code.loadConstant(intValue, p); 394 Local<?> parameter = code.getParameter(p, argTypes[p]); 395 Local<?> unboxedIfNecessary = boxIfRequired(code, parameter, temp); 396 code.aput(args, intValue, unboxedIfNecessary); 397 } 398 code.invokeInterface(methodInvoke, invokeResult, localHandler, 399 localThis, thisMethod, args); 400 generateCodeForReturnStatement(code, returnType, invokeResult, resultHolder, 401 aBoxedResult); 402 403 // This code is executed if proxy is null: call the original super method. 404 // This is required to handle the case of construction of an object which leaks the 405 // "this" pointer. 406 code.mark(handlerNullCase); 407 for (int i = 0; i < superArgs2.length; ++i) { 408 superArgs2[i] = code.getParameter(i, argTypes[i]); 409 } 410 if (void.class.equals(returnType)) { 411 code.invokeSuper(superMethod, null, localThis, superArgs2); 412 code.returnVoid(); 413 } else { 414 invokeSuper(superMethod, code, localThis, superArgs2, superResult2); 415 code.returnValue(superResult2); 416 } 417 418 /* 419 * And to allow calling the original super method, the following is also generated: 420 * 421 * public int super_doSomething(Bar param0, int param1) { 422 * int result = super.doSomething(param0, param1); 423 * return result; 424 * } 425 */ 426 String superName = "super_" + name; 427 MethodId<G, ?> callsSuperMethod = generatedType.getMethod( 428 resultType, superName, argTypes); 429 Code superCode = generator.declare(callsSuperMethod, PUBLIC); 430 Local<G> superThis = superCode.getThis(generatedType); 431 Local<?>[] superArgs = new Local<?>[argClasses.length]; 432 for (int i = 0; i < superArgs.length; ++i) { 433 superArgs[i] = superCode.getParameter(i, argTypes[i]); 434 } 435 if (void.class.equals(returnType)) { 436 superCode.invokeSuper(superMethod, null, superThis, superArgs); 437 superCode.returnVoid(); 438 } else { 439 Local<?> superResult = superCode.newLocal(resultType); 440 invokeSuper(superMethod, superCode, superThis, superArgs, superResult); 441 superCode.returnValue(superResult); 442 } 443 } 444 } 445 446 @SuppressWarnings({"unchecked", "rawtypes"}) 447 private static void invokeSuper(MethodId superMethod, Code superCode, 448 Local superThis, Local[] superArgs, Local superResult) { 449 superCode.invokeSuper(superMethod, superResult, superThis, superArgs); 450 } 451 452 private static Local<?> boxIfRequired(Code code, Local<?> parameter, Local<Object> temp) { 453 MethodId<?, ?> unboxMethod = PRIMITIVE_TYPE_TO_UNBOX_METHOD.get(parameter.getType()); 454 if (unboxMethod == null) { 455 return parameter; 456 } 457 code.invokeStatic(unboxMethod, temp, parameter); 458 return temp; 459 } 460 461 public static Object callSuper(Object proxy, Method method, Object... args) 462 throws SecurityException, IllegalAccessException, 463 InvocationTargetException, NoSuchMethodException { 464 return proxy.getClass() 465 .getMethod("super_" + method.getName(), method.getParameterTypes()) 466 .invoke(proxy, args); 467 } 468 469 private static void check(boolean condition, String message) { 470 if (!condition) { 471 throw new IllegalArgumentException(message); 472 } 473 } 474 475 private static <T, G extends T> void generateConstructorsAndFields(DexGenerator generator, 476 Type<G> generatedType, Type<T> superType, Class<T> superClass) { 477 Type<InvocationHandler> handlerType = Type.get(InvocationHandler.class); 478 Type<Method[]> methodArrayType = Type.get(Method[].class); 479 FieldId<G, InvocationHandler> handlerField = generatedType.getField( 480 handlerType, FIELD_NAME_HANDLER); 481 generator.declare(handlerField, PRIVATE, null); 482 FieldId<G, Method[]> allMethods = generatedType.getField( 483 methodArrayType, FIELD_NAME_METHODS); 484 generator.declare(allMethods, PRIVATE | STATIC, null); 485 for (Constructor<T> constructor : getConstructorsToOverwrite(superClass)) { 486 if (constructor.getModifiers() == Modifier.FINAL) { 487 continue; 488 } 489 Type<?>[] types = classArrayToTypeArray(constructor.getParameterTypes()); 490 MethodId<?, ?> method = generatedType.getConstructor(types); 491 Code constructorCode = generator.declareConstructor(method, PUBLIC); 492 Local<G> thisRef = constructorCode.getThis(generatedType); 493 Local<?>[] params = new Local[types.length]; 494 for (int i = 0; i < params.length; ++i) { 495 params[i] = constructorCode.getParameter(i, types[i]); 496 } 497 MethodId<T, ?> superConstructor = superType.getConstructor(types); 498 constructorCode.invokeDirect(superConstructor, null, thisRef, params); 499 constructorCode.returnVoid(); 500 } 501 } 502 503 // The type parameter on Constructor is the class in which the constructor is declared. 504 // The getDeclaredConstructors() method gets constructors declared only in the given class, 505 // hence this cast is safe. 506 @SuppressWarnings("unchecked") 507 private static <T> Constructor<T>[] getConstructorsToOverwrite(Class<T> clazz) { 508 return (Constructor<T>[]) clazz.getDeclaredConstructors(); 509 } 510 511 /** 512 * Gets all {@link Method} objects we can proxy in the hierarchy of the supplied class. 513 */ 514 private static <T> Method[] getMethodsToProxy(Class<T> clazz) { 515 Set<MethodSetEntry> methodsToProxy = new HashSet<MethodSetEntry>(); 516 for (Class<?> current = clazz; current != null; current = current.getSuperclass()) { 517 for (Method method : current.getDeclaredMethods()) { 518 if ((method.getModifiers() & Modifier.FINAL) != 0) { 519 // Skip final methods, we can't override them. 520 continue; 521 } 522 if ((method.getModifiers() & STATIC) != 0) { 523 // Skip static methods, overriding them has no effect. 524 continue; 525 } 526 if (method.getName().equals("finalize") && method.getParameterTypes().length == 0) { 527 // Skip finalize method, it's likely important that it execute as normal. 528 continue; 529 } 530 methodsToProxy.add(new MethodSetEntry(method)); 531 } 532 } 533 Method[] results = new Method[methodsToProxy.size()]; 534 int i = 0; 535 for (MethodSetEntry entry : methodsToProxy) { 536 results[i++] = entry.originalMethod; 537 } 538 return results; 539 } 540 541 private static <T> String getMethodNameForProxyOf(Class<T> clazz) { 542 return clazz.getSimpleName() + "_Proxy"; 543 } 544 545 private static Type<?>[] classArrayToTypeArray(Class<?>[] input) { 546 Type<?>[] result = new Type[input.length]; 547 for (int i = 0; i < input.length; ++i) { 548 result[i] = Type.get(input[i]); 549 } 550 return result; 551 } 552 553 /** 554 * Calculates the correct return statement code for a method. 555 * <p> 556 * A void method will not return anything. A method that returns a primitive will need to 557 * unbox the boxed result. Otherwise we will cast the result. 558 */ 559 // This one is tricky to fix, I gave up. 560 @SuppressWarnings({ "rawtypes", "unchecked" }) 561 private static void generateCodeForReturnStatement(Code code, Class methodReturnType, 562 Local localForResultOfInvoke, Local localOfMethodReturnType, Local aBoxedResult) { 563 if (PRIMITIVE_TO_UNBOX_METHOD.containsKey(methodReturnType)) { 564 code.typeCast(localForResultOfInvoke, aBoxedResult); 565 MethodId unboxingMethodFor = getUnboxMethodForPrimitive(methodReturnType); 566 code.invokeVirtual(unboxingMethodFor, localOfMethodReturnType, aBoxedResult); 567 code.returnValue(localOfMethodReturnType); 568 } else if (void.class.equals(methodReturnType)) { 569 code.returnVoid(); 570 } else { 571 code.typeCast(localForResultOfInvoke, localOfMethodReturnType); 572 code.returnValue(localOfMethodReturnType); 573 } 574 } 575 576 private static MethodId<?, ?> getUnboxMethodForPrimitive(Class<?> methodReturnType) { 577 return PRIMITIVE_TO_UNBOX_METHOD.get(methodReturnType); 578 } 579 580 private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_BOXED; 581 static { 582 PRIMITIVE_TO_BOXED = new HashMap<Class<?>, Class<?>>(); 583 PRIMITIVE_TO_BOXED.put(boolean.class, Boolean.class); 584 PRIMITIVE_TO_BOXED.put(int.class, Integer.class); 585 PRIMITIVE_TO_BOXED.put(byte.class, Byte.class); 586 PRIMITIVE_TO_BOXED.put(long.class, Long.class); 587 PRIMITIVE_TO_BOXED.put(short.class, Short.class); 588 PRIMITIVE_TO_BOXED.put(float.class, Float.class); 589 PRIMITIVE_TO_BOXED.put(double.class, Double.class); 590 PRIMITIVE_TO_BOXED.put(char.class, Character.class); 591 } 592 593 private static final Map<Type<?>, MethodId<?, ?>> PRIMITIVE_TYPE_TO_UNBOX_METHOD; 594 static { 595 PRIMITIVE_TYPE_TO_UNBOX_METHOD = new HashMap<Type<?>, MethodId<?, ?>>(); 596 for (Map.Entry<Class<?>, Class<?>> entry : PRIMITIVE_TO_BOXED.entrySet()) { 597 Type<?> primitiveType = Type.get(entry.getKey()); 598 Type<?> boxedType = Type.get(entry.getValue()); 599 MethodId<?, ?> valueOfMethod = boxedType.getMethod(boxedType, "valueOf", primitiveType); 600 PRIMITIVE_TYPE_TO_UNBOX_METHOD.put(primitiveType, valueOfMethod); 601 } 602 } 603 604 /** 605 * Map from primitive type to method used to unbox a boxed version of the primitive. 606 * <p> 607 * This is required for methods whose return type is primitive, since the 608 * {@link InvocationHandler} will return us a boxed result, and we'll need to convert it back to 609 * primitive value. 610 */ 611 private static final Map<Class<?>, MethodId<?, ?>> PRIMITIVE_TO_UNBOX_METHOD; 612 static { 613 Map<Class<?>, MethodId<?, ?>> map = new HashMap<Class<?>, MethodId<?, ?>>(); 614 map.put(boolean.class, Type.get(Boolean.class).getMethod(Type.BOOLEAN, "booleanValue")); 615 map.put(int.class, Type.get(Integer.class).getMethod(Type.INT, "intValue")); 616 map.put(byte.class, Type.get(Byte.class).getMethod(Type.BYTE, "byteValue")); 617 map.put(long.class, Type.get(Long.class).getMethod(Type.LONG, "longValue")); 618 map.put(short.class, Type.get(Short.class).getMethod(Type.SHORT, "shortValue")); 619 map.put(float.class, Type.get(Float.class).getMethod(Type.FLOAT, "floatValue")); 620 map.put(double.class, Type.get(Double.class).getMethod(Type.DOUBLE, "doubleValue")); 621 map.put(char.class, Type.get(Character.class).getMethod(Type.CHAR, "charValue")); 622 PRIMITIVE_TO_UNBOX_METHOD = map; 623 } 624 625 /** 626 * Wrapper class to let us disambiguate {@link Method} objects. 627 * <p> 628 * The purpose of this class is to override the {@link #equals(Object)} and {@link #hashCode()} 629 * methods so we can use a {@link Set} to remove duplicate methods that are overrides of one 630 * another. For these purposes, we consider two methods to be equal if they have the same 631 * name, return type, and parameter types. 632 */ 633 private static class MethodSetEntry { 634 private final String name; 635 private final Class<?>[] paramTypes; 636 private final Class<?> returnType; 637 private final Method originalMethod; 638 639 public MethodSetEntry(Method method) { 640 originalMethod = method; 641 name = method.getName(); 642 paramTypes = method.getParameterTypes(); 643 returnType = method.getReturnType(); 644 } 645 646 @Override 647 public boolean equals(Object o) { 648 if (o instanceof MethodSetEntry) { 649 MethodSetEntry other = (MethodSetEntry) o; 650 return name.equals(other.name) 651 && returnType.equals(other.returnType) 652 && Arrays.equals(paramTypes, other.paramTypes); 653 } 654 return false; 655 } 656 657 @Override 658 public int hashCode() { 659 int result = 17; 660 result += 31 * result + name.hashCode(); 661 result += 31 * result + returnType.hashCode(); 662 result += 31 * result + Arrays.hashCode(paramTypes); 663 return result; 664 } 665 } 666} 667