diff options
Diffstat (limited to 'dx/src/com/android/dx/gen/ProxyBuilder.java')
-rw-r--r-- | dx/src/com/android/dx/gen/ProxyBuilder.java | 667 |
1 files changed, 667 insertions, 0 deletions
diff --git a/dx/src/com/android/dx/gen/ProxyBuilder.java b/dx/src/com/android/dx/gen/ProxyBuilder.java new file mode 100644 index 000000000..852fe666f --- /dev/null +++ b/dx/src/com/android/dx/gen/ProxyBuilder.java @@ -0,0 +1,667 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.gen; + +import static com.android.dx.rop.code.AccessFlags.ACC_CONSTRUCTOR; +import static com.android.dx.rop.code.AccessFlags.ACC_PRIVATE; +import static com.android.dx.rop.code.AccessFlags.ACC_PUBLIC; +import static com.android.dx.rop.code.AccessFlags.ACC_STATIC; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.UndeclaredThrowableException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Creates dynamic proxies of concrete classes. + * <p> + * This is similar to the {@code java.lang.reflect.Proxy} class, but works for classes instead of + * interfaces. + * <h3>Example</h3> + * The following example demonstrates the creation of a dynamic proxy for {@code java.util.Random} + * which will always return 4 when asked for integers, and which logs method calls to every method. + * <pre> + * InvocationHandler handler = new InvocationHandler() { + * @Override + * public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + * if (method.getName().equals("nextInt")) { + * // Chosen by fair dice roll, guaranteed to be random. + * return 4; + * } + * Object result = ProxyBuilder.callSuper(proxy, method, args); + * System.out.println("Method: " + method.getName() + " args: " + * + Arrays.toString(args) + " result: " + result); + * return result; + * } + * }; + * Random debugRandom = ProxyBuilder.forClass(Random.class) + * .dexCache(getInstrumentation().getTargetContext().getDir("dx", Context.MODE_PRIVATE)) + * .handler(handler) + * .build(); + * assertEquals(4, debugRandom.nextInt()); + * debugRandom.setSeed(0); + * assertTrue(debugRandom.nextBoolean()); + * </pre> + * <h3>Usage</h3> + * Call {@link #forClass(Class)} for the Class you wish to proxy. Call + * {@link #handler(InvocationHandler)} passing in an {@link InvocationHandler}, and then call + * {@link #build()}. The returned instance will be a dynamically generated subclass where all method + * calls will be delegated to the invocation handler, except as noted below. + * <p> + * The static method {@link #callSuper(Object, Method, Object...)} allows you to access the original + * super method for a given proxy. This allows the invocation handler to selectively override some + * methods but not others. + * <p> + * By default, the {@link #build()} method will call the no-arg constructor belonging to the class + * being proxied. If you wish to call a different constructor, you must provide arguments for both + * {@link #constructorArgTypes(Class[])} and {@link #constructorArgValues(Object[])}. + * <p> + * This process works only for classes with public and protected level of visibility. + * <p> + * You may proxy abstract classes. You may not proxy final classes. + * <p> + * Only non-private, non-final, non-static methods will be dispatched to the invocation handler. + * Private, static or final methods will always call through to the superclass as normal. + * <p> + * The {@link #finalize()} method on {@code Object} will not be proxied. + * <p> + * You must provide a dex cache directory via the {@link #dexCache(File)} method. You should take + * care not to make this a world-writable directory, so that third parties cannot inject code into + * your application. A suitable parameter for these output directories would be something like + * this: + * <pre>{@code + * getApplicationContext().getDir("dx", Context.MODE_PRIVATE); + * }</pre> + * <p> + * If the base class to be proxied leaks the {@code this} pointer in the constructor (bad practice), + * that is to say calls a non-private non-final method from the constructor, the invocation handler + * will not be invoked. As a simple concrete example, when proxying Random we discover that it + * inernally calls setSeed during the constructor. The proxy will not intercept this call during + * proxy construction, but will intercept as normal afterwards. This behaviour may be subject to + * change in future releases. + * <p> + * This class is <b>not thread safe</b>. + */ +public final class ProxyBuilder<T> { + private static final String FIELD_NAME_HANDLER = "$__handler"; + private static final String FIELD_NAME_METHODS = "$__methodArray"; + + private final Class<T> baseClass; + private ClassLoader parentClassLoader = ProxyBuilder.class.getClassLoader(); + private InvocationHandler handler; + private File dexCache; + private Class<?>[] constructorArgTypes = new Class[0]; + private Object[] constructorArgValues = new Object[0]; + + private ProxyBuilder(Class<T> clazz) { + baseClass = clazz; + } + + public static <T> ProxyBuilder<T> forClass(Class<T> clazz) { + return new ProxyBuilder<T>(clazz); + } + + /** + * Specifies the parent ClassLoader to use when creating the proxy. + * + * <p>If null, {@code ProxyBuilder.class.getClassLoader()} will be used. + */ + public ProxyBuilder<T> parentClassLoader(ClassLoader parent) { + parentClassLoader = parent; + return this; + } + + public ProxyBuilder<T> handler(InvocationHandler handler) { + this.handler = handler; + return this; + } + + public ProxyBuilder<T> dexCache(File dexCache) { + this.dexCache = dexCache; + return this; + } + + public ProxyBuilder<T> constructorArgValues(Object... constructorArgValues) { + this.constructorArgValues = constructorArgValues; + return this; + } + + public ProxyBuilder<T> constructorArgTypes(Class<?>... constructorArgTypes) { + this.constructorArgTypes = constructorArgTypes; + return this; + } + + /** + * Create a new instance of the class to proxy. + * + * @throws UnsupportedOperationException if the class we are trying to create a proxy for is + * not accessible. + * @throws DexCacheException if an exception occurred writing to the {@code dexCache} directory. + * @throws UndeclaredThrowableException if the constructor for the base class to proxy throws + * a declared exception during construction. + * @throws IllegalArgumentException if the handler is null, if the constructor argument types + * do not match the constructor argument values, or if no such constructor exists. + */ + public T build() { + check(handler != null, "handler == null"); + check(constructorArgTypes.length == constructorArgValues.length, + "constructorArgValues.length != constructorArgTypes.length"); + DexGenerator generator = new DexGenerator(); + String generatedName = getMethodNameForProxyOf(baseClass); + Type<? extends T> generatedType = Type.get("L" + generatedName + ";"); + Type<T> superType = Type.get(baseClass); + generateConstructorsAndFields(generator, generatedType, superType, baseClass); + Method[] methodsToProxy = getMethodsToProxy(baseClass); + generateCodeForAllMethods(generator, generatedType, methodsToProxy, superType); + generator.declare(generatedType, generatedName + ".generated", ACC_PUBLIC, superType); + ClassLoader classLoader; + try { + classLoader = generator.load(parentClassLoader, dexCache, dexCache); + } catch (IOException e) { + throw new DexCacheException(e); + } + Class<? extends T> proxyClass; + try { + proxyClass = loadClass(classLoader, generatedName); + } catch (IllegalAccessError e) { + // Thrown when the base class is not accessible. + throw new UnsupportedOperationException("cannot proxy inaccessible classes", e); + } catch (ClassNotFoundException e) { + // Should not be thrown, we're sure to have generated this class. + throw new AssertionError(e); + } + setMethodsStaticField(proxyClass, methodsToProxy); + Constructor<? extends T> constructor; + try { + constructor = proxyClass.getConstructor(constructorArgTypes); + } catch (NoSuchMethodException e) { + // Thrown when the ctor to be called does not exist. + throw new IllegalArgumentException("could not find matching constructor", e); + } + T result; + try { + result = constructor.newInstance(constructorArgValues); + } catch (InstantiationException e) { + // Should not be thrown, generated class is not abstract. + throw new AssertionError(e); + } catch (IllegalAccessException e) { + // Should not be thrown, the generated constructor is accessible. + throw new AssertionError(e); + } catch (InvocationTargetException e) { + // Thrown when the base class ctor throws an exception. + throw launderCause(e); + } + setHandlerInstanceField(result, handler); + return result; + } + + // The type cast is safe: the generated type will extend the base class type. + @SuppressWarnings("unchecked") + private Class<? extends T> loadClass(ClassLoader classLoader, String generatedName) + throws ClassNotFoundException { + return (Class<? extends T>) classLoader.loadClass(generatedName); + } + + private static RuntimeException launderCause(InvocationTargetException e) { + Throwable cause = e.getCause(); + // Errors should be thrown as they are. + if (cause instanceof Error) { + throw (Error) cause; + } + // RuntimeException can be thrown as-is. + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } + // Declared exceptions will have to be wrapped. + throw new UndeclaredThrowableException(cause); + } + + private static void setHandlerInstanceField(Object instance, InvocationHandler handler) { + try { + Field handlerField = instance.getClass().getDeclaredField(FIELD_NAME_HANDLER); + handlerField.setAccessible(true); + handlerField.set(instance, handler); + } catch (NoSuchFieldException e) { + // Should not be thrown, generated proxy class has been generated with this field. + throw new AssertionError(e); + } catch (IllegalAccessException e) { + // Should not be thrown, we just set the field to accessible. + throw new AssertionError(e); + } + } + + private static void setMethodsStaticField(Class<?> proxyClass, Method[] methodsToProxy) { + try { + Field methodArrayField = proxyClass.getDeclaredField(FIELD_NAME_METHODS); + methodArrayField.setAccessible(true); + methodArrayField.set(null, methodsToProxy); + } catch (NoSuchFieldException e) { + // Should not be thrown, generated proxy class has been generated with this field. + throw new AssertionError(e); + } catch (IllegalAccessException e) { + // Should not be thrown, we just set the field to accessible. + throw new AssertionError(e); + } + } + + /** + * Returns the proxy's {@link InvocationHandler}. + * + * @throws IllegalArgumentException if the object supplied is not a proxy created by this class. + */ + public static InvocationHandler getInvocationHandler(Object instance) { + try { + Field field = instance.getClass().getDeclaredField(FIELD_NAME_HANDLER); + field.setAccessible(true); + return (InvocationHandler) field.get(instance); + } catch (NoSuchFieldException e) { + throw new IllegalArgumentException("Not a valid proxy instance", e); + } catch (IllegalAccessException e) { + // Should not be thrown, we just set the field to accessible. + throw new AssertionError(e); + } + } + + private static <T, G extends T> void generateCodeForAllMethods(DexGenerator generator, + Type<G> generatedType, Method[] methodsToProxy, Type<T> superclassType) { + Type<InvocationHandler> handlerType = Type.get(InvocationHandler.class); + Type<Method[]> methodArrayType = Type.get(Method[].class); + FieldId<G, InvocationHandler> handlerField = + generatedType.getField(handlerType, FIELD_NAME_HANDLER); + FieldId<G, Method[]> allMethods = + generatedType.getField(methodArrayType, FIELD_NAME_METHODS); + Type<Method> methodType = Type.get(Method.class); + Type<Object[]> objectArrayType = Type.get(Object[].class); + MethodId<InvocationHandler, Object> methodInvoke = handlerType.getMethod(Type.OBJECT, + "invoke", Type.OBJECT, methodType, objectArrayType); + for (int m = 0; m < methodsToProxy.length; ++m) { + /* + * If the 5th method on the superclass Example that can be overridden were to look like + * this: + * + * public int doSomething(Bar param0, int param1) { + * ... + * } + * + * Then the following code will generate a method on the proxy that looks something + * like this: + * + * public int doSomething(Bar param0, int param1) { + * int methodIndex = 4; + * Method[] allMethods = Example_Proxy.$__methodArray; + * Method thisMethod = allMethods[methodIndex]; + * int argsLength = 2; + * Object[] args = new Object[argsLength]; + * InvocationHandler localHandler = this.$__handler; + * // for-loop begins + * int p = 0; + * Bar parameter0 = param0; + * args[p] = parameter0; + * p = 1; + * int parameter1 = param1; + * Integer boxed1 = Integer.valueOf(parameter1); + * args[p] = boxed1; + * // for-loop ends + * Object result = localHandler.invoke(this, thisMethod, args); + * Integer castResult = (Integer) result; + * int unboxedResult = castResult.intValue(); + * return unboxedResult; + * } + * + * Or, in more idiomatic Java: + * + * public int doSomething(Bar param0, int param1) { + * if ($__handler == null) { + * return super.doSomething(param0, param1); + * } + * return __handler.invoke(this, __methodArray[4], + * new Object[] { param0, Integer.valueOf(param1) }); + * } + */ + Method method = methodsToProxy[m]; + String name = method.getName(); + Class<?>[] argClasses = method.getParameterTypes(); + Type<?>[] argTypes = new Type<?>[argClasses.length]; + for (int i = 0; i < argTypes.length; ++i) { + argTypes[i] = Type.get(argClasses[i]); + } + Class<?> returnType = method.getReturnType(); + Type<?> resultType = Type.get(returnType); + MethodId<T, ?> superMethod = superclassType.getMethod(resultType, name, argTypes); + MethodId<?, ?> methodId = generatedType.getMethod(resultType, name, argTypes); + Code code = generator.declare(methodId, ACC_PUBLIC); + Local<G> localThis = code.getThis(generatedType); + Local<InvocationHandler> localHandler = code.newLocal(handlerType); + Local<Object> invokeResult = code.newLocal(Type.OBJECT); + Local<Integer> intValue = code.newLocal(Type.INT); + Local<Object[]> args = code.newLocal(objectArrayType); + Local<Integer> argsLength = code.newLocal(Type.INT); + Local<Object> temp = code.newLocal(Type.OBJECT); + Local<?> resultHolder = code.newLocal(resultType); + Local<Method[]> methodArray = code.newLocal(methodArrayType); + Local<Method> thisMethod = code.newLocal(methodType); + Local<Integer> methodIndex = code.newLocal(Type.INT); + Class<?> aBoxedClass = PRIMITIVE_TO_BOXED.get(returnType); + Local<?> aBoxedResult = null; + if (aBoxedClass != null) { + aBoxedResult = code.newLocal(Type.get(aBoxedClass)); + } + Local<?>[] superArgs2 = new Local<?>[argClasses.length]; + Local<?> superResult2 = code.newLocal(resultType); + Local<InvocationHandler> nullHandler = code.newLocal(handlerType); + + code.loadConstant(methodIndex, m); + code.sget(allMethods, methodArray); + code.aget(methodArray, methodIndex, thisMethod); + code.loadConstant(argsLength, argTypes.length); + code.newArray(argsLength, args); + code.iget(handlerField, localThis, localHandler); + + // if (proxy == null) + code.loadConstant(nullHandler, null); + Label handlerNullCase = code.newLabel(); + code.compare(Comparison.EQ, nullHandler, localHandler, handlerNullCase); + + // This code is what we execute when we have a valid proxy: delegate to invocation + // handler. + for (int p = 0; p < argTypes.length; ++p) { + code.loadConstant(intValue, p); + Local<?> parameter = code.getParameter(p, argTypes[p]); + Local<?> unboxedIfNecessary = boxIfRequired(generator, code, parameter, temp); + code.aput(args, intValue, unboxedIfNecessary); + } + code.invokeInterface(methodInvoke, invokeResult, localHandler, + localThis, thisMethod, args); + generateCodeForReturnStatement(code, returnType, invokeResult, resultHolder, + aBoxedResult); + + // This code is executed if proxy is null: call the original super method. + // This is required to handle the case of construction of an object which leaks the + // "this" pointer. + code.mark(handlerNullCase); + for (int i = 0; i < superArgs2.length; ++i) { + superArgs2[i] = code.getParameter(i, argTypes[i]); + } + if (void.class.equals(returnType)) { + code.invokeSuper(superMethod, null, localThis, superArgs2); + code.returnVoid(); + } else { + invokeSuper(superMethod, code, localThis, superArgs2, superResult2); + code.returnValue(superResult2); + } + + /* + * And to allow calling the original super method, the following is also generated: + * + * public int super_doSomething(Bar param0, int param1) { + * int result = super.doSomething(param0, param1); + * return result; + * } + */ + String superName = "super_" + name; + MethodId<G, ?> callsSuperMethod = generatedType.getMethod( + resultType, superName, argTypes); + Code superCode = generator.declare(callsSuperMethod, ACC_PUBLIC); + Local<G> superThis = superCode.getThis(generatedType); + Local<?>[] superArgs = new Local<?>[argClasses.length]; + for (int i = 0; i < superArgs.length; ++i) { + superArgs[i] = superCode.getParameter(i, argTypes[i]); + } + if (void.class.equals(returnType)) { + superCode.invokeSuper(superMethod, null, superThis, superArgs); + superCode.returnVoid(); + } else { + Local<?> superResult = superCode.newLocal(resultType); + invokeSuper(superMethod, superCode, superThis, superArgs, superResult); + superCode.returnValue(superResult); + } + } + } + + // This one is tricky to fix, I gave up. + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static <T> void invokeSuper(MethodId superMethod, Code superCode, + Local superThis, Local[] superArgs, Local superResult) { + superCode.invokeSuper(superMethod, superResult, superThis, superArgs); + } + + private static Local<?> boxIfRequired(DexGenerator generator, Code code, Local<?> parameter, + Local<Object> temp) { + MethodId<?, ?> unboxMethod = PRIMITIVE_TYPE_TO_UNBOX_METHOD.get(parameter.getType()); + if (unboxMethod == null) { + return parameter; + } + code.invokeStatic(unboxMethod, temp, parameter); + return temp; + } + + public static Object callSuper(Object proxy, Method method, Object... args) + throws SecurityException, IllegalAccessException, + InvocationTargetException, NoSuchMethodException { + return proxy.getClass() + .getMethod("super_" + method.getName(), method.getParameterTypes()) + .invoke(proxy, args); + } + + private static void check(boolean condition, String message) { + if (!condition) { + throw new IllegalArgumentException(message); + } + } + + private static <T, G extends T> void generateConstructorsAndFields(DexGenerator generator, + Type<G> generatedType, Type<T> superType, Class<T> superClass) { + Type<InvocationHandler> handlerType = Type.get(InvocationHandler.class); + Type<Method[]> methodArrayType = Type.get(Method[].class); + FieldId<G, InvocationHandler> handlerField = generatedType.getField( + handlerType, FIELD_NAME_HANDLER); + generator.declare(handlerField, ACC_PRIVATE, null); + FieldId<G, Method[]> allMethods = generatedType.getField( + methodArrayType, FIELD_NAME_METHODS); + generator.declare(allMethods, ACC_PRIVATE | ACC_STATIC, null); + for (Constructor<T> constructor : getConstructorsToOverwrite(superClass)) { + if (constructor.getModifiers() == Modifier.FINAL) { + continue; + } + Type<?>[] types = classArrayToTypeArray(constructor.getParameterTypes()); + MethodId<?, ?> method = generatedType.getConstructor(types); + Code constructorCode = generator.declare(method, ACC_PUBLIC | ACC_CONSTRUCTOR); + Local<G> thisRef = constructorCode.getThis(generatedType); + Local<?>[] params = new Local[types.length]; + for (int i = 0; i < params.length; ++i) { + params[i] = constructorCode.getParameter(i, types[i]); + } + MethodId<T, ?> superConstructor = superType.getConstructor(types); + constructorCode.invokeDirect(superConstructor, null, thisRef, params); + constructorCode.returnVoid(); + } + } + + // The type parameter on Constructor is the class in which the constructor is declared. + // The getDeclaredConstructors() method gets constructors declared only in the given class, + // hence this cast is safe. + @SuppressWarnings("unchecked") + private static <T> Constructor<T>[] getConstructorsToOverwrite(Class<T> clazz) { + return (Constructor<T>[]) clazz.getDeclaredConstructors(); + } + + /** + * Gets all {@link Method} objects we can proxy in the hierarchy of the supplied class. + */ + private static <T> Method[] getMethodsToProxy(Class<T> clazz) { + Set<MethodSetEntry> methodsToProxy = new HashSet<MethodSetEntry>(); + for (Class<?> current = clazz; current != null; current = current.getSuperclass()) { + for (Method method : current.getDeclaredMethods()) { + if ((method.getModifiers() & Modifier.FINAL) != 0) { + // Skip final methods, we can't override them. + continue; + } + if ((method.getModifiers() & Modifier.STATIC) != 0) { + // Skip static methods, overriding them has no effect. + continue; + } + if (method.getName().equals("finalize") && method.getParameterTypes().length == 0) { + // Skip finalize method, it's likely important that it execute as normal. + continue; + } + methodsToProxy.add(new MethodSetEntry(method)); + } + } + Method[] results = new Method[methodsToProxy.size()]; + int i = 0; + for (MethodSetEntry entry : methodsToProxy) { + results[i++] = entry.originalMethod; + } + return results; + } + + private static <T> String getMethodNameForProxyOf(Class<T> clazz) { + return clazz.getSimpleName() + "_Proxy"; + } + + private static Type<?>[] classArrayToTypeArray(Class<?>[] input) { + Type<?>[] result = new Type[input.length]; + for (int i = 0; i < input.length; ++i) { + result[i] = Type.get(input[i]); + } + return result; + } + + /** + * Calculates the correct return statement code for a method. + * <p> + * A void method will not return anything. A method that returns a primitive will need to + * unbox the boxed result. Otherwise we will cast the result. + */ + // This one is tricky to fix, I gave up. + @SuppressWarnings({ "rawtypes", "unchecked" }) + private static void generateCodeForReturnStatement(Code code, Class methodReturnType, + Local localForResultOfInvoke, Local localOfMethodReturnType, Local aBoxedResult) { + if (PRIMITIVE_TO_UNBOX_METHOD.containsKey(methodReturnType)) { + code.typeCast(localForResultOfInvoke, aBoxedResult); + MethodId unboxingMethodFor = getUnboxMethodForPrimitive(methodReturnType); + code.invokeVirtual(unboxingMethodFor, localOfMethodReturnType, aBoxedResult); + code.returnValue(localOfMethodReturnType); + } else if (void.class.equals(methodReturnType)) { + code.returnVoid(); + } else { + code.typeCast(localForResultOfInvoke, localOfMethodReturnType); + code.returnValue(localOfMethodReturnType); + } + } + + private static MethodId<?, ?> getUnboxMethodForPrimitive(Class<?> methodReturnType) { + return PRIMITIVE_TO_UNBOX_METHOD.get(methodReturnType); + } + + private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_BOXED; + static { + PRIMITIVE_TO_BOXED = new HashMap<Class<?>, Class<?>>(); + PRIMITIVE_TO_BOXED.put(boolean.class, Boolean.class); + PRIMITIVE_TO_BOXED.put(int.class, Integer.class); + PRIMITIVE_TO_BOXED.put(byte.class, Byte.class); + PRIMITIVE_TO_BOXED.put(long.class, Long.class); + PRIMITIVE_TO_BOXED.put(short.class, Short.class); + PRIMITIVE_TO_BOXED.put(float.class, Float.class); + PRIMITIVE_TO_BOXED.put(double.class, Double.class); + PRIMITIVE_TO_BOXED.put(char.class, Character.class); + } + + private static final Map<Type<?>, MethodId<?, ?>> PRIMITIVE_TYPE_TO_UNBOX_METHOD; + static { + PRIMITIVE_TYPE_TO_UNBOX_METHOD = new HashMap<Type<?>, MethodId<?, ?>>(); + for (Map.Entry<Class<?>, Class<?>> entry : PRIMITIVE_TO_BOXED.entrySet()) { + Type<?> primitiveType = Type.get(entry.getKey()); + Type<?> boxedType = Type.get(entry.getValue()); + MethodId<?, ?> valueOfMethod = boxedType.getMethod(boxedType, "valueOf", primitiveType); + PRIMITIVE_TYPE_TO_UNBOX_METHOD.put(primitiveType, valueOfMethod); + } + } + + /** + * Map from primitive type to method used to unbox a boxed version of the primitive. + * <p> + * This is required for methods whose return type is primitive, since the + * {@link InvocationHandler} will return us a boxed result, and we'll need to convert it back to + * primitive value. + */ + private static final Map<Class<?>, MethodId<?, ?>> PRIMITIVE_TO_UNBOX_METHOD; + static { + Map<Class<?>, MethodId<?, ?>> map = new HashMap<Class<?>, MethodId<?, ?>>(); + map.put(boolean.class, Type.get(Boolean.class).getMethod(Type.BOOLEAN, "booleanValue")); + map.put(int.class, Type.get(Integer.class).getMethod(Type.INT, "intValue")); + map.put(byte.class, Type.get(Byte.class).getMethod(Type.BYTE, "byteValue")); + map.put(long.class, Type.get(Long.class).getMethod(Type.LONG, "longValue")); + map.put(short.class, Type.get(Short.class).getMethod(Type.SHORT, "shortValue")); + map.put(float.class, Type.get(Float.class).getMethod(Type.FLOAT, "floatValue")); + map.put(double.class, Type.get(Double.class).getMethod(Type.DOUBLE, "doubleValue")); + map.put(char.class, Type.get(Character.class).getMethod(Type.CHAR, "charValue")); + PRIMITIVE_TO_UNBOX_METHOD = map; + } + + /** + * Wrapper class to let us disambiguate {@link Method} objects. + * <p> + * The purpose of this class is to override the {@link #equals(Object)} and {@link #hashCode()} + * methods so we can use a {@link Set} to remove duplicate methods that are overrides of one + * another. For these purposes, we consider two methods to be equal if they have the same + * name, return type, and parameter types. + */ + private static class MethodSetEntry { + private final String name; + private final Class<?>[] paramTypes; + private final Class<?> returnType; + private final Method originalMethod; + + public MethodSetEntry(Method method) { + originalMethod = method; + name = method.getName(); + paramTypes = method.getParameterTypes(); + returnType = method.getReturnType(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof MethodSetEntry) { + MethodSetEntry other = (MethodSetEntry) o; + return name.equals(other.name) + && returnType.equals(other.returnType) + && Arrays.equals(paramTypes, other.paramTypes); + } + return false; + } + + @Override + public int hashCode() { + int result = 17; + result += 31 * result + name.hashCode(); + result += 31 * result + returnType.hashCode(); + result += 31 * result + Arrays.hashCode(paramTypes); + return result; + } + } +} |