diff options
Diffstat (limited to 'src/main/javassist/util/proxy/ProxyFactory.java')
-rw-r--r-- | src/main/javassist/util/proxy/ProxyFactory.java | 1334 |
1 files changed, 1334 insertions, 0 deletions
diff --git a/src/main/javassist/util/proxy/ProxyFactory.java b/src/main/javassist/util/proxy/ProxyFactory.java new file mode 100644 index 0000000..0750950 --- /dev/null +++ b/src/main/javassist/util/proxy/ProxyFactory.java @@ -0,0 +1,1334 @@ +/* + * 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. + */ + +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.*; +import java.lang.ref.WeakReference; + +import javassist.CannotCompileException; +import javassist.bytecode.*; + +/* + * 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 instances of 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); + * 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(); + * ((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 mi = ... ; // alternative handler + * ((ProxyObject)foo).setHandler(mi); + * </pre></ul> + * + * <p> 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: + * + * <ul><pre> + * class EmptyHandler 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 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. + * + * <p>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 ProxyObject#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)}. + * + * <p>A proxy object generated by <code>ProxyFactory</code> is serializable + * if its super class or any of its interfaces implement <code>java.io.Serializable</code>. + * However, a serialized proxy object may not be compatible with future releases. + * The serialization support should be used for short-term storage or RMI. + * + * <p>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. + * <p> + * 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 javassist.util.proxy.ProxyObjectOutputStream} and {@link javassist.util.proxy.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 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 + * <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 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 = "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"; + + 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 int SERIAL_VERSION_UID_VALUE = -1; + + /** + * 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 ProxyObject. nothing else should. + return (ProxyObject.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; + 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 <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; + // force recompute of signature + signature = null; + } + + /** + * 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; + // 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() { + if (thisClass == null) { + ClassLoader cl = getClassLoader(); + synchronized (proxyCache) { + if (factoryUseCache) + createClass2(cl); + else + createClass3(cl); + } + } + + // don't retain any unwanted references + Class result = thisClass; + 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); + } + } + + /** + * 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. + * @deprecated since 3.12 + * use of this method is incompatible with proxy class caching. + * instead clients should call method {@link ProxyObject#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); + } + + private static int counter = 0; + + private static synchronized String makeProxyName(String classname) { + return classname + "_$$_javassist_" + counter++; + } + + private ClassFile make() throws CannotCompileException { + ClassFile cf = new ClassFile(false, classname, superName); + cf.setAccessFlags(AccessFlag.PUBLIC); + setInterfaces(cf, interfaces); + 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); + int s = overrideMethods(cf, pool, classname); + addMethodsHolder(cf, pool, classname, s); + addSetter(classname, cf, pool); + 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(); + + 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) { + 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); + // also need to set serial version uid + code.addLconst(-1L); + code.addPutstatic(classname, SERIAL_VERSION_UID_FIELD, SERIAL_VERSION_UID_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 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) + 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(); + int mod = meth.getModifiers(); + if (testBit(signature, index)) { + override(className, meth, prefix, index, + keyToDesc(key), cf, cp); + } + index++; + } + + 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 + { + 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); + } + + 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); + + 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); + // JIRA JASSIST-85 + // put the method to the cache, retrieve previous definition (if any) + Method oldMethod = (Method)hash.put(key, methods[i]); + + // check if visibility has been reduced + if (null != oldMethod && Modifier.isPublic(oldMethod.getModifiers()) + && !Modifier.isPublic(methods[i].getModifiers()) ) { + // we tried to overwrite a public definition with a non-public definition, + // use the old definition instead. + hash.put(key, oldMethod); + } + } + } + + private static String keyToDesc(String key) { + 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, "<init>", 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(), "<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); + 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); + 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); + 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); + /* + * if (methods[index * 2] == null) { + * methods[index * 2] + * = RuntimeSupport.findSuperMethod(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); + + callFind2Methods(code, meth.getName(), delegatorName, origIndex, desc, arrayVar); + + 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; + } + + 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 thisMethod might be null. + */ + private static void callFind2Methods(Bytecode code, String superMethod, String thisMethod, + int index, String desc, int arrayVar) { + String findClass = RuntimeSupport.class.getName(); + String findDesc + = "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;[Ljava/lang/reflect/Method;)V"; + + code.addAload(0); + 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 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; + } +} |