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

com.google.gwt.inject.rebind.BindingsProcessor Maven / Gradle / Ivy

Go to download

GIN (GWT INjection) brings automatic dependency injection to Google Web Toolkit client-side code. GIN is built on top of Guice and uses (a subset of) Guice's binding language.

There is a newer version: 2.1.2
Show newest version
/*
 * Copyright 2009 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;

import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JConstructor;
import com.google.gwt.core.ext.typeinfo.JField;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JPackage;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.inject.client.GinModule;
import com.google.gwt.inject.client.GinModules;
import com.google.gwt.inject.rebind.adapter.GinModuleAdapter;
import com.google.gwt.inject.rebind.adapter.GwtDotCreateProvider;
import com.google.gwt.inject.rebind.binding.BindClassBinding;
import com.google.gwt.inject.rebind.binding.BindConstantBinding;
import com.google.gwt.inject.rebind.binding.BindProviderBinding;
import com.google.gwt.inject.rebind.binding.Binding;
import com.google.gwt.inject.rebind.binding.BindingIndex;
import com.google.gwt.inject.rebind.binding.CallConstructorBinding;
import com.google.gwt.inject.rebind.binding.CallGwtDotCreateBinding;
import com.google.gwt.inject.rebind.binding.GinjectorBinding;
import com.google.gwt.inject.rebind.binding.ImplicitProviderBinding;
import com.google.gwt.inject.rebind.binding.ProviderMethodBinding;
import com.google.gwt.inject.rebind.binding.RemoteServiceProxyBinding;
import com.google.gwt.inject.rebind.binding.RequiredKeys;
import com.google.gwt.inject.rebind.util.KeyUtil;
import com.google.gwt.inject.rebind.util.MemberCollector;
import com.google.gwt.inject.rebind.util.NameGenerator;
import com.google.inject.Guice;
import com.google.inject.ImplementedBy;
import com.google.inject.Inject;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.ProvidedBy;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.Singleton;
import com.google.inject.Stage;
import com.google.inject.internal.ProviderMethod;
import com.google.inject.spi.BindingScopingVisitor;
import com.google.inject.spi.DefaultBindingTargetVisitor;
import com.google.inject.spi.DefaultElementVisitor;
import com.google.inject.spi.Element;
import com.google.inject.spi.Elements;
import com.google.inject.spi.InjectionPoint;
import com.google.inject.spi.InstanceBinding;
import com.google.inject.spi.LinkedKeyBinding;
import com.google.inject.spi.Message;
import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderKeyBinding;
import com.google.inject.spi.ProviderLookup;
import com.google.inject.spi.StaticInjectionRequest;
import com.google.inject.spi.UntargettedBinding;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Builds up the bindings and scopes for this {@code Ginjector}.
 */
@Singleton
class BindingsProcessor implements BindingIndex {

  /**
   * Type array representing zero arguments for a metohod.
   */
  private static final JType[] ZERO_ARGS = new JType[0];

  private final TreeLogger logger;

  /**
   * Generates names for code we produce to resolve injection requests.
   */
  private final NameGenerator nameGenerator;

  /**
   * Map from key to binding for all types we already have a binding for.
   */
  private final Map, Binding> bindings = new HashMap, Binding>();

  /**
   * Map from key to scope for all types we have a binding for.
   */
  private final Map, GinScope> scopes = new HashMap, GinScope>();

  /**
   * Set of keys for classes that we still need to resolve. Every time a
   * binding is added to {@code bindings}, the key is removed from this set.
   * When this set and {@code unresolvedOptional} becomes empty, we know we've
   * satisfied all dependencies.
   */
  private final Set> unresolved = new HashSet>();

  /**
   * Set of keys for classes that we still need to resolve but that are
   * optionally bound. Every time a binding is added to {@code bindings},
   * the key is removed from this set. When this set and {@code unresolved}
   * becomes empty, we know we've satisfied all dependencies.
   */
  private final Set> unresolvedOptional = new HashSet>();

  /**
   * Collection of keys for which the ginjector interface provides member
   * inject methods. If a regular binding is defined for the same key, no
   * special member inject handling is required - a member inject method will
   * be created as part of a regular binding.
   */
  private final Set> memberInjectRequests = new HashSet>();

  /**
   * All types for which static injection has been requested.
   */
  private final Set> staticInjectionRequests = new HashSet>();

