package org.jboss.seam.util;
/* Derived from javassist MethodProxy.java to create a ProxyFactory that does not generate
* FINAL methods. It is a cut & paste due methods on the javassist version largely
* being static and private, and thus completely non-extensible.
*/
/*
* Javassist, a Java-bytecode translator toolkit.
* Copyright (C) 1999-2007 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.
*/
import java.lang.reflect.AccessibleObject;
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.AccessController;
import java.security.PrivilegedAction;
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.*;
import javassist.util.proxy.FactoryHelper;
import javassist.util.proxy.MethodFilter;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyObject;
import javassist.util.proxy.RuntimeSupport;
/*
* This class is implemented only with the lower-level API of Javassist.
* This design decision is for maximizing performance.
*/
/**
* Factory of dynamic proxy classes.
*
* <p>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 <code>invoke()</code> 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.
*
* <p>For example, if the following code is executed,
*
* <ul><pre>
* 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.setFilter(new MethodFilter() {
* public boolean isHandled(Method m) {
* // ignore finalize()
* return !m.getName().equals("finalize");
* }
* });
* Class c = f.createClass();
* Foo foo = (Foo)c.newInstance();
* ((ProxyObject)foo).setHandler(mi);
* </pre></ul>
*
* <p>Then, the following method call will be forwarded to MethodHandler
* <code>mi</code> and prints a message before executing the originally called method
* <code>bar()</code> in <code>Foo</code>.
*
* <ul><pre>
* foo.bar();
* </pre></ul>
*
* <p>The last three lines of the code shown above can be replaced with a call to
* the helper method <code>create</code>, which generates a proxy class, instantiates
* it, and sets the method handler of the instance:
*
* <ul><pre>
* :
* Foo foo = (Foo)f.create(new Class[0], new Object[0], mi);
* </pre></ul>
*
* <p>To change the method handler during runtime,
* execute the following code:
*
* <ul><pre>
* MethodHandler mi2 = ... ; // another handler
* ((ProxyObject)foo).setHandler(mi2);
* </pre></ul>
*
* <p>You can also specify the default method handler:
*
* <ul><pre>
* ProxyFactory f2 = new ProxyFactory();
* f2.setSuperclass(Foo.class);
* f2.setHandler(mi); // set the default handler
* Class c2 = f2.createClass();
* </pre></ul>
*
* <p>The default handler is implicitly attached to an instance of the generated class
* <code>c2</code>. Calling <code>setHandler</code> on the instance is not necessary
* unless another method handler must be attached to the instance.
*
* <p>The following code is an example of method handler. It does not execute
* anything except invoking the original method:
*
* <ul><pre>
* class SimpleHandler implements MethodHandler {
* public Object invoke(Object self, Method m,
* Method proceed, Object[] args) throws Exception {
* return proceed.invoke(self, args);
* }
* }
* </pre></ul>
*
* <p>A proxy object generated by <code>ProxyFactory</code> is serializable
* if its super class or interfaces implement a <code>java.io.Serializable</code>.
* However, a serialized proxy object will not be compatible with future releases.
* The serialization support should be used for short-term storage or RMI.
*
* @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
* <code>"."</code>, then the class file is written under the current
* directory. This method is for debugging.
*
* <p>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 METHOD_FILTER_FIELD = "_method_filter";
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 {
String classes;
MethodFilter filter;
private int hash;
WeakReference proxyClass;
MethodHandler handler;
public CacheKey(Class superClass, Class[] interfaces,
MethodFilter f, MethodHandler h)
{
classes = getKey(superClass, interfaces);
hash = classes.hashCode();
filter = f;
handler = h;
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.handler == handler
&& 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 <code>setSuperclass()</code>.
*
* @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 <code>setInterfaces</code>.
*
* @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();
synchronized (proxyCache) {
if (useCache)
createClass2(cl);
else
createClass3(cl);
}
}
return thisClass;
}
private void createClass2(ClassLoader cl) {
CacheKey key = new CacheKey(superClass, interfaces, methodFilter, handler);
/*
* 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);
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)
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());
setField(DEFAULT_INTERCEPTOR, handler);
setField(METHOD_FILTER_FIELD, methodFilter);
}
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);
// SEAM CHANGE
setAccessible(f, true);
f.set(null, value);
// SEAM CHANGE
setAccessible(f, false);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
static MethodFilter getFilter(Class clazz) {
return (MethodFilter)getField(clazz, METHOD_FILTER_FIELD);
}
static MethodHandler getHandler(Class clazz) {
return (MethodHandler)getField(clazz, DEFAULT_INTERCEPTOR);
}
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);
}
}
/**
* 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 <code>createClass()</code> for obtaining
* a class loader.
* <code>get()</code> on this <code>ClassLoaderProvider</code> object
* is called to obtain a class loader.
*
* <p>The value of this field can be updated for changing the default
* implementation.
*
* <p>Example:
* <ul><pre>
* ProxyFactory.classLoaderProvider = new ProxyFactory.ClassLoaderProvider() {
* public ClassLoader get(ProxyFactory pf) {
* return Thread.currentThread().getContextClassLoader();
* }
* };
* </pre></ul>
*
* @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);
((ProxyObject)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.
*/
public void setHandler(MethodHandler mi) {
handler = mi;
setField(DEFAULT_INTERCEPTOR, handler);
}
private static int counter = 0;
private static synchronized String makeProxyName(String classname) {
// SEAM CHANGE
return classname + "_$$_javassist_seam_" + 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);
FieldInfo finfo3 = new FieldInfo(pool, METHOD_FILTER_FIELD,
"Ljavassist/util/proxy/MethodFilter;");
finfo3.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC);
cf.addField(finfo3);
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);
try {
cf.addMethod(makeWriteReplace(pool));
}
catch (DuplicateMemberException e) {
// writeReplace() is already declared in the super class/interfaces.
}
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, "<clinit>", "()V");
minfo.setAccessFlags(AccessFlag.STATIC);
Bytecode code = new Bytecode(cp, 0, 0);
code.addIconst(size * 2);
code.addAnewarray("java.lang.reflect.Method");
code.addPutstatic(classname, HOLDER, HOLDER_TYPE);
code.addOpcode(Bytecode.RETURN);
minfo.setCodeAttribute(code.toCodeAttribute());
cf.addMethod(minfo);
}
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 int overrideMethods(ClassFile cf, ConstPool cp, String className,
HashMap allMethods)
throws CannotCompileException
{
String prefix = makeUniqueName("_d", allMethods);
Set entries = allMethods.entrySet();
Iterator it = entries.iterator();
int index = 0;
while (it.hasNext()) {
Map.Entry e = (Map.Entry)it.next();
String key = (String)e.getKey();
Method meth = (Method)e.getValue();
int mod = meth.getModifiers();
if (!Modifier.isFinal(mod) && !Modifier.isStatic(mod)
&& isVisible(mod, className, meth))
if (methodFilter == null || methodFilter.isHandled(meth))
override(className, meth, prefix, index++,
keyToDesc(key), cf, cp);
}
return index;
}
private void override(String thisClassname, Method meth, String prefix,
int index, String desc, ClassFile cf, ConstPool cp)
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);
cf.addMethod(forwarder);
}
private void makeConstructors(String thisClassName, ClassFile cf,
ConstPool cp, String classname) throws CannotCompileException
{
// SEAM CHANGE
Constructor[] cons = getDeclaredConstructors(superClass);
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, classname, c)) {
MethodInfo m = makeConstructor(thisClassName, c, cp, superClass);
cf.addMethod(m);
}
}
}
private static String makeUniqueName(String name, HashMap hash) {
Set keys = hash.keySet();
if (makeUniqueName0(name, keys.iterator()))
return name;
for (int i = 100; i < 999; i++) {
String s = name + i;
if (makeUniqueName0(s, keys.iterator()))
return s;
}
throw new RuntimeException("cannot make a unique method name");
}
private static boolean makeUniqueName0(String name, Iterator it) {
while (it.hasNext()) {
String key = (String)it.next();
if (key.startsWith(name))
return false;
}
return true;
}
/**
* Returns true if the method is visible from the class.
*
* @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);
}
private static HashMap getMethods(Class superClass, Class[] interfaceTypes) {
HashMap hash = new HashMap();
for (int i = 0; i < interfaceTypes.length; i++)
getMethods(hash, interfaceTypes[i]);
getMethods(hash, superClass);
return hash;
}
private static void getMethods(HashMap hash, Class clazz) {
Class[] ifs = clazz.getInterfaces();
for (int i = 0; i < ifs.length; i++)
getMethods(hash, ifs[i]);
Class parent = clazz.getSuperclass();
if (parent != null)
getMethods(hash, parent);
// SEAM CHANGE
Method[] methods = 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);
hash.put(key, methods[i]);
}
}
private static String keyToDesc(String key) {
return key.substring(key.indexOf(':') + 1);
}
private static MethodInfo makeConstructor(String thisClassName, Constructor cons,
ConstPool cp, Class superClass) {
String desc = RuntimeSupport.makeDescriptor(cons.getParameterTypes(),
Void.TYPE);
MethodInfo minfo = new MethodInfo(cp, "<init>", desc);
minfo.setAccessFlags(Modifier.PUBLIC); // cons.getModifiers() & ~Modifier.NATIVE
setThrows(minfo, cp, cons.getExceptionTypes());
Bytecode code = new Bytecode(cp, 0, 0);
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);
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(), "<init>", 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 static MethodInfo makeDelegator(Method meth, String desc,
ConstPool cp, Class declClass, String delegatorName) {
MethodInfo delegator = new MethodInfo(cp, delegatorName, desc);
// SEAM CHANGE: remove FINAL
delegator.setAccessFlags(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);
code.addInvokespecial(declClass.getName(), meth.getName(), desc);
addReturn(code, meth.getReturnType());
code.setMaxLocals(++s);
delegator.setCodeAttribute(code.toCodeAttribute());
return delegator;
}
/**
* @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) {
MethodInfo forwarder = new MethodInfo(cp, meth.getName(), desc);
// SEAM CHANGE: remove FINAL
forwarder.setAccessFlags((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);
/*
* if (methods[index * 2] == null) {
* methods[index * 2]
* = RuntimeSupport.findMethod(this, <overridden name>, <desc>);
* methods[index * 2 + 1]
* = RuntimeSupport.findMethod(this, <delegator name>, <desc>);
* 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);
code.addAload(arrayVar);
code.addIconst(origIndex);
code.addOpcode(Opcode.AALOAD);
code.addOpcode(Opcode.IFNONNULL);
int pc = code.currentPc();
code.addIndex(0);
callFindMethod(code, "findSuperMethod", arrayVar, origIndex, meth.getName(), desc);
callFindMethod(code, "findMethod", arrayVar, delIndex, delegatorName, desc);
int pc2 = code.currentPc();
code.write16bit(pc, pc2 - pc + 1);
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);
StackMapTable.Writer writer = new StackMapTable.Writer(32);
writer.appendFrame(pc2, new int[] { StackMapTable.OBJECT },
new int[] { cp.addClassInfo(HOLDER_TYPE) });
ca.setAttribute(writer.toStackMapTable(cp));
return forwarder;
}
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, "<init>",
FactoryHelper.wrapperDesc[index]);
return regno + FactoryHelper.dataSize[index];
}
/**
* @param methodName might be null.
*/
private static void callFindMethod(Bytecode code, String findMethod,
int arrayVar, int index, String methodName, String desc) {
String findClass = RuntimeSupport.class.getName();
String findDesc
= "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/reflect/Method;";
code.addAload(arrayVar);
code.addIconst(index);
if (methodName == null)
code.addOpcode(Opcode.ACONST_NULL);
else {
code.addAload(0);
code.addLdc(methodName);
code.addLdc(desc);
code.addInvokestatic(findClass, findMethod, findDesc);
}
code.addOpcode(Opcode.AASTORE);
}
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("javassist.util.proxy.RuntimeSupport",
"makeSerializedProxy",
"(Ljava/lang/Object;)Ljavassist/util/proxy/SerializedProxy;");
code.addOpcode(Opcode.ARETURN);
minfo.setCodeAttribute(code.toCodeAttribute());
return minfo;
}
// SEAM: from SecurityActions
static void setAccessible(final AccessibleObject ao,
final boolean accessible) {
if (System.getSecurityManager() == null) {
ao.setAccessible(accessible);
} else {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
ao.setAccessible(accessible);
return null;
}
});
}
}
// SEAM: from SecurityActions
static Method[] getDeclaredMethods(final Class clazz) {
if (System.getSecurityManager() == null) {
return clazz.getDeclaredMethods();
} else {
return (Method[]) AccessController
.doPrivileged(new PrivilegedAction() {
public Object run() {
return clazz.getDeclaredMethods();
}
});
}
}
// SEAM: from SecurityActions
static Constructor[] getDeclaredConstructors(final Class clazz) {
if (System.getSecurityManager() == null) {
return clazz.getDeclaredConstructors();
} else {
return (Constructor[]) AccessController
.doPrivileged(new PrivilegedAction() {
public Object run() {
return clazz.getDeclaredConstructors();
}
});
}
}
}