/*
 * Created on 03-May-2004
 * 
 * (c) 2003-2005 ThoughtWorks Ltd
 *
 * See license.txt for license details
 */
package com.thoughtworks.proxy.factory;

import com.thoughtworks.proxy.Invoker;
import com.thoughtworks.proxy.ProxyFactory;
import com.thoughtworks.proxy.toys.nullobject.Null;

import net.sf.cglib.core.CodeGenerationException;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.InvocationHandler;
import net.sf.cglib.proxy.Proxy;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;


/**
 * A {@link com.thoughtworks.proxy.ProxyFactory} based on <a href="http://cglib.sourceforge.net/">CGLIB</a>.
 * 
 * @author Aslak Helles&oslash;y
 * @since 0.1
 * @see com.thoughtworks.proxy.factory
 */
public class CglibProxyFactory extends AbstractProxyFactory {
    private static final long serialVersionUID = -5615928639194345818L;
    private ProxyFactory standardProxyFactory = new StandardProxyFactory();
    // @todo Make thread-safe.
    // Keeps track of what is currently being created - to avoid infinite recursion
    private List creating = new ArrayList();

    /**
     * The native invocation handler.
     * 
     * @since 0.1
     */
    static class CGLIBInvocationHandlerAdapter extends CoincidentalInvocationHandlerAdapter implements InvocationHandler {
        private static final long serialVersionUID = 418834172207536454L;

        /**
         * Construct a CGLIBInvocationHandlerAdapter.
         * 
         * @param invoker the wrapping invoker instance
         */
        public CGLIBInvocationHandlerAdapter(Invoker invoker) {
            super(invoker);
        }
    }

    private static final Map boxedClasses = new HashMap();

    static {
        boxedClasses.put(Boolean.TYPE, Boolean.class);
        boxedClasses.put(Integer.TYPE, Integer.class);
        boxedClasses.put(Byte.TYPE, Byte.class);
        boxedClasses.put(Short.TYPE, Short.class);
        boxedClasses.put(Long.TYPE, Long.class);
        boxedClasses.put(Double.TYPE, Double.class);
        boxedClasses.put(Character.TYPE, Character.class);
        boxedClasses.put(Float.TYPE, Float.class);
    }

    /**
     * {@inheritDoc}
     * <p>
     * Note: If any type the proxy instance must fullfill are all interfaces, the factory will currently create a proxy based on
     * the JDK.
     * </p>
     * 
     * @since 0.1
     */
    public Object createProxy(final Class[] types, final Invoker invoker) {
        final Class clazz = getSingleClass(types);
        if (clazz == null) {
            // slightly faster
            return standardProxyFactory.createProxy(types, invoker);
        }
        final Class[] interfaces = getInterfaces(types);
        final Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setInterfaces(interfaces);
        enhancer.setCallback(new CGLIBInvocationHandlerAdapter(invoker));
        try {
            return enhancer.create();
        } catch (CodeGenerationException e) { // cglib 2.0
        } catch (IllegalArgumentException e) { // cglib 2.0.2
        } catch (NoSuchMethodError e) {
        }
        return createWithConstructor(clazz, enhancer);
    }

    private Class[] getInterfaces(final Class[] types) {
        final List interfaces = new ArrayList(Arrays.asList(types));
        for (final Iterator iterator = interfaces.iterator(); iterator.hasNext();) {
            final Class clazz = (Class)iterator.next();
            if (!clazz.isInterface()) {
                iterator.remove();
            }
        }
        interfaces.add(InvokerReference.class);
        return (Class[])interfaces.toArray(new Class[interfaces.size()]);
    }

    private Class getSingleClass(final Class[] types) {
        for (int i = 0; i < types.length; i++) {
            final Class type = types[i];
            if (!type.isInterface()) {
                return type;
            }
        }
        return null;
    }

    private Object createWithConstructor(final Class type, final Enhancer enhancer) {
        final Constructor constructor = getConstructor(type);
        final Class[] params = constructor.getParameterTypes();
        final Object[] args = new Object[params.length];
        for (int i = 0; i < args.length; i++) {
            if (!creating.contains(params[i])) {
                creating.add(params[i]);
                args[i] = Null.object(params[i], this);
                creating.remove(params[i]);
            } else {
                args[i] = null;
            }
        }
        final Object result = enhancer.create(params, args);
        return result;
    }

    private Constructor getConstructor(final Class type) {
        Constructor constructor = null;
        try {
            constructor = type.getConstructor(null);
        } catch (NoSuchMethodException e) {
            constructor = type.getConstructors()[0];

        }
        return constructor;
    }

    public boolean canProxy(final Class type) {
        int modifiers = type.getModifiers();
        return !Modifier.isFinal(modifiers);
    }

    public boolean isProxyClass(final Class type) {
        return Factory.class.isAssignableFrom(type)
                || (!type.equals(Object.class) && Proxy.isProxyClass(type))
                || standardProxyFactory.isProxyClass(type);
    }
}
@@ -1104,231 +1104,151 @@
 ate 
-ProxyFactory standardProxyFactory = new StandardProxyFactory();%0D%0A    // @todo Make thread-safe.%0D%0A    // Keeps track of what is currently being created - to avoid infinite recursion%0D%0A    private List creating = new ArrayList
+static final ThreadLocal cycleGuard = new ThreadLocal();%0D%0A    private static final ProxyFactory standardProxyFactory = new StandardProxyFactory
 ();%0D
@@ -4443,24 +4443,177 @@
 s.length%5D;%0D%0A
+        if (cycleGuard.get() == null) %7B%0D%0A            cycleGuard.set(new ArrayList());%0D%0A        %7D%0D%0A        final List creating = (List)cycleGuard.get();%0D%0A
         for 
@@ -4733,32 +4733,59 @@
 dd(params%5Bi%5D);%0D%0A
+                try %7B%0D%0A    
                 
@@ -4821,24 +4821,57 @@
 i%5D, this);%0D%0A
+                %7D finally %7B%0D%0A    
             
@@ -4895,32 +4895,51 @@
 ve(params%5Bi%5D);%0D%0A
+                %7D%0D%0A
             %7D el

Make CglibProxyFactory thread-safe and avoid unnecessary object instantiation.