/* * Javassist, a Java-bytecode translator toolkit. * Copyright (C) 1999-2006 Shigeru Chiba. All Rights Reserved. * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. Alternatively, the contents of this file may be used under * the terms of the GNU Lesser General Public License Version 2.1 or later. * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. */ package javassist.util.proxy; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Constructor; import java.lang.reflect.Member; import java.lang.reflect.Modifier; import java.security.ProtectionDomain; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import javassist.CannotCompileException; import javassist.bytecode.*; /** * Factory of dynamic proxy classes. * *
This factory generates a class that extends the given super class and implements
* the given interfaces. The calls of the methods inherited from the super class are
* forwarded and then invoke()
is called on the method handler
* associated with the generated class. The calls of the methods from the interfaces
* are also forwarded to the method handler.
*
*
For example, if the following code is executed, * *
* ProxyFactory f = new ProxyFactory(); * f.setSuperclass(Foo.class); * MethodHandler mi = new MethodHandler() { * public Object invoke(Object self, Method m, Method proceed, * Object[] args) throws Throwable { * System.out.println("Name: " + m.getName()); * return proceed.invoke(self, args); // execute the original method. * } * }; * f.setHandler(mi); * f.setFilter(new MethodFilter() { * public boolean isHandled(Method m) { * // ignore finalize() * return !m.getName().equals("finalize"); * } * }); * Class c = f.createClass(); * Foo foo = (Foo)c.newInstance(); ** *
Then, the following method call will be forwarded to MethodHandler
* mi
and prints a message before executing the originally called method
* bar()
in Foo
.
*
*
* foo.bar(); ** *
To change the method handler during runtime, * execute the following code: * *
* MethodHandler mi2 = ... ; // another handler * ((ProxyObject)foo).setHandler(mi2); ** *
Here is an example of method handler. It does not execute * anything except invoking the original method: * *
* class SimpleHandler implements MethodHandler { * public Object invoke(Object self, Method m, * Method proceed, Object[] args) throws Exception { * return proceed.invoke(self, args); * } * } ** * @see MethodHandler * @since 3.1 */ public class ProxyFactory { private Class superClass; private Class[] interfaces; private MethodFilter methodFilter; private MethodHandler handler; private Class thisClass; /** * If the value of this variable is not null, the class file of * the generated proxy class is written under the directory specified * by this variable. For example, if the value is *
"."
, then the class file is written under the current
* directory. This method is for debugging.
*
* The default value is null.
*/
public String writeDirectory;
private static final Class OBJECT_TYPE = Object.class;
private static final String HOLDER = "_methods_";
private static final String HOLDER_TYPE = "[Ljava/lang/reflect/Method;";
private static final String HANDLER = "handler";
private static final String NULL_INTERCEPTOR_HOLDER = "javassist.util.proxy.RuntimeSupport";
private static final String DEFAULT_INTERCEPTOR = "default_interceptor";
private static final String HANDLER_TYPE
= 'L' + MethodHandler.class.getName().replace('.', '/') + ';';
private static final String HANDLER_SETTER = "setHandler";
private static final String HANDLER_SETTER_TYPE = "(" + HANDLER_TYPE + ")V";
/**
* Constructs a factory of proxy class.
*/
public ProxyFactory() {
superClass = null;
interfaces = null;
methodFilter = null;
handler = null;
thisClass = null;
writeDirectory = null;
}
/**
* Sets the super class of a proxy class.
*/
public void setSuperclass(Class clazz) {
superClass = clazz;
}
/**
* Sets the interfaces of a proxy class.
*/
public void setInterfaces(Class[] ifs) {
interfaces = ifs;
}
/**
* Sets a filter that selects the methods that will be controlled by a handler.
*/
public void setFilter(MethodFilter mf) {
methodFilter = mf;
}
/**
* Generates a proxy class.
*/
public Class createClass() {
if (thisClass == null)
try {
ClassFile cf = make();
ClassLoader cl = getClassLoader();
if (writeDirectory != null)
FactoryHelper.writeFile(cf, writeDirectory);
thisClass = FactoryHelper.toClass(cf, cl, getDomain());
setHandler();
}
catch (CannotCompileException e) {
throw new RuntimeException(e.getMessage(), e);
}
return thisClass;
}
protected ClassLoader getClassLoader() {
// return Thread.currentThread().getContextClassLoader();
ClassLoader loader = null;
if (superClass != null && !superClass.getName().equals("java.lang.Object"))
loader = superClass.getClassLoader();
else if (interfaces != null && interfaces.length > 0)
loader = interfaces[0].getClassLoader();
if (loader == null) {
loader = getClass().getClassLoader();
// In case javassist is in the endorsed dir
if (loader == null)
loader = ClassLoader.getSystemClassLoader();
}
return loader;
}
protected ProtectionDomain getDomain() {
Class clazz;
if (superClass != null && !superClass.getName().equals("java.lang.Object"))
clazz = superClass;
else if (interfaces != null && interfaces.length > 0)
clazz = interfaces[0];
else
clazz = this.getClass();
return clazz.getProtectionDomain();
}
/**
* Creates a proxy class and returns an instance of that class.
*
* @param paramTypes parameter types for a constructor.
* @param args arguments passed to a constructor.
*/
public Object create(Class[] paramTypes, Object[] args)
throws NoSuchMethodException, IllegalArgumentException,
InstantiationException, IllegalAccessException, InvocationTargetException
{
Class c = createClass();
Constructor cons = c.getConstructor(paramTypes);
return cons.newInstance(args);
}
/**
* Sets the default invocation handler. This invocation handler is shared
* among all the instances of a proxy class unless another is explicitly
* specified.
*/
public void setHandler(MethodHandler mi) {
handler = mi;
setHandler();
}
private void setHandler() {
if (thisClass != null && handler != null)
try {
Field f = thisClass.getField(DEFAULT_INTERCEPTOR);
f.setAccessible(true);
f.set(null, handler);
f.setAccessible(false);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
private static int counter = 0;
private ClassFile make() throws CannotCompileException {
String superName, classname;
if (interfaces == null)
interfaces = new Class[0];
if (superClass == null) {
superClass = OBJECT_TYPE;
superName = superClass.getName();
classname = interfaces.length == 0 ? superName
: interfaces[0].getName();
}
else {
superName = superClass.getName();
classname = superName;
}
if (Modifier.isFinal(superClass.getModifiers()))
throw new CannotCompileException(superName + " is final");
// generate a proxy name.
classname = classname + "_$$_javassist_" + counter++;
if (classname.startsWith("java."))
classname = "org.javassist.tmp." + classname;
ClassFile cf = new ClassFile(false, classname, superName);
cf.setAccessFlags(AccessFlag.PUBLIC);
setInterfaces(cf, interfaces);
ConstPool pool = cf.getConstPool();
FieldInfo finfo = new FieldInfo(pool, DEFAULT_INTERCEPTOR, HANDLER_TYPE);
finfo.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC);
cf.addField(finfo);
FieldInfo finfo2 = new FieldInfo(pool, HANDLER, HANDLER_TYPE);
finfo2.setAccessFlags(AccessFlag.PRIVATE);
cf.addField(finfo2);
HashMap allMethods = getMethods(superClass, interfaces);
int size = allMethods.size();
makeConstructors(classname, cf, pool, classname);
int s = overrideMethods(cf, pool, classname, allMethods);
addMethodsHolder(cf, pool, classname, s);
addSetter(classname, cf, pool);
thisClass = null;
return cf;
}
private static void setInterfaces(ClassFile cf, Class[] interfaces) {
String setterIntf = ProxyObject.class.getName();
String[] list;
if (interfaces == null || interfaces.length == 0)
list = new String[] { setterIntf };
else {
list = new String[interfaces.length + 1];
for (int i = 0; i < interfaces.length; i++)
list[i] = interfaces[i].getName();
list[interfaces.length] = setterIntf;
}
cf.setInterfaces(list);
}
private static void addMethodsHolder(ClassFile cf, ConstPool cp,
String classname, int size)
throws CannotCompileException
{
FieldInfo finfo = new FieldInfo(cp, HOLDER, HOLDER_TYPE);
finfo.setAccessFlags(AccessFlag.PRIVATE | AccessFlag.STATIC);
cf.addField(finfo);
MethodInfo minfo = new MethodInfo(cp, "