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