package de.skuzzle.inject.proxy;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import com.google.common.base.Preconditions;
import com.google.inject.Injector;
import com.google.inject.Provider;
import com.google.inject.internal.Errors;

import net.sf.cglib.core.DefaultNamingPolicy;
import net.sf.cglib.core.NamingPolicy;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.InvocationHandler;

/**
 * Builds a proxy instance which is backed by a scoped provider.
 *
 * @author Simon Taddiken
 * @param <T> Type of the proxy to create.
 */
final class InstanceBuilder<T> {

    /**
     * The unique callback index to which each method in the proxy object is
     * mapped.
     */
    private static final int CALLBACK_INDEX = 0;

    /** Maps all methods to index {@link #CALLBACK_INDEX}. */
    private static final CallbackFilter ZERO_CALLBACK_FILTER = new CallbackFilter() {
        @Override
        public int accept(Method method) {
            return CALLBACK_INDEX;
        }
    };

    /** Naming strategy for our enhancer */
    private static final NamingPolicy ENHANCER_NAMING = new DefaultNamingPolicy() {

        @Override
        protected String getTag() {
            return "ByGuice";
        };

        @Override
        public String getClassName(String prefix, String source, Object key,
                net.sf.cglib.core.Predicate names) {
            return super.getClassName(prefix, "ScopedProxy", key, names);
        };

    };

    private final Enhancer enhancer;
    private Callback dispatcher;
    private ConstructionStrategy constructionStrategy =
            ConstructionStrategies.NULL_VALUES;

    private InstanceBuilder(Class<T> superType) {
        this.enhancer = new Enhancer();
        this.enhancer.setSuperclass(superType);
        this.enhancer.setUseFactory(true);
        this.enhancer.setCallbackFilter(ZERO_CALLBACK_FILTER);
        this.enhancer.setNamingPolicy(ENHANCER_NAMING);
    }

    /**
     * Builds a scoped proxy instance which is a subtype of the given class.
     *
     * @param type The class.
     * @return Builder object for further configuration.
     */
    public static <E> InstanceBuilder<E> forType(Class<E> type) {
        Preconditions.checkNotNull(type, "type");
        Preconditions.checkArgument(!Modifier.isFinal(type.getModifiers()),
                "can not proxy final type %s", type.getName());
        return new InstanceBuilder<E>(type);
    }

    /**
     * Specifies the scoped provider. Every method call on the object created by
     * this builder will be delegated to the object returned by the given
     * provider.
     * <p>
     * This method overrides the callback set by {@link #withCallback(Callback)}.
     *
     * @param provider The scoped provider.
     * @return Builder object for further configuration.
     * @see #withCallback(Callback)
     */
    public InstanceBuilder<T> dispatchTo(Provider<T> provider) {
        Preconditions.checkNotNull(provider, "provider");
        this.dispatcher = new InvocationHandler() {

            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                return method.invoke(provider.get(), args);
            }
        };
        return this;
    }

    /**
     * Sets the single callback to which all method calls of the created object
     * are delegated.
     * <p>
     * This method overrides the provider based callback set by
     * {@link #dispatchTo(Provider)}.
     *
     * @param callback The callback to use for the created object.
     * @return Builder object for further configuration.
     * @see #dispatchTo(Provider)
     */
    public InstanceBuilder<T> withCallback(Callback callback) {
        Preconditions.checkNotNull(callback, "callback");
        this.dispatcher = callback;
        return this;
    }

    /**
     * Sets the strategy that is used to instantiate proxies of concrete classes
     * that require constructor arguments.
     *
     * @param strategy The strategy.
     * @return Builder object for further configuration.
     */
    public InstanceBuilder<T> withConstructionStrategy(
            ConstructionStrategy strategy) {
        Preconditions.checkNotNull(strategy);
        this.constructionStrategy = strategy;
        return this;
    }

    /**
     * Creates the scoped proxy object using the given provider.
     *
     * @param injector The injector.
     * @return The scoped proxy object.
     */
    @SuppressWarnings("unchecked")
    public T create(Injector injector) {
        Preconditions.checkNotNull(injector, "injector");
        Preconditions.checkState(this.dispatcher != null, "no provider set");

        this.enhancer.setCallbackType(this.dispatcher.getClass());
        final Class<T> enhancedClass = this.enhancer.createClass();
        final Errors errors = new Errors();
        final T proxyInstance = this.constructionStrategy
                .createInstance(enhancedClass, injector, errors);

        errors.throwProvisionExceptionIfErrorsExist();
        final Factory factory = (Factory) proxyInstance;
        factory.setCallback(CALLBACK_INDEX, this.dispatcher);
        return proxyInstance;
    }
}

@@ -31,41 +31,8 @@
 y;%0A%0A
-import java.lang.reflect.Method;%0A
 impo
@@ -764,23 +764,16 @@
 bject is
-%0A     *
  mapped.
@@ -960,142 +960,32 @@
 R = 
-new CallbackFilter() %7B%0A        @Override%0A        public int accept(Method method) %7B%0A            return CALLBACK_INDEX;%0A        %7D%0A    %7D
+method -%3E CALLBACK_INDEX
 ;%0A%0A 
@@ -1575,28 +1575,16 @@
 rategy =
-%0A           
  Constru
@@ -2398,17 +2398,16 @@
 Builder%3C
-E
 %3E(type);
@@ -2506,20 +2506,20 @@
 d by
+ this
 %0A     *
- this
  bui
@@ -2576,23 +2576,16 @@
 he given
-%0A     *
  provide
@@ -2967,190 +2967,74 @@
     
-this.dispatcher = new InvocationHandler() %7B%0A%0A            @Override%0A            public Object invoke(Object proxy, Method method, Object%5B%5D args)%0A                    throws Throwable %7B
+final InvocationHandler callback = (proxy, method, args) -%3E method
 %0A   
@@ -3050,21 +3050,8 @@
     
-return method
 .inv
@@ -3089,23 +3089,34 @@
     
-    %7D%0A        %7D
+this.dispatcher = callback
 ;%0A  
@@ -3232,19 +3232,19 @@
 ject
+ are
 %0A     *
- are
  del
@@ -3815,20 +3815,20 @@
 sses
+ that
 %0A     *
- that
  req

Fix some issues