  /**
   * Collector that gathers all methods from an injector.
   */
  private final MemberCollector completeCollector;

  private final Provider callGwtDotCreateBindingProvider;
  private final Provider remoteServiceProxyBindingProvider;
  private final Provider callConstructorBinding;
  private final Provider bindClassBindingProvider;
  private final Provider bindProviderBindingProvider;
  private final Provider implicitProviderBindingProvider;
  private final Provider providerMethodBindingProvider;
  private final Provider bindConstantBindingProvider;
  private final Provider ginjectorBindingProvider;

  private final KeyUtil keyUtil;

  /**
   * Interface of the injector that this class is implementing.
   */
  private final JClassType ginjectorInterface;

  private final LieToGuiceModule lieToGuiceModule;

  /**
   * Keeps track of whether we've found an error so we can eventually throw
   * an {@link UnableToCompleteException}. We do this instead of throwing
   * immediately so that we can find more than one error per compilation cycle.
   */
  private boolean foundError = false;

  @Inject
  BindingsProcessor(NameGenerator nameGenerator, TreeLogger logger,
      Provider collectorProvider,
      Provider callGwtDotCreateBindingProvider,
      Provider callConstructorBinding,
      KeyUtil keyUtil,
      Provider bindClassBindingProvider,
      Provider bindProviderBindingProvider,
      Provider implicitProviderBindingProvider,
      @GinjectorInterfaceType JClassType ginjectorInterface,
      LieToGuiceModule lieToGuiceModule,
      Provider bindConstantBindingProvider,
      Provider remoteServiceProxyBindingProvider,
      Provider providerMethodBindingProvider,
      Provider ginjectorBindingProvider) {
    this.nameGenerator = nameGenerator;
    this.logger = logger;
    this.callGwtDotCreateBindingProvider = callGwtDotCreateBindingProvider;
    this.callConstructorBinding = callConstructorBinding;
    this.bindClassBindingProvider = bindClassBindingProvider;
    this.implicitProviderBindingProvider = implicitProviderBindingProvider;
    this.bindProviderBindingProvider = bindProviderBindingProvider;
    this.keyUtil = keyUtil;
    this.ginjectorInterface = ginjectorInterface;
    this.lieToGuiceModule = lieToGuiceModule;
    this.remoteServiceProxyBindingProvider = remoteServiceProxyBindingProvider;
    this.bindConstantBindingProvider = bindConstantBindingProvider;
    this.providerMethodBindingProvider = providerMethodBindingProvider;
    this.ginjectorBindingProvider = ginjectorBindingProvider;

    completeCollector = collectorProvider.get();
    completeCollector.setMethodFilter(MemberCollector.ALL_METHOD_FILTER);
  }

  public void process() throws UnableToCompleteException {
    validateMethods();
    addUnresolvedEntriesForInjectorInterface();

    List modules = createModules();

    createBindingsForModules(modules);
    createImplicitBindingsForUnresolved();
    validateModulesUsingGuice(modules);
  }

  private void createImplicitBindingsForUnresolved() throws UnableToCompleteException {
    while (!unresolved.isEmpty() || !unresolvedOptional.isEmpty()) {
      // Iterate through copies because we will modify sets during iteration
      for (Key key : new ArrayList>(unresolved)) {
        createImplicitBindingForUnresolved(key, false);
      }

      for (Key key : new ArrayList>(unresolvedOptional)) {
        createImplicitBindingForUnresolved(key, true);
      }

      checkForError();
    }
  }

  private void createImplicitBindingForUnresolved(Key key, boolean optional) {
    Binding binding = createImplicitBinding(key, optional);

    if (binding != null) {
      logger.log(TreeLogger.TRACE, "Implicit binding for " + key + ": " + binding);
      if (binding instanceof CallGwtDotCreateBinding || binding instanceof GinjectorBinding){
        // Need to lie to Guice about any implicit GWT.create bindings and
        // ginjector bindings we install that Guice would otherwise not see.
        // http://code.google.com/p/google-gin/issues/detail?id=13
        lieToGuiceModule.registerImplicitBinding(key);
      }

      addBinding(key, binding);
    } else if (optional) {
      unresolvedOptional.remove(key);
    }
  }

  private void checkForError() throws UnableToCompleteException {
    if (foundError) {
      throw new UnableToCompleteException();
    }
  }

