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