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
127 IDynAccess dynaProxy = (IDynAccess) cache.get( cacheKey );
128
129
130 if ( dynaProxy == null )
131 {
132
133
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
144 boolean isConstructor = tClass.getSimpleName().equals( mName );
145 Class[] realTypes = new Class[types.length];
146 Member targetMtd = null;
147
148
149
150
151
152 if ( !isConstructor )
153 targetMtd = getCompatibleMethod( tClass, mName, types, realTypes );
154 else
155 targetMtd = getCompatibleConstructor( tClass, mName, types, realTypes );
156
157
158 if ( targetMtd == null ) throw new NoSuchMethodException( cacheKey );
159
160
161 CtClass clas = _pool.makeClass( proxyName );
162 CtClass target = _pool.get( tClasName );
163 CtClass ctIDynProxy = _pool.get( IDynAccess.class.getName() );
164
165
166 CtField field = new CtField( target, "m_dynTarget", clas );
167 clas.addField( field );
168
169
170 CtConstructor cons = new CtConstructor( NO_ARGS, clas );
171 cons.setBody( "m_dynTarget = new " + tClasName + "();" );
172 clas.addConstructor( cons );
173
174
175 CtMethod ctMeth = new CtMethod( ctIDynProxy, "createCopy", NO_ARGS, clas );
176 ctMeth.setBody( "return new " + proxyName + "();" );
177 clas.addMethod( ctMeth );
178
179
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
186 ctMeth = buildMethodAccessor( clas, targetMtd, types, realTypes, isConstructor );
187 clas.addMethod( ctMeth );
188
189
190 clas.addInterface( _pool.get( IDynAccess.class.getName() ) );
191
192
193 dynaProxy = (IDynAccess) clas.toClass().newInstance();
194
195 cache.put( cacheKey, dynaProxy );
196 }
197
198
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
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
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
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 }