View Javadoc

1   /**********************************************************************
2    * DynProxyGenerator.java
3    * created on 21.07.2006 by netseeker
4    * $Id: DynProxyGenerator.java,v 1.15 2006/11/12 20:34:43 netseeker Exp $
5    *
6    *
7    * ====================================================================
8    *
9    *  Copyright 2006 netseeker aka Michael Manske
10   *
11   *  Licensed under the Apache License, Version 2.0 (the "License");
12   *  you may not use this file except in compliance with the License.
13   *  You may obtain a copy of the License at
14   *
15   *      http://www.apache.org/licenses/LICENSE-2.0
16   *
17   *  Unless required by applicable law or agreed to in writing, software
18   *  distributed under the License is distributed on an "AS IS" BASIS,
19   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   *  See the License for the specific language governing permissions and
21   *  limitations under the License.
22   * ====================================================================
23   *
24   * This file is part of the EJOE framework.
25   * For more information on the author, please see
26   * <http://www.manskes.de/>.
27   *
28   *********************************************************************/
29  
30  package de.netseeker.ejoe.handler.gen;
31  
32  import java.lang.reflect.Constructor;
33  import java.lang.reflect.Member;
34  import java.lang.reflect.Method;
35  import java.util.Random;
36  import java.util.logging.Level;
37  import java.util.logging.Logger;
38  
39  import javassist.CannotCompileException;
40  import javassist.ClassClassPath;
41  import javassist.ClassPool;
42  import javassist.CtClass;
43  import javassist.CtConstructor;
44  import javassist.CtField;
45  import javassist.CtMethod;
46  import javassist.CtNewMethod;
47  import de.netseeker.ejoe.EJConstants;
48  import de.netseeker.ejoe.cache.LRUMap;
49  import de.netseeker.ejoe.handler.BaseRemotingHandler;
50  import de.netseeker.ejoe.util.ContentStringBuilder;
51  
52  /***
53   * Javassist based default proxy generator for accelerated access on reflected methods and constructors. This generator
54   * creates and caches one class for each class-method-pair respectively class-constrcutor-pair.
55   * 
56   * @author netseeker
57   * @see http://www.jboss.org/products/javassist
58   * @since 0.3.9.1
59   */
60  public class DynProxyGenerator implements IProxyGenerator
61  {
62      private static final Logger    logger       = Logger.getLogger( DynProxyGenerator.class.getName() );
63  
64      private static final CtClass[] NO_ARGS      = {};
65  
66      /***
67       * LRU cache for the generated proxy classes
68       */
69      private static LRUMap          cache        = new LRUMap( 5000 );
70  
71      /***
72       * cache for Wrapper#[prmitive]Value method names
73       */
74      private static LRUMap          valueOfCache = new LRUMap( 100 );
75  
76      private ClassPool              _pool        = ClassPool.getDefault();
77  
78      private static Random          random       = new Random();
79  
80      private static Object          mutex        = new Object();
81  
82      /***
83       * Creates a new instance of DynProxyGenerator This constructor will throw an IllegalStateException if the current
84       * java version is lesser than 1.5.0
85       */
86      public DynProxyGenerator()
87      {
88          if ( EJConstants.JAVA_VERSION_INT < 150 )
89          {
90              throw new IllegalStateException( "DynProxyGenerator supports Java >= 1.5.0 only!" );
91          }
92  
93          _pool.insertClassPath( new ClassClassPath( this.getClass() ) );
94      }
95  
96      /***
97       * Either returns a copy of an already cached instance of a proxy or creates a new one, instaniate it, add it to
98       * cache and return a copy.
99       * 
100      * @param tClasName the targetted class
101      * @param mName the targetted method/constructor name
102      * @param params array of arguments for the targetted method/constructor
103      * @return a copy of an instance of IDynAccess
104      * @throws Exception
105      */
106     public IDynAccess getDynAccessProxy( Class tClass, String mName, Object[] params ) throws Exception
107     {
108 
109         String tClasName = tClass.getName();
110         int index = tClasName.lastIndexOf( '.' );
111         String pName = null;
112         if ( index != -1 )
113         {
114             pName = tClasName.substring( index + 1 );
115         }
116         else
117         {
118             pName = tClasName;
119         }
120 
121         pName = pName.replaceAll( "//[", "" );
122         pName = pName.replaceAll( "//]", "" );
123         Class[] types = getParamTypes( params );
124         String cacheKey = pName + '#' + mName + '(' + ContentStringBuilder.toString( types ) + ')';
125 
126         // first try to locate the proxy in the cache
127         IDynAccess dynaProxy = (IDynAccess) cache.get( cacheKey );
128 
129         // if not found then create a new one
130         if ( dynaProxy == null )
131         {
132             // because we are just using class+method names as class names for
133             // our proxy classes we have to add a unique suffix to avoid name-collisions
134             int uid = -1;
135             synchronized ( mutex )
136             {
137                 uid = random.nextInt( Integer.MAX_VALUE );
138             }
139             String proxyName = pName + "DynAccessProxy" + uid;
140 
141             logger.log( Level.FINEST, "generated name of proxy class: " + proxyName );
142 
143             // pre-check if the requested method is a constructor
144             boolean isConstructor = tClass.getSimpleName().equals( mName );
145             Class[] realTypes = new Class[types.length];
146             Member targetMtd = null;
147 
148             /*
149              * try to find a method/constructor in the target class which: 1. has the same name 2. takes the given
150              * paramter types or their primitive or wrapper counterparts
151              */
152             if ( !isConstructor )
153                 targetMtd = getCompatibleMethod( tClass, mName, types, realTypes );
154             else
155                 targetMtd = getCompatibleConstructor( tClass, mName, types, realTypes );
156 
157             // ooops, nothing found
158             if ( targetMtd == null ) throw new NoSuchMethodException( cacheKey );
159 
160             // create a clean, empty proxy class
161             CtClass clas = _pool.makeClass( proxyName );
162             CtClass target = _pool.get( tClasName );
163             CtClass ctIDynProxy = _pool.get( IDynAccess.class.getName() );
164 
165             // add target object field to class
166             CtField field = new CtField( target, "m_dynTarget", clas );
167             clas.addField( field );
168 
169             // add public default constructor method to class
170             CtConstructor cons = new CtConstructor( NO_ARGS, clas );
171             cons.setBody( "m_dynTarget = new " + tClasName + "();" );
172             clas.addConstructor( cons );
173 
174             // add public <code>createCopy</code> method
175             CtMethod ctMeth = new CtMethod( ctIDynProxy, "createCopy", NO_ARGS, clas );
176             ctMeth.setBody( "return new " + proxyName + "();" );
177             clas.addMethod( ctMeth );
178 
179             // add public <code>setDynTarget</code> method
180             ctMeth = new CtMethod( CtClass.voidType, "setDynTarget", new CtClass[] { _pool.get( "java.lang.Object" ) },
181                                    clas );
182             ctMeth.setBody( "m_dynTarget = (" + tClasName + ")$1;" );
183             clas.addMethod( ctMeth );
184 
185             // add public <code>invokeDynMethod</code> method
186             ctMeth = buildMethodAccessor( clas, targetMtd, types, realTypes, isConstructor );
187             clas.addMethod( ctMeth );
188 
189             // apply our IDynAccess interface to the new proxy class
190             clas.addInterface( _pool.get( IDynAccess.class.getName() ) );
191 
192             // create a new instance of the new proxy
193             dynaProxy = (IDynAccess) clas.toClass().newInstance();
194             // cache the instance
195             cache.put( cacheKey, dynaProxy );
196         }
197 
198         // return a copy to avoid synchronize-issues
199         return dynaProxy.createCopy();
200     }
201 
202     /***
203      * @param clas
204      * @param method
205      * @param types
206      * @param realTypes
207      * @param isConstructor
208      * @return
209      * @throws CannotCompileException
210      */
211     private CtMethod buildMethodAccessor( CtClass clas, Member method, Class[] types, Class[] realTypes,
212                                           boolean isConstructor ) throws CannotCompileException
213     {
214         StringBuffer sb = new StringBuffer(
215                                             "public Object invokeDynMethod(String method, Object[] params) throws Exception { " );
216         boolean wrap = false;
217 
218         if ( !isConstructor )
219         {
220             Class retType = ((Method) method).getReturnType();
221 
222             if ( retType.isPrimitive() )
223             {
224                 sb.append( " return new " );
225                 sb.append( BaseRemotingHandler.getWrapperForPrimitive( retType ).getSimpleName() );
226                 sb.append( "( m_dynTarget." );
227                 wrap = true;
228             }
229             else
230                 sb.append( " return m_dynTarget." );
231         }
232         else
233         {
234             sb.append( "return new " );
235         }
236 
237         sb.append( method.getName() );
238         sb.append( '(' );
239 
240         for ( int i = 0; i < types.length; i++ )
241         {
242             sb.append( "((" );
243             sb.append( types[i].getSimpleName() );
244             sb.append( ')' );
245             sb.append( "params[" );
246             sb.append( i );
247             sb.append( "])" );
248             if ( realTypes[i] != null && (!types[i].equals( realTypes[i] )) )
249             {
250                 String valueOf = getValueOfName( types[i], realTypes[i] );
251                 if ( valueOf != null )
252                 {
253                     sb.append( '.' );
254                     sb.append( valueOf );
255                     sb.append( "()" );
256                 }
257                 else
258                 {
259                     throw new IllegalArgumentException( "Can't autobox " + types[i].getSimpleName() + " to "
260                             + realTypes[i].getSimpleName() );
261                 }
262             }
263 
264             if ( i < types.length - 1 )
265             {
266                 sb.append( ',' );
267             }
268         }
269 
270         if ( wrap )
271             sb.append( ") ); }" );
272         else
273             sb.append( "); }" );
274 
275         // System.out.print(sb);
276 
277         return CtNewMethod.make( sb.toString(), clas );
278     }
279 
280     /***
281      * @param params
282      * @return
283      */
284     private Class[] getParamTypes( Object[] params )
285     {
286         Class[] types = new Class[params.length];
287 
288         for ( int i = 0; i < params.length; i++ )
289         {
290             types[i] = params[i].getClass();
291         }
292 
293         return types;
294     }
295 
296     /***
297      * @param clazz
298      * @param mName
299      * @param parameterTypes
300      * @param targetParameterTypes
301      * @return
302      * @throws SecurityException
303      */
304     private Method getCompatibleMethod( Class clazz, String mName, Class[] parameterTypes, Class[] targetParameterTypes )
305             throws SecurityException
306     {
307 
308         try
309         {
310             Method mtd = clazz.getMethod( mName, parameterTypes );
311             return mtd;
312         }
313         catch ( NoSuchMethodException e1 )
314         {
315             // can happen
316         }
317 
318         Method[] mtds = clazz.getMethods();
319         for ( int i = 0; i < mtds.length; i++ )
320         {
321             if ( mtds[i].getName().equals( mName ) )
322             {
323                 Class[] types = mtds[i].getParameterTypes();
324                 if ( types.length == parameterTypes.length )
325                 {
326                     if ( checkTypes( types, parameterTypes ) )
327                     {
328                         System.arraycopy( types, 0, targetParameterTypes, 0, types.length );
329                         return mtds[i];
330                     }
331                 }
332             }
333         }
334 
335         return null;
336     }
337 
338     /***
339      * @param clazz
340      * @param cName
341      * @param parameterTypes
342      * @param targetParameterTypes
343      * @return
344      */
345     private Constructor getCompatibleConstructor( Class clazz, String cName, Class[] parameterTypes,
346                                                   Class[] targetParameterTypes )
347     {
348         try
349         {
350             Constructor cTor = clazz.getConstructor( parameterTypes );
351             return cTor;
352         }
353         catch ( NoSuchMethodException e1 )
354         {
355             // can happen
356         }
357 
358         Constructor[] ctors = clazz.getConstructors();
359         for ( int i = 0; i < ctors.length; i++ )
360         {
361             Class[] types = ctors[i].getParameterTypes();
362             if ( types.length == parameterTypes.length )
363             {
364                 if ( checkTypes( types, parameterTypes ) )
365                 {
366                     System.arraycopy( types, 0, targetParameterTypes, 0, types.length );
367                     return ctors[i];
368                 }
369             }
370         }
371 
372         return null;
373     }
374 
375     /***
376      * @param types
377      * @param parameterTypes
378      * @return
379      */
380     private boolean checkTypes( Class[] types, Class[] parameterTypes )
381     {
382         for ( int i = 0; i < types.length; i++ )
383         {
384             Class paramType = parameterTypes[i];
385             if ( types[i].isPrimitive() && !paramType.isPrimitive() )
386             {
387                 paramType = BaseRemotingHandler.getPrimitiveForWrapper( parameterTypes[i] );
388             }
389 
390             if ( !(types[i].isAssignableFrom( paramType )) )
391             {
392                 return false;
393             }
394         }
395 
396         return true;
397     }
398 
399     /***
400      * @param wrapper
401      * @param primitive
402      * @return
403      */
404     private String getValueOfName( Class wrapper, Class primitive )
405     {
406         String primitiveName = primitive.getName();
407         String mtdName = (String) valueOfCache.get( wrapper.getName() + ':' + primitiveName );
408 
409         if ( mtdName == null )
410         {
411             Method[] mtds = wrapper.getMethods();
412 
413             for ( int i = 0; i < mtds.length; i++ )
414             {
415                 if ( mtds[i].getName().endsWith( "Value" ) && mtds[i].getParameterTypes().length == 0 )
416                 {
417                     if ( mtds[i].getReturnType().equals( primitive ) )
418                     {
419                         mtdName = mtds[i].getName();
420                         valueOfCache.put( wrapper.getName() + ':' + primitiveName, mtdName );
421                         break;
422                     }
423                 }
424             }
425         }
426 
427         return mtdName;
428     }
429 }