  public Map, Binding> getBindings() {
    return bindings;
  }

  public Map, GinScope> getScopes() {
    return scopes;
  }

  public Set> getStaticInjectionRequests() {
    return staticInjectionRequests;
  }

  public Set> getMemberInjectRequests() {
    return memberInjectRequests;
  }

  public GinScope determineScope(Key key) {
    GinScope scope = getScopes().get(key);
    if (scope == null) {
      Class raw = keyUtil.getRawType(key);
      if (raw.getAnnotation(Singleton.class) != null) {
        // Look for scope annotation as a fallback
        scope = GinScope.SINGLETON;
      } else if (RemoteServiceProxyBinding.isRemoteServiceProxy(keyUtil.getRawClassType(key))) {
        // Special case for remote services
        scope = GinScope.SINGLETON;
      } else {
        scope = GinScope.NO_SCOPE;
      }
    }

    logger.log(TreeLogger.TRACE, "scope for " + key + ": " + scope);
    return scope;
  }

  public boolean isBound(Key key) {
    return bindings.containsKey(key);
  }

  private void validateMethods() throws UnableToCompleteException {
    for (JMethod method : completeCollector.getMethods(ginjectorInterface)) {
      if (method.getParameters().length > 1) {
        logError("Injector methods cannot have more than one parameter, "
            + " found: " + method.getReadableDeclaration());
      }

      if (method.getParameters().length == 1) {
        // Member inject method.
        if (method.getParameters()[0].getType().isClassOrInterface() == null) {
          logError("Injector method parameter types must be a class or "
              + "interface, found: " + method.getReadableDeclaration());
        }

        if (method.getReturnType() != JPrimitiveType.VOID) {
          logError("Injector methods with a parameter must have a void "
              + "return type, found: " + method.getReadableDeclaration());
        }
      } else if (method.getReturnType() == JPrimitiveType.VOID) {
        // Constructor injection.
        logError("Injector methods with no parameters cannot return void");
      }
    }

    checkForError();
  }

  private void addUnresolvedEntriesForInjectorInterface() {
    for (JMethod method : completeCollector.getMethods(ginjectorInterface)) {
      nameGenerator.markAsUsed(method.getName());
      Key key = keyUtil.getKey(method);
      logger.log(TreeLogger.TRACE, "Add unresolved key from injector interface: " + key);

      // Member inject types do not need to be gin-creatable themselves but we
      // need to provide all dependencies.
      if (keyUtil.isMemberInject(method)) {
        if (!unresolved.contains(key)) {
          memberInjectRequests.add(key);
          RequiredKeys requiredKeys = keyUtil.getRequiredKeys(keyUtil.getClassType(key));
          unresolved.addAll(requiredKeys.getRequiredKeys());
          unresolvedOptional.addAll(requiredKeys.getOptionalKeys());
        }
      } else {
        unresolved.add(key);
      }
    }
  }

  private void createBindingsForModules(List modules) throws UnableToCompleteException {
    List elements = Elements.getElements(modules);
    for (Element element : elements) {
      GuiceElementVisitor visitor = new GuiceElementVisitor();
      element.acceptVisitor(visitor);

      // Capture any binding errors, any of which we treat as fatal
      List messages = visitor.getMessages();
      if (!messages.isEmpty()) {
        for (Message message : messages) {
          // tostring has both source and message so use that
          logError(message.toString(), message.getCause());
        }
      }
    }

    checkForError();
  }

  private List createModules() {
    List modules = new ArrayList();
    populateModulesFromInjectorInterface(ginjectorInterface, modules);
    return modules;
  }

  private void validateModulesUsingGuice(List modules) throws UnableToCompleteException {
    // Validate module consistency using Guice.
    try {
      List modulesForGuice = new ArrayList(modules.size() + 1);
      modulesForGuice.add(lieToGuiceModule);
      modulesForGuice.addAll(modules);
      Guice.createInjector(Stage.TOOL, modulesForGuice);
    } catch (Exception e) {
      logError("Errors from Guice: " + e.getMessage(), e);
      throw new UnableToCompleteException();
    }
  }

