scouter.javassist.util.proxy.ProxyFactory Maven / Gradle / Ivy
/*
* Javassist, a Java-bytecode translator toolkit.
* Copyright (C) 1999- 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,
* or the Apache License Version 2.0.
*
* 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 scouter.javassist.util.proxy;
import scouter.javassist.CannotCompileException;
import scouter.javassist.bytecode.AccessFlag;
import scouter.javassist.bytecode.Bytecode;
import scouter.javassist.bytecode.ClassFile;
import scouter.javassist.bytecode.CodeAttribute;
import scouter.javassist.bytecode.ConstPool;
import scouter.javassist.bytecode.Descriptor;
import scouter.javassist.bytecode.DuplicateMemberException;
import scouter.javassist.bytecode.ExceptionsAttribute;
import scouter.javassist.bytecode.FieldInfo;
import scouter.javassist.bytecode.MethodInfo;
import scouter.javassist.bytecode.Opcode;
import scouter.javassist.bytecode.StackMapTable;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
/*
* This class is implemented only with the lower-level API of Javassist.
* This design decision is for maximizing performance.
*/
/**
* 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 instances of 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);
* f.setFilter(new MethodFilter() {
* public boolean isHandled(Method m) {
* // ignore finalize()
* return !m.getName().equals("finalize");
* }
* });
* Class c = f.createClass();
* 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.
* }
* };
* Foo foo = (Foo)c.newInstance();
* ((Proxy)foo).setHandler(mi);
*
*
* Here, Method
is java.lang.reflect.Method
.
*
* 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();
*
*
* The last three lines of the code shown above can be replaced with a call to
* the helper method create
, which generates a proxy class, instantiates
* it, and sets the method handler of the instance:
*
*
* :
* Foo foo = (Foo)f.create(new Class[0], new Object[0], mi);
*
*
* To change the method handler during runtime,
* execute the following code:
*
*
* MethodHandler mi = ... ; // alternative handler
* ((Proxy)foo).setHandler(mi);
*
*
* If setHandler is never called for a proxy instance then it will
* employ the default handler which proceeds by invoking the original method.
* The behaviour of the default handler is identical to the following
* handler:
*
*
* class EmptyHandler implements MethodHandler {
* public Object invoke(Object self, Method m,
* Method proceed, Object[] args) throws Exception {
* return proceed.invoke(self, args);
* }
* }
*
*
* A proxy factory caches and reuses proxy classes by default. It is possible to reset
* this default globally by setting static field {@link ProxyFactory#useCache} to false.
* Caching may also be configured for a specific factory by calling instance method
* {@link ProxyFactory#setUseCache(boolean)}. It is strongly recommended that new clients
* of class ProxyFactory enable caching. Failure to do so may lead to exhaustion of
* the heap memory area used to store classes.
*
*
Caching is automatically disabled for any given proxy factory if deprecated instance
* method {@link ProxyFactory#setHandler(MethodHandler)} is called. This method was
* used to specify a default handler which newly created proxy classes should install
* when they create their instances. It is only retained to provide backward compatibility
* with previous releases of javassist. Unfortunately,this legacy behaviour makes caching
* and reuse of proxy classes impossible. The current programming model expects javassist
* clients to set the handler of a proxy instance explicitly by calling method
* {@link Proxy#setHandler(MethodHandler)} as shown in the sample code above. New
* clients are strongly recommended to use this model rather than calling
* {@link ProxyFactory#setHandler(MethodHandler)}.
*
*
A proxy object generated by ProxyFactory
is serializable
* if its super class or any of its interfaces implement java.io.Serializable
.
* However, a serialized proxy object may not be compatible with future releases.
* The serialization support should be used for short-term storage or RMI.
*
*
For compatibility with older releases serialization of proxy objects is implemented by
* adding a writeReplace method to the proxy class. This allows a proxy to be serialized
* to a conventional {@link java.io.ObjectOutputStream} and deserialized from a corresponding
* {@link java.io.ObjectInputStream}. However this method suffers from several problems, the most
* notable one being that it fails to serialize state inherited from the proxy's superclass.
*
* An alternative method of serializing proxy objects is available which fixes these problems. It
* requires inhibiting generation of the writeReplace method and instead using instances of
* {@link ProxyObjectOutputStream} and {@link ProxyObjectInputStream}
* (which are subclasses of {@link java.io.ObjectOutputStream} and {@link java.io.ObjectInputStream})
* to serialize and deserialize, respectively, the proxy. These streams recognise javassist proxies and ensure
* that they are serialized and deserialized without the need for the proxy class to implement special methods
* such as writeReplace. Generation of the writeReplace method can be disabled globally by setting static field
* {@link ProxyFactory#useWriteReplace} to false. Alternatively, it may be
* configured per factory by calling instance method {@link ProxyFactory#setUseWriteReplace(boolean)}.
*
* @see MethodHandler
* @since 3.1
* @author Muga Nishizawa
* @author Shigeru Chiba
* @author Andrew Dinn
*/
public class ProxyFactory {
private Class superClass;
private Class[] interfaces;
private MethodFilter methodFilter;
private MethodHandler handler; // retained for legacy usage
private List signatureMethods;
private boolean hasGetHandler;
private byte[] signature;
private String classname;
private String basename;
private String superName;
private Class thisClass;
/**
* per factory setting initialised from current setting for useCache but able to be reset before each create call
*/
private boolean factoryUseCache;
/**
* per factory setting initialised from current setting for useWriteReplace but able to be reset before each create call
*/
private boolean factoryWriteReplace;
/**
* 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 FILTER_SIGNATURE_FIELD = "_filter_signature";
private static final String FILTER_SIGNATURE_TYPE = "[B";
private static final String HANDLER = "handler";
private static final String NULL_INTERCEPTOR_HOLDER = "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";
private static final String HANDLER_GETTER = "getHandler";
private static final String HANDLER_GETTER_TYPE = "()" + HANDLER_TYPE;
private static final String SERIAL_VERSION_UID_FIELD = "serialVersionUID";
private static final String SERIAL_VERSION_UID_TYPE = "J";
private static final long SERIAL_VERSION_UID_VALUE = -1L;
/**
* 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.
*
* Note that this value merely specifies the initial setting employed by any newly created
* proxy factory. The factory setting may be overwritten by calling factory instance method
* {@link #setUseCache(boolean)}
*
* @since 3.4
*/
public static volatile boolean useCache = true;
/**
* If true, a generated proxy class will implement method writeReplace enabling
* serialization of its proxies to a conventional ObjectOutputStream. this (default)
* setting retains the old javassist behaviour which has the advantage that it
* retains compatibility with older releases and requires no extra work on the part
* of the client performing the serialization. However, it has the disadvantage that
* state inherited from the superclasses of the proxy is lost during serialization.
* if false then serialization/deserialization of the proxy instances will preserve
* all fields. However, serialization must be performed via a {@link ProxyObjectOutputStream}
* and deserialization must be via {@link ProxyObjectInputStream}. Any attempt to serialize
* proxies whose class was created with useWriteReplace set to false via a normal
* {@link java.io.ObjectOutputStream} will fail.
*
* Note that this value merely specifies the initial setting employed by any newly created
* proxy factory. The factory setting may be overwritten by calling factory instance method
* {@link #setUseWriteReplace(boolean)}
*
* @since 3.4
*/
public static volatile boolean useWriteReplace = true;
/*
* methods allowing individual factory settings for factoryUseCache and factoryWriteReplace to be reset
*/
/**
* test whether this factory uses the proxy cache
* @return true if this factory uses the proxy cache otherwise false
*/
public boolean isUseCache()
{
return factoryUseCache;
}
/**
* configure whether this factory should use the proxy cache
* @param useCache true if this factory should use the proxy cache and false if it should not use the cache
* @throws RuntimeException if a default interceptor has been set for the factory
*/
public void setUseCache(boolean useCache)
{
// we cannot allow caching to be used if the factory is configured to install a default interceptor
// field into generated classes
if (handler != null && useCache) {
throw new RuntimeException("caching cannot be enabled if the factory default interceptor has been set");
}
factoryUseCache = useCache;
}
/**
* test whether this factory installs a writeReplace method in created classes
* @return true if this factory installs a writeReplace method in created classes otherwise false
*/
public boolean isUseWriteReplace()
{
return factoryWriteReplace;
}
/**
* configure whether this factory should add a writeReplace method to created classes
* @param useWriteReplace true if this factory should add a writeReplace method to created classes and false if it
* should not add a writeReplace method
*/
public void setUseWriteReplace(boolean useWriteReplace)
{
factoryWriteReplace = useWriteReplace;
}
private static WeakHashMap proxyCache = new WeakHashMap();
/**
* determine if a class is a javassist proxy class
* @param cl
* @return true if the class is a javassist proxy class otherwise false
*/
public static boolean isProxyClass(Class cl)
{
// all proxies implement Proxy or ProxyObject. nothing else should.
return (Proxy.class.isAssignableFrom(cl));
}
/**
* used to store details of a specific proxy class in the second tier of the proxy cache. this entry
* will be located in a hashmap keyed by the unique identifying name of the proxy class. the hashmap is
* located in a weak hashmap keyed by the classloader common to all proxy classes in the second tier map.
*/
static class ProxyDetails {
/**
* the unique signature of any method filter whose behaviour will be met by this class. each bit in
* the byte array is set if the filter redirects the corresponding super or interface method and clear
* if it does not redirect it.
*/
byte[] signature;
/**
* a hexadecimal string representation of the signature bit sequence. this string also forms part
* of the proxy class name.
*/
WeakReference proxyClass;
/**
* a flag which is true this class employs writeReplace to perform serialization of its instances
* and false if serialization must employ of a ProxyObjectOutputStream and ProxyObjectInputStream
*/
boolean isUseWriteReplace;
ProxyDetails(byte[] signature, Class proxyClass, boolean isUseWriteReplace)
{
this.signature = signature;
this.proxyClass = new WeakReference(proxyClass);
this.isUseWriteReplace = isUseWriteReplace;
}
}
/**
* Constructs a factory of proxy class.
*/
public ProxyFactory() {
superClass = null;
interfaces = null;
methodFilter = null;
handler = null;
signature = null;
signatureMethods = null;
hasGetHandler = false;
thisClass = null;
writeDirectory = null;
factoryUseCache = useCache;
factoryWriteReplace = useWriteReplace;
}
/**
* Sets the super class of a proxy class.
*/
public void setSuperclass(Class clazz) {
superClass = clazz;
// force recompute of signature
signature = null;
}
/**
* 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;
// force recompute of signature
signature = null;
}
/**
* 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;
// force recompute of signature
signature = null;
}
/**
* Generates a proxy class using the current filter.
*/
public Class createClass() {
if (signature == null) {
computeSignature(methodFilter);
}
return createClass1();
}
/**
* Generates a proxy class using the supplied filter.
*/
public Class createClass(MethodFilter filter) {
computeSignature(filter);
return createClass1();
}
/**
* Generates a proxy class with a specific signature.
* access is package local so ProxyObjectInputStream can use this
* @param signature
* @return
*/
Class createClass(byte[] signature)
{
installSignature(signature);
return createClass1();
}
private Class createClass1() {
Class result = thisClass;
if (result == null) {
ClassLoader cl = getClassLoader();
synchronized (proxyCache) {
if (factoryUseCache)
createClass2(cl);
else
createClass3(cl);
result = thisClass;
// don't retain any unwanted references
thisClass = null;
}
}
return result;
}
private static char[] hexDigits =
{ '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
public String getKey(Class superClass, Class[] interfaces, byte[] signature, boolean useWriteReplace)
{
StringBuffer sbuf = new StringBuffer();
if (superClass != null){
sbuf.append(superClass.getName());
}
sbuf.append(":");
for (int i = 0; i < interfaces.length; i++) {
sbuf.append(interfaces[i].getName());
sbuf.append(":");
}
for (int i = 0; i < signature.length; i++) {
byte b = signature[i];
int lo = b & 0xf;
int hi = (b >> 4) & 0xf;
sbuf.append(hexDigits[lo]);
sbuf.append(hexDigits[hi]);
}
if (useWriteReplace) {
sbuf.append(":w");
}
return sbuf.toString();
}
private void createClass2(ClassLoader cl) {
String key = getKey(superClass, interfaces, signature, factoryWriteReplace);
/*
* Excessive concurrency causes a large memory footprint and slows the
* execution speed down (with JDK 1.5). Thus, we use a jumbo lock for
* reducing concrrency.
*/
// synchronized (proxyCache) {
HashMap cacheForTheLoader = (HashMap)proxyCache.get(cl);
ProxyDetails details;
if (cacheForTheLoader == null) {
cacheForTheLoader = new HashMap();
proxyCache.put(cl, cacheForTheLoader);
}
details = (ProxyDetails)cacheForTheLoader.get(key);
if (details != null) {
WeakReference reference = details.proxyClass;
thisClass = (Class)reference.get();
if (thisClass != null) {
return;
}
}
createClass3(cl);
details = new ProxyDetails(signature, thisClass, factoryWriteReplace);
cacheForTheLoader.put(key, details);
// }
}
private void createClass3(ClassLoader cl) {
// we need a new class so we need a new class name
allocateClassName();
try {
ClassFile cf = make();
if (writeDirectory != null)
FactoryHelper.writeFile(cf, writeDirectory);
thisClass = FactoryHelper.toClass(cf, cl, getDomain());
setField(FILTER_SIGNATURE_FIELD, signature);
// legacy behaviour : we only set the default interceptor static field if we are not using the cache
if (!factoryUseCache) {
setField(DEFAULT_INTERCEPTOR, handler);
}
}
catch (CannotCompileException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
private void setField(String fieldName, Object value) {
if (thisClass != null && value != null)
try {
Field f = thisClass.getField(fieldName);
SecurityActions.setAccessible(f, true);
f.set(null, value);
SecurityActions.setAccessible(f, false);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
static byte[] getFilterSignature(Class clazz) {
return (byte[])getField(clazz, FILTER_SIGNATURE_FIELD);
}
private static Object getField(Class clazz, String fieldName) {
try {
Field f = clazz.getField(fieldName);
f.setAccessible(true);
Object value = f.get(null);
f.setAccessible(false);
return value;
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Obtains the method handler of the given proxy object.
*
* @param p a proxy object.
* @return the method handler.
* @since 3.16
*/
public static MethodHandler getHandler(Proxy p) {
try {
Field f = p.getClass().getDeclaredField(HANDLER);
f.setAccessible(true);
Object value = f.get(p);
f.setAccessible(false);
return (MethodHandler)value;
}
catch (Exception e) {
throw new RuntimeException(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.
* @param mh the method handler for the proxy class.
* @since 3.4
*/
public Object create(Class[] paramTypes, Object[] args, MethodHandler mh)
throws NoSuchMethodException, IllegalArgumentException,
InstantiationException, IllegalAccessException, InvocationTargetException
{
Object obj = create(paramTypes, args);
((Proxy)obj).setHandler(mh);
return obj;
}
/**
* 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.
* @deprecated since 3.12
* use of this method is incompatible with proxy class caching.
* instead clients should call method {@link Proxy#setHandler(MethodHandler)} to set the handler
* for each newly created proxy instance.
* calling this method will automatically disable caching of classes created by the proxy factory.
*/
public void setHandler(MethodHandler mi) {
// if we were using the cache and the handler is non-null then we must stop caching
if (factoryUseCache && mi != null) {
factoryUseCache = false;
// clear any currently held class so we don't try to reuse it or set its handler field
thisClass = null;
}
handler = mi;
// this retains the behaviour of the old code which resets any class we were holding on to
// this is probably not what is wanted
setField(DEFAULT_INTERCEPTOR, handler);
}
/**
* A unique class name generator.
*/
public static interface UniqueName {
/**
* Returns a unique class name.
*
* @param classname the super class name of the proxy class.
*/
String get(String classname);
}
/**
* A unique class name generator.
* Replacing this generator changes the algorithm to generate a
* unique name. The get
method does not have to be
* a synchronized
method since the access to this field
* is mutually exclusive and thus thread safe.
*/
public static UniqueName nameGenerator = new UniqueName() {
private final String sep = "_$$_jvst" + Integer.toHexString(this.hashCode() & 0xfff) + "_";
private int counter = 0;
public String get(String classname) {
return classname + sep + Integer.toHexString(counter++);
}
};
private static String makeProxyName(String classname) {
synchronized (nameGenerator) {
return nameGenerator.get(classname);
}
}
private ClassFile make() throws CannotCompileException {
ClassFile cf = new ClassFile(false, classname, superName);
cf.setAccessFlags(AccessFlag.PUBLIC);
setInterfaces(cf, interfaces, hasGetHandler ? Proxy.class : ProxyObject.class);
ConstPool pool = cf.getConstPool();
// legacy: we only add the static field for the default interceptor if caching is disabled
if (!factoryUseCache) {
FieldInfo finfo = new FieldInfo(pool, DEFAULT_INTERCEPTOR, HANDLER_TYPE);
finfo.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC);
cf.addField(finfo);
}
// handler is per instance
FieldInfo finfo2 = new FieldInfo(pool, HANDLER, HANDLER_TYPE);
finfo2.setAccessFlags(AccessFlag.PRIVATE);
cf.addField(finfo2);
// filter signature is per class
FieldInfo finfo3 = new FieldInfo(pool, FILTER_SIGNATURE_FIELD, FILTER_SIGNATURE_TYPE);
finfo3.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC);
cf.addField(finfo3);
// the proxy class serial uid must always be a fixed value
FieldInfo finfo4 = new FieldInfo(pool, SERIAL_VERSION_UID_FIELD, SERIAL_VERSION_UID_TYPE);
finfo4.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC| AccessFlag.FINAL);
cf.addField(finfo4);
// HashMap allMethods = getMethods(superClass, interfaces);
// int size = allMethods.size();
makeConstructors(classname, cf, pool, classname);
ArrayList forwarders = new ArrayList();
int s = overrideMethods(cf, pool, classname, forwarders);
addClassInitializer(cf, pool, classname, s, forwarders);
addSetter(classname, cf, pool);
if (!hasGetHandler)
addGetter(classname, cf, pool);
if (factoryWriteReplace) {
try {
cf.addMethod(makeWriteReplace(pool));
}
catch (DuplicateMemberException e) {
// writeReplace() is already declared in the super class/interfaces.
}
}
thisClass = null;
return cf;
}
private void checkClassAndSuperName() {
if (interfaces == null)
interfaces = new Class[0];
if (superClass == null) {
superClass = OBJECT_TYPE;
superName = superClass.getName();
basename = interfaces.length == 0 ? superName
: interfaces[0].getName();
} else {
superName = superClass.getName();
basename = superName;
}
if (Modifier.isFinal(superClass.getModifiers()))
throw new RuntimeException(superName + " is final");
if (basename.startsWith("java."))
basename = "org.javassist.tmp." + basename;
}
private void allocateClassName() {
classname = makeProxyName(basename);
}
private static Comparator sorter = new Comparator() {
public int compare(Object o1, Object o2) {
Map.Entry e1 = (Map.Entry)o1;
Map.Entry e2 = (Map.Entry)o2;
String key1 = (String)e1.getKey();
String key2 = (String)e2.getKey();
return key1.compareTo(key2);
}
};
private void makeSortedMethodList() {
checkClassAndSuperName();
hasGetHandler = false; // getMethods() may set this to true.
HashMap allMethods = getMethods(superClass, interfaces);
signatureMethods = new ArrayList(allMethods.entrySet());
Collections.sort(signatureMethods, sorter);
}
private void computeSignature(MethodFilter filter) // throws CannotCompileException
{
makeSortedMethodList();
int l = signatureMethods.size();
int maxBytes = ((l + 7) >> 3);
signature = new byte[maxBytes];
for (int idx = 0; idx < l; idx++)
{
Map.Entry e = (Map.Entry)signatureMethods.get(idx);
Method m = (Method)e.getValue();
int mod = m.getModifiers();
if (!Modifier.isFinal(mod) && !Modifier.isStatic(mod)
&& isVisible(mod, basename, m) && (filter == null || filter.isHandled(m))) {
setBit(signature, idx);
}
}
}
private void installSignature(byte[] signature) // throws CannotCompileException
{
makeSortedMethodList();
int l = signatureMethods.size();
int maxBytes = ((l + 7) >> 3);
if (signature.length != maxBytes) {
throw new RuntimeException("invalid filter signature length for deserialized proxy class");
}
this.signature = signature;
}
private boolean testBit(byte[] signature, int idx) {
int byteIdx = idx >> 3;
if (byteIdx > signature.length) {
return false;
} else {
int bitIdx = idx & 0x7;
int mask = 0x1 << bitIdx;
int sigByte = signature[byteIdx];
return ((sigByte & mask) != 0);
}
}
private void setBit(byte[] signature, int idx) {
int byteIdx = idx >> 3;
if (byteIdx < signature.length) {
int bitIdx = idx & 0x7;
int mask = 0x1 << bitIdx;
int sigByte = signature[byteIdx];
signature[byteIdx] = (byte)(sigByte | mask);
}
}
private static void setInterfaces(ClassFile cf, Class[] interfaces, Class proxyClass) {
String setterIntf = proxyClass.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 addClassInitializer(ClassFile cf, ConstPool cp,
String classname, int size, ArrayList forwarders)
throws CannotCompileException
{
FieldInfo finfo = new FieldInfo(cp, HOLDER, HOLDER_TYPE);
finfo.setAccessFlags(AccessFlag.PRIVATE | AccessFlag.STATIC);
cf.addField(finfo);
MethodInfo minfo = new MethodInfo(cp, "", "()V");
minfo.setAccessFlags(AccessFlag.STATIC);
setThrows(minfo, cp, new Class[] { ClassNotFoundException.class });
Bytecode code = new Bytecode(cp, 0, 2);
code.addIconst(size * 2);
code.addAnewarray("java.lang.reflect.Method");
final int varArray = 0;
code.addAstore(varArray);
// forName() must be called here. Otherwise, the class might be
// invisible.
code.addLdc(classname);
code.addInvokestatic("java.lang.Class",
"forName", "(Ljava/lang/String;)Ljava/lang/Class;");
final int varClass = 1;
code.addAstore(varClass);
Iterator it = forwarders.iterator();
while (it.hasNext()) {
Find2MethodsArgs args = (Find2MethodsArgs)it.next();
callFind2Methods(code, args.methodName, args.delegatorName,
args.origIndex, args.descriptor, varClass, varArray);
}
code.addAload(varArray);
code.addPutstatic(classname, HOLDER, HOLDER_TYPE);
code.addLconst(SERIAL_VERSION_UID_VALUE);
code.addPutstatic(classname, SERIAL_VERSION_UID_FIELD, SERIAL_VERSION_UID_TYPE);
code.addOpcode(Bytecode.RETURN);
minfo.setCodeAttribute(code.toCodeAttribute());
cf.addMethod(minfo);
}
/**
* @param thisMethod might be null.
*/
private static void callFind2Methods(Bytecode code, String superMethod, String thisMethod,
int index, String desc, int classVar, int arrayVar) {
String findClass = RuntimeSupport.class.getName();
String findDesc
= "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;[Ljava/lang/reflect/Method;)V";
code.addAload(classVar);
code.addLdc(superMethod);
if (thisMethod == null)
code.addOpcode(Opcode.ACONST_NULL);
else
code.addLdc(thisMethod);
code.addIconst(index);
code.addLdc(desc);
code.addAload(arrayVar);
code.addInvokestatic(findClass, "find2Methods", findDesc);
}
private static void addSetter(String classname, ClassFile cf, ConstPool cp)
throws CannotCompileException
{
MethodInfo minfo = new MethodInfo(cp, HANDLER_SETTER,
HANDLER_SETTER_TYPE);
minfo.setAccessFlags(AccessFlag.PUBLIC);
Bytecode code = new Bytecode(cp, 2, 2);
code.addAload(0);
code.addAload(1);
code.addPutfield(classname, HANDLER, HANDLER_TYPE);
code.addOpcode(Bytecode.RETURN);
minfo.setCodeAttribute(code.toCodeAttribute());
cf.addMethod(minfo);
}
private static void addGetter(String classname, ClassFile cf, ConstPool cp)
throws CannotCompileException
{
MethodInfo minfo = new MethodInfo(cp, HANDLER_GETTER,
HANDLER_GETTER_TYPE);
minfo.setAccessFlags(AccessFlag.PUBLIC);
Bytecode code = new Bytecode(cp, 1, 1);
code.addAload(0);
code.addGetfield(classname, HANDLER, HANDLER_TYPE);
code.addOpcode(Bytecode.ARETURN);
minfo.setCodeAttribute(code.toCodeAttribute());
cf.addMethod(minfo);
}
private int overrideMethods(ClassFile cf, ConstPool cp, String className, ArrayList forwarders)
throws CannotCompileException
{
String prefix = makeUniqueName("_d", signatureMethods);
Iterator it = signatureMethods.iterator();
int index = 0;
while (it.hasNext()) {
Map.Entry e = (Map.Entry)it.next();
String key = (String)e.getKey();
Method meth = (Method)e.getValue();
if (ClassFile.MAJOR_VERSION < ClassFile.JAVA_5 || !isBridge(meth))
if (testBit(signature, index)) {
override(className, meth, prefix, index,
keyToDesc(key, meth), cf, cp, forwarders);
}
index++;
}
return index;
}
private static boolean isBridge(Method m) {
return m.isBridge();
}
private void override(String thisClassname, Method meth, String prefix,
int index, String desc, ClassFile cf, ConstPool cp, ArrayList forwarders)
throws CannotCompileException
{
Class declClass = meth.getDeclaringClass();
String delegatorName = prefix + index + meth.getName();
if (Modifier.isAbstract(meth.getModifiers()))
delegatorName = null;
else {
MethodInfo delegator
= makeDelegator(meth, desc, cp, declClass, delegatorName);
// delegator is not a bridge method. See Sec. 15.12.4.5 of JLS 3rd Ed.
delegator.setAccessFlags(delegator.getAccessFlags() & ~AccessFlag.BRIDGE);
cf.addMethod(delegator);
}
MethodInfo forwarder
= makeForwarder(thisClassname, meth, desc, cp, declClass,
delegatorName, index, forwarders);
cf.addMethod(forwarder);
}
private void makeConstructors(String thisClassName, ClassFile cf,
ConstPool cp, String classname) throws CannotCompileException
{
Constructor[] cons = SecurityActions.getDeclaredConstructors(superClass);
// legacy: if we are not caching then we need to initialise the default handler
boolean doHandlerInit = !factoryUseCache;
for (int i = 0; i < cons.length; i++) {
Constructor c = cons[i];
int mod = c.getModifiers();
if (!Modifier.isFinal(mod) && !Modifier.isPrivate(mod)
&& isVisible(mod, basename, c)) {
MethodInfo m = makeConstructor(thisClassName, c, cp, superClass, doHandlerInit);
cf.addMethod(m);
}
}
}
private static String makeUniqueName(String name, List sortedMethods) {
if (makeUniqueName0(name, sortedMethods.iterator()))
return name;
for (int i = 100; i < 999; i++) {
String s = name + i;
if (makeUniqueName0(s, sortedMethods.iterator()))
return s;
}
throw new RuntimeException("cannot make a unique method name");
}
private static boolean makeUniqueName0(String name, Iterator it) {
while (it.hasNext()) {
Map.Entry e = (Map.Entry)it.next();
String key = (String)e.getKey();
if (key.startsWith(name))
return false;
}
return true;
}
/**
* Returns true if the method is visible from the package.
*
* @param mod the modifiers of the method.
*/
private static boolean isVisible(int mod, String from, Member meth) {
if ((mod & Modifier.PRIVATE) != 0)
return false;
else if ((mod & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0)
return true;
else {
String p = getPackageName(from);
String q = getPackageName(meth.getDeclaringClass().getName());
if (p == null)
return q == null;
else
return p.equals(q);
}
}
private static String getPackageName(String name) {
int i = name.lastIndexOf('.');
if (i < 0)
return null;
else
return name.substring(0, i);
}
/* getMethods() may set hasGetHandler to true.
*/
private HashMap getMethods(Class superClass, Class[] interfaceTypes) {
HashMap hash = new HashMap();
HashSet set = new HashSet();
for (int i = 0; i < interfaceTypes.length; i++)
getMethods(hash, interfaceTypes[i], set);
getMethods(hash, superClass, set);
return hash;
}
private void getMethods(HashMap hash, Class clazz, Set visitedClasses) {
// This both speeds up scanning by avoiding duplicate interfaces and is needed to
// ensure that superinterfaces are always scanned before subinterfaces.
if (!visitedClasses.add(clazz))
return;
Class[] ifs = clazz.getInterfaces();
for (int i = 0; i < ifs.length; i++)
getMethods(hash, ifs[i], visitedClasses);
Class parent = clazz.getSuperclass();
if (parent != null)
getMethods(hash, parent, visitedClasses);
/* Java 5 or later allows covariant return types.
* It also allows contra-variant parameter types
* if a super class is a generics with concrete type arguments
* such as Foo. So the method-overriding rule is complex.
*/
Method[] methods = SecurityActions.getDeclaredMethods(clazz);
for (int i = 0; i < methods.length; i++)
if (!Modifier.isPrivate(methods[i].getModifiers())) {
Method m = methods[i];
String key = m.getName() + ':' + RuntimeSupport.makeDescriptor(m); // see keyToDesc().
if (key.startsWith(HANDLER_GETTER_KEY))
hasGetHandler = true;
// JIRA JASSIST-85
// put the method to the cache, retrieve previous definition (if any)
Method oldMethod = (Method)hash.put(key, m);
// JIRA JASSIST-244
// ignore a bridge method with the same signature that the overridden one has.
if (null != oldMethod && isBridge(m)
&& !Modifier.isPublic(oldMethod.getDeclaringClass().getModifiers())
&& !Modifier.isAbstract(oldMethod.getModifiers()) && !isOverloaded(i, methods))
hash.put(key, oldMethod);
// check if visibility has been reduced
if (null != oldMethod && Modifier.isPublic(oldMethod.getModifiers())
&& !Modifier.isPublic(m.getModifiers())) {
// we tried to overwrite a public definition with a non-public definition,
// use the old definition instead.
hash.put(key, oldMethod);
}
}
}
private static boolean isOverloaded(int index, Method[] methods) {
String name = methods[index].getName();
for (int i = 0; i < methods.length; i++)
if (i != index)
if (name.equals(methods[i].getName()))
return true;
return false;
}
private static final String HANDLER_GETTER_KEY
= HANDLER_GETTER + ":()";
private static String keyToDesc(String key, Method m) {
return key.substring(key.indexOf(':') + 1);
}
private static MethodInfo makeConstructor(String thisClassName, Constructor cons,
ConstPool cp, Class superClass, boolean doHandlerInit) {
String desc = RuntimeSupport.makeDescriptor(cons.getParameterTypes(),
Void.TYPE);
MethodInfo minfo = new MethodInfo(cp, "", desc);
minfo.setAccessFlags(Modifier.PUBLIC); // cons.getModifiers() & ~Modifier.NATIVE
setThrows(minfo, cp, cons.getExceptionTypes());
Bytecode code = new Bytecode(cp, 0, 0);
// legacy: if we are not using caching then we initialise the instance's handler
// from the class's static default interceptor and skip the next few instructions if
// it is non-null
if (doHandlerInit) {
code.addAload(0);
code.addGetstatic(thisClassName, DEFAULT_INTERCEPTOR, HANDLER_TYPE);
code.addPutfield(thisClassName, HANDLER, HANDLER_TYPE);
code.addGetstatic(thisClassName, DEFAULT_INTERCEPTOR, HANDLER_TYPE);
code.addOpcode(Opcode.IFNONNULL);
code.addIndex(10);
}
// if caching is enabled then we don't have a handler to initialise so this else branch will install
// the handler located in the static field of class RuntimeSupport.
code.addAload(0);
code.addGetstatic(NULL_INTERCEPTOR_HOLDER, DEFAULT_INTERCEPTOR, HANDLER_TYPE);
code.addPutfield(thisClassName, HANDLER, HANDLER_TYPE);
int pc = code.currentPc();
code.addAload(0);
int s = addLoadParameters(code, cons.getParameterTypes(), 1);
code.addInvokespecial(superClass.getName(), "", desc);
code.addOpcode(Opcode.RETURN);
code.setMaxLocals(s + 1);
CodeAttribute ca = code.toCodeAttribute();
minfo.setCodeAttribute(ca);
StackMapTable.Writer writer = new StackMapTable.Writer(32);
writer.sameFrame(pc);
ca.setAttribute(writer.toStackMapTable(cp));
return minfo;
}
private MethodInfo makeDelegator(Method meth, String desc,
ConstPool cp, Class declClass, String delegatorName) {
MethodInfo delegator = new MethodInfo(cp, delegatorName, desc);
delegator.setAccessFlags(Modifier.FINAL | Modifier.PUBLIC
| (meth.getModifiers() & ~(Modifier.PRIVATE
| Modifier.PROTECTED
| Modifier.ABSTRACT
| Modifier.NATIVE
| Modifier.SYNCHRONIZED)));
setThrows(delegator, cp, meth);
Bytecode code = new Bytecode(cp, 0, 0);
code.addAload(0);
int s = addLoadParameters(code, meth.getParameterTypes(), 1);
Class targetClass = invokespecialTarget(declClass);
code.addInvokespecial(targetClass.isInterface(), cp.addClassInfo(targetClass.getName()),
meth.getName(), desc);
addReturn(code, meth.getReturnType());
code.setMaxLocals(++s);
delegator.setCodeAttribute(code.toCodeAttribute());
return delegator;
}
/* Suppose that the receiver type is S, the invoked method
* is declared in T, and U is the immediate super class of S
* (or its interface). If S <: U <: T (S <: T reads "S extends T"),
* the target type of invokespecial has to be not T but U.
*/
private Class invokespecialTarget(Class declClass) {
if (declClass.isInterface())
for (Class i: interfaces)
if (declClass.isAssignableFrom(i))
return i;
return superClass;
}
/**
* @param delegatorName null if the original method is abstract.
*/
private static MethodInfo makeForwarder(String thisClassName,
Method meth, String desc, ConstPool cp,
Class declClass, String delegatorName, int index,
ArrayList forwarders) {
MethodInfo forwarder = new MethodInfo(cp, meth.getName(), desc);
forwarder.setAccessFlags(Modifier.FINAL
| (meth.getModifiers() & ~(Modifier.ABSTRACT
| Modifier.NATIVE
| Modifier.SYNCHRONIZED)));
setThrows(forwarder, cp, meth);
int args = Descriptor.paramSize(desc);
Bytecode code = new Bytecode(cp, 0, args + 2);
/*
* static {
* methods[index * 2]
* = RuntimeSupport.findSuperMethod(this, , );
* methods[index * 2 + 1]
* = RuntimeSupport.findMethod(this, , );
* or = null // the original method is abstract.
* }
* :
* return ($r)handler.invoke(this, methods[index * 2],
* methods[index * 2 + 1], $args);
*/
int origIndex = index * 2;
int delIndex = index * 2 + 1;
int arrayVar = args + 1;
code.addGetstatic(thisClassName, HOLDER, HOLDER_TYPE);
code.addAstore(arrayVar);
forwarders.add(new Find2MethodsArgs(meth.getName(), delegatorName, desc, origIndex));
code.addAload(0);
code.addGetfield(thisClassName, HANDLER, HANDLER_TYPE);
code.addAload(0);
code.addAload(arrayVar);
code.addIconst(origIndex);
code.addOpcode(Opcode.AALOAD);
code.addAload(arrayVar);
code.addIconst(delIndex);
code.addOpcode(Opcode.AALOAD);
makeParameterList(code, meth.getParameterTypes());
code.addInvokeinterface(MethodHandler.class.getName(), "invoke",
"(Ljava/lang/Object;Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;",
5);
Class retType = meth.getReturnType();
addUnwrapper(code, retType);
addReturn(code, retType);
CodeAttribute ca = code.toCodeAttribute();
forwarder.setCodeAttribute(ca);
return forwarder;
}
static class Find2MethodsArgs {
String methodName, delegatorName, descriptor;
int origIndex;
Find2MethodsArgs(String mname, String dname, String desc, int index) {
methodName = mname;
delegatorName = dname;
descriptor = desc;
origIndex = index;
}
}
private static void setThrows(MethodInfo minfo, ConstPool cp, Method orig) {
Class[] exceptions = orig.getExceptionTypes();
setThrows(minfo, cp, exceptions);
}
private static void setThrows(MethodInfo minfo, ConstPool cp,
Class[] exceptions) {
if (exceptions.length == 0)
return;
String[] list = new String[exceptions.length];
for (int i = 0; i < exceptions.length; i++)
list[i] = exceptions[i].getName();
ExceptionsAttribute ea = new ExceptionsAttribute(cp);
ea.setExceptions(list);
minfo.setExceptionsAttribute(ea);
}
private static int addLoadParameters(Bytecode code, Class[] params,
int offset) {
int stacksize = 0;
int n = params.length;
for (int i = 0; i < n; ++i)
stacksize += addLoad(code, stacksize + offset, params[i]);
return stacksize;
}
private static int addLoad(Bytecode code, int n, Class type) {
if (type.isPrimitive()) {
if (type == Long.TYPE) {
code.addLload(n);
return 2;
}
else if (type == Float.TYPE)
code.addFload(n);
else if (type == Double.TYPE) {
code.addDload(n);
return 2;
}
else
code.addIload(n);
}
else
code.addAload(n);
return 1;
}
private static int addReturn(Bytecode code, Class type) {
if (type.isPrimitive()) {
if (type == Long.TYPE) {
code.addOpcode(Opcode.LRETURN);
return 2;
}
else if (type == Float.TYPE)
code.addOpcode(Opcode.FRETURN);
else if (type == Double.TYPE) {
code.addOpcode(Opcode.DRETURN);
return 2;
}
else if (type == Void.TYPE) {
code.addOpcode(Opcode.RETURN);
return 0;
}
else
code.addOpcode(Opcode.IRETURN);
}
else
code.addOpcode(Opcode.ARETURN);
return 1;
}
private static void makeParameterList(Bytecode code, Class[] params) {
int regno = 1;
int n = params.length;
code.addIconst(n);
code.addAnewarray("java/lang/Object");
for (int i = 0; i < n; i++) {
code.addOpcode(Opcode.DUP);
code.addIconst(i);
Class type = params[i];
if (type.isPrimitive())
regno = makeWrapper(code, type, regno);
else {
code.addAload(regno);
regno++;
}
code.addOpcode(Opcode.AASTORE);
}
}
private static int makeWrapper(Bytecode code, Class type, int regno) {
int index = FactoryHelper.typeIndex(type);
String wrapper = FactoryHelper.wrapperTypes[index];
code.addNew(wrapper);
code.addOpcode(Opcode.DUP);
addLoad(code, regno, type);
code.addInvokespecial(wrapper, "",
FactoryHelper.wrapperDesc[index]);
return regno + FactoryHelper.dataSize[index];
}
private static void addUnwrapper(Bytecode code, Class type) {
if (type.isPrimitive()) {
if (type == Void.TYPE)
code.addOpcode(Opcode.POP);
else {
int index = FactoryHelper.typeIndex(type);
String wrapper = FactoryHelper.wrapperTypes[index];
code.addCheckcast(wrapper);
code.addInvokevirtual(wrapper,
FactoryHelper.unwarpMethods[index],
FactoryHelper.unwrapDesc[index]);
}
}
else
code.addCheckcast(type.getName());
}
private static MethodInfo makeWriteReplace(ConstPool cp) {
MethodInfo minfo = new MethodInfo(cp, "writeReplace", "()Ljava/lang/Object;");
String[] list = new String[1];
list[0] = "java.io.ObjectStreamException";
ExceptionsAttribute ea = new ExceptionsAttribute(cp);
ea.setExceptions(list);
minfo.setExceptionsAttribute(ea);
Bytecode code = new Bytecode(cp, 0, 1);
code.addAload(0);
code.addInvokestatic("RuntimeSupport",
"makeSerializedProxy",
"(Ljava/lang/Object;)Ljavassist/util/proxy/SerializedProxy;");
code.addOpcode(Opcode.ARETURN);
minfo.setCodeAttribute(code.toCodeAttribute());
return minfo;
}
}