/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.reflect.plugins.javassist;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.concurrent.atomic.AtomicInteger;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import org.jboss.util.JBossStringBuilder;
import org.jboss.util.UnreachableStatementException;
/**
* JavassistReflectionFactory.
*
* TODO JBMICROCONT-122 proper classpool with pruning
* TODO JBMICROCONT-121 non compiler based implementation
* @author <a href="adrian@jboss.com">Adrian Brock</a>
* @version $Revision: 69888 $
*/
public class JavassistReflectionFactory
{
/** The method class counter */
private static final AtomicInteger counter = new AtomicInteger(0);
/** Whether to check arguments */
private final boolean check;
/**
* Create a new JavassistReflectionFactory.
*
* @param check whether to check arguments
*/
public JavassistReflectionFactory(boolean check)
{
this.check = check;
}
/**
* Create a javassist method
*
* @param ctMethod the method
* @return the method
* @throws Throwable for any error
*/
public JavassistMethod createMethod(CtMethod ctMethod) throws Throwable
{
ClassPool pool = JavassistTypeInfoFactoryImpl.pool;
final CtClass result = pool.makeClass(JavassistMethod.class.getName() + counter.incrementAndGet());
/* TODO JBMICROCONT-133 figure out how to do this on all JDKs
try
{
CtClass magic = pool.get("sun.reflect.MagicAccessorImpl");
result.setSuperclass(magic);
}
catch (NotFoundException ignored)
{
}*/
result.addInterface(pool.get(JavassistMethod.class.getName()));
CtConstructor constructor = new CtConstructor(null, result);
constructor.setBody("super();");
result.addConstructor(constructor);
CtClass object = pool.get(Object.class.getName());
JBossStringBuilder buffer = new JBossStringBuilder();
// Signature
buffer.append("public Object invoke(Object target, Object[] args) throws Throwable {");
boolean isInstanceMethod = Modifier.isStatic(ctMethod.getModifiers()) == false;
// Check for null target
if (check && isInstanceMethod)
{
buffer.append("if (target == null) throw new IllegalArgumentException(\"Null target for ");
buffer.append(ctMethod.getName()).append(ctMethod.getSignature()).append("\");");
}
// Check the target
CtClass declaring = ctMethod.getDeclaringClass();
boolean needsCast = isInstanceMethod && object.equals(declaring) == false;
if (check && needsCast)
{
buffer.append("if (target instanceof ").append(declaring.getName()).append(" == false) ");
buffer.append("throw new IllegalArgumentException(\"Target \" + target + \"");
buffer.append(" is not an instance of ").append(declaring.getName());
buffer.append(" for ").append(ctMethod.getName()).append(ctMethod.getSignature()).append("\");");
}
// Check the parameters
CtClass[] params = ctMethod.getParameterTypes();
if (check)
{
// Wrong number of args?
if (params.length != 0)
{
buffer.append("if (args == null || args.length != ").append(params.length).append(") ");
buffer.append("throw new IllegalArgumentException(\"Expected ").append(params.length).append(" parameter(s)");
buffer.append(" for ").append(ctMethod.getName()).append(ctMethod.getSignature()).append("\");");
}
else
{
// Didn't expect args
buffer.append("if (args != null && args.length != 0)");
buffer.append("throw new IllegalArgumentException(\"Expected no parameters");
buffer.append(" for ").append(ctMethod.getName()).append(ctMethod.getSignature()).append("\");");
}
for (int i = 0; i < params.length; ++i)
{
if (object.equals(params[i]) == false)
{
String paramType = getBoxedType(params[i]);
// Primitives can't be null
if (params[i].isPrimitive())
{
buffer.append("if (args[").append(i).append("] == null) ");
buffer.append("throw new IllegalArgumentException(\"Parameter ").append(i);
buffer.append(" cannot be null for ").append(params[i].getName());
buffer.append(" for ").append(ctMethod.getName()).append(ctMethod.getSignature()).append("\");");
}
// Check the parameter types
buffer.append("if (args[").append(i).append("] != null && ");
buffer.append("args[").append(i).append("] instanceof ").append(paramType).append(" == false) ");
buffer.append("throw new IllegalArgumentException(\"Parameter ").append(i).append(" \" + args[").append(i).append("] + \"");
buffer.append(" is not an instance of ").append(paramType);
buffer.append(" for ").append(ctMethod.getName()).append(ctMethod.getSignature()).append("\");");
}
}
}
// Add a return and box the return type if necessary
CtClass returnType = ctMethod.getReturnType();
boolean isVoid = CtClass.voidType.equals(returnType);
boolean isPrimitive = returnType.isPrimitive();
if (isVoid == false)
{
buffer.append("return ");
if (isPrimitive)
buffer.append("new ").append(getBoxedType(returnType)).append('(');
}
// Instance method
if (isInstanceMethod)
{
buffer.append('(');
if (needsCast)
buffer.append("(").append(declaring.getName()).append(')');
buffer.append("target).");
}
else
{
// Static method
buffer.append(declaring.getName()).append('.');
}
// Add the method name
buffer.append(ctMethod.getName()).append('(');
// Add the parameters
for (int i = 0; i < params.length; ++i)
{
buffer.append('(');
// Cast the parameters
if (object.equals(params[i]) == false)
buffer.append("(").append(getBoxedType(params[i])).append(')');
buffer.append("args[").append(i).append("])");
// Unbox primitive parameters
if (params[i].isPrimitive())
unbox(buffer, params[i]);
if (i < params.length - 1)
buffer.append(", ");
}
buffer.append(')');
// Complete the boxing of the return value
if (isVoid == false && isPrimitive)
buffer.append(')');
buffer.append(';');
// Add a return null if there is no return value
if (isVoid)
buffer.append("return null;");
buffer.append('}');
// Compile it
String code = buffer.toString();
try
{
CtMethod invoke = CtNewMethod.make(code, result);
result.addMethod(invoke);
}
catch (Exception e)
{
throw new RuntimeException("Cannot compile " + code, e);
}
// Create it
try
{
return AccessController.doPrivileged(new PrivilegedExceptionAction<JavassistMethod>()
{
public JavassistMethod run() throws Exception
{
Class<?> clazz = result.toClass();
return (JavassistMethod) clazz.newInstance();
}
});
}
catch (PrivilegedActionException e)
{
throw e.getCause();
}
}
/**
* Create a javassist constructor
*
* @param ctConstructor the constructor
* @return the constructor
* @throws Throwable for any error
*/
public JavassistConstructor createConstructor(CtConstructor ctConstructor) throws Throwable
{
ClassPool pool = JavassistTypeInfoFactoryImpl.pool;
final CtClass result = pool.makeClass(JavassistConstructor.class.getName() + counter.incrementAndGet());
try
{
CtClass magic = pool.get("sun.reflect.MagicAccessorImpl");
result.setSuperclass(magic);
}
catch (NotFoundException ignored)
{
}
result.addInterface(pool.get(JavassistConstructor.class.getName()));
CtConstructor constructor = new CtConstructor(null, result);
constructor.setBody("super();");
result.addConstructor(constructor);
CtClass object = pool.get(Object.class.getName());
JBossStringBuilder buffer = new JBossStringBuilder();
// Signature
buffer.append("public Object newInstance(Object[] args) throws Throwable {");
String declaring = ctConstructor.getDeclaringClass().getName();
// Check the parameters
CtClass[] params = ctConstructor.getParameterTypes();
if (check)
{
// Wrong number of args?
if (params.length != 0)
{
buffer.append("if (args == null || args.length != ").append(params.length).append(") ");
buffer.append("throw new IllegalArgumentException(\"Expected ").append(params.length).append(" parameter(s)");
buffer.append(" for ").append("new ").append(declaring).append(ctConstructor.getSignature()).append("\");");
}
else
{
// Didn't expect args
buffer.append("if (args != null && args.length != 0)");
buffer.append("throw new IllegalArgumentException(\"Expected no parameters");
buffer.append(" for ").append("new ").append(declaring).append(ctConstructor.getSignature()).append("\");");
}
for (int i = 0; i < params.length; ++i)
{
if (object.equals(params[i]) == false)
{
String paramType = getBoxedType(params[i]);
// Primitives can't be null
if (params[i].isPrimitive())
{
buffer.append("if (args[").append(i).append("] == null) ");
buffer.append("throw new IllegalArgumentException(\"Parameter ").append(i);
buffer.append(" cannot be null for ").append(params[i].getName());
buffer.append(" for ").append("new ").append(declaring).append(ctConstructor.getSignature()).append("\");");
}
// Check the parameter types
buffer.append("if (args[").append(i).append("] != null && ");
buffer.append("args[").append(i).append("] instanceof ").append(paramType).append(" == false) ");
buffer.append("throw new IllegalArgumentException(\"Parameter ").append(i).append(" \" + args[").append(i).append("] + \"");
buffer.append(" is not an instance of ").append(paramType);
buffer.append(" for ").append("new ").append(declaring).append(ctConstructor.getSignature()).append("\");");
}
}
}
// Add the return new
buffer.append("return new ").append(declaring).append('(');
// Add the parameters
for (int i = 0; i < params.length; ++i)
{
buffer.append('(');
// Cast the parameters
if (object.equals(params[i]) == false)
buffer.append("(").append(getBoxedType(params[i])).append(')');
buffer.append("args[").append(i).append("])");
// Unbox primitive parameters
if (params[i].isPrimitive())
unbox(buffer, params[i]);
if (i < params.length - 1)
buffer.append(", ");
}
buffer.append(");}");
// Compile it
String code = buffer.toString();
try
{
CtMethod newInstance = CtNewMethod.make(code, result);
result.addMethod(newInstance);
}
catch (Exception e)
{
throw new RuntimeException("Cannot compile " + code, e);
}
// Create it
try
{
return AccessController.doPrivileged(new PrivilegedExceptionAction<JavassistConstructor>()
{
public JavassistConstructor run() throws Exception
{
Class<?> clazz = result.toClass();
return (JavassistConstructor) clazz.newInstance();
}
});
}
catch (PrivilegedActionException e)
{
throw e.getCause();
}
}
/**
* Create a javassist field
*
* @param ctField the field
* @return the field
* @throws Throwable for any error
*/
public JavassistField createField(CtField ctField) throws Throwable
{
ClassPool pool = JavassistTypeInfoFactoryImpl.pool;
final CtClass result = pool.makeClass(JavassistField.class.getName() + counter.incrementAndGet());
try
{
CtClass magic = pool.get("sun.reflect.MagicAccessorImpl");
result.setSuperclass(magic);
}
catch (NotFoundException ignored)
{
}
result.addInterface(pool.get(JavassistField.class.getName()));
CtConstructor constructor = new CtConstructor(null, result);
constructor.setBody("super();");
result.addConstructor(constructor);
CtClass object = pool.get(Object.class.getName());
// GET
JBossStringBuilder buffer = new JBossStringBuilder();
// Signature
buffer.append("public Object get(Object target) throws Throwable {");
boolean isInstanceField= Modifier.isStatic(ctField.getModifiers()) == false;
// Check for null target
if (check && isInstanceField)
{
buffer.append("if (target == null) throw new IllegalArgumentException(\"Null target");
buffer.append(" for ").append(ctField.getName()).append("\");");
}
// Check the target
CtClass declaring = ctField.getDeclaringClass();
boolean needsCast = isInstanceField && object.equals(declaring) == false;
if (check && needsCast)
{
buffer.append("if (target instanceof ").append(declaring.getName()).append(" == false) ");
buffer.append("throw new IllegalArgumentException(\"Target \" + target + \"");
buffer.append(" is not an instance of ").append(declaring.getName());
buffer.append(" for ").append(ctField.getName()).append("\");");
}
// Add a return and box the return type if necessary
CtClass type = ctField.getType();
boolean isPrimitive = type.isPrimitive();
buffer.append("return ");
if (isPrimitive)
buffer.append("new ").append(getBoxedType(type)).append('(');
// Instance field
if (isInstanceField)
{
buffer.append('(');
if (needsCast)
buffer.append("(").append(declaring.getName()).append(')');
buffer.append("target).");
}
else
{
// Static field
buffer.append(declaring.getName()).append('.');
}
// Add the field name
buffer.append(ctField.getName());
// Complete the boxing of the return value
if (isPrimitive)
buffer.append(')');
buffer.append(";}");
// Compile it
String code = buffer.toString();
CtMethod get = CtNewMethod.make(code, result);
try
{
result.addMethod(get);
}
catch (Exception e)
{
throw new RuntimeException("Cannot compile " + code, e);
}
// SET
buffer = new JBossStringBuilder();
// Signature
buffer.append("public void set(Object target, Object value) throws Throwable {");
// Check for null target
if (check && isInstanceField)
{
buffer.append("if (target == null) throw new IllegalArgumentException(\"Null target");
buffer.append(" for ").append(ctField.getName()).append("\");");
}
// Check the target
if (check && needsCast)
{
buffer.append("if (target instanceof ").append(declaring.getName()).append(" == false) ");
buffer.append("throw new IllegalArgumentException(\"Target \" + target + \"");
buffer.append(" is not an instance of ").append(declaring.getName());
buffer.append(" for ").append(ctField.getName()).append("\");");
}
// Check the parameter
if (check)
{
if (object.equals(type) == false)
{
String paramType = getBoxedType(type);
// Primitives can't be null
if (type.isPrimitive())
{
buffer.append("if (type == null) ");
buffer.append("throw new IllegalArgumentException(\"Value ");
buffer.append(" cannot be null for ").append(type.getName());
buffer.append(" for ").append(ctField.getName()).append("\");");
}
// Check the parameter types
buffer.append("if (value != null && ");
buffer.append("value instanceof ").append(paramType).append(" == false) ");
buffer.append("throw new IllegalArgumentException(\"Value \" + value + \"");
buffer.append(" is not an instance of ").append(paramType);
buffer.append(" for ").append(ctField.getName()).append("\");");
}
}
// Instance Field
if (isInstanceField)
{
buffer.append('(');
if (needsCast)
buffer.append("(").append(declaring.getName()).append(')');
buffer.append("target).");
}
else
{
// Static field
buffer.append(declaring.getName()).append('.');
}
// Add the field name
buffer.append(ctField.getName()).append("=");
// Add the value
buffer.append('(');
// Cast the value
if (object.equals(type) == false)
buffer.append("(").append(getBoxedType(type)).append(')');
buffer.append("value)");
// Unbox primitive parameters
if (type.isPrimitive())
unbox(buffer, type);
buffer.append(";}");
// Compile it
code = buffer.toString();
try
{
CtMethod set = CtNewMethod.make(code, result);
result.addMethod(set);
}
catch (Exception e)
{
throw new RuntimeException("Cannot compile " + code, e);
}
// Create it
try
{
return AccessController.doPrivileged(new PrivilegedExceptionAction<JavassistField>()
{
public JavassistField run() throws Exception
{
Class<?> clazz = result.toClass();
return (JavassistField) clazz.newInstance();
}
});
}
catch (PrivilegedActionException e)
{
throw e.getCause();
}
}
/**
* Unbox a primitive
*
* @param buffer the buffer
* @param primitive the primitive
*/
protected void unbox(JBossStringBuilder buffer, CtClass primitive)
{
if (CtClass.booleanType.equals(primitive))
buffer.append(".booleanValue()");
else if (CtClass.byteType.equals(primitive))
buffer.append(".byteValue()");
else if (CtClass.charType.equals(primitive))
buffer.append(".charValue()");
else if (CtClass.doubleType.equals(primitive))
buffer.append(".doubleValue()");
else if (CtClass.floatType.equals(primitive))
buffer.append(".floatValue()");
else if (CtClass.intType.equals(primitive))
buffer.append(".intValue()");
else if (CtClass.longType.equals(primitive))
buffer.append(".longValue()");
else if (CtClass.shortType.equals(primitive))
buffer.append(".shortValue()");
else
{
throw new UnreachableStatementException();
}
}
/**
* Get the boxed type
*
* TODO JBMICROCONT-119 integer progression?
* @param type the type to box
* @return the boxed type name
*/
protected String getBoxedType(CtClass type)
{
if (type.isPrimitive())
{
if (CtClass.booleanType.equals(type))
return Boolean.class.getName();
else if (CtClass.byteType.equals(type))
return Byte.class.getName();
else if (CtClass.charType.equals(type))
return Character.class.getName();
else if (CtClass.doubleType.equals(type))
return Double.class.getName();
else if (CtClass.floatType.equals(type))
return Float.class.getName();
else if (CtClass.intType.equals(type))
return Integer.class.getName();
else if (CtClass.longType.equals(type))
return Long.class.getName();
else if (CtClass.shortType.equals(type))
return Short.class.getName();
throw new UnreachableStatementException();
}
return type.getName();
}
}