/* * 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.WeakHashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.lang.ref.WeakReference; 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";
/**
* If true, a generated proxy class is cached and it will be reused
* when generating the proxy class with the same properties is requested.
* The default value is true.
*
* @since 3.4
*/
public static boolean useCache = true;
private static WeakHashMap proxyCache = new WeakHashMap();
static class CacheKey {
private String classes;
private MethodFilter filter;
private int hash;
WeakReference proxyClass;
public CacheKey(Class superClass, Class[] interfaces, MethodFilter f) {
classes = getKey(superClass, interfaces);
hash = classes.hashCode();
filter = f;
proxyClass = null;
}
public int hashCode() { return hash; }
public boolean equals(Object obj) {
if (obj instanceof CacheKey) {
CacheKey target = (CacheKey)obj;
return target.filter == filter && target.classes.equals(classes);
}
else
return false;
}
static String getKey(Class superClass, Class[] interfaces) {
StringBuffer sbuf = new StringBuffer();
if (superClass != null)
sbuf.append(superClass.getName());
sbuf.append(':');
if (interfaces != null) {
int len = interfaces.length;
for (int i = 0; i < len; i++)
sbuf.append(interfaces[i].getName()).append(',');
}
return sbuf.toString();
}
}
/**
* 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;
}
/**
* Obtains the super class set by setSuperclass()
.
*
* @since 3.4
*/
public Class getSuperclass() { return superClass; }
/**
* Sets the interfaces of a proxy class.
*/
public void setInterfaces(Class[] ifs) {
interfaces = ifs;
}
/**
* Obtains the interfaces set by setInterfaces
.
*
* @since 3.4
*/
public Class[] getInterfaces() { return interfaces; }
/**
* 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) {
ClassLoader cl = getClassLoader();
if (useCache)
createClass2(cl);
else
createClass3(cl);
}
return thisClass;
}
private void createClass2(ClassLoader cl) {
CacheKey key = new CacheKey(superClass, interfaces, methodFilter);
synchronized (proxyCache) {
HashMap cacheForTheLoader = (HashMap)proxyCache.get(cl);
if (cacheForTheLoader == null) {
cacheForTheLoader = new HashMap();
proxyCache.put(cl, cacheForTheLoader);
cacheForTheLoader.put(key, key);
}
else {
CacheKey found = (CacheKey)cacheForTheLoader.get(key);
if (found == null)
cacheForTheLoader.put(key, key);
else {
key = found;
Class c = isValidEntry(key); // no need to synchronize
if (c != null) {
thisClass = c;
return;
}
}
}
}
synchronized (key) {
Class c = isValidEntry(key);
if (c == null) {
createClass3(cl);
key.proxyClass = new WeakReference(thisClass);
}
else
thisClass = c;
}
}
private Class isValidEntry(CacheKey key) {
WeakReference ref = key.proxyClass;
if (ref != null) {
Class c = (Class)ref.get();
if(c != null && getHandler(c) == handler)
return c;
}
return null;
}
private void createClass3(ClassLoader cl) {
try {
ClassFile cf = make();
if (writeDirectory != null)
FactoryHelper.writeFile(cf, writeDirectory);
thisClass = FactoryHelper.toClass(cf, cl, getDomain());
setHandler();
}
catch (CannotCompileException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
/**
* A provider of class loaders.
*
* @see #classLoaderProvider
* @since 3.4
*/
public static interface ClassLoaderProvider {
/**
* Returns a class loader.
*
* @param pf a proxy factory that is going to obtain a class loader.
*/
public ClassLoader get(ProxyFactory pf);
}
/**
* A provider used by createClass()
for obtaining
* a class loader.
* get()
on this ClassLoaderProvider
object
* is called to obtain a class loader.
*
*
The value of this field can be updated for changing the default * implementation. * *
Example: *
* ProxyFactory.classLoaderProvider = new ProxyFactory.ClassLoaderProvider() { * public ClassLoader get(ProxyFactory pf) { * return Thread.currentThread().getContextClassLoader(); * } * }; ** * @since 3.4 */ public static ClassLoaderProvider classLoaderProvider = new ClassLoaderProvider() { public ClassLoader get(ProxyFactory pf) { return pf.getClassLoader0(); } }; protected ClassLoader getClassLoader() { return classLoaderProvider.get(this); } protected ClassLoader getClassLoader0() { 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 = Thread.currentThread().getContextClassLoader(); 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); } } static MethodHandler getHandler(Class clazz) { try { Field f = clazz.getField(DEFAULT_INTERCEPTOR); f.setAccessible(true); MethodHandler h = (MethodHandler)f.get(null); f.setAccessible(false); return h; } catch (Exception e) { throw new RuntimeException(e); } } private static int counter = 0; private static synchronized String makeProxyName(String classname) { return classname + "_$$_javassist_" + counter++; } 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"); classname = makeProxyName(classname); 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, "