]> source.dussan.org Git - javassist.git/commitdiff
fixes for JASSIST-42 and JASSIST-97
authoradinn <adinn@30ef5769-5b8d-40dd-aea6-55b5d6557bb3>
Thu, 8 Apr 2010 08:12:32 +0000 (08:12 +0000)
committeradinn <adinn@30ef5769-5b8d-40dd-aea6-55b5d6557bb3>
Thu, 8 Apr 2010 08:12:32 +0000 (08:12 +0000)
git-svn-id: http://anonsvn.jboss.org/repos/javassist/trunk@522 30ef5769-5b8d-40dd-aea6-55b5d6557bb3

build.xml
src/main/javassist/util/proxy/ProxyFactory.java
src/main/javassist/util/proxy/ProxyObjectInputStream.java [new file with mode: 0644]
src/main/javassist/util/proxy/ProxyObjectOutputStream.java [new file with mode: 0644]
src/main/javassist/util/proxy/RuntimeSupport.java
src/main/javassist/util/proxy/SerializedProxy.java
src/test/test/javassist/proxy/ProxyCacheGCTest.java
src/test/test/javassist/proxy/ProxyFactoryCompatibilityTest.java [new file with mode: 0644]
src/test/test/javassist/proxy/ProxySerializationTest.java [new file with mode: 0644]

index 2a4fac13f7e18769a082c2f1cae44ec076810ca5..729b197a6ac13701d70bf3ec6d1b9c94d8f6c045 100644 (file)
--- a/build.xml
+++ b/build.xml
     </junit>
   </target>
 
+  <target name="foo" depends="test-compile">
+    <junit fork="true" showoutput="true">
+      <classpath refid="test.classpath"/>
+      <test name="test.javassist.proxy.ProxyFactoryCompatibilityTest"/>
+      <jvmarg value="-Xdebug"/>
+      <jvmarg  value="-Xnoagent"/>
+      <jvmarg  value="-Djava.compiler=NONE"/>
+      <jvmarg  value="-Xrunjdwp:transport=dt_socket,server=n,suspend=y,address=5005"/>
+    </junit>
+  </target>
+
   <target name="sample" depends="compile">
     <javac srcdir="${basedir}"
            destdir="${build.classes.dir}"
index 6f0a1c46d168f5c8c456dacba95767edaf1c89dc..93b449c3cc92cda34fc435ead9401b5e01a283c2 100644 (file)
@@ -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 (file)
index 0000000..8296d89
--- /dev/null
@@ -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 (file)
index 0000000..d06153c
--- /dev/null
@@ -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);
+        }
+    }
+}
index ea7cbfe906c4c082d2c5331eab1fae8cf854593d..817ab4ca7178410be4797df8181712c81cdb776e 100644 (file)
@@ -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);
     }
 }
index 5092e9927f4ae3badee9ed509d2e105e621e2126..cddfab40dcd5d0f941d7dedba6d386b5bc7e5e03 100644 (file)
@@ -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());
index 8f583a3705593df31b17a267f436895a958f089d..062872b72fc9b6850756f627df57a6aed838a020 100644 (file)
@@ -4,6 +4,7 @@ import javassist.*;
 import javassist.util.proxy.MethodFilter;
 import javassist.util.proxy.MethodHandler;
 import javassist.util.proxy.ProxyFactory;
