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