View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.proxy2.asm;
18  
19  import java.io.Serializable;
20  import java.lang.reflect.InvocationTargetException;
21  import java.lang.reflect.Method;
22  import java.lang.reflect.UndeclaredThrowableException;
23  import java.util.concurrent.atomic.AtomicInteger;
24  
25  import org.apache.commons.lang3.ArrayUtils;
26  import org.apache.commons.lang3.ObjectUtils;
27  import org.apache.commons.proxy2.Interceptor;
28  import org.apache.commons.proxy2.Invocation;
29  import org.apache.commons.proxy2.Invoker;
30  import org.apache.commons.proxy2.ObjectProvider;
31  import org.apache.commons.proxy2.ProxyUtils;
32  import org.apache.commons.proxy2.exception.ProxyFactoryException;
33  import org.apache.commons.proxy2.impl.AbstractProxyClassGenerator;
34  import org.apache.commons.proxy2.impl.AbstractSubclassingProxyFactory;
35  import org.apache.commons.proxy2.impl.ProxyClassCache;
36  import org.objectweb.asm.ClassWriter;
37  import org.objectweb.asm.Label;
38  import org.objectweb.asm.Opcodes;
39  import org.objectweb.asm.Type;
40  import org.objectweb.asm.commons.GeneratorAdapter;
41  
42  public class ASMProxyFactory extends AbstractSubclassingProxyFactory
43  {
44      private static final ProxyClassCache PROXY_CLASS_CACHE = new ProxyClassCache(new ProxyGenerator());
45  
46      @Override
47      public <T> T createDelegatorProxy(final ClassLoader classLoader, final ObjectProvider<?> delegateProvider,
48              final Class<?>... proxyClasses)
49      {
50          return createProxy(classLoader, new DelegatorInvoker(delegateProvider), proxyClasses);
51      }
52  
53      @Override
54      public <T> T createInterceptorProxy(final ClassLoader classLoader, final Object target,
55              final Interceptor interceptor, final Class<?>... proxyClasses)
56      {
57          return createProxy(classLoader, new InterceptorInvoker(target, interceptor), proxyClasses);
58      }
59  
60      @Override
61      public <T> T createInvokerProxy(final ClassLoader classLoader, final Invoker invoker,
62              final Class<?>... proxyClasses)
63      {
64          return createProxy(classLoader, new InvokerInvoker(invoker), proxyClasses);
65      }
66  
67      private <T> T createProxy(final ClassLoader classLoader, final AbstractInvoker invoker,
68              final Class<?>... proxyClasses)
69      {
70          final Class<?> proxyClass = PROXY_CLASS_CACHE.getProxyClass(classLoader, proxyClasses);
71          try
72          {
73              @SuppressWarnings("unchecked") // type inference
74              final T result = (T) proxyClass.getConstructor(Invoker.class).newInstance(invoker);
75              return result;
76          }
77          catch (Exception e)
78          {
79              throw e instanceof RuntimeException ? ((RuntimeException) e) : new RuntimeException(e);
80          }
81      }
82  
83      private static class ProxyGenerator extends AbstractProxyClassGenerator implements Opcodes
84      {
85          private static final AtomicInteger CLASS_NUMBER = new AtomicInteger(0);
86          private static final String CLASSNAME_PREFIX = "CommonsProxyASM_";
87          private static final String HANDLER_NAME = "__handler";
88          private static final Type INVOKER_TYPE = Type.getType(Invoker.class);
89  
90          @Override
91          public Class<?> generateProxyClass(final ClassLoader classLoader, final Class<?>... proxyClasses)
92          {
93              final Class<?> superclass = getSuperclass(proxyClasses);
94              final String proxyName = CLASSNAME_PREFIX + CLASS_NUMBER.incrementAndGet();
95              final Method[] implementationMethods = getImplementationMethods(proxyClasses);
96              final Class<?>[] interfaces = toInterfaces(proxyClasses);
97              final String classFileName = proxyName.replace('.', '/');
98  
99              try
100             {
101                 final byte[] proxyBytes = generateProxy(superclass, classFileName, implementationMethods, interfaces);
102                 return loadClass(classLoader, proxyName, proxyBytes);
103             }
104             catch (final Exception e)
105             {
106                 throw new ProxyFactoryException(e);
107             }
108         }
109 
110         private static byte[] generateProxy(final Class<?> classToProxy, final String proxyName,
111                 final Method[] methods, final Class<?>... interfaces) throws ProxyFactoryException
112         {
113             final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
114 
115             final Type proxyType = Type.getObjectType(proxyName);
116 
117             // push class signature
118             final String[] interfaceNames = new String[interfaces.length];
119             for (int i = 0; i < interfaces.length; i++)
120             {
121                 interfaceNames[i] = Type.getType(interfaces[i]).getInternalName();
122             }
123 
124             final Type superType = Type.getType(classToProxy);
125             cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, proxyType.getInternalName(), null, superType.getInternalName(),
126                     interfaceNames);
127 
128             // create Invoker field
129             cw.visitField(ACC_FINAL + ACC_PRIVATE, HANDLER_NAME, INVOKER_TYPE.getDescriptor(), null, null).visitEnd();
130 
131             init(cw, proxyType, superType);
132 
133             for (final Method method : methods)
134             {
135                 processMethod(cw, method, proxyType, HANDLER_NAME);
136             }
137 
138             return cw.toByteArray();
139         }
140 
141         private static void init(final ClassWriter cw, final Type proxyType, Type superType)
142         {
143             final GeneratorAdapter mg = new GeneratorAdapter(ACC_PUBLIC, new org.objectweb.asm.commons.Method("<init>",
144                     Type.VOID_TYPE, new Type[] { INVOKER_TYPE }), null, null, cw);
145             // invoke super constructor:
146             mg.loadThis();
147             mg.invokeConstructor(superType, org.objectweb.asm.commons.Method.getMethod("void <init> ()"));
148 
149             // assign handler:
150             mg.loadThis();
151             mg.loadArg(0);
152             mg.putField(proxyType, HANDLER_NAME, INVOKER_TYPE);
153             mg.returnValue();
154             mg.endMethod();
155         }
156 
157         private static void processMethod(final ClassWriter cw, final Method method, final Type proxyType,
158                 final String handlerName) throws ProxyFactoryException
159         {
160             final Type sig = Type.getType(method);
161             final Type[] exceptionTypes = getTypes(method.getExceptionTypes());
162 
163             // push the method definition
164             final int access = (ACC_PUBLIC | ACC_PROTECTED) & method.getModifiers();
165             final org.objectweb.asm.commons.Method m = org.objectweb.asm.commons.Method.getMethod(method);
166             final GeneratorAdapter mg = new GeneratorAdapter(access, m, null, getTypes(method.getExceptionTypes()), cw);
167 
168             final Label tryBlock = exceptionTypes.length > 0 ? mg.mark() : null;
169 
170             mg.push(Type.getType(method.getDeclaringClass()));
171 
172             // the following code generates the bytecode for this line of Java:
173             // Method method = <proxy>.class.getMethod("add", new Class[] {
174             // <array of function argument classes> });
175 
176             // get the method name to invoke, and push to stack
177 
178             mg.push(method.getName());
179 
180             // create the Class[]
181             mg.push(sig.getArgumentTypes().length);
182             final Type classType = Type.getType(Class.class);
183             mg.newArray(classType);
184 
185             // push parameters into array
186             for (int i = 0; i < sig.getArgumentTypes().length; i++)
187             {
188                 // keep copy of array on stack
189                 mg.dup();
190 
191                 // push index onto stack
192                 mg.push(i);
193                 mg.push(sig.getArgumentTypes()[i]);
194                 mg.arrayStore(classType);
195             }
196 
197             // invoke getMethod() with the method name and the array of types
198             mg.invokeVirtual(classType, org.objectweb.asm.commons.Method
199                     .getMethod("java.lang.reflect.Method getDeclaredMethod(String, Class[])"));
200             // store the returned method for later
201 
202             // the following code generates bytecode equivalent to:
203             // return ((<returntype>) invoker.invoke(this, method, new Object[]
204             // { <function arguments }))[.<primitive>Value()];
205 
206             mg.loadThis();
207 
208             mg.getField(proxyType, handlerName, INVOKER_TYPE);
209             // put below method:
210             mg.swap();
211 
212             // we want to pass "this" in as the first parameter
213             mg.loadThis();
214             // put below method:
215             mg.swap();
216 
217             // need to construct the array of objects passed in
218 
219             // create the Object[]
220             mg.push(sig.getArgumentTypes().length);
221             final Type objectType = Type.getType(Object.class);
222             mg.newArray(objectType);
223 
224             // push parameters into array
225             for (int i = 0; i < sig.getArgumentTypes().length; i++)
226             {
227                 // keep copy of array on stack
228                 mg.dup();
229 
230                 // push index onto stack
231                 mg.push(i);
232 
233                 mg.loadArg(i);
234                 mg.valueOf(sig.getArgumentTypes()[i]);
235                 mg.arrayStore(objectType);
236             }
237 
238             // invoke the invoker
239             mg.invokeInterface(INVOKER_TYPE, org.objectweb.asm.commons.Method
240                     .getMethod("Object invoke(Object, java.lang.reflect.Method, Object[])"));
241 
242             // cast the result
243             mg.unbox(sig.getReturnType());
244 
245             // push return
246             mg.returnValue();
247 
248             // catch InvocationTargetException
249             if (exceptionTypes.length > 0)
250             {
251                 final Type caughtExceptionType = Type.getType(InvocationTargetException.class);
252                 mg.catchException(tryBlock, mg.mark(), caughtExceptionType);
253 
254                 final Label throwCause = new Label();
255 
256                 mg.invokeVirtual(caughtExceptionType,
257                         org.objectweb.asm.commons.Method.getMethod("Throwable getCause()"));
258 
259                 for (int i = 0; i < exceptionTypes.length; i++)
260                 {
261                     mg.dup();
262                     mg.push(exceptionTypes[i]);
263                     mg.swap();
264                     mg.invokeVirtual(classType,
265                             org.objectweb.asm.commons.Method.getMethod("boolean isInstance(Object)"));
266                     // if true, throw cause:
267                     mg.ifZCmp(GeneratorAdapter.NE, throwCause);
268                 }
269                 // no exception types matched; throw
270                 // UndeclaredThrowableException:
271                 final int cause = mg.newLocal(Type.getType(Exception.class));
272                 mg.storeLocal(cause);
273                 final Type undeclaredType = Type.getType(UndeclaredThrowableException.class);
274                 mg.newInstance(undeclaredType);
275                 mg.dup();
276                 mg.loadLocal(cause);
277                 mg.invokeConstructor(undeclaredType, new org.objectweb.asm.commons.Method("<init>", Type.VOID_TYPE,
278                         new Type[] { Type.getType(Throwable.class) }));
279                 mg.throwException();
280 
281                 mg.mark(throwCause);
282                 mg.throwException();
283             }
284 
285             // finish this method
286             mg.endMethod();
287         }
288 
289         private static Type[] getTypes(Class<?>... src)
290         {
291             final Type[] result = new Type[src.length];
292             for (int i = 0; i < result.length; i++)
293             {
294                 result[i] = Type.getType(src[i]);
295             }
296             return result;
297         }
298 
299         /**
300          * Adapted from http://asm.ow2.org/doc/faq.html#Q5
301          * 
302          * @param b
303          * @return Class<?>
304          */
305         private static Class<?> loadClass(final ClassLoader loader, String className, byte[] b)
306         {
307             // override classDefine (as it is protected) and define the class.
308             try
309             {
310                 final Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class,
311                         int.class, int.class);
312 
313                 // protected method invocation
314                 final boolean accessible = method.isAccessible();
315                 if (!accessible)
316                 {
317                     method.setAccessible(true);
318                 }
319                 try
320                 {
321                     return (Class<?>) method
322                             .invoke(loader, className, b, Integer.valueOf(0), Integer.valueOf(b.length));
323                 }
324                 finally
325                 {
326                     if (!accessible)
327                     {
328                         method.setAccessible(false);
329                     }
330                 }
331             }
332             catch (Exception e)
333             {
334                 throw e instanceof RuntimeException ? ((RuntimeException) e) : new RuntimeException(e);
335             }
336         }
337     }
338 
339     @SuppressWarnings("serial")
340     private static class DelegatorInvoker extends AbstractInvoker
341     {
342         private final ObjectProvider<?> delegateProvider;
343 
344         protected DelegatorInvoker(ObjectProvider<?> delegateProvider)
345         {
346             this.delegateProvider = delegateProvider;
347         }
348 
349         @Override
350         public Object invokeImpl(Object proxy, Method method, Object[] args) throws Throwable
351         {
352             try
353             {
354                 return method.invoke(delegateProvider.getObject(), args);
355             }
356             catch (InvocationTargetException e)
357             {
358                 throw e.getTargetException();
359             }
360         }
361     }
362 
363     @SuppressWarnings("serial")
364     private static class InterceptorInvoker extends AbstractInvoker
365     {
366         private final Object target;
367         private final Interceptor methodInterceptor;
368 
369         public InterceptorInvoker(Object target, Interceptor methodInterceptor)
370         {
371             this.target = target;
372             this.methodInterceptor = methodInterceptor;
373         }
374 
375         @Override
376         public Object invokeImpl(Object proxy, Method method, Object[] args) throws Throwable
377         {
378             final ReflectionInvocation invocation = new ReflectionInvocation(target, proxy, method, args);
379             return methodInterceptor.intercept(invocation);
380         }
381     }
382 
383     @SuppressWarnings("serial")
384     private abstract static class AbstractInvoker implements Invoker, Serializable
385     {
386         @Override
387         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
388         {
389             if (isHashCode(method))
390             {
391                 return Integer.valueOf(System.identityHashCode(proxy));
392             }
393             if (isEqualsMethod(method))
394             {
395                 return Boolean.valueOf(proxy == args[0]);
396             }
397             return invokeImpl(proxy, method, args);
398         }
399 
400         protected abstract Object invokeImpl(Object proxy, Method method, Object[] args) throws Throwable;
401     }
402 
403     @SuppressWarnings("serial")
404     private static class InvokerInvoker extends AbstractInvoker
405     {
406         private final Invoker invoker;
407 
408         public InvokerInvoker(Invoker invoker)
409         {
410             this.invoker = invoker;
411         }
412 
413         @Override
414         public Object invokeImpl(Object proxy, Method method, Object[] args) throws Throwable
415         {
416             return invoker.invoke(proxy, method, args);
417         }
418     }
419 
420     protected static boolean isHashCode(Method method)
421     {
422         return "hashCode".equals(method.getName()) && Integer.TYPE.equals(method.getReturnType())
423                 && method.getParameterTypes().length == 0;
424     }
425 
426     protected static boolean isEqualsMethod(Method method)
427     {
428         return "equals".equals(method.getName()) && Boolean.TYPE.equals(method.getReturnType())
429                 && method.getParameterTypes().length == 1 && Object.class.equals(method.getParameterTypes()[0]);
430     }
431 
432     private static class ReflectionInvocation implements Invocation
433     {
434         private final Method method;
435         private final Object[] arguments;
436         private final Object proxy;
437         private final Object target;
438 
439         public ReflectionInvocation(final Object target, final Object proxy, final Method method,
440                 final Object[] arguments)
441         {
442             this.method = method;
443             this.arguments = ObjectUtils.defaultIfNull(ArrayUtils.clone(arguments), ProxyUtils.EMPTY_ARGUMENTS);
444             this.proxy = proxy;
445             this.target = target;
446         }
447 
448         @Override
449         public Object[] getArguments()
450         {
451             return arguments;
452         }
453 
454         @Override
455         public Method getMethod()
456         {
457             return method;
458         }
459 
460         @Override
461         public Object getProxy()
462         {
463             return proxy;
464         }
465 
466         @Override
467         public Object proceed() throws Throwable
468         {
469             try
470             {
471                 return method.invoke(target, arguments);
472             }
473             catch (InvocationTargetException e)
474             {
475                 throw e.getTargetException();
476             }
477         }
478     }
479 }