  private void populateModulesFromInjectorInterface(JClassType iface, List modules) {
    GinModules gmodules = iface.getAnnotation(GinModules.class);
    if (gmodules != null) {
      for (Class moduleClass : gmodules.value()) {
        Module module = instantiateGModuleClass(moduleClass);
        if (module != null) {
          modules.add(module);
        }
      }
    }

    for (JClassType superIface : iface.getImplementedInterfaces()) {
      populateModulesFromInjectorInterface(superIface, modules);
    }
  }

  private Module instantiateGModuleClass(Class moduleClassName) {
    try {
      Constructor constructor = moduleClassName.getDeclaredConstructor();
      try {
        constructor.setAccessible(true);
        return new GinModuleAdapter(constructor.newInstance());
      } finally {
        constructor.setAccessible(false);
      }
    } catch (IllegalAccessException e) {
      logError("Error creating module: " + moduleClassName, e);
    } catch (InstantiationException e) {
      logError("Error creating module: " + moduleClassName, e);
    } catch (NoSuchMethodException e) {
      logError("Error creating module: " + moduleClassName, e);
    } catch (InvocationTargetException e) {
      logError("Error creating module: " + moduleClassName, e);
    }

    return null;
  }

  private Binding createImplicitBinding(Key key, boolean optional) {
    // All steps per:
    // http://code.google.com/p/google-guice/wiki/BindingResolution

    JClassType rawClassType = keyUtil.getRawClassType(key);

    // 1. Explicit binding - already finished at this point.

    // This is really an explicit binding, we add it here.
    // TODO(schmitt): Can we just add a binding to the module?
    if (rawClassType.equals(ginjectorInterface)) {
      return ginjectorBindingProvider.get();
    }

    // 2. Ask parent injector.
    // TODO(schmitt): Implement parent/child injectors.

    // 3. Ask child injector.
    // TODO(schmitt): Implement parent/child injectors.

    // 4. Provider injections.
    if (isProviderKey(key)) {
      ImplicitProviderBinding binding = implicitProviderBindingProvider.get();
      binding.setProviderKey(key);

      if (optional) {
        // We have to take special measures for optional implicit providers
        // since they are only created/injected if their provided type can be
        // bound.
        return checkOptionalBindingAvailability(binding);
      }

      return binding;

      // TODO(bstoler): Scope the provider binding like the thing being provided?
    }

    // 5. Convert constants.
    // Already covered by resolving explicit bindings.
    if (BindConstantBinding.isConstantKey(key)) {
      if (!optional) {
        logError("Binding requested for constant key " + key
            + " but no explicit binding was found.");
      }

      return null;
    }

    // 6. If the dependency has a binding annotation, give up.
    if (key.getAnnotation() != null || key.getAnnotationType() != null) {
      if (!optional) {
        logError("No implementation bound for key " + key);
      }

      return null;
    }

    // 7. If the dependency is an array or enum, give up.
    // Covered by step 5 (enum) and 11 (array).

    // 8. Handle TypeLiteral injections.
    // TODO(schmitt): Implement TypeLiteral injections.

    // 9. Use resolution annotations (@ImplementedBy, @ProvidedBy)
    ImplementedBy implementedBy = rawClassType.getAnnotation(ImplementedBy.class);
    if (implementedBy != null) {
      return createImplementedByBinding(key, implementedBy, optional);
    }

    ProvidedBy providedBy = rawClassType.getAnnotation(ProvidedBy.class);
    if (providedBy != null) {
      return createProvidedByBinding(key, providedBy, optional);
    }

    // 10. If the dependency is abstract or a non-static inner class, give up.
    // Abstract classes are handled by GWT.create.
    // TODO(schmitt): Introduce check.

    // 11. Use a single @Inject or public no-arguments constructor.
    JClassType classType = keyUtil.getClassType(key);
    if (classType != null) {
      return createImplicitBindingForClass(classType, optional, key);
    } else if (!optional) {
      logError("Class not found: " + key);
    }

    return null;
  }

  private Binding createImplicitBindingForClass(JClassType classType, boolean optional,
      Key key) {
    // Either call the @Inject constructor or use GWT.create
    JConstructor injectConstructor = getInjectConstructor(classType);

    if (injectConstructor != null) {
      CallConstructorBinding binding = callConstructorBinding.get();
      binding.setConstructor(injectConstructor, key);
      return binding;
    }

    if (hasAccessibleZeroArgConstructor(classType)) {
      if (RemoteServiceProxyBinding.isRemoteServiceProxy(classType)) {
        RemoteServiceProxyBinding binding = remoteServiceProxyBindingProvider.get();
        binding.setClassType(classType, key);
        return binding;
      } else {
        CallGwtDotCreateBinding binding = callGwtDotCreateBindingProvider.get();
        binding.setClassType(classType, key);
        return binding;
      }
    }

    if (!optional) {
      logError("No @Inject or default constructor found for " + classType);
    }

    return null;
  }

