Package com.google.gwt.inject.rebind.binding

Source Code of com.google.gwt.inject.rebind.binding.FactoryBinding$AssistData

/*
* Copyright 2010 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.gwt.inject.rebind.binding;

import static com.google.inject.internal.Annotations.getKey;

import com.google.gwt.inject.rebind.reflect.MethodLiteral;
import com.google.gwt.inject.rebind.reflect.NoSourceNameException;
import com.google.gwt.inject.rebind.reflect.ReflectUtil;
import com.google.gwt.inject.rebind.util.GuiceUtil;
import com.google.gwt.inject.rebind.util.InjectorMethod;
import com.google.gwt.inject.rebind.util.MethodCallUtil;
import com.google.gwt.inject.rebind.util.NameGenerator;
import com.google.gwt.inject.rebind.util.Preconditions;
import com.google.gwt.inject.rebind.util.PrettyPrinter;
import com.google.gwt.inject.rebind.util.SourceSnippet;
import com.google.gwt.inject.rebind.util.SourceSnippetBuilder;
import com.google.gwt.inject.rebind.util.SourceSnippets;
import com.google.inject.Inject;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import com.google.inject.internal.Errors;
import com.google.inject.internal.ErrorsException;
import com.google.inject.spi.InjectionPoint;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* Binder producing assisted inject factories.
* <p>
* For each method in the factory interface, the binder will determine the
* implementation type from the return type and the provided bindings. It will
* then check all constructors in the implementation type against the method
* parameters (taking named {@literal @}{@link Assisted} annotations into
* account) and pick a matching one (if available). To inject the selected
* constructor the binder will write a common method injection, replacing the
* parameters commonly retrieved through key-specific getter methods with
* values obtained from the matching method arguments. Finally, after an
* instance has been constructed, it will be member-injected before it is
* returned to the caller of the method.
*/
public class FactoryBinding extends AbstractBinding implements Binding {

