net.sf.cglib.proxy.TestEnhancer.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.cglib.proxy.TestEnhancer.java

Source

/*
 * Copyright 2002,2003,2004 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.sf.cglib.proxy;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import net.sf.cglib.CodeGenTestCase;
import net.sf.cglib.core.AbstractClassGenerator;
import net.sf.cglib.core.DefaultNamingPolicy;
import net.sf.cglib.core.NamingPolicy;
import net.sf.cglib.core.Predicate;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.reflect.FastClass;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

/**
 *@author     Juozas Baliuka <a href="mailto:baliuka@mwm.lt">
 *      baliuka@mwm.lt</a>
 *@version    $Id: TestEnhancer.java,v 1.58 2012/07/27 16:02:49 baliuka Exp $
 */
public class TestEnhancer extends CodeGenTestCase {
    private static final MethodInterceptor TEST_INTERCEPTOR = new TestInterceptor();

    private static final Class[] EMPTY_ARG = new Class[] {};

    private boolean invokedProtectedMethod = false;

    private boolean invokedPackageMethod = false;

    private boolean invokedAbstractMethod = false;

    public TestEnhancer(String testName) {
        super(testName);
    }

    public static Test suite() {
        return new TestSuite(TestEnhancer.class);
    }

    public static void main(String args[]) {
        String[] testCaseName = { TestEnhancer.class.getName() };
        junit.textui.TestRunner.main(testCaseName);
    }

    public void testEnhance() throws Throwable {

        java.util.Vector vector1 = (java.util.Vector) Enhancer.create(java.util.Vector.class,
                new Class[] { java.util.List.class }, TEST_INTERCEPTOR);

        java.util.Vector vector2 = (java.util.Vector) Enhancer.create(java.util.Vector.class,
                new Class[] { java.util.List.class }, TEST_INTERCEPTOR);

        assertTrue("Cache failed", vector1.getClass() == vector2.getClass());
    }

    public void testMethods() throws Throwable {

        MethodInterceptor interceptor = new TestInterceptor() {

            public Object afterReturn(Object obj, Method method, Object args[], boolean invokedSuper,
                    Object retValFromSuper, java.lang.Throwable e) throws java.lang.Throwable {

                int mod = method.getModifiers();

                if (Modifier.isProtected(mod)) {
                    invokedProtectedMethod = true;
                }

                if (Modifier.isAbstract(mod)) {
                    invokedAbstractMethod = true;
                }

                if (!(Modifier.isProtected(mod) || Modifier.isPublic(mod))) {
                    invokedPackageMethod = true;
                }

                return retValFromSuper;//return the same as supper
            }

        };

        Source source = (Source) Enhancer.create(Source.class, null, interceptor);

        source.callAll();
        assertTrue("protected", invokedProtectedMethod);
        assertTrue("package", invokedPackageMethod);
        assertTrue("abstract", invokedAbstractMethod);
    }

    public void testEnhanced() throws Throwable {

        Source source = (Source) Enhancer.create(Source.class, null, TEST_INTERCEPTOR);

        TestCase.assertTrue("enhance", Source.class != source.getClass());

    }

    public void testFinalizeNotProxied() throws Throwable {
        Source source = (Source) Enhancer.create(Source.class, null, TEST_INTERCEPTOR);

        try {
            Method finalize = source.getClass().getDeclaredMethod("finalize");
            assertNull(
                    "CGLIB should enhanced object should not declare finalize() method so proxy objects are not eligible for finalization, thus faster",
                    finalize);
        } catch (NoSuchMethodException e) {
            // expected
        }
    }

    public void testEnhanceObject() throws Throwable {
        EA obj = new EA();
        EA save = obj;
        obj.setName("herby");
        EA proxy = (EA) Enhancer.create(EA.class, new DelegateInterceptor(save));

        assertEquals("proxy.getName()", "herby", proxy.getName());

        Factory factory = (Factory) proxy;
        assertEquals("((EA)factory.newInstance(factory.getCallbacks())).getName()", "herby",
                ((EA) factory.newInstance(factory.getCallbacks())).getName());
    }

    class DelegateInterceptor implements MethodInterceptor {
        Object delegate;

        DelegateInterceptor(Object delegate) {
            this.delegate = delegate;
        }