  /**
   * Returns true iff the passed type has a constructor with zero arguments
   * (default constructors included) and that constructor is non-private,
   * excepting constructors for private classes where the constructor may be of
   * any visibility.
   *
   * @param classType type to be checked for matching constructor
   * @return true if a matching constructor is present on the passed type
   */
  private boolean hasAccessibleZeroArgConstructor(JClassType classType) {
    if (classType.isInterface() != null) {
      return true;
    }

    // This will return one constructor on any class that doesn't have any
    // constructors specified:  The JDT compiler (internally used by GWT) adds
    // a synthetic default constructor to every class with the class's
    // visibility that gets picked up by GWT as a regular constructor.
    //
    // See also:
    // http://code.google.com/p/google-web-toolkit/issues/detail?id=3514
    // http://code.google.com/p/google-guice/wiki/Injections
    JConstructor constructor = classType.findConstructor(ZERO_ARGS);
    return constructor != null && (!constructor.isPrivate() || classType.isPrivate());
  }

  private void addBinding(Key key, Binding binding) {
    if (bindings.containsKey(key)) {
      logError("Double-bound: " + key + ". " + bindings.get(key) + ", " + binding);
      return;
    }

    JClassType classType = keyUtil.getRawClassType(key);
    if (classType != null && !isClassAccessibleFromGinjector(classType)) {
      logError("Can not inject an instance of an inaccessible class. Key=" + key);
      return;
    }

    bindings.put(key, binding);
    unresolved.remove(key);
    unresolvedOptional.remove(key);
    memberInjectRequests.remove(key);

    addRequiredKeys(key, binding.getRequiredKeys());

    logger.log(TreeLogger.TRACE, "bound " + key + " to " + binding);
  }

  private void addRequiredKeys(Key key, RequiredKeys requiredKeys) {
    // Resolve optional keys.
    // Clone the returned set so we can safely mutate it
    Set> optionalKeys = new HashSet>(requiredKeys.getOptionalKeys());
    optionalKeys.removeAll(bindings.keySet());
    if (!optionalKeys.isEmpty()) {
      logger.log(TreeLogger.TRACE, "Add optional unresolved as dep from binding to "
          + key + ": " + optionalKeys);
      unresolvedOptional.addAll(optionalKeys);
    }

    // Resolve required keys.
    // Clone the returned set so we can safely mutate it
    Set> nowUnresolved = new HashSet>(requiredKeys.getRequiredKeys());
    nowUnresolved.removeAll(bindings.keySet());
    if (!nowUnresolved.isEmpty()) {
      logger.log(TreeLogger.TRACE, "Add unresolved as dep from binding to "
          + key + ": " + nowUnresolved);
      unresolved.addAll(nowUnresolved);
    }
  }

  private  T checkOptionalBindingAvailability(T binding) {
    RequiredKeys requiredKeys = binding.getRequiredKeys();

    assert(requiredKeys.getOptionalKeys().isEmpty());

    // Find out whether all requirements of this provider can be satisfied.
    Set> unresolved = new HashSet>(requiredKeys.getRequiredKeys());
    unresolved.removeAll(bindings.keySet());
    for (Key requiredKey : unresolved) {

      // Note: This call doesn't cause a binding to be registered.
      if (createImplicitBinding(requiredKey, true) == null) {

        // A dependency cannot be constructed, this binding is not available.
        return null;
      }
    }

    return binding;
  }

  private BindClassBinding createImplementedByBinding(Key key, ImplementedBy implementedBy,
      boolean optional) {
    Class rawType = key.getTypeLiteral().getRawType();
    Class implementationType = implementedBy.value();

    if (implementationType == rawType) {
      logError("@ImplementedBy points to the same class it annotates: " + rawType);
      return null;
    }

    if (!rawType.isAssignableFrom(implementationType)) {
      logError(implementationType + " doesn't extend " + rawType
          + " (while resolving @ImplementedBy)");
      return null;
    }

    BindClassBinding implementedByBinding = bindClassBindingProvider.get();
    implementedByBinding.setBoundClassKey(Key.get(implementationType));

    if (optional) {
      return checkOptionalBindingAvailability(implementedByBinding);
    }

    return implementedByBinding;
  }

