diff options
author | adinn <adinn@30ef5769-5b8d-40dd-aea6-55b5d6557bb3> | 2010-04-08 08:12:32 +0000 |
---|---|---|
committer | adinn <adinn@30ef5769-5b8d-40dd-aea6-55b5d6557bb3> | 2010-04-08 08:12:32 +0000 |
commit | bd1c47e0f555ada2d250a52f4a3b6697107486b0 (patch) | |
tree | a328392fa9d2749b5738096aa6f18083a82b6844 /src/main/javassist/util | |
parent | afb9e148bf74f4726a783869feaa3f0e54c2a261 (diff) | |
download | javassist-bd1c47e0f555ada2d250a52f4a3b6697107486b0.tar.gz javassist-bd1c47e0f555ada2d250a52f4a3b6697107486b0.zip |
fixes for JASSIST-42 and JASSIST-97
git-svn-id: http://anonsvn.jboss.org/repos/javassist/trunk@522 30ef5769-5b8d-40dd-aea6-55b5d6557bb3
Diffstat (limited to 'src/main/javassist/util')
5 files changed, 566 insertions, 200 deletions
diff --git a/src/main/javassist/util/proxy/ProxyFactory.java b/src/main/javassist/util/proxy/ProxyFactory.java index 6f0a1c46..93b449c3 100644 --- a/src/main/javassist/util/proxy/ProxyFactory.java +++ b/src/main/javassist/util/proxy/ProxyFactory.java @@ -39,21 +39,14 @@ import javassist.bytecode.*; * <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. + * 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); - * 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() @@ -61,6 +54,13 @@ import javassist.bytecode.*; * } * }); * 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> @@ -86,28 +86,17 @@ import javassist.bytecode.*; * 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(); + * MethodHandler mi = ... ; // alternative handler + * ((ProxyObject)foo).setHandler(mi); * </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: + * <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 SimpleHandler implements MethodHandler { + * class EmptyHandler implements MethodHandler { * public Object invoke(Object self, Method m, * Method proceed, Object[] args) throws Exception { * return proceed.invoke(self, args); @@ -115,22 +104,71 @@ import javassist.bytecode.*; * } * </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 interfaces implement a <code>java.io.Serializable</code>. - * However, a serialized proxy object will not be compatible with future releases. + * 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; + private MethodHandler handler; // retained for legacy usage + private List signatureMethods; + private byte[] signature; + private String classname; + private String superName; + private String packageName; 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 @@ -147,7 +185,8 @@ public class ProxyFactory { 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 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"; @@ -159,95 +198,132 @@ public class ProxyFactory { 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 boolean useCache = true; + public static volatile boolean useCache = true; - private static WeakHashMap proxyCache = new WeakHashMap(); + /** + * 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 + */ /** - * store details of a specific proxy created using a given method filter and handler + * test whether this factory uses the proxy cache + * @return true if this factory uses the proxy cache otherwise false */ - static class ProxyDetails { - MethodFilter filter; - MethodHandler handler; - Class proxyClass; + public boolean isUseCache() + { + return factoryUseCache; + } - ProxyDetails(MethodFilter filter, MethodHandler handler, Class proxyClass) - { - this.filter = filter; - this.handler = handler; - this.proxyClass = proxyClass; + /** + * 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; } /** - * collect together details of all proxies associated with a given classloader which are constructed - * from a given superclass and set of interfaces + * 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 */ - static class ProxySet { - String classes; // key constructed from super/interfaces names - List proxyDetails; // hold details of all proxies with given super/interfaces + public boolean isUseWriteReplace() + { + return factoryWriteReplace; + } - public ProxySet(String key) - { - classes = key; - proxyDetails = new ArrayList(); - } + /** + * 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 { /** - * retrieve an entry from the set with the given filter and handler - * @param filter - * @param handler - * @return the proxy details or null if it is not found + * 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. */ - public ProxyDetails lookup(MethodFilter filter, MethodHandler handler) - { - Iterator iterator = proxyDetails.iterator(); - while (iterator.hasNext()) { - ProxyDetails details = (ProxyDetails)iterator.next(); - if (details.filter == filter && details.handler == handler) { - return details; - } - } - return null; - } - + byte[] signature; /** - * add details of a new proxy to the set - * @param details must not contain the same filter and handler as any existing entry in the set + * a hexadecimal string representation of the signature bit sequence. this string also forms part + * of the proxy class name. */ - public void add(ProxyDetails details) - { - proxyDetails.add(details); - } - + WeakReference proxyClass; /** - * remove details of an existing proxy from the set - * @param details must be in the set + * 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 */ - public void remove(ProxyDetails details) - { - proxyDetails.remove(details); - } + boolean isUseWriteReplace; - 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(); + ProxyDetails(byte[] signature, Class proxyClass, boolean isUseWriteReplace) + { + this.signature = signature; + this.proxyClass = new WeakReference(proxyClass); + this.isUseWriteReplace = isUseWriteReplace; } } @@ -259,8 +335,12 @@ public class ProxyFactory { interfaces = null; methodFilter = null; handler = null; + signature = null; + signatureMethods = null; thisClass = null; writeDirectory = null; + factoryUseCache = useCache; + factoryWriteReplace = useWriteReplace; } /** @@ -268,6 +348,8 @@ public class ProxyFactory { */ public void setSuperclass(Class clazz) { superClass = clazz; + // force recompute of signature + signature = null; } /** @@ -282,6 +364,8 @@ public class ProxyFactory { */ public void setInterfaces(Class[] ifs) { interfaces = ifs; + // force recompute of signature + signature = null; } /** @@ -296,29 +380,89 @@ public class ProxyFactory { */ public void setFilter(MethodFilter mf) { methodFilter = mf; + // force recompute of signature + signature = null; } /** - * Generates a proxy class. + * 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) + { + this.signature = signature; + return createClass1(); + } + + private Class createClass1() { if (thisClass == null) { ClassLoader cl = getClassLoader(); synchronized (proxyCache) { - if (useCache) + if (factoryUseCache) createClass2(cl); else createClass3(cl); } } - return thisClass; + // 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 = ProxySet.getKey(superClass, interfaces); - WeakReference reference; - ProxySet set; + 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 @@ -326,41 +470,40 @@ public class ProxyFactory { */ // synchronized (proxyCache) { HashMap cacheForTheLoader = (HashMap)proxyCache.get(cl); + ProxyDetails details; if (cacheForTheLoader == null) { cacheForTheLoader = new HashMap(); proxyCache.put(cl, cacheForTheLoader); } - reference = (WeakReference)cacheForTheLoader.get(key); - if (reference != null) { - set = (ProxySet)reference.get(); - } else { - set = null; - } - if (set == null) { - set = new ProxySet(key); - reference = new WeakReference(set); - cacheForTheLoader.put(key, reference); - } - ProxyDetails details = set.lookup(methodFilter, handler); - if (details == null) { - createClass3(cl); - details = new ProxyDetails(methodFilter, handler, thisClass); - set.add(details); - } else { - thisClass = details.proxyClass; + 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(DEFAULT_INTERCEPTOR, handler); - setField(METHOD_FILTER_FIELD, methodFilter); + 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); @@ -381,12 +524,8 @@ public class ProxyFactory { } } - static MethodFilter getFilter(Class clazz) { - return (MethodFilter)getField(clazz, METHOD_FILTER_FIELD); - } - - static MethodHandler getHandler(Class clazz) { - return (MethodHandler)getField(clazz, DEFAULT_INTERCEPTOR); + static byte[] getFilterSignature(Class clazz) { + return (byte[])getField(clazz, FILTER_SIGNATURE_FIELD); } private static Object getField(Class clazz, String fieldName) { @@ -516,9 +655,22 @@ public class ProxyFactory { * 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); } @@ -529,64 +681,135 @@ public class ProxyFactory { } 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); + // 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); - FieldInfo finfo3 = new FieldInfo(pool, METHOD_FILTER_FIELD, - "Ljavassist/util/proxy/MethodFilter;"); + // 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); - HashMap allMethods = getMethods(superClass, interfaces); - int size = allMethods.size(); + // 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, allMethods); + int s = overrideMethods(cf, pool, classname); addMethodsHolder(cf, pool, classname, s); addSetter(classname, cf, pool); addGetter(classname, cf, pool); - try { - cf.addMethod(makeWriteReplace(pool)); - } - catch (DuplicateMemberException e) { - // writeReplace() is already declared in the super class/interfaces. + if (factoryWriteReplace) { + try { + cf.addMethod(makeWriteReplace(pool)); + } + catch (DuplicateMemberException e) { + // writeReplace() is already declared in the super class/interfaces. + } } - thisClass = null; + thisClass = null; return cf; } + private void checkClassAndSuperName() + { + if (interfaces == null) + interfaces = new Class[0]; + + if (superClass == null) { + superClass = OBJECT_TYPE; + } + superName = superClass.getName(); + + if (Modifier.isFinal(superClass.getModifiers())) + throw new RuntimeException(superName + " is final"); + packageName = getPackageName(superName); + if (packageName.startsWith("java.")) + packageName = "org.javassist.tmp." + packageName; + } + + private void allocateClassName() + { + classname = makeProxyName(superName); + if (classname.startsWith("java.")) + classname = "org.javassist.tmp." + classname; + } + + 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 computeSignature(MethodFilter filter) // throws CannotCompileException + { + checkClassAndSuperName(); + + HashMap allMethods = getMethods(superClass, interfaces); + signatureMethods = new ArrayList(allMethods.entrySet()); + Collections.sort(signatureMethods, sorter); + 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, packageName, m) && filter.isHandled(m)) { + setBit(signature, idx); + } + } + } + + 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; @@ -616,6 +839,9 @@ public class ProxyFactory { 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); @@ -650,13 +876,11 @@ public class ProxyFactory { cf.addMethod(minfo); } - private int overrideMethods(ClassFile cf, ConstPool cp, String className, - HashMap allMethods) + private int overrideMethods(ClassFile cf, ConstPool cp, String className) throws CannotCompileException { - String prefix = makeUniqueName("_d", allMethods); - Set entries = allMethods.entrySet(); - Iterator it = entries.iterator(); + String prefix = makeUniqueName("_d", signatureMethods); + Iterator it = signatureMethods.iterator(); int index = 0; while (it.hasNext()) { Map.Entry e = (Map.Entry)it.next(); @@ -664,10 +888,12 @@ public class ProxyFactory { Method meth = (Method)e.getValue(); int mod = meth.getModifiers(); if (!Modifier.isFinal(mod) && !Modifier.isStatic(mod) - && isVisible(mod, className, meth)) + && isVisible(mod, packageName, meth)) + if (testBit(signature, index)) if (methodFilter == null || methodFilter.isHandled(meth)) - override(className, meth, prefix, index++, + override(className, meth, prefix, index, keyToDesc(key), cf, cp); + index++; } return index; @@ -699,25 +925,26 @@ public class ProxyFactory { 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, classname, c)) { - MethodInfo m = makeConstructor(thisClassName, c, cp, superClass); + && isVisible(mod, packageName, c)) { + MethodInfo m = makeConstructor(thisClassName, c, cp, superClass, doHandlerInit); cf.addMethod(m); } } } - private static String makeUniqueName(String name, HashMap hash) { - Set keys = hash.keySet(); - if (makeUniqueName0(name, keys.iterator())) + 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, keys.iterator())) + if (makeUniqueName0(s, sortedMethods.iterator())) return s; } @@ -726,7 +953,8 @@ public class ProxyFactory { private static boolean makeUniqueName0(String name, Iterator it) { while (it.hasNext()) { - String key = (String)it.next(); + Map.Entry e = (Map.Entry)it.next(); + String key = (String)e.getKey(); if (key.startsWith(name)) return false; } @@ -735,22 +963,21 @@ public class ProxyFactory { } /** - * Returns true if the method is visible from the class. + * 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) { + private static boolean isVisible(int mod, String fromPackage, 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) + if (fromPackage == null) return q == null; else - return p.equals(q); + return fromPackage.equals(q); } } @@ -804,7 +1031,7 @@ public class ProxyFactory { } private static MethodInfo makeConstructor(String thisClassName, Constructor cons, - ConstPool cp, Class superClass) { + ConstPool cp, Class superClass, boolean doHandlerInit) { String desc = RuntimeSupport.makeDescriptor(cons.getParameterTypes(), Void.TYPE); MethodInfo minfo = new MethodInfo(cp, "<init>", desc); @@ -812,12 +1039,19 @@ public class ProxyFactory { 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); + // 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); @@ -1056,7 +1290,7 @@ public class ProxyFactory { FactoryHelper.unwrapDesc[index]); } } - else + else code.addCheckcast(type.getName()); } diff --git a/src/main/javassist/util/proxy/ProxyObjectInputStream.java b/src/main/javassist/util/proxy/ProxyObjectInputStream.java new file mode 100644 index 00000000..8296d895 --- /dev/null +++ b/src/main/javassist/util/proxy/ProxyObjectInputStream.java @@ -0,0 +1,82 @@ +package javassist.util.proxy; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectStreamClass; + +/** + * An input stream class which knows how to deserialize proxies created via {@link ProxyFactory} and + * serializedo via a {@link ProxyObjectOutputStream}. It must be used when deserialising proxies created + * from a proxy factory configured with {@link ProxyFactory#useWriteReplace} set to false. + */ +public class ProxyObjectInputStream extends ObjectInputStream +{ + /** + * create an input stream which can be used to deserialize an object graph which includes proxies created + * using class ProxyFactory. the classloader used to resolve proxy superclass and interface names + * read from the input stream will default to the current thread's context class loader or the system + * classloader if the context class loader is null. + * @param in + * @throws java.io.StreamCorruptedException whenever ObjectInputStream would also do so + * @throws IOException whenever ObjectInputStream would also do so + * @throws SecurityException whenever ObjectInputStream would also do so + * @throws NullPointerException if in is null + */ + public ProxyObjectInputStream(InputStream in) throws IOException + { + super(in); + loader = Thread.currentThread().getContextClassLoader(); + if (loader == null) { + loader = ClassLoader.getSystemClassLoader(); + } + } + + /** + * Reset the loader to be + * @param loader + */ + public void setClassLoader(ClassLoader loader) + { + if (loader != null) { + this.loader = loader; + } else { + loader = ClassLoader.getSystemClassLoader(); + } + } + + protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException { + boolean isProxy = readBoolean(); + if (isProxy) { + String name = (String)readObject(); + Class superClass = loader.loadClass(name); + int length = readInt(); + Class[] interfaces = new Class[length]; + for (int i = 0; i < length; i++) { + name = (String)readObject(); + interfaces[i] = loader.loadClass(name); + } + length = readInt(); + byte[] signature = new byte[length]; + read(signature); + ProxyFactory factory = new ProxyFactory(); + // we must always use the cache and never use writeReplace when using + // ProxyObjectOutputStream and ProxyObjectInputStream + factory.setUseCache(true); + factory.setUseWriteReplace(false); + factory.setSuperclass(superClass); + factory.setInterfaces(interfaces); + Class proxyClass = factory.createClass(signature); + return ObjectStreamClass.lookup(proxyClass); + } else { + return super.readClassDescriptor(); + } + } + + /** + * the loader to use to resolve classes for proxy superclass and interface names read + * from the stream. defaults to the context class loader of the thread which creates + * the input stream or the system class loader if the context class loader is null. + */ + private ClassLoader loader; +}
\ No newline at end of file diff --git a/src/main/javassist/util/proxy/ProxyObjectOutputStream.java b/src/main/javassist/util/proxy/ProxyObjectOutputStream.java new file mode 100644 index 00000000..d06153cd --- /dev/null +++ b/src/main/javassist/util/proxy/ProxyObjectOutputStream.java @@ -0,0 +1,54 @@ +package javassist.util.proxy; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; +import java.io.OutputStream; + +/** + * An input stream class which knows how to serialize proxies created via {@link ProxyFactory}. It must + * be used when serialising proxies created from a proxy factory configured with + * {@link ProxyFactory#useWriteReplace} set to false. Subsequent deserialization of the serialized data + * must employ a {@link ProxyObjectInputStream} + */ +public class ProxyObjectOutputStream extends ObjectOutputStream +{ + /** + * create an output stream which can be used to serialize an object graph which includes proxies created + * using class ProxyFactory + * @param out + * @throws IOException whenever ObjectOutputStream would also do so + * @throws SecurityException whenever ObjectOutputStream would also do so + * @throws NullPointerException if out is null + */ + public ProxyObjectOutputStream(OutputStream out) throws IOException + { + super(out); + } + + protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException { + Class cl = desc.forClass(); + if (ProxyFactory.isProxyClass(cl)) { + writeBoolean(true); + Class superClass = cl.getSuperclass(); + Class[] interfaces = cl.getInterfaces(); + byte[] signature = ProxyFactory.getFilterSignature(cl); + String name = superClass.getName(); + writeObject(name); + // we don't write the marker interface ProxyObject + writeInt(interfaces.length - 1); + for (int i = 0; i < interfaces.length; i++) { + Class interfaze = interfaces[i]; + if (interfaze != ProxyObject.class) { + name = interfaces[i].getName(); + writeObject(name); + } + } + writeInt(signature.length); + write(signature); + } else { + writeBoolean(false); + super.writeClassDescriptor(desc); + } + } +} diff --git a/src/main/javassist/util/proxy/RuntimeSupport.java b/src/main/javassist/util/proxy/RuntimeSupport.java index ea7cbfe9..817ab4ca 100644 --- a/src/main/javassist/util/proxy/RuntimeSupport.java +++ b/src/main/javassist/util/proxy/RuntimeSupport.java @@ -206,10 +206,6 @@ public class RuntimeSupport { if (proxy instanceof ProxyObject) methodHandler = ((ProxyObject)proxy).getHandler(); - if (methodHandler == null) - methodHandler = ProxyFactory.getHandler(clazz); - - return new SerializedProxy(clazz, ProxyFactory.getFilter(clazz), - methodHandler); + return new SerializedProxy(clazz, ProxyFactory.getFilterSignature(clazz), methodHandler); } } diff --git a/src/main/javassist/util/proxy/SerializedProxy.java b/src/main/javassist/util/proxy/SerializedProxy.java index 5092e992..cddfab40 100644 --- a/src/main/javassist/util/proxy/SerializedProxy.java +++ b/src/main/javassist/util/proxy/SerializedProxy.java @@ -31,11 +31,11 @@ import java.security.ProtectionDomain; class SerializedProxy implements Serializable { private String superClass; private String[] interfaces; - private MethodFilter filter; + private byte[] filterSignature; private MethodHandler handler; - SerializedProxy(Class proxy, MethodFilter f, MethodHandler h) { - filter = f; + SerializedProxy(Class proxy, byte[] sig, MethodHandler h) { + filterSignature = sig; handler = h; superClass = proxy.getSuperclass().getName(); Class[] infs = proxy.getInterfaces(); @@ -80,9 +80,9 @@ class SerializedProxy implements Serializable { ProxyFactory f = new ProxyFactory(); f.setSuperclass(loadClass(superClass)); f.setInterfaces(infs); - f.setFilter(filter); - f.setHandler(handler); - return f.createClass().newInstance(); + ProxyObject proxy = (ProxyObject)f.createClass(filterSignature).newInstance(); + proxy.setHandler(handler); + return proxy; } catch (ClassNotFoundException e) { throw new java.io.InvalidClassException(e.getMessage()); |