All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.inject.assistedinject.FactoryProvider2 Maven / Gradle / Ivy

package com.google.inject.assistedinject;

import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.Binding;
import com.google.inject.ConfigurationException;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.Annotations;
import com.google.inject.internal.Errors;
import com.google.inject.internal.ErrorsException;
import com.google.inject.internal.UniqueAnnotations;
import com.google.inject.internal.util.Classes;
import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.HasDependencies;
import com.google.inject.spi.InjectionPoint;
import com.google.inject.spi.Message;
import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderWithExtensionVisitor;
import com.google.inject.spi.Toolable;
import com.google.inject.util.Providers;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.getOnlyElement;

/**
 * The newer implementation of factory provider. This implementation uses a child injector to
 * create values.
 */
final class FactoryProvider2 implements InvocationHandler,
        ProviderWithExtensionVisitor, HasDependencies, AssistedInjectBinding {

    /**
     * A constant annotation to denote the return value, instead of creating a new one each time.
     */
    static final Annotation RETURN_ANNOTATION = UniqueAnnotations.create();

    // use the logger under a well-known name, not FactoryProvider2
    static final Logger logger = Logger.getLogger(AssistedInject.class.getName());

    /**
     * if a factory method parameter isn't annotated, it gets this annotation.
     */
    static final Assisted DEFAULT_ANNOTATION = new Assisted() {
        public String value() {
            return "";
        }

        public Class annotationType() {
            return Assisted.class;
        }

        @Override
        public boolean equals(Object o) {
            return o instanceof Assisted && ((Assisted) o).value().isEmpty();
        }

        @Override
        public int hashCode() {
            return 127 * "value".hashCode() ^ "".hashCode();
        }

        @Override
        public String toString() {
            return "@" + Assisted.class.getName() + "(value=\"\")";
        }
    };
    /**
     * Mapping from method to the data about how the method will be assisted.
     */
    private final ImmutableMap assistDataByMethod;
    /**
     * Mapping from method to method handle, for generated default methods.
     */
    private final ImmutableMap methodHandleByMethod;
    /**
     * the factory interface, implemented and provided
     */
    private final F factory;
    /**
     * The key that this is bound to.
     */
    private final Key factoryKey;
    /**
     * The binding collector, for equality/hashing purposes.
     */
    private final BindingCollector collector;
    /**
     * the hosting injector, or null if we haven't been initialized yet
     */
    private Injector injector;

    /**
     * @param factoryKey a key for a Java interface that defines one or more create methods.
     * @param collector  binding configuration that maps method return types to
     *                   implementation types.
     */
    FactoryProvider2(Key factoryKey, BindingCollector collector) {
        this.factoryKey = factoryKey;
        this.collector = collector;

        TypeLiteral factoryType = factoryKey.getTypeLiteral();
        Errors errors = new Errors();

        @SuppressWarnings("unchecked") // we imprecisely treat the class literal of T as a Class
                Class factoryRawType = (Class) (Class) factoryType.getRawType();

        try {
            if (!factoryRawType.isInterface()) {
                throw errors.addMessage("%s must be an interface.", factoryRawType).toException();
            }

            Multimap defaultMethods = HashMultimap.create();
            Multimap otherMethods = HashMultimap.create();
            ImmutableMap.Builder assistDataBuilder = ImmutableMap.builder();
            // TODO: also grab methods from superinterfaces
            for (Method method : factoryRawType.getMethods()) {
                // Skip default methods that java8 may have created.
                if (isDefault(method) && (method.isBridge() || method.isSynthetic())) {
                    // Even synthetic default methods need the return type validation...
                    // unavoidable consequence of javac8. :-(
                    validateFactoryReturnType(errors, method.getReturnType(), factoryRawType);
                    defaultMethods.put(method.getName(), method);
                    continue;
                }
                otherMethods.put(method.getName(), method);

                TypeLiteral returnTypeLiteral = factoryType.getReturnType(method);
                Key returnType;
                try {
                    returnType = Annotations.getKey(returnTypeLiteral, method, method.getAnnotations(), errors);
                } catch (ConfigurationException ce) {
                    // If this was an error due to returnTypeLiteral not being specified, rephrase
                    // it as our factory not being specified, so it makes more sense to users.
                    if (isTypeNotSpecified(returnTypeLiteral, ce)) {
                        throw errors.keyNotFullySpecified(TypeLiteral.get(factoryRawType)).toException();
                    } else {
                        throw ce;
                    }
                }
                validateFactoryReturnType(errors, returnType.getTypeLiteral().getRawType(), factoryRawType);
                List> params = factoryType.getParameterTypes(method);
                Annotation[][] paramAnnotations = method.getParameterAnnotations();
                int p = 0;
                List> keys = Lists.newArrayList();
                for (TypeLiteral param : params) {
                    Key paramKey = Annotations.getKey(param, method, paramAnnotations[p++], errors);
                    Class underlylingType = paramKey.getTypeLiteral().getRawType();
                    if (underlylingType.equals(Provider.class)
                            || underlylingType.equals(javax.inject.Provider.class)) {
                        errors.addMessage("A Provider may not be a type in a factory method of an AssistedInject."
                                        + "\n  Offending instance is parameter [%s] with key [%s] on method [%s]",
                                p, paramKey, method);
                    }
                    keys.add(assistKey(method, paramKey, errors));
                }
                ImmutableList> immutableParamList = ImmutableList.copyOf(keys);

                // try to match up the method to the constructor
                TypeLiteral implementation = collector.getBindings().get(returnType);
                if (implementation == null) {
                    implementation = returnType.getTypeLiteral();
                }
                Class scope =
                        Annotations.findScopeAnnotation(errors, implementation.getRawType());
                if (scope != null) {
                    errors.addMessage("Found scope annotation [%s] on implementation class "
                                    + "[%s] of AssistedInject factory [%s].\nThis is not allowed, please"
                                    + " remove the scope annotation.",
                            scope, implementation.getRawType(), factoryType);
                }

                InjectionPoint ctorInjectionPoint;
                try {
                    ctorInjectionPoint =
                            findMatchingConstructorInjectionPoint(method, returnType, implementation, immutableParamList);
                } catch (ErrorsException ee) {
                    errors.merge(ee.getErrors());
                    continue;
                }

                Constructor constructor = (Constructor) ctorInjectionPoint.getMember();
                List providers = Collections.emptyList();
                Set> deps = getDependencies(ctorInjectionPoint, implementation);
                boolean optimized = false;
                // Now go through all dependencies of the implementation and see if it is OK to
                // use an optimized form of assistedinject2.  The optimized form requires that
                // all injections directly inject the object itself (and not a Provider of the object,
                // or an Injector), because it caches a single child injector and mutates the Provider
                // of the arguments in a ThreadLocal.
                if (isValidForOptimizedAssistedInject(deps, implementation.getRawType(), factoryType)) {
                    ImmutableList.Builder providerListBuilder = ImmutableList.builder();
                    for (int i = 0; i < params.size(); i++) {
                        providerListBuilder.add(new ThreadLocalProvider());
                    }
                    providers = providerListBuilder.build();
                    optimized = true;
                }

                AssistData data = new AssistData(constructor,
                        returnType,
                        immutableParamList,
                        implementation,
                        method,
                        removeAssistedDeps(deps),
                        optimized,
                        providers);
                assistDataBuilder.put(method, data);
            }

            factory = factoryRawType.cast(Proxy.newProxyInstance(
                    factoryRawType.getClassLoader(), new Class[]{factoryRawType}, this));

            // Now go back through default methods. Try to use MethodHandles to make things
            // work.  If that doesn't work, fallback to trying to find compatible method
            // signatures.
            Map dataSoFar = assistDataBuilder.build();
            ImmutableMap.Builder methodHandleBuilder = ImmutableMap.builder();
            for (Map.Entry entry : defaultMethods.entries()) {
                Method defaultMethod = entry.getValue();
                MethodHandleWrapper handle = MethodHandleWrapper.create(defaultMethod, factory);
                if (handle != null) {
                    methodHandleBuilder.put(defaultMethod, handle);
                } else {
                    boolean foundMatch = false;
                    for (Method otherMethod : otherMethods.get(defaultMethod.getName())) {
                        if (dataSoFar.containsKey(otherMethod) && isCompatible(defaultMethod, otherMethod)) {
                            if (foundMatch) {
                                errors.addMessage("Generated default method %s with parameters %s is"
                                                + " signature-compatible with more than one non-default method."
                                                + " Unable to create factory. As a workaround, remove the override"
                                                + " so javac stops generating a default method.",
                                        defaultMethod, Arrays.asList(defaultMethod.getParameterTypes()));
                            } else {
                                assistDataBuilder.put(defaultMethod, dataSoFar.get(otherMethod));
                                foundMatch = true;
                            }
                        }
                    }
                    if (!foundMatch) {
                        throw new IllegalStateException("Can't find method compatible with: " + defaultMethod);
                    }
                }
            }

            // If we generated any errors (from finding matching constructors, for instance), throw an exception.
            if (errors.hasErrors()) {
                throw errors.toException();
            }

            assistDataByMethod = assistDataBuilder.build();
            methodHandleByMethod = methodHandleBuilder.build();
        } catch (ErrorsException e) {
            throw new ConfigurationException(e.getErrors().getMessages());
        }
    }

    static boolean isDefault(Method method) {
        // Per the javadoc, default methods are non-abstract, public, non-static.
        // They're also in interfaces, but we can guarantee that already since we only act
        // on interfaces.
        return (method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC))
                == Modifier.PUBLIC;
    }

    /**
     * Returns true if {@code thrown} can be thrown by {@code invoked} without wrapping.
     */
    static boolean canRethrow(Method invoked, Throwable thrown) {
        if (thrown instanceof Error || thrown instanceof RuntimeException) {
            return true;
        }

        for (Class declared : invoked.getExceptionTypes()) {
            if (declared.isInstance(thrown)) {
                return true;
            }
        }

        return false;
    }

    private boolean isCompatible(Method src, Method dst) {
        if (!src.getReturnType().isAssignableFrom(dst.getReturnType())) {
            return false;
        }
        Class[] srcParams = src.getParameterTypes();
        Class[] dstParams = dst.getParameterTypes();
        if (srcParams.length != dstParams.length) {
            return false;
        }
        for (int i = 0; i < srcParams.length; i++) {
            if (!srcParams[i].isAssignableFrom(dstParams[i])) {
                return false;
            }
        }
        return true;
    }

    public F get() {
        return factory;
    }

    public Set> getDependencies() {
        Set> combinedDeps = new HashSet>();
        for (AssistData data : assistDataByMethod.values()) {
            combinedDeps.addAll(data.dependencies);
        }
        return ImmutableSet.copyOf(combinedDeps);
    }

    public Key getKey() {
        return factoryKey;
    }

    // Safe cast because values are typed to AssistedData, which is an AssistedMethod, and
    // the collection is immutable.
    @SuppressWarnings("unchecked")
    public Collection getAssistedMethods() {
        return (Collection) (Collection) assistDataByMethod.values();
    }

    @SuppressWarnings("unchecked")
    public  V acceptExtensionVisitor(BindingTargetVisitor visitor,
                                           ProviderInstanceBinding binding) {
        if (visitor instanceof AssistedInjectTargetVisitor) {
            return ((AssistedInjectTargetVisitor) visitor).visit((AssistedInjectBinding) this);
        }
        return visitor.visit(binding);
    }

    private void validateFactoryReturnType(Errors errors, Class returnType, Class factoryType) {
        if (Modifier.isPublic(factoryType.getModifiers())
                && !Modifier.isPublic(returnType.getModifiers())) {
            errors.addMessage("%s is public, but has a method that returns a non-public type: %s. "
                            + "Due to limitations with java.lang.reflect.Proxy, this is not allowed. "
                            + "Please either make the factory non-public or the return type public.",
                    factoryType, returnType);
        }
    }

    /**
     * Returns true if the ConfigurationException is due to an error of TypeLiteral not being fully
     * specified.
     */
    private boolean isTypeNotSpecified(TypeLiteral typeLiteral, ConfigurationException ce) {
        Collection messages = ce.getErrorMessages();
        if (messages.size() == 1) {
            Message msg = Iterables.getOnlyElement(
                    new Errors().keyNotFullySpecified(typeLiteral).getMessages());
            return msg.getMessage().equals(Iterables.getOnlyElement(messages).getMessage());
        } else {
            return false;
        }
    }

    /**
     * Finds a constructor suitable for the method.  If the implementation contained any constructors
     * marked with {@link AssistedInject}, this requires all {@link Assisted} parameters to exactly
     * match the parameters (in any order) listed in the method.  Otherwise, if no
     * {@link AssistedInject} constructors exist, this will default to looking for an
     * {@literal @}{@link Inject} constructor.
     */
    private  InjectionPoint findMatchingConstructorInjectionPoint(
            Method method, Key returnType, TypeLiteral implementation, List> paramList)
            throws ErrorsException {
        Errors errors = new Errors(method);
        if (returnType.getTypeLiteral().equals(implementation)) {
            errors = errors.withSource(implementation);
        } else {
            errors = errors.withSource(returnType).withSource(implementation);
        }

        Class rawType = implementation.getRawType();
        if (Modifier.isInterface(rawType.getModifiers())) {
            errors.addMessage(
                    "%s is an interface, not a concrete class.  Unable to create AssistedInject factory.",
                    implementation);
            throw errors.toException();
        } else if (Modifier.isAbstract(rawType.getModifiers())) {
            errors.addMessage(
                    "%s is abstract, not a concrete class.  Unable to create AssistedInject factory.",
                    implementation);
            throw errors.toException();
        } else if (Classes.isInnerClass(rawType)) {
            errors.cannotInjectInnerClass(rawType);
            throw errors.toException();
        }

        Constructor matchingConstructor = null;
        boolean anyAssistedInjectConstructors = false;
        // Look for AssistedInject constructors...
        for (Constructor constructor : rawType.getDeclaredConstructors()) {
            if (constructor.isAnnotationPresent(AssistedInject.class)) {
                anyAssistedInjectConstructors = true;
                if (constructorHasMatchingParams(implementation, constructor, paramList, errors)) {
                    if (matchingConstructor != null) {
                        errors
                                .addMessage(
                                        "%s has more than one constructor annotated with @AssistedInject"
                                                + " that matches the parameters in method %s.  Unable to create "
                                                + "AssistedInject factory.",
                                        implementation, method);
                        throw errors.toException();
                    } else {
                        matchingConstructor = constructor;
                    }
                }
            }
        }

        if (!anyAssistedInjectConstructors) {
            // If none existed, use @Inject.
            try {
                return InjectionPoint.forConstructorOf(implementation);
            } catch (ConfigurationException e) {
                errors.merge(e.getErrorMessages());
                throw errors.toException();
            }
        } else {
            // Otherwise, use it or fail with a good error message.
            if (matchingConstructor != null) {
                // safe because we got the constructor from this implementation.
                @SuppressWarnings("unchecked")
                InjectionPoint ip = InjectionPoint.forConstructor(
                        (Constructor) matchingConstructor, implementation);
                return ip;
            } else {
                errors.addMessage(
                        "%s has @AssistedInject constructors, but none of them match the"
                                + " parameters in method %s.  Unable to create AssistedInject factory.",
                        implementation, method);
                throw errors.toException();
            }
        }
    }

    /**
     * Matching logic for constructors annotated with AssistedInject.
     * This returns true if and only if all @Assisted parameters in the
     * constructor exactly match (in any order) all @Assisted parameters
     * the method's parameter.
     */
    private boolean constructorHasMatchingParams(TypeLiteral type,
                                                 Constructor constructor, List> paramList, Errors errors)
            throws ErrorsException {
        List> params = type.getParameterTypes(constructor);
        Annotation[][] paramAnnotations = constructor.getParameterAnnotations();
        int p = 0;
        List> constructorKeys = Lists.newArrayList();
        for (TypeLiteral param : params) {
            Key paramKey = Annotations.getKey(param, constructor, paramAnnotations[p++],
                    errors);
            constructorKeys.add(paramKey);
        }
        // Require that every key exist in the constructor to match up exactly.
        for (Key key : paramList) {
            // If it didn't exist in the constructor set, we can't use it.
            if (!constructorKeys.remove(key)) {
                return false;
            }
        }
        // If any keys remain and their annotation is Assisted, we can't use it.
        for (Key key : constructorKeys) {
            if (key.getAnnotationType() == Assisted.class) {
                return false;
            }
        }
        // All @Assisted params match up to the method's parameters.
        return true;
    }

    /**
     * Calculates all dependencies required by the implementation and constructor.
     */
    private Set> getDependencies(InjectionPoint ctorPoint, TypeLiteral implementation) {
        ImmutableSet.Builder> builder = ImmutableSet.builder();
        builder.addAll(ctorPoint.getDependencies());
        if (!implementation.getRawType().isInterface()) {
            for (InjectionPoint ip : InjectionPoint.forInstanceMethodsAndFields(implementation)) {
                builder.addAll(ip.getDependencies());
            }
        }
        return builder.build();
    }

    /**
     * Return all non-assisted dependencies.
     */
    private Set> removeAssistedDeps(Set> deps) {
        ImmutableSet.Builder> builder = ImmutableSet.builder();
        for (Dependency dep : deps) {
            Class annotationType = dep.getKey().getAnnotationType();
            if (annotationType == null || !annotationType.equals(Assisted.class)) {
                builder.add(dep);
            }
        }
        return builder.build();
    }

    /**
     * Returns true if all dependencies are suitable for the optimized version of AssistedInject. The
     * optimized version caches the binding & uses a ThreadLocal Provider, so can only be applied if
     * the assisted bindings are immediately provided. This looks for hints that the values may be
     * lazily retrieved, by looking for injections of Injector or a Provider for the assisted values.
     */
    private boolean isValidForOptimizedAssistedInject(Set> dependencies,
                                                      Class implementation, TypeLiteral factoryType) {
        Set> badDeps = null; // optimization: create lazily
        for (Dependency dep : dependencies) {
            if (isInjectorOrAssistedProvider(dep)) {
                if (badDeps == null) {
                    badDeps = Sets.newHashSet();
                }
                badDeps.add(dep);
            }
        }
        if (badDeps != null && !badDeps.isEmpty()) {
            logger.log(Level.WARNING, "AssistedInject factory {0} will be slow "
                            + "because {1} has assisted Provider dependencies or injects the Injector. "
                            + "Stop injecting @Assisted Provider (instead use @Assisted T) "
                            + "or Injector to speed things up. (It will be a ~6500% speed bump!)  "
                            + "The exact offending deps are: {2}",
                    new Object[] { factoryType, implementation, badDeps } );
            return false;
        }
        return true;
    }

    /**
     * Returns true if the dependency is for {@link Injector} or if the dependency
     * is a {@link Provider} for a parameter that is {@literal @}{@link Assisted}.
     */
    private boolean isInjectorOrAssistedProvider(Dependency dependency) {
        Class annotationType = dependency.getKey().getAnnotationType();
        if (annotationType != null && annotationType.equals(Assisted.class)) { // If it's assisted..
            if (dependency.getKey().getTypeLiteral().getRawType().equals(Provider.class)) { // And a Provider...
                return true;
            }
        } else if (dependency.getKey().getTypeLiteral().getRawType().equals(Injector.class)) { // If it's the Injector...
            return true;
        }
        return false;
    }

    /**
     * Returns a key similar to {@code key}, but with an {@literal @}Assisted binding annotation.
     * This fails if another binding annotation is clobbered in the process. If the key already has
     * the {@literal @}Assisted annotation, it is returned as-is to preserve any String value.
     */
    private  Key assistKey(Method method, Key key, Errors errors) throws ErrorsException {
        if (key.getAnnotationType() == null) {
            return Key.get(key.getTypeLiteral(), DEFAULT_ANNOTATION);
        } else if (key.getAnnotationType() == Assisted.class) {
            return key;
        } else {
            errors.withSource(method).addMessage(
                    "Only @Assisted is allowed for factory parameters, but found @%s",
                    key.getAnnotationType());
            throw errors.toException();
        }
    }

    /**
     * At injector-creation time, we initialize the invocation handler. At this time we make sure
     * all factory methods will be able to build the target types.
     */
    @Inject
    @Toolable
    void initialize(Injector injector) {
        if (this.injector != null) {
            throw new ConfigurationException(ImmutableList.of(new Message(FactoryProvider2.class,
                    "Factories.create() factories may only be used in one Injector!")));
        }

        this.injector = injector;

        for (Map.Entry entry : assistDataByMethod.entrySet()) {
            Method method = entry.getKey();
            AssistData data = entry.getValue();
            Object[] args;
            if (!data.optimized) {
                args = new Object[method.getParameterTypes().length];
                Arrays.fill(args, "dummy object for validating Factories");
            } else {
                args = null; // won't be used -- instead will bind to data.providers.
            }
            getBindingFromNewInjector(method, args, data); // throws if the binding isn't properly configured
        }
    }

    /**
     * Creates a child injector that binds the args, and returns the binding for the method's result.
     */
    public Binding getBindingFromNewInjector(
            final Method method, final Object[] args, final AssistData data) {
        checkState(injector != null,
                "Factories.create() factories cannot be used until they're initialized by Guice.");

        final Key returnType = data.returnType;

        // We ignore any pre-existing binding annotation.
        final Key returnKey = Key.get(returnType.getTypeLiteral(), RETURN_ANNOTATION);

        Module assistedModule = new AbstractModule() {
            @Override
            @SuppressWarnings({
                    "unchecked", "rawtypes"}) // raw keys are necessary for the args array and return value
            protected void configure() {
                Binder binder = binder().withSource(method);

                int p = 0;
                if (!data.optimized) {
                    for (Key paramKey : data.paramTypes) {
                        // Wrap in a Provider to cover null, and to prevent Guice from injecting the parameter
                        binder.bind((Key) paramKey).toProvider(Providers.of(args[p++]));
                    }
                } else {
                    for (Key paramKey : data.paramTypes) {
                        // Bind to our ThreadLocalProviders.
                        binder.bind((Key) paramKey).toProvider(data.providers.get(p++));
                    }
                }

                Constructor constructor = data.constructor;
                // Constructor *should* always be non-null here,
                // but if it isn't, we'll end up throwing a fairly good error
                // message for the user.
                if (constructor != null) {
                    binder.bind(returnKey)
                            .toConstructor(constructor, (TypeLiteral) data.implementationType)
                            .in(Scopes.NO_SCOPE); // make sure we erase any scope on the implementation type
                }
            }
        };

        Injector forCreate = injector.createChildInjector(assistedModule);
        Binding binding = forCreate.getBinding(returnKey);
        // If we have providers cached in data, cache the binding for future optimizations.
        if (data.optimized) {
            data.cachedBinding = binding;
        }
        return binding;
    }

    /**
     * When a factory method is invoked, we create a child injector that binds all parameters, then
     * use that to get an instance of the return type.
     */
    public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
        // If we setup a method handle earlier for this method, call it.
        // This is necessary for default methods that java8 creates, so we
        // can call the default method implementation (and not our proxied version of it).
        if (methodHandleByMethod.containsKey(method)) {
            return methodHandleByMethod.get(method).invokeWithArguments(args);
        }

        if (method.getDeclaringClass().equals(Object.class)) {
            if ("equals".equals(method.getName())) {
                return proxy == args[0];
            } else if ("hashCode".equals(method.getName())) {
                return System.identityHashCode(proxy);
            } else {
                return method.invoke(this, args);
            }
        }

        AssistData data = assistDataByMethod.get(method);
        checkState(data != null, "No data for method: %s", method);
        Provider provider;
        if (data.cachedBinding != null) { // Try to get optimized form...
            provider = data.cachedBinding.getProvider();
        } else {
            provider = getBindingFromNewInjector(method, args, data).getProvider();
        }
        try {
            int p = 0;
            for (ThreadLocalProvider tlp : data.providers) {
                tlp.set(args[p++]);
            }
            return provider.get();
        } catch (ProvisionException e) {
            // if this is an exception declared by the factory method, throw it as-is
            if (e.getErrorMessages().size() == 1) {
                Message onlyError = getOnlyElement(e.getErrorMessages());
                Throwable cause = onlyError.getCause();
                if (cause != null && canRethrow(method, cause)) {
                    throw cause;
                }
            }
            throw e;
        } finally {
            for (ThreadLocalProvider tlp : data.providers) {
                tlp.remove();
            }
        }
    }

    @Override
    public String toString() {
        return factory.getClass().getInterfaces()[0].getName();
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(factoryKey, collector);
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof FactoryProvider2)) {
            return false;
        }
        FactoryProvider2 other = (FactoryProvider2) obj;
        return factoryKey.equals(other.factoryKey) && Objects.equal(collector, other.collector);
    }

    /**
     * All the data necessary to perform an assisted inject.
     */
    private static class AssistData implements AssistedMethod {
        /**
         * the constructor the implementation is constructed with.
         */
        final Constructor constructor;
        /**
         * the return type in the factory method that the constructor is bound to.
         */
        final Key returnType;
        /**
         * the parameters in the factory method associated with this data.
         */
        final ImmutableList> paramTypes;
        /**
         * the type of the implementation constructed
         */
        final TypeLiteral implementationType;

        /**
         * All non-assisted dependencies required by this method.
         */
        final Set> dependencies;
        /**
         * The factory method associated with this data
         */
        final Method factoryMethod;

        /**
         * true if {@link #isValidForOptimizedAssistedInject} returned true.
         */
        final boolean optimized;
        /**
         * the list of optimized providers, empty if not optimized.
         */
        final List providers;
        /**
         * used to perform optimized factory creations.
         */
        volatile Binding cachedBinding; // TODO: volatile necessary?

        AssistData(Constructor constructor, Key returnType, ImmutableList> paramTypes,
                   TypeLiteral implementationType, Method factoryMethod,
                   Set> dependencies,
                   boolean optimized, List providers) {
            this.constructor = constructor;
            this.returnType = returnType;
            this.paramTypes = paramTypes;
            this.implementationType = implementationType;
            this.factoryMethod = factoryMethod;
            this.dependencies = dependencies;
            this.optimized = optimized;
            this.providers = providers;
        }

        @Override
        public String toString() {
            return MoreObjects.toStringHelper(getClass())
                    .add("ctor", constructor)
                    .add("return type", returnType)
                    .add("param type", paramTypes)
                    .add("implementation type", implementationType)
                    .add("dependencies", dependencies)
                    .add("factory method", factoryMethod)
                    .add("optimized", optimized)
                    .add("providers", providers)
                    .add("cached binding", cachedBinding)
                    .toString();
        }

        public Set> getDependencies() {
            return dependencies;
        }

        public Method getFactoryMethod() {
            return factoryMethod;
        }

        public Constructor getImplementationConstructor() {
            return constructor;
        }

        public TypeLiteral getImplementationType() {
            return implementationType;
        }
    }

    // not  because we'll never know and this is easier than suppressing warnings.
    private static class ThreadLocalProvider extends ThreadLocal implements Provider {
        @Override
        protected Object initialValue() {
            throw new IllegalStateException(
                    "Cannot use optimized @Assisted provider outside the scope of the constructor."
                            + " (This should never happen.  If it does, please report it.)");
        }
    }

    /**
     * Wrapper around MethodHandles/MethodHandle, so we can compile+run on java6.
     */
    private static class MethodHandleWrapper {
        static final int ALL_MODES = Modifier.PRIVATE
                | Modifier.STATIC /* package */
                | Modifier.PUBLIC
                | Modifier.PROTECTED;

        static final Method unreflectSpecial;
        static final Method bindTo;
        static final Method invokeWithArguments;
        static final Constructor lookupCxtor;
        static final boolean valid;

        static {
            Method unreflectSpecialTmp = null;
            Method bindToTmp = null;
            Method invokeWithArgumentsTmp = null;
            boolean validTmp = false;
            Constructor lookupCxtorTmp = null;
            try {
                Class lookupClass = Class.forName("java.lang.invoke.MethodHandles$Lookup");
                unreflectSpecialTmp = lookupClass.getMethod("unreflectSpecial", Method.class, Class.class);
                Class methodHandleClass = Class.forName("java.lang.invoke.MethodHandle");
                bindToTmp = methodHandleClass.getMethod("bindTo", Object.class);
                invokeWithArgumentsTmp = methodHandleClass.getMethod("invokeWithArguments", Object[].class);
                lookupCxtorTmp = lookupClass.getDeclaredConstructor(Class.class, int.class);
                lookupCxtorTmp.setAccessible(true);
                validTmp = true;
            } catch (Exception invalid) {
                // Ignore the exception, store the values & exit early in create(..) if invalid.
            }

            // Store refs to later.
            valid = validTmp;
            unreflectSpecial = unreflectSpecialTmp;
            bindTo = bindToTmp;
            invokeWithArguments = invokeWithArgumentsTmp;
            lookupCxtor = lookupCxtorTmp;
        }

        final Object handle;

        MethodHandleWrapper(Object handle) {
            this.handle = handle;
        }

        static MethodHandleWrapper create(Method method, Object proxy) {
            if (!valid) {
                return null;
            }
            try {
                Class declaringClass = method.getDeclaringClass();
                // Note: this isn't a public API, but we need to use it in order to call default methods.
                Object lookup = lookupCxtor.newInstance(declaringClass, ALL_MODES);
                method.setAccessible(true);
                // These are part of the public API, but we use reflection since we run on java6
                // and they were introduced in java7.
                lookup = unreflectSpecial.invoke(lookup, method, declaringClass);
                Object handle = bindTo.invoke(lookup, proxy);
                return new MethodHandleWrapper(handle);
            } catch (InvocationTargetException ite) {
                return null;
            } catch (IllegalAccessException iae) {
                return null;
            } catch (InstantiationException ie) {
                return null;
            }
        }

        Object invokeWithArguments(Object[] args) throws Exception {
            // We must cast the args to an object so the Object[] is the first param,
            // as opposed to each individual varargs param.
            return invokeWithArguments.invoke(handle, (Object) args);
        }

        @Override
        public String toString() {
            return handle.toString();
        }
    }
}