        public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy)
                throws Throwable {
            return proxy.invoke(delegate, args);
        }

    }

    public void testEnhanceObjectDelayed() throws Throwable {

        DelegateInterceptor mi = new DelegateInterceptor(null);
        EA proxy = (EA) Enhancer.create(EA.class, mi);
        EA obj = new EA();
        obj.setName("herby");
        mi.delegate = obj;
        assertTrue(proxy.getName().equals("herby"));
    }

    public void testTypes() throws Throwable {

        Source source = (Source) Enhancer.create(Source.class, null, TEST_INTERCEPTOR);
        TestCase.assertTrue("intType", 1 == source.intType(1));
        TestCase.assertTrue("longType", 1L == source.longType(1L));
        TestCase.assertTrue("floatType", 1.1f == source.floatType(1.1f));
        TestCase.assertTrue("doubleType", 1.1 == source.doubleType(1.1));
        TestCase.assertEquals("objectType", "1", source.objectType("1"));
        TestCase.assertEquals("objectType", "", source.toString());
        source.arrayType(new int[] {});

    }

    public void testModifiers() throws Throwable {

        Source source = (Source) Enhancer.create(Source.class, null, TEST_INTERCEPTOR);

        Class enhancedClass = source.getClass();

        assertTrue("isProtected",
                Modifier.isProtected(enhancedClass.getDeclaredMethod("protectedMethod", EMPTY_ARG).getModifiers()));
        int mod = enhancedClass.getDeclaredMethod("packageMethod", EMPTY_ARG).getModifiers();
        assertTrue("isPackage", !(Modifier.isProtected(mod) || Modifier.isPublic(mod)));

        //not sure about this (do we need it for performace ?)
        assertTrue("isFinal", Modifier.isFinal(mod));

        mod = enhancedClass.getDeclaredMethod("synchronizedMethod", EMPTY_ARG).getModifiers();
        assertTrue("isSynchronized", !Modifier.isSynchronized(mod));

    }

    public void testObject() throws Throwable {

        Object source = Enhancer.create(null, null, TEST_INTERCEPTOR);

        assertTrue("parent is object", source.getClass().getSuperclass() == Object.class);

    }

    public void testSystemClassLoader() throws Throwable {

        Object source = enhance(null, null, TEST_INTERCEPTOR, ClassLoader.getSystemClassLoader());
        source.toString();
        assertTrue("SystemClassLoader", source.getClass().getClassLoader() == ClassLoader.getSystemClassLoader());

    }

    public void testCustomClassLoader() throws Throwable {

        ClassLoader custom = new ClassLoader(this.getClass().getClassLoader()) {
        };

        Object source = enhance(null, null, TEST_INTERCEPTOR, custom);
        source.toString();
        assertTrue("Custom classLoader", source.getClass().getClassLoader() == custom);

        custom = new ClassLoader() {
        };

        source = enhance(null, null, TEST_INTERCEPTOR, custom);
        source.toString();
        assertTrue("Custom classLoader", source.getClass().getClassLoader() == custom);

    }

    public void testProxyClassReuseAcrossGC() throws InterruptedException {
        String proxyClassName = null;
        for (int i = 0; i < 50; i++) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(Source.class);
            enhancer.setCallbackFilter(new CallbackFilter() {
                public int accept(Method method) {
                    return 0;
                }

                @Override
                public boolean equals(Object obj) {
                    return true;
                }

                @Override
                public int hashCode() {
                    return 0;
                }
            });
            enhancer.setInterfaces(new Class[] { Serializable.class });
            enhancer.setCallback(new InvocationHandler() {
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    if (method.getDeclaringClass() != Object.class && method.getReturnType() == String.class) {
                        return null;
                    } else {
                        throw new RuntimeException("Do not know what to do.");
                    }
                }
            });
            Source proxy = (Source) enhancer.create();
            String actualProxyClassName = proxy.getClass().getName();
            if (proxyClassName == null) {
                proxyClassName = actualProxyClassName;
            } else {
                assertEquals("GC iteration " + i + ", proxy class should survive GC and be reused even across GC",
                        proxyClassName, actualProxyClassName);
            }
            System.gc();
        }
    }

    /**
     * Verifies that the cache in {@link AbstractClassGenerator} SOURCE doesn't
     * leak class definitions of classloaders that are no longer used.
     */
    public void testSourceCleanAfterClassLoaderDispose() throws Throwable {
        ClassLoader custom = new ClassLoader(this.getClass().getClassLoader()) {

            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                if (EA.class.getName().equals(name)) {
                    InputStream classStream = this.getClass().getResourceAsStream("/net/sf/cglib/proxy/EA.class");
                    byte[] classBytes;
                    try {
                        classBytes = toByteArray(classStream);
                        return this.defineClass(null, classBytes, 0, classBytes.length);
                    } catch (IOException e) {
                        return super.loadClass(name);
                    }
                } else {
                    return super.loadClass(name);
                }
            }
        };

        PhantomReference<ClassLoader> clRef = new PhantomReference<ClassLoader>(custom,
                new ReferenceQueue<ClassLoader>());

        buildAdvised(custom);
        custom = null;

        for (int i = 0; i < 10; ++i) {
            System.gc();
            Thread.sleep(100);
            if (clRef.isEnqueued()) {
                break;
            }
        }
        assertTrue(
                "CGLIB should allow classloaders to be evicted. PhantomReference<ClassLoader> was not cleared after 10 gc cycles,"
                        + "thus it is likely some cache is preventing the class loader to be garbage collected",
                clRef.isEnqueued());

    }

    protected Object buildAdvised(ClassLoader custom)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        final Class<?> eaClassFromCustomClassloader = custom.loadClass(EA.class.getName());

        CallbackFilter callbackFilter = new CallbackFilter() {
            Object advised = eaClassFromCustomClassloader.newInstance();

            public int accept(Method method) {
                return 0;
            }

        };

        // Need to test both orders: "null classloader first" and "null last" just in case
        Object source_ = enhance(eaClassFromCustomClassloader, null, callbackFilter, TEST_INTERCEPTOR, null);
        Object source = enhance(eaClassFromCustomClassloader, null, callbackFilter, TEST_INTERCEPTOR, custom);
        assertSame(
                "Same proxy class is expected since Enhancer with null (default) ClassLoader should use "
                        + " target class.getClassLoader(), thus the same cache key instance should be reused",
                source.getClass(), source_.getClass());

        Object source2 = enhance(eaClassFromCustomClassloader, null, callbackFilter, TEST_INTERCEPTOR, custom);
        assertSame("enhance should return cached Enhancer when calling with same parameters", source.getClass(),
                source2.getClass());

        Object source2_ = enhance(eaClassFromCustomClassloader, null, callbackFilter, TEST_INTERCEPTOR, null);
        assertSame(
                "Same proxy class is expected since Enhancer with null (default) ClassLoader should use "
                        + " target class.getClassLoader(), thus the same cache key instance should be reused",
                source.getClass(), source2_.getClass());

        Object source3 = enhance(eaClassFromCustomClassloader, null, null, TEST_INTERCEPTOR, custom);
        assertNotSame("enhance should return different instance when callbackFilter differs", source.getClass(),
                source3.getClass());

        return source;
    }

    private static byte[] toByteArray(InputStream input) throws IOException {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        int n = 0;

        while (-1 != (n = input.read(buffer))) {
            output.write(buffer, 0, n);
        }

        return output.toByteArray();

    }

    private static class TestFilter implements CallbackFilter {
        private final int pk;

        TestFilter(int pk) {
            this.pk = pk;
        }

        public int accept(Method method) {
            return 0;
        }

        @Override
        public int hashCode() {
            return 1; // Make sure Enhancer uses equals, not just hashCode alone
        }

        @Override
        public boolean equals(Object obj) {
            return obj instanceof TestFilter && ((TestFilter) obj).pk == pk;
        }
    }

    public void testCallbackFilterEqualsVsClassReuse() {
        Callback[] callbacks = new Callback[] { NoOp.INSTANCE };
        Object a = Enhancer.create(Source.class, null, new TestFilter(1), callbacks);
        Object b = Enhancer.create(Source.class, null, new TestFilter(1), callbacks);
        assertSame("Using the same as per .equal() CallbackFilter, thus Enhancer should reuse the same proxy class",
                a.getClass(), b.getClass());
    }

    public void testCallbackFilterNotEqualsVsClassReuse() {
        Callback[] callbacks = new Callback[] { NoOp.INSTANCE };
        Object a = Enhancer.create(Source.class, null, new TestFilter(1), callbacks);
        Object b = Enhancer.create(Source.class, null, new TestFilter(2), callbacks);
        assertNotSame("Using the different CallbackFilter instances, thus Enhancer should generate new proxy class",
                a.getClass(), b.getClass());
    }

    public void testRuntimException() throws Throwable {

        Source source = (Source) Enhancer.create(Source.class, null, TEST_INTERCEPTOR);

        try {

            source.throwIndexOutOfBoundsException();
            fail("must throw an exception");

        } catch (IndexOutOfBoundsException ok) {

        }

    }

    static abstract class CastTest {
        CastTest() {
        }

        abstract int getInt();
    }

    class CastTestInterceptor implements MethodInterceptor {

        public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy)
                throws Throwable {
            return new Short((short) 0);
        }

    }

    public void testCast() throws Throwable {

        CastTest castTest = (CastTest) Enhancer.create(CastTest.class, null, new CastTestInterceptor());

        assertTrue(castTest.getInt() == 0);

    }

    public void testABC() throws Throwable {
        Enhancer.create(EA.class, null, TEST_INTERCEPTOR);
        Enhancer.create(EC1.class, null, TEST_INTERCEPTOR).toString();
        ((EB) Enhancer.create(EB.class, null, TEST_INTERCEPTOR)).finalTest();
        assertTrue("abstract method",
                ((EC1) Enhancer.create(EC1.class, null, TEST_INTERCEPTOR)).compareTo(new EC1()) == -1);
        Enhancer.create(ED.class, null, TEST_INTERCEPTOR).toString();
        Enhancer.create(ClassLoader.class, null, TEST_INTERCEPTOR).toString();
    }

    public static class AroundDemo {
        public String getFirstName() {
            return "Chris";
        }

        public String getLastName() {
            return "Nokleberg";
        }
    }

    public void testAround() throws Throwable {
        AroundDemo demo = (AroundDemo) Enhancer.create(AroundDemo.class, null, new MethodInterceptor() {
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                if (method.getName().equals("getFirstName")) {
                    return "Christopher";
                }
                return proxy.invokeSuper(obj, args);
            }
        });
        assertTrue(demo.getFirstName().equals("Christopher"));
        assertTrue(demo.getLastName().equals("Nokleberg"));
    }

    public static interface TestClone extends Cloneable {
        public Object clone() throws java.lang.CloneNotSupportedException;

    }

    public static class TestCloneImpl implements TestClone {
        public Object clone() throws java.lang.CloneNotSupportedException {
            return super.clone();
        }
    }

    public void testClone() throws Throwable {

        TestClone testClone = (TestClone) Enhancer.create(TestCloneImpl.class, TEST_INTERCEPTOR);
        assertTrue(testClone.clone() != null);

        testClone = (TestClone) Enhancer.create(TestClone.class, new MethodInterceptor() {

            public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy)
                    throws Throwable {
                return proxy.invokeSuper(obj, args);
            }

        });

        assertTrue(testClone.clone() != null);

    }

    public static interface FinalA {
        void foo();
    }

    public static class FinalB implements FinalA {
        final public void foo() {
        }
    }

    public void testFinal() throws Throwable {
        ((FinalA) Enhancer.create(FinalB.class, TEST_INTERCEPTOR)).foo();
    }

    public static interface ConflictA {
        int foo();
    }

    public static interface ConflictB {
        String foo();
    }

    public void testConflict() throws Throwable {
        Object foo = Enhancer.create(Object.class, new Class[] { ConflictA.class, ConflictB.class },
                TEST_INTERCEPTOR);
        ((ConflictA) foo).foo();
        ((ConflictB) foo).foo();
    }

    // TODO: make this work again
    public void testArgInit() throws Throwable {

        Enhancer e = new Enhancer();
        e.setSuperclass(ArgInit.class);
        e.setCallbackType(MethodInterceptor.class);
        Class f = e.createClass();
        ArgInit a = (ArgInit) ReflectUtils.newInstance(f, new Class[] { String.class }, new Object[] { "test" });
        assertEquals("test", a.toString());
        ((Factory) a).setCallback(0, TEST_INTERCEPTOR);
        assertEquals("test", a.toString());

        Callback[] callbacks = new Callback[] { TEST_INTERCEPTOR };
        ArgInit b = (ArgInit) ((Factory) a).newInstance(new Class[] { String.class }, new Object[] { "test2" },
                callbacks);
        assertEquals("test2", b.toString());
        try {
            ((Factory) a).newInstance(new Class[] { String.class, String.class }, new Object[] { "test" },
                    callbacks);
            fail("must throw exception");
        } catch (IllegalArgumentException iae) {

        }
    }

    public static class Signature {
        public int interceptor() {
            return 42;
        }
    }

    public void testSignature() throws Throwable {
        Signature sig = (Signature) Enhancer.create(Signature.class, TEST_INTERCEPTOR);
        assertTrue(((Factory) sig).getCallback(0) == TEST_INTERCEPTOR);
        assertTrue(sig.interceptor() == 42);
    }

    public abstract static class AbstractMethodCallInConstructor {
        public AbstractMethodCallInConstructor() {
            foo();
        }

        public abstract void foo();
    }

    public void testAbstractMethodCallInConstructor() throws Throwable {
        AbstractMethodCallInConstructor obj = (AbstractMethodCallInConstructor) Enhancer
                .create(AbstractMethodCallInConstructor.class, TEST_INTERCEPTOR);
        obj.foo();
    }

    public void testProxyIface() throws Throwable {
        final DI1 other = new DI1() {
            public String herby() {
                return "boop";
            }
        };
        DI1 d = (DI1) Enhancer.create(DI1.class, new MethodInterceptor() {
            public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy)
                    throws Throwable {
                return proxy.invoke(other, args);
            }
        });
        assertTrue("boop".equals(d.herby()));
    }

    static class NamingPolicyDummy {
    }

    public void testNamingPolicy() throws Throwable {
        Enhancer e = new Enhancer();
        e.setSuperclass(NamingPolicyDummy.class);
        e.setUseCache(false);
        e.setUseFactory(false);
        e.setNamingPolicy(new DefaultNamingPolicy() {
            public String getTag() {
                return "ByHerby";
            }

            public String toString() {
                return getTag();
            }
        });
        e.setCallbackType(MethodInterceptor.class);
        Class proxied = e.createClass();
        final boolean[] ran = new boolean[1];
        Enhancer.registerStaticCallbacks(proxied, new Callback[] { new MethodInterceptor() {
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                ran[0] = true;
                assertTrue(proxy.getSuperFastClass().getClass().getName().indexOf("$FastClassByHerby$") >= 0);
                return proxy.invokeSuper(obj, args);
            }
        } });
        NamingPolicyDummy dummy = (NamingPolicyDummy) proxied.newInstance();
        dummy.toString();
        assertTrue(ran[0]);
    }

    public void testBadNamingPolicyStillReservesNames() throws Throwable {
        Enhancer e = new Enhancer();
        e.setUseCache(false);
        e.setCallback(NoOp.INSTANCE);
        e.setClassLoader(new ClassLoader(this.getClass().getClassLoader()) {
        });
        e.setNamingPolicy(new NamingPolicy() {
            public String getClassName(String prefix, String source, Object key, Predicate names) {
                return "net.sf.cglib.empty.Object$$ByDerby$$123";
            }
        });
        Class proxied = e.create().getClass();
        final String name = proxied.getCanonicalName();
        final boolean[] ran = new boolean[1];
        e.setNamingPolicy(new NamingPolicy() {
            public String getClassName(String prefix, String source, Object key, Predicate names) {
                ran[0] = true;
                assertTrue(names.evaluate(name));
                return name + "45";
            }
        });
        Class proxied2 = e.create().getClass();
        assertTrue(ran[0]);
        assertEquals(name + "45", proxied2.getCanonicalName());
    }

    /**
     * In theory, every sane implementation of {@link NamingPolicy} should check if the class name is occupied,
     * however, in practice there are implementations in the wild that just return whatever they feel is good.
     *
     * @throws Throwable if something wrong happens
     */
    public void testNamingPolicyThatReturnsConstantNames() throws Throwable {
        Enhancer e = new Enhancer();
        final String desiredClassName = "net.sf.cglib.empty.Object$$42";
        e.setCallback(NoOp.INSTANCE);
        e.setClassLoader(new ClassLoader(this.getClass().getClassLoader()) {
        });
        e.setNamingPolicy(new NamingPolicy() {
            public String getClassName(String prefix, String source, Object key, Predicate names) {
                return desiredClassName;
            }
        });
        Class proxied = e.create().getClass();
        assertEquals("Class name should match the one returned by NamingPolicy", desiredClassName,
                proxied.getName());
    }

    public static Object enhance(Class cls, Class interfaces[], Callback callback, ClassLoader loader) {
        Enhancer e = new Enhancer();
        e.setSuperclass(cls);
        e.setInterfaces(interfaces);
        e.setCallback(callback);
        e.setClassLoader(loader);
        return e.create();
    }

    public static Object enhance(Class cls, Class interfaces[], CallbackFilter callbackFilter, Callback callback,
            ClassLoader loader) {
        Enhancer e = new Enhancer();
        e.setSuperclass(cls);
        e.setInterfaces(interfaces);
        e.setCallbackFilter(callbackFilter);
        e.setCallback(callback);
        e.setClassLoader(loader);
        return e.create();
    }

    public interface PublicClone extends Cloneable {
        Object clone() throws CloneNotSupportedException;
    }

    public void testNoOpClone() throws Exception {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(PublicClone.class);
        enhancer.setCallback(NoOp.INSTANCE);
        ((PublicClone) enhancer.create()).clone();
    }

    public void testNoFactory() throws Exception {
        noFactoryHelper();
        noFactoryHelper();
    }

    private void noFactoryHelper() {
        MethodInterceptor mi = new MethodInterceptor() {
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                return "Foo";
            }
        };
        Enhancer enhancer = new Enhancer();
        enhancer.setUseFactory(false);
        enhancer.setSuperclass(AroundDemo.class);
        enhancer.setCallback(mi);
        AroundDemo obj = (AroundDemo) enhancer.create();
        assertTrue(obj.getFirstName().equals("Foo"));
        assertTrue(!(obj instanceof Factory));
    }

    interface MethDec {
        void foo();
    }

    abstract static class MethDecImpl implements MethDec {
    }

    public void testMethodDeclarer() throws Exception {
        final boolean[] result = new boolean[] { false };
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(MethDecImpl.class);
        enhancer.setCallback(new MethodInterceptor() {
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

                result[0] = method.getDeclaringClass().getName().equals(MethDec.class.getName());
                return null;
            }
        });
        ((MethDecImpl) enhancer.create()).foo();
        assertTrue(result[0]);
    }

    interface ClassOnlyX {
    }

    public void testClassOnlyFollowedByInstance() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(ClassOnlyX.class);
        enhancer.setCallbackType(NoOp.class);
        Class type = enhancer.createClass();

        enhancer = new Enhancer();
        enhancer.setSuperclass(ClassOnlyX.class);
        enhancer.setCallback(NoOp.INSTANCE);
        Object instance = enhancer.create();

        assertTrue(instance instanceof ClassOnlyX);
        assertEquals("types of enhancer.createClass() and enhancer.create().getClass() should match", type,
                instance.getClass());
    }

    public void testSql() {
        Enhancer.create(null, new Class[] { java.sql.PreparedStatement.class }, TEST_INTERCEPTOR);
    }

    public void testEquals() throws Exception {
        final boolean[] result = new boolean[] { false };
        EqualsInterceptor intercept = new EqualsInterceptor();
        Object obj = Enhancer.create(null, intercept);
        obj.equals(obj);
        assertTrue(intercept.called);
    }

    public static class EqualsInterceptor implements MethodInterceptor {
        final static Method EQUALS_METHOD = ReflectUtils.findMethod("Object.equals(Object)");
        boolean called;

        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            if (method.equals(EQUALS_METHOD)) {
                return proxy.invoke(this, args);
            } else {
                return proxy.invokeSuper(obj, args);
            }
        }

        public boolean equals(Object other) {
            called = true;
            return super.equals(other);
        }
    }

    private static interface ExceptionThrower {
        void throwsThrowable(int arg) throws Throwable;

        void throwsException(int arg) throws Exception;

        void throwsNothing(int arg);
    }

    private static class MyThrowable extends Throwable {
    }

    private static class MyException extends Exception {
    }

    private static class MyRuntimeException extends RuntimeException {
    }

    public void testExceptions() {
        Enhancer e = new Enhancer();
        e.setSuperclass(ExceptionThrower.class);
        e.setCallback(new MethodInterceptor() {
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                switch (((Integer) args[0]).intValue()) {
                case 1:
                    throw new MyThrowable();
                case 2:
                    throw new MyException();
                case 3:
                    throw new MyRuntimeException();
                default:
                    return null;
                }
            }
        });
        ExceptionThrower et = (ExceptionThrower) e.create();
        try {
            et.throwsThrowable(1);
        } catch (MyThrowable t) {
        } catch (Throwable t) {
            fail();
        }
        try {
            et.throwsThrowable(2);
        } catch (MyException t) {
        } catch (Throwable t) {
            fail();
        }
        try {
            et.throwsThrowable(3);
        } catch (MyRuntimeException t) {
        } catch (Throwable t) {
            fail();
        }

        try {
            et.throwsException(1);
        } catch (Throwable t) {
            assertTrue(t instanceof MyThrowable);
        }
        try {
            et.throwsException(2);
        } catch (MyException t) {
        } catch (Throwable t) {
            fail();
        }
        try {
            et.throwsException(3);
        } catch (MyRuntimeException t) {
        } catch (Throwable t) {
            fail();
        }
        try {
            et.throwsException(4);
        } catch (Throwable t) {
            fail();
        }

        try {
            et.throwsNothing(1);
        } catch (Throwable t) {
            assertTrue(t instanceof MyThrowable);
        }
        try {
            et.throwsNothing(2);
        } catch (Exception t) {
            assertTrue(t instanceof MyException);
        }
        try {
            et.throwsNothing(3);
        } catch (MyRuntimeException t) {
        } catch (Throwable t) {
            fail();
        }
        try {
            et.throwsNothing(4);
        } catch (Throwable t) {
            fail();
        }
    }

    public void testUnusedCallback() {
        Enhancer e = new Enhancer();
        e.setCallbackTypes(new Class[] { MethodInterceptor.class, NoOp.class });
        e.setCallbackFilter(new CallbackFilter() {
            public int accept(Method method) {
                return 0;
            }
        });
        e.createClass();
    }

    private static ArgInit newArgInit(Class clazz, String value) {
        return (ArgInit) ReflectUtils.newInstance(clazz, new Class[] { String.class }, new Object[] { value });
    }

    private static class StringValue implements MethodInterceptor {
        private String value;

        public StringValue(String value) {
            this.value = value;
        }

        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) {
            return value;
        }
    }

    public void testRegisterCallbacks() throws InterruptedException {
        Enhancer e = new Enhancer();
        e.setSuperclass(ArgInit.class);
        e.setCallbackType(MethodInterceptor.class);
        e.setUseFactory(false);
        final Class clazz = e.createClass();

        assertTrue(!Factory.class.isAssignableFrom(clazz));
        assertEquals("test", newArgInit(clazz, "test").toString());

        Enhancer.registerCallbacks(clazz, new Callback[] { new StringValue("fizzy") });
        assertEquals("fizzy", newArgInit(clazz, "test").toString());
        assertEquals("fizzy", newArgInit(clazz, "test").toString());

        Enhancer.registerCallbacks(clazz, new Callback[] { null });
        assertEquals("test", newArgInit(clazz, "test").toString());

        Enhancer.registerStaticCallbacks(clazz, new Callback[] { new StringValue("soda") });
        assertEquals("test", newArgInit(clazz, "test").toString());

        Enhancer.registerCallbacks(clazz, null);
        assertEquals("soda", newArgInit(clazz, "test").toString());

        Thread thread = new Thread() {
            public void run() {
                assertEquals("soda", newArgInit(clazz, "test").toString());
            }
        };
        thread.start();
        thread.join();

        // clean-up static callback
        Enhancer.registerStaticCallbacks(clazz, null);
        assertEquals("test", newArgInit(clazz, "test").toString());
    }

    public void perform(ClassLoader loader) throws Exception {

        enhance(Source.class, null, TEST_INTERCEPTOR, loader);

    }

    public void testCallbackHelper() {
        final ArgInit delegate = new ArgInit("helper");
        Class sc = ArgInit.class;
        Class[] interfaces = new Class[] { DI1.class, DI2.class };

        CallbackHelper helper = new CallbackHelper(sc, interfaces) {
            protected Object getCallback(final Method method) {
                return new FixedValue() {
                    public Object loadObject() {
                        return "You called method " + method.getName();
                    }
                };
            }
        };

        Enhancer e = new Enhancer();
        e.setSuperclass(sc);
        e.setInterfaces(interfaces);
        e.setCallbacks(helper.getCallbacks());
        e.setCallbackFilter(helper);

        ArgInit proxy = (ArgInit) e.create(new Class[] { String.class }, new Object[] { "whatever" });
        assertEquals("You called method toString", proxy.toString());
        assertEquals("You called method herby", ((DI1) proxy).herby());
        assertEquals("You called method derby", ((DI2) proxy).derby());
    }

    public void testSerialVersionUID() throws Exception {
        Long suid = new Long(0xABBADABBAD00L);

        Enhancer e = new Enhancer();
        e.setSerialVersionUID(suid);
        e.setCallback(NoOp.INSTANCE);
        Object obj = e.create();

        Field field = obj.getClass().getDeclaredField("serialVersionUID");
        field.setAccessible(true);
        assertEquals(suid, field.get(obj));
    }

    interface ReturnTypeA {
        int foo(String x);
    }

    interface ReturnTypeB {
        String foo(String x);
    }

    public void testMethodsDifferingByReturnTypeOnly() throws IOException {
        Enhancer e = new Enhancer();
        e.setInterfaces(new Class[] { ReturnTypeA.class, ReturnTypeB.class });
        e.setCallback(new MethodInterceptor() {
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                if (method.getReturnType().equals(String.class))
                    return "hello";
                return new Integer(42);
            }
        });
        Object obj = e.create();
        assertEquals(42, ((ReturnTypeA) obj).foo("foo"));
        assertEquals("hello", ((ReturnTypeB) obj).foo("foo"));
        assertEquals(-1, FastClass.create(obj.getClass()).getIndex("foo", new Class[] { String.class }));
    }

    public static class ConstructorCall {
        private String x;

        public ConstructorCall() {
            x = toString();
        }

        public String toString() {
            return "foo";
        }
    }

    public void testInterceptDuringConstruction() {
        FixedValue fixedValue = new FixedValue() {
            public Object loadObject() {
                return "bar";
            }
        };

        Enhancer e = new Enhancer();
        e.setSuperclass(ConstructorCall.class);
        e.setCallback(fixedValue);
        assertEquals("bar", ((ConstructorCall) e.create()).x);

        e = new Enhancer();
        e.setSuperclass(ConstructorCall.class);
        e.setCallback(fixedValue);
        e.setInterceptDuringConstruction(false);
        assertEquals("foo", ((ConstructorCall) e.create()).x);
    }

    void assertThreadLocalCallbacks(Class cls) throws Exception {

        Field field = cls.getDeclaredField("CGLIB$THREAD_CALLBACKS");
        field.setAccessible(true);

        assertNull(((ThreadLocal) field.get(null)).get());
    }

    public void testThreadLocalCleanup1() throws Exception {

        Enhancer e = new Enhancer();
        e.setUseCache(false);
        e.setCallbackType(NoOp.class);
        Class cls = e.createClass();

        assertThreadLocalCallbacks(cls);

    }

    public void testThreadLocalCleanup2() throws Exception {

        Enhancer e = new Enhancer();
        e.setCallback(NoOp.INSTANCE);
        Object obj = e.create();

        assertThreadLocalCallbacks(obj.getClass());

    }

    public void testThreadLocalCleanup3() throws Exception {

        Enhancer e = new Enhancer();
        e.setCallback(NoOp.INSTANCE);
        Factory obj = (Factory) e.create();
        obj.newInstance(NoOp.INSTANCE);

        assertThreadLocalCallbacks(obj.getClass());

    }

    public void testUseCache() throws Exception {
        Enhancer noCache = new Enhancer();
        noCache.setUseCache(false);
        noCache.setSuperclass(Foo.class);
        noCache.setCallback(NoOp.INSTANCE);
        Class<?> a = noCache.create().getClass();
        Class<?> b = noCache.create().getClass();
        assertNotSame(a, b);

        Enhancer withCache = new Enhancer();
        withCache.setUseCache(true);
        withCache.setSuperclass(Foo.class);
        withCache.setCallback(NoOp.INSTANCE);
        Class<?> c = withCache.create().getClass();
        Class<?> d = withCache.create().getClass();
        assertNotSame(a, c);
        assertNotSame(b, c);
        assertSame(c, d);
    }

    static class Foo {
        Foo() {
        }
    }

    public void testBridgeForcesInvokeVirtual() {
        List<Class> retTypes = new ArrayList<Class>();
        List<Class> paramTypes = new ArrayList<Class>();
        Interceptor interceptor = new Interceptor(retTypes, paramTypes);

        Enhancer e = new Enhancer();
        e.setSuperclass(Impl.class);
        e.setCallbackFilter(new CallbackFilter() {
            public int accept(Method method) {
                return method.getDeclaringClass() != Object.class ? 0 : 1;
            }
        });
        e.setCallbacks(new Callback[] { interceptor, NoOp.INSTANCE });
        // We expect the bridge ('ret') to be called & forward us to the non-bridge 'erased'
        Interface intf = (Interface) e.create();
        intf.aMethod(null);
        // Make sure the right things got called in the right order:
        assertEquals(Arrays.asList(RetType.class, ErasedType.class), retTypes);

        // Validate calling the refined just gives us that.
        retTypes.clear();
        Impl impl = (Impl) intf;
        impl.aMethod((Refined) null);
        assertEquals(Arrays.asList(Refined.class), retTypes);

        // When calling from the impl, we are dispatched directly to the non-bridge,
        // because that's just how it works.
        retTypes.clear();
        impl.aMethod((RetType) null);
        assertEquals(Arrays.asList(ErasedType.class), retTypes);

        // Do a whole bunch of checks for the other methods too

        paramTypes.clear();
        intf.intReturn(null);
        assertEquals(Arrays.asList(RetType.class, ErasedType.class), paramTypes);

        paramTypes.clear();
        intf.voidReturn(null);
        assertEquals(Arrays.asList(RetType.class, ErasedType.class), paramTypes);

        paramTypes.clear();
        intf.widenReturn(null);
        assertEquals(Arrays.asList(RetType.class, ErasedType.class), paramTypes);
    }

    public void testBridgeForcesInvokeVirtualEvenWithoutInterceptingBridge() {
        List<Class> retTypes = new ArrayList<Class>();
        Interceptor interceptor = new Interceptor(retTypes);

        Enhancer e = new Enhancer();
        e.setSuperclass(Impl.class);
        e.setCallbackFilter(new CallbackFilter() {
            public int accept(Method method) {
                // Ideally this would be:
                // return !method.isBridge() && method.getDeclaringClass() != Object.class ? 0 : 1;
                // But Eclipse sometimes labels the wrong things as bridge methods, so we're more
                // explicit:
                return method.getDeclaringClass() != Object.class && method.getReturnType() != RetType.class ? 0
                        : 1;
            }
        });
        e.setCallbacks(new Callback[] { interceptor, NoOp.INSTANCE });
        // We expect the bridge ('ret') to be called & forward us to non-bridge ('erased'),
        // and we only intercept on the non-bridge.
        Interface intf = (Interface) e.create();
        intf.aMethod(null);
        assertEquals(Arrays.asList(ErasedType.class), retTypes);

        // Validate calling the refined just gives us that.
        retTypes.clear();
        Impl impl = (Impl) intf;
        impl.aMethod((Refined) null);
        assertEquals(Arrays.asList(Refined.class), retTypes);

        // Make sure we still get our non-bride interception if we didn't intercept the bridge.
        retTypes.clear();
        impl.aMethod((RetType) null);
        assertEquals(Arrays.asList(ErasedType.class), retTypes);
    }

    public void testReverseBridge() {
        List<Class> retTypes = new ArrayList<Class>();
        Interceptor interceptor = new Interceptor(retTypes);

        Enhancer e = new Enhancer();
        e.setSuperclass(ReverseImpl.class);
        e.setCallbackFilter(new CallbackFilter() {
            public int accept(Method method) {
                return method.getDeclaringClass() != Object.class ? 0 : 1;
            }
        });
        e.setCallbacks(new Callback[] { interceptor, NoOp.INSTANCE });
        // We expect the bridge ('erased') to be called & forward us to 'ret' (non-bridge)
        ReverseSuper superclass = (ReverseSuper) e.create();
        superclass.aMethod(null, null, null, null);
        assertEquals(Arrays.asList(ErasedType.class, RetType.class), retTypes);

        // Calling the Refined type gives us just that.
        retTypes.clear();
        ReverseImpl impl2 = (ReverseImpl) superclass;
        impl2.aMethod(null, (Refined) null, null, null);
        assertEquals(Arrays.asList(Refined.class), retTypes);

        retTypes.clear();
        impl2.aMethod(null, (RetType) null, null, null);
        assertEquals(Arrays.asList(RetType.class), retTypes);
    }

    public void testBridgeForMoreViz() {
        List<Class> retTypes = new ArrayList<Class>();
        List<Class> paramTypes = new ArrayList<Class>();
        Interceptor interceptor = new Interceptor(retTypes, paramTypes);

        Enhancer e = new Enhancer();
        e.setSuperclass(PublicViz.class);
        e.setCallbackFilter(new CallbackFilter() {
            public int accept(Method method) {
                return method.getDeclaringClass() != Object.class ? 0 : 1;
            }
        });
        e.setCallbacks(new Callback[] { interceptor, NoOp.INSTANCE });

        VizIntf intf = (VizIntf) e.create();
        intf.aMethod(null);
        assertEquals(Arrays.asList(Concrete.class), paramTypes);
    }

    public void testBridgeParameterCheckcast() throws Exception {

        // If the compiler used for Z omits the bridge method, and X is compiled with javac,
        // javac will generate an invokespecial bridge in X.

        // public interface I<A, B> {
        //   public A f(B b);
        // }
        // public abstract class Z<U extends Number> implements I<U, Long> {
        //   public U f(Long id) {
        //     return null;
        //   }
        // }
        // public class X extends Z<Integer> {}

        final Map<String, byte[]> classes = new HashMap<String, byte[]>();

        {
            ClassWriter cw = new ClassWriter(0);
            cw.visit(49, Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT | Opcodes.ACC_INTERFACE, "I",
                    "<A:Ljava/lang/Object;B:Ljava/lang/Object;>Ljava/lang/Object;", "java/lang/Object", null);
            {
                MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT, "f",
                        "(Ljava/lang/Object;)Ljava/lang/Object;", "(TB;)TA;", null);
                mv.visitEnd();
            }
            cw.visitEnd();
            classes.put("I.class", cw.toByteArray());
        }
        {
            ClassWriter cw = new ClassWriter(0);
            cw.visit(49, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER | Opcodes.ACC_ABSTRACT, "Z",
                    "<U:Ljava/lang/Number;>Ljava/lang/Object;LI<TU;Ljava/lang/String;>;", "java/lang/Object",
                    new String[] { "I" });
            {
                MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
                mv.visitCode();
                mv.visitVarInsn(Opcodes.ALOAD, 0);
                mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
                mv.visitInsn(Opcodes.RETURN);
                mv.visitMaxs(1, 1);
                mv.visitEnd();
            }
            {
                MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "f", "(Ljava/lang/String;)Ljava/lang/Number;",
                        "(Ljava/lang/String;)TU;", null);
                mv.visitCode();
                mv.visitInsn(Opcodes.ACONST_NULL);
                mv.visitInsn(Opcodes.ARETURN);
                mv.visitMaxs(1, 2);
                mv.visitEnd();
            }
            cw.visitEnd();
            classes.put("Z.class", cw.toByteArray());
        }
        {
            ClassWriter cw = new ClassWriter(0);
            cw.visit(49, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, "X", "LZ<Ljava/lang/Integer;>;", "Z", null);
            {
                MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
                mv.visitCode();
                mv.visitVarInsn(Opcodes.ALOAD, 0);
                mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "Z", "<init>", "()V", false);
                mv.visitInsn(Opcodes.RETURN);
                mv.visitMaxs(1, 1);
                mv.visitEnd();
            }
            {
                MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_BRIDGE | Opcodes.ACC_SYNTHETIC,
                        "f", "(Ljava/lang/Object;)Ljava/lang/Object;", null, null);
                mv.visitCode();
                mv.visitVarInsn(Opcodes.ALOAD, 0);
                mv.visitVarInsn(Opcodes.ALOAD, 1);
                mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/String");
                mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "Z", "f", "(Ljava/lang/String;)Ljava/lang/Number;",
                        false);
                mv.visitInsn(Opcodes.ARETURN);
                mv.visitMaxs(2, 2);
                mv.visitEnd();
            }

            cw.visitEnd();

            classes.put("X.class", cw.toByteArray());
        }

        ClassLoader classLoader = new ClassLoader(getClass().getClassLoader()) {
            @Override
            public InputStream getResourceAsStream(String name) {
                InputStream is = super.getResourceAsStream(name);
                if (is != null) {
                    return is;
                }
                if (classes.containsKey(name)) {
                    return new ByteArrayInputStream(classes.get(name));
                }
                return null;
            }

            public Class findClass(String name) throws ClassNotFoundException {
                byte[] ba = classes.get(name.replace('.', '/') + ".class");
                if (ba != null) {
                    return defineClass(name, ba, 0, ba.length);
                }
                throw new ClassNotFoundException(name);
            }
        };

        List<Class> retTypes = new ArrayList<Class>();
        List<Class> paramTypes = new ArrayList<Class>();
        Interceptor interceptor = new Interceptor(retTypes, paramTypes);

        Enhancer e = new Enhancer();
        e.setClassLoader(classLoader);
        e.setSuperclass(classLoader.loadClass("X"));
        e.setCallback(interceptor);

        Object c = e.create();

        for (Method m : c.getClass().getDeclaredMethods()) {
            if (m.getName().equals("f") && m.getReturnType().equals(Object.class)) {
                m.invoke(c, new Object[] { null });
            }
        }

        // f(Object)Object should bridge to f(Number)String
        assertEquals(Arrays.asList(Object.class, Number.class), retTypes);
        assertEquals(Arrays.asList(Object.class, String.class), paramTypes);
    }

    static class ErasedType {
    }

    static class RetType extends ErasedType {
    }

    static class Refined extends RetType {
    }

    static abstract class Superclass<T extends ErasedType> {
        // Check narrowing return value & parameters
        public T aMethod(T t) {
            return null;
        }

        // Check void return value
        public void voidReturn(T t) {
        }

        // Check primitive return value
        public int intReturn(T t) {
            return 1;
        }

        // Check widening return value
        public RetType widenReturn(T t) {
            return null;
        }
    }

    public interface Interface { // the usage of the interface forces the bridge
        RetType aMethod(RetType obj);

        void voidReturn(RetType obj);

        int intReturn(RetType obj);

        // a wider type than in superclass
        ErasedType widenReturn(RetType obj);
    }

    public static class Impl extends Superclass<RetType> implements Interface {
        // An even more narrowed type, just to make sure
        // it doesn't confuse us.
        public Refined aMethod(Refined obj) {
            return null;
        }
    }

    // Another set of classes -- this time with the bridging in reverse,
    // to make sure that if we define the concrete type, a bridge
    // is created to call it from an erased type.
    static abstract class ReverseSuper<T extends ErasedType> {
        // the various parameters are to make sure we only
        // change signature when we have to -- only 'c' goes
        // from ErasedType -> RetType
        public T aMethod(Concrete b, T c, RetType d, ErasedType e) {
            return null;
        }
    }

    static class Concrete {
    }

    static class ReverseImpl extends ReverseSuper<RetType> {
        public Refined aMethod(Concrete b, Refined c, RetType d, ErasedType e) {
            return null;
        }

        public RetType aMethod(Concrete b, RetType c, RetType d, ErasedType e) {
            return null;
        }
    }

    public interface VizIntf {
        public void aMethod(Concrete a);
    }

    static abstract class PackageViz implements VizIntf {
        public void aMethod(Concrete e) {
        }
    }

    // inherits aMethod from PackageViz, but bridges to make it
    // publicly accessible.  the bridge here has the same
    // target signature, so it absolutely requires invokespecial,
    // otherwise we recurse forever.
    public static class PublicViz extends PackageViz implements VizIntf {
    }

    private static class Interceptor implements MethodInterceptor {
        private final List<Class> retList;
        private final List<Class> paramList;

        public Interceptor(List<Class> retList) {
            this(retList, new ArrayList<Class>());
        }

        public Interceptor(List<Class> retList, List<Class> paramList) {
            this.retList = retList;
            this.paramList = paramList;
        }

        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            retList.add(method.getReturnType());
            if (method.getParameterTypes().length > 0) {
                paramList.add(method.getParameterTypes()[0]);
            }
            return proxy.invokeSuper(obj, args);
        }
    }

}