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