/*********************************************************************
* DynProxyGenerator.java
* created on 21.07.2006 by netseeker
* $Id: DynProxyGenerator.java,v 1.15 2006/11/12 20:34:43 netseeker Exp $
*
*
* ====================================================================
*
* Copyright 2006 netseeker aka Michael Manske
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ====================================================================
*
* This file is part of the EJOE framework.
* For more information on the author, please see
* <http://www.manskes.de/>.
*
*********************************************************************/
package de.netseeker.ejoe.handler.gen;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javassist.CannotCompileException;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import de.netseeker.ejoe.EJConstants;
import de.netseeker.ejoe.cache.LRUMap;
import de.netseeker.ejoe.handler.BaseRemotingHandler;
import de.netseeker.ejoe.util.ContentStringBuilder;
/**
* Javassist based default proxy generator for accelerated access on reflected methods and constructors. This generator
* creates and caches one class for each class-method-pair respectively class-constrcutor-pair.
*
* @author netseeker
* @see http://www.jboss.org/products/javassist
* @since 0.3.9.1
*/
public class DynProxyGenerator implements IProxyGenerator
{
private static final Logger logger = Logger.getLogger( DynProxyGenerator.class.getName() );
private static final CtClass[] NO_ARGS = {};
/**
* LRU cache for the generated proxy classes
*/
private static LRUMap cache = new LRUMap( 5000 );
/**
* cache for Wrapper#[prmitive]Value method names
*/
private static LRUMap valueOfCache = new LRUMap( 100 );
private ClassPool _pool = ClassPool.getDefault();
private static Random random = new Random();
private static Object mutex = new Object();
/**
* Creates a new instance of DynProxyGenerator This constructor will throw an IllegalStateException if the current
* java version is lesser than 1.5.0
*/
public DynProxyGenerator()
{
if ( EJConstants.JAVA_VERSION_INT < 150 )
{
throw new IllegalStateException( "DynProxyGenerator supports Java >= 1.5.0 only!" );
}
_pool.insertClassPath( new ClassClassPath( this.getClass() ) );
}
/**
* Either returns a copy of an already cached instance of a proxy or creates a new one, instaniate it, add it to
* cache and return a copy.
*
* @param tClasName the targetted class
* @param mName the targetted method/constructor name
* @param params array of arguments for the targetted method/constructor
* @return a copy of an instance of IDynAccess
* @throws Exception
*/
public IDynAccess getDynAccessProxy( Class tClass, String mName, Object[] params ) throws Exception
{
String tClasName = tClass.getName();
int index = tClasName.lastIndexOf( '.' );
String pName = null;
if ( index != -1 )
{
pName = tClasName.substring( index + 1 );
}
else
{
pName = tClasName;
}
pName = pName.replaceAll( "\\[", "" );
pName = pName.replaceAll( "\\]", "" );
Class[] types = getParamTypes( params );
String cacheKey = pName + '#' + mName + '(' + ContentStringBuilder.toString( types ) + ')';
// first try to locate the proxy in the cache
IDynAccess dynaProxy = (IDynAccess) cache.get( cacheKey );
// if not found then create a new one
if ( dynaProxy == null )
{
// because we are just using class+method names as class names for
// our proxy classes we have to add a unique suffix to avoid name-collisions
int uid = -1;
synchronized ( mutex )
{
uid = random.nextInt( Integer.MAX_VALUE );
}
String proxyName = pName + "DynAccessProxy" + uid;
logger.log( Level.FINEST, "generated name of proxy class: " + proxyName );
// pre-check if the requested method is a constructor
boolean isConstructor = tClass.getSimpleName().equals( mName );
Class[] realTypes = new Class[types.length];
Member targetMtd = null;
/*
* try to find a method/constructor in the target class which: 1. has the same name 2. takes the given
* paramter types or their primitive or wrapper counterparts
*/
if ( !isConstructor )
targetMtd = getCompatibleMethod( tClass, mName, types, realTypes );
else
targetMtd = getCompatibleConstructor( tClass, mName, types, realTypes );
// ooops, nothing found
if ( targetMtd == null ) throw new NoSuchMethodException( cacheKey );
// create a clean, empty proxy class
CtClass clas = _pool.makeClass( proxyName );
CtClass target = _pool.get( tClasName );
CtClass ctIDynProxy = _pool.get( IDynAccess.class.getName() );
// add target object field to class
CtField field = new CtField( target, "m_dynTarget", clas );
clas.addField( field );
// add public default constructor method to class
CtConstructor cons = new CtConstructor( NO_ARGS, clas );
cons.setBody( "m_dynTarget = new " + tClasName + "();" );
clas.addConstructor( cons );
// add public <code>createCopy</code> method
CtMethod ctMeth = new CtMethod( ctIDynProxy, "createCopy", NO_ARGS, clas );
ctMeth.setBody( "return new " + proxyName + "();" );
clas.addMethod( ctMeth );
// add public <code>setDynTarget</code> method
ctMeth = new CtMethod( CtClass.voidType, "setDynTarget", new CtClass[] { _pool.get( "java.lang.Object" ) },
clas );
ctMeth.setBody( "m_dynTarget = (" + tClasName + ")$1;" );
clas.addMethod( ctMeth );
// add public <code>invokeDynMethod</code> method
ctMeth = buildMethodAccessor( clas, targetMtd, types, realTypes, isConstructor );
clas.addMethod( ctMeth );
// apply our IDynAccess interface to the new proxy class
clas.addInterface( _pool.get( IDynAccess.class.getName() ) );
// create a new instance of the new proxy
dynaProxy = (IDynAccess) clas.toClass().newInstance();
// cache the instance
cache.put( cacheKey, dynaProxy );
}
// return a copy to avoid synchronize-issues
return dynaProxy.createCopy();
}
/**
* @param clas
* @param method
* @param types
* @param realTypes
* @param isConstructor
* @return
* @throws CannotCompileException
*/
private CtMethod buildMethodAccessor( CtClass clas, Member method, Class[] types, Class[] realTypes,
boolean isConstructor ) throws CannotCompileException
{
StringBuffer sb = new StringBuffer(
"public Object invokeDynMethod(String method, Object[] params) throws Exception { " );
boolean wrap = false;
if ( !isConstructor )
{
Class retType = ((Method) method).getReturnType();
if ( retType.isPrimitive() )
{
sb.append( " return new " );
sb.append( BaseRemotingHandler.getWrapperForPrimitive( retType ).getSimpleName() );
sb.append( "( m_dynTarget." );
wrap = true;
}
else
sb.append( " return m_dynTarget." );
}
else
{
sb.append( "return new " );
}
sb.append( method.getName() );
sb.append( '(' );
for ( int i = 0; i < types.length; i++ )
{
sb.append( "((" );
sb.append( types[i].getSimpleName() );
sb.append( ')' );
sb.append( "params[" );
sb.append( i );
sb.append( "])" );
if ( realTypes[i] != null && (!types[i].equals( realTypes[i] )) )
{
String valueOf = getValueOfName( types[i], realTypes[i] );
if ( valueOf != null )
{
sb.append( '.' );
sb.append( valueOf );
sb.append( "()" );
}
else
{
throw new IllegalArgumentException( "Can't autobox " + types[i].getSimpleName() + " to "
+ realTypes[i].getSimpleName() );
}
}
if ( i < types.length - 1 )
{
sb.append( ',' );
}
}
if ( wrap )
sb.append( ") ); }" );
else
sb.append( "); }" );
// System.out.print(sb);
return CtNewMethod.make( sb.toString(), clas );
}
/**
* @param params
* @return
*/
private Class[] getParamTypes( Object[] params )
{
Class[] types = new Class[params.length];
for ( int i = 0; i < params.length; i++ )
{
types[i] = params[i].getClass();
}
return types;
}
/**
* @param clazz
* @param mName
* @param parameterTypes
* @param targetParameterTypes
* @return
* @throws SecurityException
*/
private Method getCompatibleMethod( Class clazz, String mName, Class[] parameterTypes, Class[] targetParameterTypes )
throws SecurityException
{
try
{
Method mtd = clazz.getMethod( mName, parameterTypes );
return mtd;
}
catch ( NoSuchMethodException e1 )
{
// can happen
}
Method[] mtds = clazz.getMethods();
for ( int i = 0; i < mtds.length; i++ )
{
if ( mtds[i].getName().equals( mName ) )
{
Class[] types = mtds[i].getParameterTypes();
if ( types.length == parameterTypes.length )
{
if ( checkTypes( types, parameterTypes ) )
{
System.arraycopy( types, 0, targetParameterTypes, 0, types.length );
return mtds[i];
}
}
}
}
return null;
}
/**
* @param clazz
* @param cName
* @param parameterTypes
* @param targetParameterTypes
* @return
*/
private Constructor getCompatibleConstructor( Class clazz, String cName, Class[] parameterTypes,
Class[] targetParameterTypes )
{
try
{
Constructor cTor = clazz.getConstructor( parameterTypes );
return cTor;
}
catch ( NoSuchMethodException e1 )
{
// can happen
}
Constructor[] ctors = clazz.getConstructors();
for ( int i = 0; i < ctors.length; i++ )
{
Class[] types = ctors[i].getParameterTypes();
if ( types.length == parameterTypes.length )
{
if ( checkTypes( types, parameterTypes ) )
{
System.arraycopy( types, 0, targetParameterTypes, 0, types.length );
return ctors[i];
}
}
}
return null;
}
/**
* @param types
* @param parameterTypes
* @return
*/
private boolean checkTypes( Class[] types, Class[] parameterTypes )
{
for ( int i = 0; i < types.length; i++ )
{
Class paramType = parameterTypes[i];
if ( types[i].isPrimitive() && !paramType.isPrimitive() )
{
paramType = BaseRemotingHandler.getPrimitiveForWrapper( parameterTypes[i] );
}
if ( !(types[i].isAssignableFrom( paramType )) )
{
return false;
}
}
return true;
}
/**
* @param wrapper
* @param primitive
* @return
*/
private String getValueOfName( Class wrapper, Class primitive )
{
String primitiveName = primitive.getName();
String mtdName = (String) valueOfCache.get( wrapper.getName() + ':' + primitiveName );
if ( mtdName == null )
{
Method[] mtds = wrapper.getMethods();
for ( int i = 0; i < mtds.length; i++ )
{
if ( mtds[i].getName().endsWith( "Value" ) && mtds[i].getParameterTypes().length == 0 )
{
if ( mtds[i].getReturnType().equals( primitive ) )
{
mtdName = mtds[i].getName();
valueOfCache.put( wrapper.getName() + ':' + primitiveName, mtdName );
break;
}
}
}
}
return mtdName;
}
}