com.google.inject.assistedinject.FactoryProvider2.java Source code

Java tutorial

Introduction

Here is the source code for com.google.inject.assistedinject.FactoryProvider2.java

Source

/**
 * Copyright (C) 2008 Google Inc.
 *
 * 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 com.google.inject.assistedinject;

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

import com.google.common.base.Objects;
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.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.BytecodeGen;
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.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;

/**
 * The newer implementation of factory provider. This implementation uses a child injector to
 * create values.
 *
 * @author jessewilson@google.com (Jesse Wilson)
 * @author dtm@google.com (Daniel Martin)
 * @author schmitt@google.com (Peter Schmitt)
 * @author sameb@google.com (Sam Berlin)
 */
final class FactoryProvider2<F>
        implements InvocationHandler, ProviderWithExtensionVisitor<F>, HasDependencies, AssistedInjectBinding<F> {

    /** 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<? extends Annotation> 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=)";
        }
    };

    /** 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<Key<?>> paramTypes;
        /** the type of the implementation constructed */
        final TypeLiteral<?> implementationType;

        /** All non-assisted dependencies required by this method. */
        final Set<Dependency<?>> 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<ThreadLocalProvider> providers;
        /** used to perform optimized factory creations. */
        volatile Binding<?> cachedBinding; // TODO: volatile necessary?

        AssistData(Constructor<?> constructor, Key<?> returnType, ImmutableList<Key<?>> paramTypes,
                TypeLiteral<?> implementationType, Method factoryMethod, Set<Dependency<?>> dependencies,
                boolean optimized, List<ThreadLocalProvider> 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 Objects.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<Dependency<?>> getDependencies() {
            return dependencies;
        }

        public Method getFactoryMethod() {
            return factoryMethod;
        }

        public Constructor<?> getImplementationConstructor() {
            return constructor;
        }

        public TypeLiteral<?> getImplementationType() {
            return implementationType;
        }
    }

    /** Mapping from method to the data about how the method will be assisted. */
    private final ImmutableMap<Method, AssistData> assistDataByMethod;

    /** the hosting injector, or null if we haven't been initialized yet */
    private Injector injector;

    /** the factory interface, implemented and provided */
    private final F factory;

    /** The key that this is bound to. */
    private final Key<F> factoryKey;

    /** The binding collector, for equality/hashing purposes. */
    private final BindingCollector collector;

    /**
     * @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<F> factoryKey, BindingCollector collector) {
        this.factoryKey = factoryKey;
        this.collector = collector;

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

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

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

            ImmutableMap.Builder<Method, AssistData> assistDataBuilder = ImmutableMap.builder();
            // TODO: also grab methods from superinterfaces
            for (Method method : factoryRawType.getMethods()) {
                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<TypeLiteral<?>> params = factoryType.getParameterTypes(method);
                Annotation[][] paramAnnotations = method.getParameterAnnotations();
                int p = 0;
                List<Key<?>> 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<Key<?>> 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<? extends Annotation> 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<ThreadLocalProvider> providers = Collections.emptyList();
                Set<Dependency<?>> 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<ThreadLocalProvider> providerListBuilder = ImmutableList.builder();
                    for (int i = 0; i < params.size(); i++) {
                        providerListBuilder.add(new ThreadLocalProvider());
                    }
                    providers = providerListBuilder.build();
                    optimized = true;
                }
                assistDataBuilder.put(method, new AssistData(constructor, returnType, immutableParamList,
                        implementation, method, removeAssistedDeps(deps), optimized, providers));
            }

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

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

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

    public F get() {
        return factory;
    }

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

    public Key<F> getKey() {
        return factoryKey;
    }

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

    @SuppressWarnings("unchecked")
    public <T, V> V acceptExtensionVisitor(BindingTargetVisitor<T, V> visitor,
            ProviderInstanceBinding<? extends T> binding) {
        if (visitor instanceof AssistedInjectTargetVisitor) {
            return ((AssistedInjectTargetVisitor<T, V>) visitor).visit((AssistedInjectBinding<T>) 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<Message> 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 <T> InjectionPoint findMatchingConstructorInjectionPoint(Method method, Key<?> returnType,
            TypeLiteral<T> implementation, List<Key<?>> 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<? super T>) 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<Key<?>> paramList, Errors errors) throws ErrorsException {
        List<TypeLiteral<?>> params = type.getParameterTypes(constructor);
        Annotation[][] paramAnnotations = constructor.getParameterAnnotations();
        int p = 0;
        List<Key<?>> 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<Dependency<?>> getDependencies(InjectionPoint ctorPoint, TypeLiteral<?> implementation) {
        ImmutableSet.Builder<Dependency<?>> 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<Dependency<?>> removeAssistedDeps(Set<Dependency<?>> deps) {
        ImmutableSet.Builder<Dependency<?>> 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<Dependency<?>> dependencies, Class<?> implementation,
            TypeLiteral<?> factoryType) {
        Set<Dependency<?>> 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<T> (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 <T> Key<T> assistKey(Method method, Key<T> 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<Method, AssistData> 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 (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);
        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);
    }

    /** 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;
    }

    // not <T> because we'll never know and this is easier than suppressing warnings.
    private static class ThreadLocalProvider extends ThreadLocal<Object> implements Provider<Object> {
        @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.)");
        }
    }
}