/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file 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.proxy.compiler;
import java.lang.reflect.Method;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.BasicType;
import org.apache.bcel.generic.Type;
import org.jboss.logging.Logger;
/**
* Manages bytecode assembly for dynamic proxy generation.
*
* @version <tt>$Revision: 81030 $</tt>
* @author Unknown
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
*/
public class ProxyCompiler
{
/** Class logger. */
private static final Logger log = Logger.getLogger(ProxyCompiler.class);
/** The path (if non-null) where generated classes will be dumped for debugging. */
public final static String CLASS_DUMP_PATH =
System.getProperty(ProxyCompiler.class.getName() + ".dumpPath", null);
/** The suffix for proxy implementation classnames. */
public final static String IMPL_SUFFIX = "$Proxy";
/** The Runtime classloader for the target proxy. */
Runtime runtime;
/** The superclass of the target proxy. */
Class superclass;
/** The implementing types of the target proxy. */
Class targetTypes[];
/** The implementing methods of the target proxy. */
Method methods[];
/** The class of the targret proxy (set by runtime). */
Class proxyType;
/**
* Creates a new <code>ProxyCompiler</code> instance.
*
* @param parent a <code>ClassLoader</code> value
* @param superclass a <code>Class</code> value
* @param targetTypes a <code>Class</code> value
* @param methods a <code>Method</code> value
*/
public ProxyCompiler(final ClassLoader parent,
final Class superclass,
final Class targetTypes[],
final Method methods[])
throws Exception
{
this.superclass = superclass;
this.targetTypes = targetTypes;
this.methods = methods;
this.runtime = new Runtime(parent);
this.runtime.targetTypes = targetTypes;
this.runtime.methods = methods;
runtime.makeProxyType(this);
}
public Class getProxyType() {
return proxyType;
}
public String getProxyClassName() {
// Note: We could reasonably put the $Impl class in either
// of two packges: The package of Proxies, or the same package
// as the target type. We choose to put it in same package as
// the target type, to avoid name encoding issues.
//
// Note that all infrastructure must be public, because the
// $Impl class is inside a different class loader.
return targetTypes[0].getName() + IMPL_SUFFIX;
}
/**
* Create the implementation class for the given target.
*
* @return a <code>byte[]</code> value
*/
public byte[] getCode()
{
boolean trace = log.isTraceEnabled();
final String proxyClassName = getProxyClassName();
final String superClassName = superclass.getName();
int icount = 1; // don't forget ProxyTarget
for (int i = 0; i < targetTypes.length; i++) {
Class targetType = targetTypes[i];
if (targetType.isInterface()) {
icount++;
}
}
String interfaceNames[] = new String[icount];
interfaceNames[0] = Proxies.ProxyTarget.class.getName();
icount = 1;
for (int i = 0; i < targetTypes.length; i++) {
Class targetType = targetTypes[i];
if (targetType.isInterface()) {
interfaceNames[icount++] = targetType.getName();
}
else if (!superclass.isAssignableFrom(targetType)) {
throw new RuntimeException("unexpected: " + targetType);
}
}
ClassGen cg = new ClassGen(proxyClassName,
superClassName,
"<generated>",
Constants.ACC_PUBLIC | Constants.ACC_FINAL,
interfaceNames);
ProxyImplementationFactory factory =
new ProxyImplementationFactory(superClassName, proxyClassName, cg);
cg.addField(factory.createInvocationHandlerField());
cg.addField(factory.createRuntimeField());
cg.addMethod(factory.createConstructor());
// ProxyTarget implementation
cg.addMethod(factory.createGetInvocationHandler());
cg.addMethod(factory.createGetTargetTypes());
boolean haveToString = false;
if (trace) log.trace("Creating proxy methods...");
// Implement the methods of the target types.
for (int i = 0; i < methods.length; i++)
{
Method m = methods[i];
if (trace) log.trace("Reflected method: " + m);
String name = m.getName();
Class rTypeClass = m.getReturnType();
String rTypeName = rTypeClass.getName();
Type rType = Utility.getType(rTypeClass);
Type[] pTypes = Utility.getTypes(m.getParameterTypes());
String[] exceptionNames = getNames(m.getExceptionTypes());
if (name.equals("toString") && pTypes.length == 0) {
haveToString = true;
}
org.apache.bcel.classfile.Method proxyMethod =
factory.createProxyMethod(name, i, rType, pTypes, exceptionNames);
if (trace) log.trace("Created proxy method: " + proxyMethod);
cg.addMethod(proxyMethod);
}
if (!haveToString) {
cg.addMethod(factory.createToString());
}
JavaClass jclass = cg.getJavaClass();
if (trace) log.trace("Generated Java class: " + jclass);
// dump the class if we have been configured todo so
if (CLASS_DUMP_PATH != null) {
try {
String filename = CLASS_DUMP_PATH + java.io.File.separator + proxyClassName + ".class";
log.info("Dumping generated proxy class to " + filename);
jclass.dump(filename);
}
catch (Exception e) {
log.error("Failed to dump class file", e);
}
}
return jclass.getBytes();
}
/**
* Returns an array of class names for the given array
* of classes.
*/
private String[] getNames(Class[] classes) {
String[] names = new String[classes.length];
for ( int i = 0; i < classes.length; i++ ) {
names[i] = classes[i].getName();
}
return names;
}
}