| /* |
| * 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.stock; |
| |
| import com.android.dx.Code; |
| import com.android.dx.Comparison; |
| import com.android.dx.DexMaker; |
| import com.android.dx.FieldId; |
| import com.android.dx.Label; |
| import com.android.dx.Local; |
| import com.android.dx.MethodId; |
| import com.android.dx.TypeId; |
| |
| 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.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.CopyOnWriteArraySet; |
| |
| import static java.lang.reflect.Modifier.PRIVATE; |
| import static java.lang.reflect.Modifier.PUBLIC; |
| import static java.lang.reflect.Modifier.STATIC; |
| |
| /** |
| * 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 |
| * internally 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> { |
| // Version of ProxyBuilder. It should be updated if the implementation |
| // of the generated proxy class changes. |
| public static final int VERSION = 1; |
| |
| private static final String FIELD_NAME_HANDLER = "$__handler"; |
| private static final String FIELD_NAME_METHODS = "$__methodArray"; |
| |
| /** |
| * A cache of all proxy classes ever generated. At the time of writing, |
| * Android's runtime doesn't support class unloading so there's little |
| * value in using weak references. |
| */ |
| private static final Map<Class<?>, Class<?>> generatedProxyClasses |
| = Collections.synchronizedMap(new HashMap<Class<?>, Class<?>>()); |
| |
| 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 Set<Class<?>> interfaces = new HashSet<>(); |
| private Method[] methods; |
| private boolean sharedClassLoader; |
| |
| 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; |
| } |
| |
| /** |
| * Sets the directory where executable code is stored. See {@link |
| * DexMaker#generateAndLoad DexMaker.generateAndLoad()} for guidance on |
| * choosing a secure location for the dex cache. |
| */ |
| public ProxyBuilder<T> dexCache(File dexCacheParent) { |
| dexCache = new File(dexCacheParent, "v" + Integer.toString(VERSION)); |
| dexCache.mkdir(); |
| return this; |
| } |
| |
| public ProxyBuilder<T> implementing(Class<?>... interfaces) { |
| for (Class<?> i : interfaces) { |
| if (!i.isInterface()) { |
| throw new IllegalArgumentException("Not an interface: " + i.getName()); |
| } |
| this.interfaces.add(i); |
| } |
| return this; |
| } |
| |
| public ProxyBuilder<T> constructorArgValues(Object... constructorArgValues) { |
| this.constructorArgValues = constructorArgValues; |
| return this; |
| } |
| |
| public ProxyBuilder<T> constructorArgTypes(Class<?>... constructorArgTypes) { |
| this.constructorArgTypes = constructorArgTypes; |
| return this; |
| } |
| |
| public ProxyBuilder<T> onlyMethods(Method[] methods) { |
| this.methods = methods; |
| return this; |
| } |
| |
| public ProxyBuilder<T> withSharedClassLoader() { |
| this.sharedClassLoader = true; |
| 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 IOException 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() throws IOException { |
| check(handler != null, "handler == null"); |
| check(constructorArgTypes.length == constructorArgValues.length, |
| "constructorArgValues.length != constructorArgTypes.length"); |
| Class<? extends T> proxyClass = buildProxyClass(); |
| Constructor<? extends T> constructor; |
| try { |
| constructor = proxyClass.getConstructor(constructorArgTypes); |
| } catch (NoSuchMethodException e) { |
| throw new IllegalArgumentException("No constructor for " + baseClass.getName() |
| + " with parameter types " + Arrays.toString(constructorArgTypes)); |
| } |
| 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 constructor throws an exception. |
| throw launderCause(e); |
| } |
| setInvocationHandler(result, handler); |
| return result; |
| } |
| |
| // TODO: test coverage for this |
| |
| /** |
| * Generate a proxy class. Note that new instances of this class will not automatically have an |
| * an invocation handler, even if {@link #handler(InvocationHandler)} was called. The handler |
| * must be set on each instance after it is created, using |
| * {@link #setInvocationHandler(Object, InvocationHandler)}. |
| */ |
| public Class<? extends T> buildProxyClass() throws IOException { |
| // try the cache to see if we've generated this one before |
| // we only populate the map with matching types |
| @SuppressWarnings("unchecked") |
| Class<? extends T> proxyClass = (Class) generatedProxyClasses.get(baseClass); |
| if (proxyClass != null) { |
| boolean validClassLoader; |
| if (sharedClassLoader) { |
| ClassLoader parent = parentClassLoader != null ? parentClassLoader : baseClass |
| .getClassLoader(); |
| validClassLoader = proxyClass.getClassLoader() == parent; |
| } else { |
| validClassLoader = proxyClass.getClassLoader().getParent() == parentClassLoader; |
| } |
| if (validClassLoader && interfaces.equals(asSet(proxyClass.getInterfaces()))) { |
| return proxyClass; // cache hit! |
| } |
| } |
| |
| // the cache missed; generate the class |
| DexMaker dexMaker = new DexMaker(); |
| String generatedName = getMethodNameForProxyOf(baseClass); |
| TypeId<? extends T> generatedType = TypeId.get("L" + generatedName + ";"); |
| TypeId<T> superType = TypeId.get(baseClass); |
| generateConstructorsAndFields(dexMaker, generatedType, superType, baseClass); |
| |
| Method[] methodsToProxy; |
| if (methods == null) { |
| methodsToProxy = getMethodsToProxyRecursive(); |
| } else { |
| methodsToProxy = methods; |
| } |
| |
| // Sort the results array so that they are in a deterministic fashion. |
| Arrays.sort(methodsToProxy, new Comparator<Method>() { |
| @Override |
| public int compare(Method method1, Method method2) { |
| return method1.toString().compareTo(method2.toString()); |
| } |
| }); |
| |
| generateCodeForAllMethods(dexMaker, generatedType, methodsToProxy, superType); |
| dexMaker.declare(generatedType, generatedName + ".generated", PUBLIC, superType, getInterfacesAsTypeIds()); |
| if (sharedClassLoader) { |
| dexMaker.setSharedClassLoader(baseClass.getClassLoader()); |
| } |
| ClassLoader classLoader = dexMaker.generateAndLoad(parentClassLoader, dexCache); |
| try { |
| proxyClass = loadClass(classLoader, generatedName); |
| } catch (IllegalAccessError e) { |
| // Thrown when the base class is not accessible. |
| throw new UnsupportedOperationException( |
| "cannot proxy inaccessible class " + baseClass, e); |
| } catch (ClassNotFoundException e) { |
| // Should not be thrown, we're sure to have generated this class. |
| throw new AssertionError(e); |
| } |
| setMethodsStaticField(proxyClass, methodsToProxy); |
| generatedProxyClasses.put(baseClass, proxyClass); |
| return proxyClass; |
| } |
| |
| // 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 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); |
| } |
| } |
| |
| /** |
| * Sets the proxy's {@link InvocationHandler}. |
| * <p> |
| * If you create a proxy with {@link #build()}, the proxy will already have a handler set, |
| * provided that you configured one with {@link #handler(InvocationHandler)}. |
| * <p> |
| * If you generate a proxy class with {@link #buildProxyClass()}, instances of the proxy class |
| * will not automatically have a handler set, and it is necessary to use this method with each |
| * instance. |
| * |
| * @throws IllegalArgumentException if the object supplied is not a proxy created by this class. |
| */ |
| public static void setInvocationHandler(Object instance, InvocationHandler handler) { |
| try { |
| Field handlerField = instance.getClass().getDeclaredField(FIELD_NAME_HANDLER); |
| handlerField.setAccessible(true); |
| handlerField.set(instance, handler); |
| } 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); |
| } |
| } |
| |
| // TODO: test coverage for isProxyClass |
| |
| /** |
| * Returns true if {@code c} is a proxy class created by this builder. |
| */ |
| public static boolean isProxyClass(Class<?> c) { |
| // TODO: use a marker interface instead? |
| try { |
| c.getDeclaredField(FIELD_NAME_HANDLER); |
| return true; |
| } catch (NoSuchFieldException e) { |
| return false; |
| } |
| } |
| |
| private static <T, G extends T> void generateCodeForAllMethods(DexMaker dexMaker, |
| TypeId<G> generatedType, Method[] methodsToProxy, TypeId<T> superclassType) { |
| TypeId<InvocationHandler> handlerType = TypeId.get(InvocationHandler.class); |
| TypeId<Method[]> methodArrayType = TypeId.get(Method[].class); |
| FieldId<G, InvocationHandler> handlerField = |
| generatedType.getField(handlerType, FIELD_NAME_HANDLER); |
| FieldId<G, Method[]> allMethods = |
| generatedType.getField(methodArrayType, FIELD_NAME_METHODS); |
| TypeId<Method> methodType = TypeId.get(Method.class); |
| TypeId<Object[]> objectArrayType = TypeId.get(Object[].class); |
| MethodId<InvocationHandler, Object> methodInvoke = handlerType.getMethod(TypeId.OBJECT, |
| "invoke", TypeId.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(); |
| TypeId<?>[] argTypes = new TypeId<?>[argClasses.length]; |
| for (int i = 0; i < argTypes.length; ++i) { |
| argTypes[i] = TypeId.get(argClasses[i]); |
| } |
| Class<?> returnType = method.getReturnType(); |
| TypeId<?> resultType = TypeId.get(returnType); |
| MethodId<T, ?> superMethod = superclassType.getMethod(resultType, name, argTypes); |
| MethodId<?, ?> methodId = generatedType.getMethod(resultType, name, argTypes); |
| Code code = dexMaker.declare(methodId, PUBLIC); |
| Local<G> localThis = code.getThis(generatedType); |
| Local<InvocationHandler> localHandler = code.newLocal(handlerType); |
| Local<Object> invokeResult = code.newLocal(TypeId.OBJECT); |
| Local<Integer> intValue = code.newLocal(TypeId.INT); |
| Local<Object[]> args = code.newLocal(objectArrayType); |
| Local<Integer> argsLength = code.newLocal(TypeId.INT); |
| Local<Object> temp = code.newLocal(TypeId.OBJECT); |
| Local<?> resultHolder = code.newLocal(resultType); |
| Local<Method[]> methodArray = code.newLocal(methodArrayType); |
| Local<Method> thisMethod = code.newLocal(methodType); |
| Local<Integer> methodIndex = code.newLocal(TypeId.INT); |
| Class<?> aBoxedClass = PRIMITIVE_TO_BOXED.get(returnType); |
| Local<?> aBoxedResult = null; |
| if (aBoxedClass != null) { |
| aBoxedResult = code.newLocal(TypeId.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(thisMethod, methodArray, methodIndex); |
| code.loadConstant(argsLength, argTypes.length); |
| code.newArray(args, argsLength); |
| code.iget(handlerField, localHandler, localThis); |
| |
| // if (proxy == null) |
| code.loadConstant(nullHandler, null); |
| Label handlerNullCase = new Label(); |
| code.compare(Comparison.EQ, handlerNullCase, nullHandler, localHandler); |
| |
| // 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(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 String super$doSomething$java_lang_String(Bar param0, int param1) { |
| * int result = super.doSomething(param0, param1); |
| * return result; |
| * } |
| */ |
| // TODO: don't include a super_ method if the target is abstract! |
| MethodId<G, ?> callsSuperMethod = generatedType.getMethod( |
| resultType, superMethodName(method), argTypes); |
| Code superCode = dexMaker.declare(callsSuperMethod, 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); |
| } |
| } |
| } |
| |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| private static void invokeSuper(MethodId superMethod, Code superCode, |
| Local superThis, Local[] superArgs, Local superResult) { |
| superCode.invokeSuper(superMethod, superResult, superThis, superArgs); |
| } |
| |
| private static Local<?> boxIfRequired(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 Throwable { |
| try { |
| return proxy.getClass() |
| .getMethod(superMethodName(method), method.getParameterTypes()) |
| .invoke(proxy, args); |
| } catch (InvocationTargetException e) { |
| throw e.getCause(); |
| } |
| } |
| |
| /** |
| * The super method must include the return type, otherwise its ambiguous |
| * for methods with covariant return types. |
| */ |
| private static String superMethodName(Method method) { |
| String returnType = method.getReturnType().getName(); |
| return "super$" + method.getName() + "$" |
| + returnType.replace('.', '_').replace('[', '_').replace(';', '_'); |
| } |
| |
| private static void check(boolean condition, String message) { |
| if (!condition) { |
| throw new IllegalArgumentException(message); |
| } |
| } |
| |
| private static <T, G extends T> void generateConstructorsAndFields(DexMaker dexMaker, |
| TypeId<G> generatedType, TypeId<T> superType, Class<T> superClass) { |
| TypeId<InvocationHandler> handlerType = TypeId.get(InvocationHandler.class); |
| TypeId<Method[]> methodArrayType = TypeId.get(Method[].class); |
| FieldId<G, InvocationHandler> handlerField = generatedType.getField( |
| handlerType, FIELD_NAME_HANDLER); |
| dexMaker.declare(handlerField, PRIVATE, null); |
| FieldId<G, Method[]> allMethods = generatedType.getField( |
| methodArrayType, FIELD_NAME_METHODS); |
| dexMaker.declare(allMethods, PRIVATE | STATIC, null); |
| for (Constructor<T> constructor : getConstructorsToOverwrite(superClass)) { |
| if (constructor.getModifiers() == Modifier.FINAL) { |
| continue; |
| } |
| TypeId<?>[] types = classArrayToTypeArray(constructor.getParameterTypes()); |
| MethodId<?, ?> method = generatedType.getConstructor(types); |
| Code constructorCode = dexMaker.declare(method, PUBLIC); |
| 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(); |
| } |
| |
| private TypeId<?>[] getInterfacesAsTypeIds() { |
| TypeId<?>[] result = new TypeId<?>[interfaces.size()]; |
| int i = 0; |
| for (Class<?> implemented : interfaces) { |
| result[i++] = TypeId.get(implemented); |
| } |
| return result; |
| } |
| |
| /** |
| * Gets all {@link Method} objects we can proxy in the hierarchy of the |
| * supplied class. |
| */ |
| private Method[] getMethodsToProxyRecursive() { |
| Set<MethodSetEntry> methodsToProxy = new HashSet<>(); |
| Set<MethodSetEntry> seenFinalMethods = new HashSet<>(); |
| // Traverse the class hierarchy to ensure that all concrete methods (which could be marked |
| // as final) are visited before any abstract methods from interfaces. |
| for (Class<?> c = baseClass; c != null; c = c.getSuperclass()) { |
| getMethodsToProxy(methodsToProxy, seenFinalMethods, c); |
| } |
| // Now traverse the interface hierarchy, starting with the ones implemented by the class, |
| // followed by any extra interfaces. |
| for (Class<?> c = baseClass; c != null; c = c.getSuperclass()) { |
| for (Class<?> i : c.getInterfaces()) { |
| getMethodsToProxy(methodsToProxy, seenFinalMethods, i); |
| } |
| } |
| for (Class<?> c : interfaces) { |
| getMethodsToProxy(methodsToProxy, seenFinalMethods, c); |
| } |
| |
| Method[] results = new Method[methodsToProxy.size()]; |
| int i = 0; |
| for (MethodSetEntry entry : methodsToProxy) { |
| results[i++] = entry.originalMethod; |
| } |
| |
| return results; |
| } |
| |
| private void getMethodsToProxy(Set<MethodSetEntry> sink, Set<MethodSetEntry> seenFinalMethods, |
| Class<?> c) { |
| for (Method method : c.getDeclaredMethods()) { |
| if ((method.getModifiers() & Modifier.FINAL) != 0) { |
| // Skip final methods, we can't override them. We |
| // also need to remember them, in case the same |
| // method exists in a parent class. |
| MethodSetEntry entry = new MethodSetEntry(method); |
| seenFinalMethods.add(entry); |
| // We may have seen this method already, from an interface |
| // implemented by a child class. We need to remove it here. |
| sink.remove(entry); |
| continue; |
| } |
| if ((method.getModifiers() & STATIC) != 0) { |
| // Skip static methods, overriding them has no effect. |
| continue; |
| } |
| if (!Modifier.isPublic(method.getModifiers()) |
| && !Modifier.isProtected(method.getModifiers()) |
| && (!sharedClassLoader || Modifier.isPrivate(method.getModifiers()))) { |
| // Skip private methods, since they are invoked through direct |
| // invocation (as opposed to virtual). Therefore, it would not |
| // be possible to intercept any private method defined inside |
| // the proxy class except through reflection. |
| |
| // Skip package-private methods as well (for non-shared class |
| // loaders). The proxy class does |
| // not actually inherit package-private methods from the parent |
| // class because it is not a member of the parent's package. |
| // This is even true if the two classes have the same package |
| // name, as they use different class loaders. |
| continue; |
| } |
| if (method.getName().equals("finalize") && method.getParameterTypes().length == 0) { |
| // Skip finalize method, it's likely important that it execute as normal. |
| continue; |
| } |
| MethodSetEntry entry = new MethodSetEntry(method); |
| if (seenFinalMethods.contains(entry)) { |
| // This method is final in a child class. |
| // We can't override it. |
| continue; |
| } |
| sink.add(entry); |
| } |
| |
| // Only visit the interfaces of this class if it is itself an interface. That prevents |
| // visiting interfaces of a class before its super classes. |
| if (c.isInterface()) { |
| for (Class<?> i : c.getInterfaces()) { |
| getMethodsToProxy(sink, seenFinalMethods, i); |
| } |
| } |
| } |
| |
| private static <T> String getMethodNameForProxyOf(Class<T> clazz) { |
| return clazz.getName().replace(".", "/") + "_Proxy"; |
| } |
| |
| private static TypeId<?>[] classArrayToTypeArray(Class<?>[] input) { |
| TypeId<?>[] result = new TypeId[input.length]; |
| for (int i = 0; i < input.length; ++i) { |
| result[i] = TypeId.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.cast(aBoxedResult, localForResultOfInvoke); |
| MethodId unboxingMethodFor = getUnboxMethodForPrimitive(methodReturnType); |
| code.invokeVirtual(unboxingMethodFor, localOfMethodReturnType, aBoxedResult); |
| code.returnValue(localOfMethodReturnType); |
| } else if (void.class.equals(methodReturnType)) { |
| code.returnVoid(); |
| } else { |
| code.cast(localOfMethodReturnType, localForResultOfInvoke); |
| code.returnValue(localOfMethodReturnType); |
| } |
| } |
| |
| private static <T> Set<T> asSet(T... array) { |
| return new CopyOnWriteArraySet<>(Arrays.asList(array)); |
| } |
| |
| 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<>(); |
| 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<TypeId<?>, MethodId<?, ?>> PRIMITIVE_TYPE_TO_UNBOX_METHOD; |
| static { |
| PRIMITIVE_TYPE_TO_UNBOX_METHOD = new HashMap<>(); |
| for (Map.Entry<Class<?>, Class<?>> entry : PRIMITIVE_TO_BOXED.entrySet()) { |
| TypeId<?> primitiveType = TypeId.get(entry.getKey()); |
| TypeId<?> boxedType = TypeId.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<>(); |
| map.put(boolean.class, TypeId.get(Boolean.class).getMethod(TypeId.BOOLEAN, "booleanValue")); |
| map.put(int.class, TypeId.get(Integer.class).getMethod(TypeId.INT, "intValue")); |
| map.put(byte.class, TypeId.get(Byte.class).getMethod(TypeId.BYTE, "byteValue")); |
| map.put(long.class, TypeId.get(Long.class).getMethod(TypeId.LONG, "longValue")); |
| map.put(short.class, TypeId.get(Short.class).getMethod(TypeId.SHORT, "shortValue")); |
| map.put(float.class, TypeId.get(Float.class).getMethod(TypeId.FLOAT, "floatValue")); |
| map.put(double.class, TypeId.get(Double.class).getMethod(TypeId.DOUBLE, "doubleValue")); |
| map.put(char.class, TypeId.get(Character.class).getMethod(TypeId.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. |
| */ |
| public static class MethodSetEntry { |
| public final String name; |
| public final Class<?>[] paramTypes; |
| public final Class<?> returnType; |
| public 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; |
| } |
| } |
| } |