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  
18  package org.apache.commons.proxy2.javassist;
19  
20  import java.lang.ref.WeakReference;
21  import java.lang.reflect.Method;
22  import java.util.HashMap;
23  import java.util.Map;
24  import java.util.WeakHashMap;
25  
26  import javassist.CannotCompileException;
27  import javassist.CtClass;
28  import javassist.CtConstructor;
29  import javassist.CtMethod;
30  
31  import org.apache.commons.lang3.ArrayUtils;
32  import org.apache.commons.lang3.ObjectUtils;
33  import org.apache.commons.proxy2.Invocation;
34  import org.apache.commons.proxy2.ProxyUtils;
35  
36  /**
37   * A <a href="http://www.jboss.org/products/javassist">Javassist</a>-based {@link Invocation} implementation. This class
38   * actually serves as the superclass for all <a href="http://www.jboss.org/products/javassist">Javassist</a>-based
39   * method invocations. Subclasses are dynamically created to deal with specific interface methods (they're hard-wired).
40   * 
41   * @since 1.0
42   */
43  public abstract class JavassistInvocation implements Invocation
44  {
45      //******************************************************************************************************************
46      // Fields
47      //******************************************************************************************************************
48  
49      private static WeakHashMap<ClassLoader, Map<String, WeakReference<Class<?>>>> loaderToClassCache
50          = new WeakHashMap<ClassLoader, Map<String, WeakReference<Class<?>>>>();
51  
52      /** The proxy object */
53      private final Object proxy;
54  
55      /** The target object */
56      private final Object target;
57  
58      /** The invoked method */
59      private final Method method;
60  
61      /** The method arguments */
62      private final Object[] arguments;
63  
64      //******************************************************************************************************************
65      // Static Methods
66      //******************************************************************************************************************
67  
68      private static String createCastExpression(Class<?> type, String objectToCast)
69      {
70          if (!type.isPrimitive())
71          {
72              return "( " + ProxyUtils.getJavaClassName(type) + " )" + objectToCast;
73          }
74          else
75          {
76              return "( ( " + ProxyUtils.getWrapperClass(type).getName() + " )" + objectToCast + " )." + type.getName()
77                      + "Value()";
78          }
79      }
80  
81      private static Class<?> createInvocationClass(ClassLoader classLoader, Method interfaceMethod)
82              throws CannotCompileException
83      {
84          final CtClass ctClass = JavassistUtils.createClass(getSimpleName(interfaceMethod.getDeclaringClass()) + "_"
85                  + interfaceMethod.getName() + "_invocation", JavassistInvocation.class);
86          final CtConstructor constructor = new CtConstructor(JavassistUtils.resolve(new Class[] { Object.class,
87                  Object.class, Method.class, Object[].class }), ctClass);
88          constructor.setBody("{\n\tsuper($$);\n}");
89          ctClass.addConstructor(constructor);
90          final CtMethod proceedMethod = new CtMethod(JavassistUtils.resolve(Object.class), "proceed",
91                  JavassistUtils.resolve(new Class[0]), ctClass);
92          final Class<?>[] argumentTypes = interfaceMethod.getParameterTypes();
93          final StringBuilder proceedBody = new StringBuilder("{\n");
94          if (!Void.TYPE.equals(interfaceMethod.getReturnType()))
95          {
96              proceedBody.append("\treturn ");
97              if (interfaceMethod.getReturnType().isPrimitive())
98              {
99                  proceedBody.append("new ");
100                 proceedBody.append(ProxyUtils.getWrapperClass(interfaceMethod.getReturnType()).getName());
101                 proceedBody.append("( ");
102             }
103         }
104         else
105         {
106             proceedBody.append("\t");
107         }
108         proceedBody.append("( (");
109         proceedBody.append(ProxyUtils.getJavaClassName(interfaceMethod.getDeclaringClass()));
110         proceedBody.append(" )getTarget() ).");
111         proceedBody.append(interfaceMethod.getName());
112         proceedBody.append("(");
113         for (int i = 0; i < argumentTypes.length; ++i)
114         {
115             final Class<?> argumentType = argumentTypes[i];
116             proceedBody.append(createCastExpression(argumentType, "getArguments()[" + i + "]"));
117             if (i != argumentTypes.length - 1)
118             {
119                 proceedBody.append(", ");
120             }
121         }
122         if (!Void.TYPE.equals(interfaceMethod.getReturnType()) && interfaceMethod.getReturnType().isPrimitive())
123         {
124             proceedBody.append(") );\n");
125         }
126         else
127         {
128             proceedBody.append(");\n");
129         }
130         if (Void.TYPE.equals(interfaceMethod.getReturnType()))
131         {
132             proceedBody.append("\treturn null;\n");
133         }
134         proceedBody.append("}");
135         final String body = proceedBody.toString();
136         proceedMethod.setBody(body);
137         ctClass.addMethod(proceedMethod);
138 
139         @SuppressWarnings("deprecation")
140         final Class<?> invocationClass = ctClass.toClass(classLoader);
141         return invocationClass;
142     }
143 
144     private static Map<String, WeakReference<Class<?>>> getClassCache(ClassLoader classLoader)
145     {
146         Map<String, WeakReference<Class<?>>> cache = loaderToClassCache.get(classLoader);
147         if (cache == null)
148         {
149             cache = new HashMap<String, WeakReference<Class<?>>>();
150             loaderToClassCache.put(classLoader, cache);
151         }
152         return cache;
153     }
154 
155     /**
156      * Returns a method invocation class specifically coded to invoke the supplied interface method.
157      * 
158      * @param classLoader
159      *            the classloader to use
160      * @param interfaceMethod
161      *            the interface method
162      * @return a method invocation class specifically coded to invoke the supplied interface method
163      * @throws CannotCompileException
164      *             if a compilation error occurs
165      */
166     static synchronized Class<?> getMethodInvocationClass(ClassLoader classLoader, Method interfaceMethod)
167             throws CannotCompileException
168     {
169         final Map<String, WeakReference<Class<?>>> classCache = getClassCache(classLoader);
170         final String key = toClassCacheKey(interfaceMethod);
171         final WeakReference<Class<?>> invocationClassRef = classCache.get(key);
172         Class<?> invocationClass;
173         if (invocationClassRef == null)
174         {
175             invocationClass = createInvocationClass(classLoader, interfaceMethod);
176             classCache.put(key, new WeakReference<Class<?>>(invocationClass));
177         }
178         else
179         {
180             synchronized (invocationClassRef)
181             {
182                 invocationClass = invocationClassRef.get();
183                 if (invocationClass == null)
184                 {
185                     invocationClass = createInvocationClass(classLoader, interfaceMethod);
186                     classCache.put(key, new WeakReference<Class<?>>(invocationClass));
187                 }
188             }
189         }
190         return invocationClass;
191     }
192 
193     private static String getSimpleName(Class<?> c)
194     {
195         final String name = c.getName();
196         final int ndx = name.lastIndexOf('.');
197         return ndx == -1 ? name : name.substring(ndx + 1);
198     }
199 
200     private static String toClassCacheKey(Method method)
201     {
202         return String.valueOf(method);
203     }
204 
205     //******************************************************************************************************************
206     // Constructors
207     //******************************************************************************************************************
208 
209     protected JavassistInvocation(Object proxy, Object target, Method method, Object[] arguments)
210     {
211         this.proxy = proxy;
212         this.target = target;
213         this.method = method;
214         this.arguments = ObjectUtils.defaultIfNull(ArrayUtils.clone(arguments), ProxyUtils.EMPTY_ARGUMENTS);
215     }
216 
217     //******************************************************************************************************************
218     // Invocation Implementation
219     //******************************************************************************************************************
220     protected final Object getTarget()
221     {
222         return target;
223     }
224 
225     @Override
226     public Object[] getArguments()
227     {
228         return arguments;
229     }
230 
231     @Override
232     public Method getMethod()
233     {
234         return method;
235     }
236 
237     @Override
238     public Object getProxy()
239     {
240         return proxy;
241     }
242 }