  private BindProviderBinding createProvidedByBinding(Key key, ProvidedBy providedBy,
      boolean optional) {
    Class rawType = key.getTypeLiteral().getRawType();
    Class> providerType = providedBy.value();

    if (providerType == rawType) {
      logError("@ProvidedBy points to the same class it annotates: " + rawType);
      return null;
    }

    BindProviderBinding implementedByBinding = bindProviderBindingProvider.get();
    implementedByBinding.setProviderKey(Key.get(providerType));

    if (optional) {
      return checkOptionalBindingAvailability(implementedByBinding);
    }

    return implementedByBinding;
  }

  private boolean isProviderKey(Key key) {
    Type keyType = key.getTypeLiteral().getType();
    return keyType instanceof ParameterizedType &&
        ((ParameterizedType) keyType).getRawType() == Provider.class;
  }

  private boolean isClassAccessibleFromGinjector(JClassType classType) {
    if (classType.isPublic()) {
      return true;
    }

    // Null class package could be if it's not an object type
    JPackage classPackage = classType.getPackage();
    if (classPackage == null) {
      return false;
    }

    JPackage ginjectorPackage = ginjectorInterface.getPackage();
    return (ginjectorPackage.isDefault() && classPackage.isDefault())
        || classPackage.getName().equals(ginjectorPackage.getName());
  }

  private void logError(String message) {
    logError(message, null);
  }

  private void logError(String message, Throwable t) {
    logger.log(TreeLogger.ERROR, message, t);
    foundError = true;
  }

  private JConstructor getInjectConstructor(JClassType classType) {
    JConstructor[] constructors = classType.getConstructors();

    JConstructor injectConstructor = null;
    for (JConstructor constructor : constructors) {
      if (constructor.getAnnotation(Inject.class) != null) {
        if (injectConstructor == null) {
          injectConstructor = constructor;
        } else {
          logError("More than one @Inject constructor found for "
              + classType + "; " + injectConstructor + ", " + constructor);
          return null;
        }
      }
    }

    return injectConstructor;
  }

  private class GuiceElementVisitor extends DefaultElementVisitor {
    private final List messages = new ArrayList();

    @Override
    public  Void visit(com.google.inject.Binding command) {
      GuiceBindingVisitor bindingVisitor = new GuiceBindingVisitor(command.getKey(),
          messages);
      command.acceptTargetVisitor(bindingVisitor);
      command.acceptScopingVisitor(bindingVisitor);
      return null;
    }

    @Override
    public Void visit(Message message) {
      messages.add(message);
      return null;
    }

    @Override
    public  Void visit(ProviderLookup providerLookup) {
      // Ignore provider lookups for now
      // TODO(bstoler): I guess we should error if you try to lookup a provider
      // that is not bound?
      return null;
    }

    @Override
    protected Void visitOther(Element element) {
      visit(new Message(element.getSource(),
          "Ignoring unsupported Module element: " + element));
      return null;
    }

    @Override
    public Void visit(StaticInjectionRequest staticInjectionRequest) {
      addStaticInjectionRequest(staticInjectionRequest);
      return null;
    }

    public List getMessages() {
      return messages;
    }

    private void addStaticInjectionRequest(StaticInjectionRequest staticInjectionRequest) {
      Class type = staticInjectionRequest.getType();
      staticInjectionRequests.add(type);

      // Calculate required bindings and add to unresolved.
      Set> unresolved = new HashSet>();
      Set> unresolvedOptional = new HashSet>();
      for (InjectionPoint injectionPoint : InjectionPoint.forStaticMethodsAndFields(type)) {
        Member member = injectionPoint.getMember();
        if (member instanceof Method) {
          JMethod method = null;
          try {
            method = keyUtil.javaToGwtMethod((Method) member);
          } catch (NotFoundException e) {
            messages.add(
                new Message(new ArrayList(), "Could not resolve GWT method: " + member, e));
            return;
          }
          RequiredKeys keys = keyUtil.getRequiredKeys(method);
          unresolved.addAll(keys.getRequiredKeys());
          unresolvedOptional.addAll(keys.getOptionalKeys());
        } else if (member instanceof Field) {
          JField field = keyUtil.javaToGwtField((Field) member);
          Key key = keyUtil.getKey(field);
          if (keyUtil.isOptional(field)) {
            unresolvedOptional.add(key);
          } else {
            unresolved.add(key);
          }
        }
      }
      addRequiredKeys(keyUtil.getKey(type), new RequiredKeys(unresolved, unresolvedOptional));
    }
  }