+import javassist.util.proxy.ProxyObject;
 import junit.framework.TestCase;
 
 /**
@@ -92,13 +93,13 @@ public class ProxyCacheGCTest extends TestCase
             MethodFilter filter = (MethodFilter)javaFilterClass.newInstance();
 
             // ok, now create a factory and a proxy class and proxy from that factory
-            factory.setHandler(handler);
             factory.setFilter(filter);
             factory.setSuperclass(javaTargetClass);
             // factory.setSuperclass(Object.class);
 
             Class proxyClass = factory.createClass();
             Object target = proxyClass.newInstance();
+            ((ProxyObject)target).setHandler(handler);
         } catch (Exception e) {
             e.printStackTrace();
             fail("cannot create proxy " + e);
diff --git a/src/test/test/javassist/proxy/ProxyFactoryCompatibilityTest.java b/src/test/test/javassist/proxy/ProxyFactoryCompatibilityTest.java
new file mode 100644 (file)
index 0000000..069d0c9
--- /dev/null
@@ -0,0 +1,114 @@
+package test.javassist.proxy;
+
+import javassist.*;
+import javassist.util.proxy.MethodFilter;
+import javassist.util.proxy.MethodHandler;
+import javassist.util.proxy.ProxyFactory;
+import javassist.util.proxy.ProxyObject;
+import junit.framework.TestCase;
+
+import java.lang.reflect.Method;
+
+/**
+ * test which checks that it is still possible to use the old style proxy factory api
+ * to create proxy classes which set their own handler. it checks that caching is
+ * automatically disabled if this legacy api is used. it also exercises the new style
+ * api, ensuring that caching works correctly with this model.
+ */
+public class ProxyFactoryCompatibilityTest extends TestCase
+{
+    private ClassPool basePool;
+    MethodFilter filter;
+    MethodHandler handler;
+
+    protected void setUp()
+    {
+        basePool = ClassPool.getDefault();
+        filter =  new MethodFilter() {
+            public boolean isHandled(Method m) {
+                return !m.getName().equals("finalize");
+            }
+        };
+
+        handler = new MethodHandler() {
+            public Object invoke(Object self, Method m, Method proceed,
+                                 Object[] args) throws Throwable {
+                System.out.println("calling: " + m.getName());
+                return proceed.invoke(self, args);  // execute the original method.
+            }
+        };
+    }
+
+    public void testFactoryCompatibility() throws Exception
+    {
+        // create a factory which, by default, uses caching
+        ProxyFactory factory = new ProxyFactory();
+        factory.setSuperclass(TestClass.class);
+        factory.setInterfaces(new Class[] { TestInterface.class});
+        factory.setFilter(filter);
+
+        // create the same class twice and check that it is reused
+        Class proxyClass1 =  factory.createClass();
+        System.out.println("created first class " + proxyClass1.getName());
+        TestClass proxy1 = (TestClass)proxyClass1.newInstance();
+        ((ProxyObject) proxy1).setHandler(handler);
+        proxy1.testMethod();
+        assertTrue(proxy1.isTestCalled());
+
+        Class proxyClass2 =  factory.createClass();
+        System.out.println("created second class " + proxyClass2.getName());
+        TestClass proxy2 = (TestClass)proxyClass2.newInstance();
+        ((ProxyObject) proxy2).setHandler(handler);
+        proxy2.testMethod();
+        assertTrue(proxy2.isTestCalled());
+
+        assertTrue(proxyClass1 == proxyClass2);
+
+        // create a factory which, by default, uses caching then set the handler so it creates
+        // classes which do not get cached.
+        ProxyFactory factory2 = new ProxyFactory();
+        factory.setSuperclass(TestClass.class);
+        factory.setInterfaces(new Class[] { TestInterface.class});
+        factory.setFilter(filter);
+        factory.setHandler(handler);
+
+        // create the same class twice and check that it is reused
+        Class proxyClass3 =  factory.createClass();
+        System.out.println("created third class " + proxyClass3.getName());
+        TestClass proxy3 = (TestClass)proxyClass3.newInstance();
+        proxy3.testMethod();
+        assertTrue(proxy3.isTestCalled());
+
+        Class proxyClass4 =  factory.createClass();
+        System.out.println("created fourth class " + proxyClass4.getName());
+        TestClass proxy4 = (TestClass)proxyClass4.newInstance();
+        proxy4.testMethod();
+        assertTrue(proxy4.isTestCalled());
+
+        assertTrue(proxyClass3 != proxyClass4);
+    }
+
+    /**
+     * test class used as the super for the proxy
+     */
+    public static class TestClass {
+        private boolean testCalled = false;
+        public void testMethod()
+        {
+            // record the call
+            testCalled = true;
+        }
+        public boolean isTestCalled()
+        {
+            return testCalled;
+        }
+    }
+
+    /**
+     * test interface used as an interface implemented by the proxy
+     */
+    public static interface TestInterface {
+        public void testMethod();
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/test/javassist/proxy/ProxySerializationTest.java b/src/test/test/javassist/proxy/ProxySerializationTest.java
new file mode 100644 (file)
index 0000000..e79bcb4
--- /dev/null
@@ -0,0 +1,151 @@
+package test.javassist.proxy;
+
+import javassist.util.proxy.*;
+import junit.framework.TestCase;
+
+import java.io.*;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Test to ensure that serialization and deserialization of javassist proxies via
+ * {@link javassist.util.proxy.ProxyObjectOutputStream} and  @link javassist.util.proxy.ProxyObjectInputStream}
+ * reuses classes located in the proxy cache. This tests the fixes provided for JASSIST-42 and JASSIST-97.
+ */
+public class ProxySerializationTest extends TestCase
+{
+    public void testSerialization()
+    {
+        ProxyFactory factory = new ProxyFactory();
+        factory.setSuperclass(TestClass.class);
+        factory.setInterfaces(new Class[] {TestInterface.class});
+
+        factory.setUseWriteReplace(true);
+        Class proxyClass = factory.createClass(new TestFilter());
+
+        MethodHandler handler = new TestHandler();
+
+        // first try serialization using writeReplace
+
+        try {
+            String name = "proxytest_1";
+            Constructor constructor = proxyClass.getConstructor(String.class);
+            TestClass proxy = (TestClass)constructor.newInstance(name);
+            ((ProxyObject)proxy).setHandler(handler);
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            ObjectOutputStream out = new ObjectOutputStream(bos);
+            out.writeObject(proxy);
+            out.close();
+            byte[] bytes = bos.toByteArray();
+            ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
+            ObjectInputStream in = new ObjectInputStream(bis);
+            TestClass newProxy = (TestClass)in.readObject();
+            // inherited fields should have been deserialized
+            assert(newProxy.getName() == null);
+            // since we are reading into the same JVM the new proxy should have the same class as the old proxy
+            assert(newProxy.getClass() == proxy.getClass());
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail();
+        }
+
+        // second try serialization using proxy object output/input streams
+
+        factory.setUseWriteReplace(false);
+        proxyClass = factory.createClass(new TestFilter());
+
+        try {
+            String name = "proxytest_2";
+            Constructor constructor = proxyClass.getConstructor(String.class);
+            TestClass proxy = (TestClass)constructor.newInstance(name);
+            ((ProxyObject)proxy).setHandler(handler);
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            ProxyObjectOutputStream out = new ProxyObjectOutputStream(bos);
+            out.writeObject(proxy);
+            out.close();
+            byte[] bytes = bos.toByteArray();
+            ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
+            ProxyObjectInputStream in = new ProxyObjectInputStream(bis);
+            TestClass newProxy = (TestClass)in.readObject();
+            // inherited fields should have been deserialized
+            assert(proxy.getName() == newProxy.getName());
+            // since we are reading into the same JVM the new proxy should have the same class as the old proxy
+            assert(newProxy.getClass() == proxy.getClass());
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail();
+        }
+    }
+
+    public static class TestFilter implements MethodFilter, Serializable
+    {
+        public boolean isHandled(Method m) {
+            if (m.getName().equals("getName")) {
+                return true;
+            }
+            return false;
+        }
+
+        public boolean equals(Object o)
+        {
+            if (o instanceof TestFilter) {
+                // all test filters are equal
+                return true;
+            }
+
+            return false;
+        }
+
+        public int hashCode()
+        {
+            return TestFilter.class.hashCode();
+        }
+    }
+
+    public static class TestHandler implements MethodHandler, Serializable
+    {
+        public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable
+        {
+            return proceed.invoke(self, args);
+        }
+        public boolean equals(Object o)
+        {
+            if (o instanceof TestHandler) {
+                // all test handlers are equal
+                return true;
+            }
+
+            return false;
+        }
+
+        public int hashCode()
+        {
+            return TestHandler.class.hashCode();
+        }
+    }
+
+    public static class TestClass implements Serializable
+    {
+        public String name;
+
+        public TestClass()
+        {
+        }
+
+        public TestClass(String name)
+        {
+            this.name = name;
+        }
+
+        public String getName()
+        {
+            return name;
+        }
+    }
+
+    public static interface TestInterface
+    {
+        public String getName();
+    }
+}