/*
* 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;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.inject.client.GinModule;
import com.google.gwt.inject.client.Ginjector;
import com.google.gwt.inject.client.PrivateGinModule;
import com.google.gwt.inject.client.assistedinject.FactoryModule;
import com.google.gwt.inject.rebind.adapter.GinModuleAdapter;
import com.google.gwt.inject.rebind.adapter.PrivateGinModuleAdapter;
import com.google.gwt.inject.rebind.binding.BindingFactory;
import com.google.gwt.inject.rebind.binding.Context;
import com.google.gwt.inject.rebind.binding.FactoryBinding;
import com.google.gwt.inject.rebind.reflect.MethodLiteral;
import com.google.gwt.inject.rebind.reflect.ReflectUtil;
import com.google.gwt.inject.rebind.util.MemberCollector;
import com.google.inject.ConfigurationException;
import com.google.inject.Inject;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.Elements;
import javax.inject.Provider;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* Builds up the bindings and scopes for this {@code Ginjector}. This uses
* Guice SPI to inspect the modules and build up details about the necessary
* bindings in the {@link GinjectorBindings}.
*/
@Singleton
class BindingsProcessor {
/**
* Collector that gathers all methods from an injector.
*/
private final MemberCollector completeCollector;
private final BindingFactory bindingFactory;
/**
* Interface of the injector that this class is implementing.
*/
private final TypeLiteral<? extends Ginjector> ginjectorInterface;
private final ErrorManager errorManager;
private final GinjectorBindings rootGinjectorBindings;
private final GuiceElementVisitor.GuiceElementVisitorFactory guiceElementVisitorFactory;
private final Set<Class<? extends GinModule>> moduleClasses;
private DoubleBindingChecker doubleBindingChecker;
@Inject
BindingsProcessor(Provider<MemberCollector> collectorProvider,
@GinjectorInterfaceType Class<? extends Ginjector> ginjectorInterface,
ErrorManager errorManager,
@RootBindings GinjectorBindings rootGinjectorBindings,
GuiceElementVisitor.GuiceElementVisitorFactory guiceElementVisitorFactory,
BindingFactory bindingFactory,
@ModuleClasses Set<Class<? extends GinModule>> moduleClasses,
DoubleBindingChecker doubleBindingChecker) {
this.bindingFactory = bindingFactory;
this.moduleClasses = moduleClasses;
this.ginjectorInterface = TypeLiteral.get(ginjectorInterface);
this.errorManager = errorManager;
this.rootGinjectorBindings = rootGinjectorBindings;
this.guiceElementVisitorFactory = guiceElementVisitorFactory;
this.doubleBindingChecker = doubleBindingChecker;
completeCollector = collectorProvider.get();
completeCollector.setMethodFilter(MemberCollector.ALL_METHOD_FILTER);
}
public void process() throws UnableToCompleteException {
validateMethods();
rootGinjectorBindings.setModule(ginjectorInterface.getRawType());
rootGinjectorBindings.addUnresolvedEntriesForInjectorInterface();
registerGinjectorBinding();
createBindingsForModules(instantiateModules());
errorManager.checkForError();
resolveAllUnresolvedBindings(rootGinjectorBindings);
errorManager.checkForError();
doubleBindingChecker.checkBindings(rootGinjectorBindings);
errorManager.checkForError();
}
/**
* Create an explicit binding for the Ginjector.
*/
private void registerGinjectorBinding() {
Key<? extends Ginjector> ginjectorKey = Key.get(ginjectorInterface);
rootGinjectorBindings.addBinding(ginjectorKey, bindingFactory.getGinjectorBinding());
}
/**
* Create bindings for factories and resolve all implicit bindings for all
* unresolved bindings in the each injector.
*
* <p> This performs a depth-first iteration over all the nodes, and fills in the
* bindings on the way up the tree. This order is important because creating
* implicit bindings in a child {@link GinjectorBindings} may add dependencies to the
* parent. By processing on the way up, we ensure that we only need to
* process each set once.
*
* @param collection {@link GinjectorBindings} to resolve bindings for
* @throws UnableToCompleteException if binding failed
*/
private void resolveAllUnresolvedBindings(GinjectorBindings collection)
throws UnableToCompleteException {
// Create known/explicit bindings before descending into children. This ensures that they are
// available to any children that may need to depend on them.
createBindingsForFactories(collection);
// Visit all children and resolve bindings as appropriate. This visitation may add implicit
// bindings (and dependencies) to this ginjector
for (GinjectorBindings child : collection.getChildren()) {
resolveAllUnresolvedBindings(child);
}
// Resolve bindings within this ginjector and validate that everything looks OK.
collection.resolveBindings();
}
private void createBindingsForFactories(GinjectorBindings bindings) {
for (final FactoryModule<?> factoryModule : bindings.getFactoryModules()) {
FactoryBinding binding;
try {
binding = bindingFactory.getFactoryBinding(
factoryModule.getBindings(),
factoryModule.getFactoryType(),
Context.forText(factoryModule.getSource()));
} catch (ConfigurationException e) {
errorManager.logError("Factory %s could not be created", e, factoryModule.getFactoryType());
continue;
}
bindings.addBinding(factoryModule.getFactoryType(), binding);
}
}
private void validateMethods() throws UnableToCompleteException {
for (MethodLiteral<?, Method> method : completeCollector.getMethods(ginjectorInterface)) {
List<TypeLiteral<?>> parameters = method.getParameterTypes();
if (parameters.size() > 1) {
errorManager.logError("Injector methods cannot have more than one parameter, found: %s",
method);
}
if (parameters.size() == 1) {
// Member inject method.
Class<?> paramType = parameters.get(0).getRawType();
if (!ReflectUtil.isClassOrInterface(paramType)) {
errorManager.logError(
"Injector method parameter types must be a class or interface, found: %s",
method);
}
if (!method.getReturnType().getRawType().equals(Void.TYPE)) {
errorManager.logError(
"Injector methods with a parameter must have a void return type, found: %s",
method);
}
} else if (method.getReturnType().getRawType().equals(Void.TYPE)) {
// Constructor injection.
errorManager.logError("Injector methods with no parameters cannot return void");
}
}
errorManager.checkForError();
}
private void createBindingsForModules(List<Module> modules) {
GuiceElementVisitor visitor = guiceElementVisitorFactory.create(rootGinjectorBindings);
visitor.visitElementsAndReportErrors(Elements.getElements(modules));
}
private List<Module> instantiateModules() {
List<Module> modules = new ArrayList<Module>();
for (Class<? extends GinModule> clazz : moduleClasses) {
Module module = instantiateModuleClass(clazz);
if (module != null) {
modules.add(module);
}
}
return modules;
}
private Module instantiateModuleClass(Class<? extends GinModule> moduleClass) {
try {
Constructor<? extends GinModule> constructor = moduleClass.getDeclaredConstructor();
try {
constructor.setAccessible(true);
if (PrivateGinModule.class.isAssignableFrom(moduleClass)) {
return new PrivateGinModuleAdapter((PrivateGinModule) constructor.newInstance(),
rootGinjectorBindings);
} else {
return new GinModuleAdapter(constructor.newInstance(), rootGinjectorBindings);
}
} finally {
constructor.setAccessible(false);
}
} catch (IllegalAccessException e) {
errorManager.logError("Error creating module: " + moduleClass, e);
} catch (InstantiationException e) {
errorManager.logError("Error creating module: " + moduleClass, e);
} catch (NoSuchMethodException e) {
errorManager.logError("Error creating module: " + moduleClass, e);
} catch (InvocationTargetException e) {
errorManager.logError("Error creating module: " + moduleClass, e);
}
return null;
}
}