  private class GuiceBindingVisitor extends DefaultBindingTargetVisitor
      implements BindingScopingVisitor {
    private final Key targetKey;
    private final List messages;

    public GuiceBindingVisitor(Key targetKey, List messages) {
      this.targetKey = targetKey;
      this.messages = messages;
    }

    @Override
    public Void visit(ProviderKeyBinding providerKeyBinding) {
      BindProviderBinding binding = bindProviderBindingProvider.get();
      binding.setProviderKey(providerKeyBinding.getProviderKey());
      addBinding(targetKey, binding);
      return null;
    }

    @Override
    public Void visit(ProviderInstanceBinding providerInstanceBinding) {
      // Detect provider methods and handle them
      // TODO(bstoler): Update this when the SPI explicitly has a case for provider methods
      Provider provider = providerInstanceBinding.getProviderInstance();
      if (provider instanceof ProviderMethod) {
        ProviderMethodBinding binding = providerMethodBindingProvider.get();
        try {
          binding.setProviderMethod((ProviderMethod) provider);
          addBinding(targetKey, binding);
        } catch (UnableToCompleteException e) {
          messages.add(new Message(providerInstanceBinding.getSource(),
              "Error processing provider method"));
        }
        return null;
      }

      if (provider instanceof GwtDotCreateProvider) {
        addImplicitBinding();
        return null;
      }

      // OTt, use the normal default handler (and error)
      return super.visit(providerInstanceBinding);
    }

    @Override
    public Void visit(LinkedKeyBinding linkedKeyBinding) {
      BindClassBinding binding = bindClassBindingProvider.get();
      binding.setBoundClassKey(linkedKeyBinding.getLinkedKey());
      addBinding(targetKey, binding);
      return null;
    }

    @Override
    public Void visit(InstanceBinding instanceBinding) {
      T instance = instanceBinding.getInstance();
      if (BindConstantBinding.isConstantKey(targetKey)) {
        BindConstantBinding binding = bindConstantBindingProvider.get();
        binding.setKeyAndInstance(targetKey, instance);
        addBinding(targetKey, binding);
      } else {
        messages.add(new Message(instanceBinding.getSource(),
            "Instance binding not supported; key=" + targetKey + " inst=" + instance));
      }

      return null;
    }

    @Override
    public Void visit(UntargettedBinding untargettedBinding) {
      addImplicitBinding();

      return null;
    }

    private void addImplicitBinding() {
      // Register a Gin binding for the default-case binding that
      // Guice saw. We need to register this to avoid later adding
      // this key to the Guice-lies module, which would make it
      // double bound. If binding was null, an error was already logged.
      Binding binding = createImplicitBinding(targetKey, false);
      if (binding != null) {
        logger.log(TreeLogger.TRACE, "Implicit binding for " + targetKey + ": " + binding);
        addBinding(targetKey, binding);
      }
    }

    @Override
    protected Void visitOther(com.google.inject.Binding binding) {
      messages.add(new Message(binding.getSource(),
          "Unsupported binding provided for key: " + targetKey + ": " + binding));
      return null;
    }

    public Void visitEagerSingleton() {
      scopes.put(targetKey, GinScope.EAGER_SINGLETON);
      return null;
    }

    public Void visitScope(Scope scope) {
      messages.add(new Message("Explicit scope unsupported: key=" + targetKey
          + " scope=" + scope));
      return null;
    }

    public Void visitScopeAnnotation(Class scopeAnnotation) {
      if (scopeAnnotation == Singleton.class) {
        scopes.put(targetKey, GinScope.SINGLETON);
      } else {
        messages.add(new Message("Unsupported scope annoation: key=" + targetKey
            + " scope=" + scopeAnnotation));
      }
      return null;
    }

    public Void visitNoScoping() {
      scopes.put(targetKey, GinScope.NO_SCOPE);
      return null;
    }
  }
}