  /**
   * If a factory method parameter isn't annotated it gets this annotation.
   */
  private 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().equals("");
    }

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

    @Override public String toString() {
      return "@" + Assisted.class.getName() + "(value=)";
    }
  };


  private final List<AssistData> assistData = new ArrayList<AssistData>();
  private final Map<Key<?>, TypeLiteral<?>> collector;
  private final Key<?> factoryKey;
  private final TypeLiteral<?> factoryType;
  private final Set<Dependency> dependencies = new LinkedHashSet<Dependency>();

  /**
   * Collection of all implementations produced by this factory, each annotated
   * with @Assisted. This is used to gather all required member-inject methods.
   */
  private final Set<TypeLiteral<?>> implementations = new LinkedHashSet<TypeLiteral<?>>();

  private final GuiceUtil guiceUtil;
  private final MethodCallUtil methodCallUtil;

  FactoryBinding(Map<Key<?>, TypeLiteral<?>> collector, Key<?> factoryKey, Context context,
      GuiceUtil guiceUtil, MethodCallUtil methodCallUtil) {
    super(context, factoryKey);

    this.collector = Preconditions.checkNotNull(collector);
    this.factoryKey = factoryKey;
    this.factoryType = factoryKey.getTypeLiteral();
    this.guiceUtil = guiceUtil;
    this.methodCallUtil = methodCallUtil;

    try {
      matchMethods(Preconditions.checkNotNull(factoryKey));
    } catch (ErrorsException e) {
      e.getErrors().throwConfigurationExceptionIfErrorsExist();
    }
  }

  public SourceSnippet getCreationStatements(NameGenerator nameGenerator,
      List<InjectorMethod> methodsOutput) throws NoSourceNameException {
    String factoryTypeName = ReflectUtil.getSourceName(factoryType);
    SourceSnippetBuilder sb = new SourceSnippetBuilder();

    sb.append(factoryTypeName).append(" result = new ").append(factoryTypeName).append("() {");

    for (AssistData assisted : assistData) {
      // While it might seem that we could just create the return type directly
      // in the factory, that won't work.  The problem is that the return type
      // might have to be created in a different package from the factory: for
      // instance, it might inject a package-private object from its own
      // package.
      //
      // So here's the strategy: we generate a separate injector method that,
      // given the assisted parameters, creates the return value and performs
      // member injection on it, named "assistedCreate_FACTORY_RETURNTYPE".
      // Then we create a factory method that dispatches to that injector method
      // (which, again, may be in some other injector fragment).

      String returnName = ReflectUtil.getSourceName(assisted.implementation);

      String signature = ReflectUtil.signatureBuilder(assisted.method)
          .removeAbstractModifier()
          .build();

      SourceSnippet methodCall = methodCallUtil.createMethodCallWithInjection(
          assisted.constructor, null, assisted.parameterNames, nameGenerator, methodsOutput);

      SourceSnippet assistedCreateCall = callAssistedCreate(assisted, nameGenerator, methodsOutput);

      sb.append("\n\n    ").append(signature).append(" {")
         .append("\n      return ").append(assistedCreateCall).append(";")
         .append("\n    }"); // End method.
    }

    sb.append("\n};"); // End factory implementation.

    return sb.build();
  }

  private SourceSnippet callAssistedCreate(AssistData assisted, NameGenerator nameGenerator,
      List<InjectorMethod> methodsOutput) throws NoSourceNameException {
    String returnTypeName = ReflectUtil.getSourceName(assisted.implementation);
    String packageName = ReflectUtil.getUserPackageName(assisted.implementation);
    String factoryTypeName = ReflectUtil.getSourceName(factoryType);

    String assistedInjectMethodName =
        nameGenerator.getAssistedInjectMethodName(factoryKey, assisted.method.getName());
    String assistedInjectSignature = ReflectUtil.signatureBuilder(assisted.method)
        .withMethodName(assistedInjectMethodName)
        .removeAbstractModifier()
        .build();

    SourceSnippet memberInjectCall =
        SourceSnippets.callMemberInject(assisted.implementation, "result");
    SourceSnippet methodCall = methodCallUtil.createMethodCallWithInjection(
        assisted.constructor, null, assisted.parameterNames, nameGenerator, methodsOutput);

    SourceSnippet assistedInjectMethodBody = new SourceSnippetBuilder()
        .append(returnTypeName).append(" result = ").append(methodCall)
        .append("\n").append(memberInjectCall)
        .append("\nreturn result;")
        .build();

    methodsOutput.add(SourceSnippets.asMethod(false, assistedInjectSignature, packageName,
        assistedInjectMethodBody));

    List<String> parameterNames = new ArrayList<String>();
    for (int i = 0; i < assisted.method.getParameterKeys().size(); ++i) {
      parameterNames.add(ReflectUtil.formatParameterName(i));
    }

    return SourceSnippets.callMethod(assistedInjectMethodName, packageName, parameterNames);
  }

  public Collection<Dependency> getDependencies() {
    return dependencies;
  }

  @Override
  public Collection<TypeLiteral<?>> getMemberInjectRequests() {
    return Collections.unmodifiableCollection(implementations);
  }

  private void matchMethods(Key<?> factoryKey) throws ErrorsException {
    Errors errors = new Errors();
    dependencies.add(new Dependency(Dependency.GINJECTOR, factoryKey, getContext()));
    Class<?> factoryRawType = factoryType.getRawType();

    // getMethods() includes inherited methods from super-interfaces.
    for (Method method : factoryRawType.getMethods()) {
      Key<?> returnType = getKey(factoryType.getReturnType(method), method,
          method.getAnnotations(), errors);

      // Get parameters with annotations.
      List<TypeLiteral<?>> params = factoryType.getParameterTypes(method);
      Annotation[][] paramAnnotations = method.getParameterAnnotations();
      int p = 0;
      List<Key<?>> paramList = new ArrayList<Key<?>>();
      for (TypeLiteral<?> param : params) {
        Key<?> paramKey = getKey(param, method, paramAnnotations[p++], errors);
        paramList.add(assistKey(method, paramKey, errors));
      }

      // Try to match up the method to the constructor.
      TypeLiteral<?> implementation = collector.get(returnType);
      if (implementation == null) {
        implementation = returnType.getTypeLiteral();
      }
      Constructor<?> constructor =
          findMatchingConstructor(method, implementation, paramList, errors);

      if (constructor == null) {
        continue; // Errors are collected and thrown below.
      }

      // Calculate a map from method to constructor parameters and required
      // keys.
      String[] parameterNames = extractConstructorParameters(factoryKey,
          implementation, constructor, paramList, errors, dependencies);

      TypeLiteral<?> methodDeclaringType = factoryType.getSupertype(method.getDeclaringClass());
      assistData.add(new AssistData(implementation, MethodLiteral.get(constructor, implementation),
          MethodLiteral.get(method, methodDeclaringType), parameterNames));
      implementations.add(implementation);

      dependencies.addAll(guiceUtil.getMemberInjectionDependencies(factoryKey, implementation));
    }

    errors.throwConfigurationExceptionIfErrorsExist();
  }

  /**
   * Matches constructor parameters to method parameters for injection and
   * records remaining parameters as required keys.
   */
  private String[] extractConstructorParameters(Key<?> factoryKey, TypeLiteral<?> implementation,
      Constructor constructor, List<Key<?>> methodParams, Errors errors,
      Set<Dependency> dependencyCollector) throws ErrorsException {

    // Get parameters with annotations.
    List<TypeLiteral<?>> ctorParams = implementation.getParameterTypes(constructor);
    Annotation[][] ctorParamAnnotations = constructor.getParameterAnnotations();

    int p = 0;
    String[] parameterNames = new String[ctorParams.size()];
    Set<Key<?>> keySet = new LinkedHashSet<Key<?>>();
    for (TypeLiteral<?> ctorParam : ctorParams) {
      Key<?> ctorParamKey = getKey(ctorParam, constructor, ctorParamAnnotations[p], errors);

      if (ctorParamKey.getAnnotationType() == Assisted.class) {
        if (!keySet.add(ctorParamKey)) {
          errors.addMessage(PrettyPrinter.format(
              "%s has more than one parameter of type %s annotated with @Assisted(\"%s\").  " +
              "Please specify a unique value with the annotation to avoid confusion.",
              implementation, ctorParamKey.getTypeLiteral().getType(),
              ((Assisted) ctorParamKey.getAnnotation()).value()));
        }
       
        int location = methodParams.indexOf(ctorParamKey);

        // This should never happen since the constructor was already checked
        // in #[inject]constructorHasMatchingParams(..).
        Preconditions.checkState(location != -1);

        parameterNames[p] = ReflectUtil.formatParameterName(location);
      } else {
        dependencyCollector.add(new Dependency(factoryKey, ctorParamKey, false, true,
            constructor.toString()));
      }

      p++;
    }

    return parameterNames;
  }

  /**
   * 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 a {@literal @}{@link Inject} constructor.
   */
  private Constructor findMatchingConstructor(Method method, TypeLiteral<?> implementation,
      List<Key<?>> paramList, Errors errors) throws ErrorsException {
    Constructor<?> matchingConstructor = null;
    boolean anyAssistedInjectConstructors = false;

    // Look for AssistedInject constructors...
    for (Constructor<?> constructor : implementation.getRawType().getDeclaredConstructors()) {
      if (constructor.isAnnotationPresent(AssistedInject.class)) {
        anyAssistedInjectConstructors = true;

        if (constructorHasMatchingParams(implementation, constructor, paramList, errors)) {
          if (matchingConstructor != null) {
            errors.addMessage(PrettyPrinter.format(
                "%s has more than one constructor annotated with @AssistedInject "
                    + "that matches the parameters in method %s.",
                implementation, method));
            return null;
          } else {
            matchingConstructor = constructor;
          }
        }
      }
    }

    if (matchingConstructor != null) {
      return matchingConstructor;
    }

    if (anyAssistedInjectConstructors) {
      errors.addMessage(PrettyPrinter.format(
          "%s has @AssistedInject constructors, but none of them match the "
              + "parameters in method %s.", implementation, method));
      return null;
    }

    // Look for @Inject constructors...
    Constructor<?> injectConstructor =
        (Constructor) InjectionPoint.forConstructorOf(implementation).getMember();

    if (injectConstructorHasMatchingParams(implementation, injectConstructor, paramList, errors)) {
      return injectConstructor;
    }

    // No matching constructor exists, complain.
    errors.addMessage(PrettyPrinter.format(
        "%s has no constructors matching the parameters in method %s.",
        implementation, method));
    return null;
  }

  /**
   * Matching logic for {@literal @}{@link AssistedInject} constructor and
   * method parameters.
   *
   * 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 = new ArrayList<Key<?>>();
    for (TypeLiteral<?> param : params) {
      constructorKeys.add(getKey(param, constructor, paramAnnotations[p++], errors));
    }

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

  /**
   * Matching logic for {@literal @}{@link Inject} constructor and method
   * parameters.
   *
   * This returns true if all assisted parameters required by the constructor
   * are provided by the factory method.
   */
  private boolean injectConstructorHasMatchingParams(TypeLiteral<?> type,
      Constructor<?> constructor, List<Key<?>> paramList, Errors errors) throws ErrorsException {
    List<TypeLiteral<?>> params = type.getParameterTypes(constructor);
    Annotation[][] paramAnnotations = constructor.getParameterAnnotations();
    int p = 0;
    for (TypeLiteral<?> param : params) {
      Key<?> paramKey = getKey(param, constructor, paramAnnotations[p++], errors);
      if(paramKey.getAnnotationType() == Assisted.class && !paramList.contains(paramKey)) {
        return false;
      }
    }

    return true;
  }

  /**
   * 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(PrettyPrinter.format(
          "Only @Assisted is allowed for factory parameters, but found @%s",
          key.getAnnotationType()));
      throw errors.toException();
    }
  }

  private static class AssistData {
    final TypeLiteral<?> implementation;
    final MethodLiteral<?, Constructor<?>> constructor;
    final MethodLiteral<?, Method> method;
    final String[] parameterNames;

    private AssistData(TypeLiteral<?> implementation, MethodLiteral<?, Constructor<?>> constructor,
        MethodLiteral<?, Method> method, String[] parameterNames) {
      this.implementation = implementation;
      this.parameterNames = parameterNames;
      this.method = method;
      this.constructor = constructor;
    }
  }
}
TOP

Related Classes of com.google.gwt.inject.rebind.binding.FactoryBinding$AssistData

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.