com.google.gwt.inject.rebind.resolution.ImplicitBindingCreator Maven / Gradle / Ivy
Show all versions of gin Show documentation
/*
* Copyright 2011 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.resolution;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.inject.client.AsyncProvider;
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.BindingFactory;
import com.google.gwt.inject.rebind.binding.Context;
import com.google.gwt.inject.rebind.binding.RemoteServiceProxyBinding;
import com.google.gwt.inject.rebind.reflect.MethodLiteral;
import com.google.gwt.inject.rebind.reflect.ReflectUtil;
import com.google.gwt.inject.rebind.util.GuiceUtil;
import com.google.gwt.inject.rebind.util.PrettyPrinter;
import com.google.inject.ImplementedBy;
import com.google.inject.Inject;
import com.google.inject.Key;
import com.google.inject.ProvidedBy;
import com.google.inject.TypeLiteral;
import com.google.inject.assistedinject.Assisted;
import java.lang.reflect.Constructor;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import javax.inject.Provider;
/**
* Class responsible for creating implicit bindings. This returns the binding entry
* that should be used for the given type. It does not concern itself with satisfying
* the dependencies or with figuring out where the binding should appear. For that,
* see {@link BindingResolver}.
*/
public class ImplicitBindingCreator {
/**
* Exception thrown to indicate an error occurred during binding creation.
*/
public static class BindingCreationException extends Exception {
/**
* Create a new BindingCreationException using the given format string and arguments. Will
* create an exception with a message constructed with {@code String.format(msgFmt, args)}.
*/
public BindingCreationException(String msgFmt, Object... args) {
super(PrettyPrinter.format(msgFmt, args));
}
}
private final BindingFactory bindingFactory;
private final GeneratorContext generatorContext;
private final TreeLogger logger;
@Inject
public ImplicitBindingCreator(BindingFactory bindingFactory, GeneratorContext generatorContext,
@Assisted TreeLogger logger) {
this.bindingFactory = bindingFactory;
this.generatorContext = generatorContext;
this.logger = logger;
}
/**
* Creates the implicit binding
*/
public Binding create(Key> key) throws BindingCreationException {
TypeLiteral> type = key.getTypeLiteral();
// All steps per:
// http://code.google.com/p/google-guice/wiki/BindingResolution
// 1. Explicit binding - already finished at this point.
// 2. Ask parent injector.
// 3. Ask child injector.
// These bindings are created in BindingResolver and are not necessary here.
// 4. Provider injections.
if (isProviderKey(key)) {
return bindingFactory.getImplicitProviderBinding(key);
// TODO(bstoler): Scope the provider binding like the thing being provided?
}
// 4b. AsyncProvider injections.
if (isAsyncProviderKey(key)) {
return bindingFactory.getAsyncProviderBinding(key);
}
// 5. Convert constants.
// Already covered by resolving explicit bindings.
if (BindConstantBinding.isConstantKey(key)) {
throw new BindingCreationException(
"Binding requested for constant key '%s' but no explicit binding was found", key);
}
// 6. If the dependency has a binding annotation, give up.
if (key.getAnnotation() != null || key.getAnnotationType() != null) {
throw new BindingCreationException("No implementation bound for '%s' and an implicit binding"
+ " cannot be created because the type is annotated.", key);
}
// 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 = type.getRawType().getAnnotation(ImplementedBy.class);
if (implementedBy != null) {
return createImplementedByBinding(key, implementedBy);
}
ProvidedBy providedBy = type.getRawType().getAnnotation(ProvidedBy.class);
if (providedBy != null) {
return createProvidedByBinding(key, providedBy);
}
// 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.
return createImplicitBindingForClass(type);
}
private Binding createImplicitBindingForClass(TypeLiteral> type)
throws BindingCreationException {
// Either call the @Inject constructor or use GWT.create
MethodLiteral, Constructor>> injectConstructor = getInjectConstructor(type);
if (injectConstructor != null) {
return bindingFactory.getCallConstructorBinding(injectConstructor);
}
if (shouldGwtDotCreate(type)) {
if (RemoteServiceProxyBinding.isRemoteServiceProxy(type)) {
return bindingFactory.getRemoteServiceProxyBinding(type);
} else {
return bindingFactory.getCallGwtDotCreateBinding(type);
}
}
throw new BindingCreationException("No @Inject or default constructor found for %s", type);
}
/**
* Returns {@code true} if the given type should be created automatically with
* {@code GWT.create()}.
*
* {@code GWT.create()} is used to create concrete classes with accessible
* zero-argument constructors, and interfaces or classes with GWT rebind
* rules.
*/
private boolean shouldGwtDotCreate(TypeLiteral> typeLiteral) throws BindingCreationException {
Class> rawType = typeLiteral.getRawType();
if (rawType.isInterface()) {
// Check whether we can GWT.create() the interface.
// Remote service proxies don't have rebind rules; we handle them
// specially by creating the corresponding synchronous interface (which
// does have a rebind rule).
if (RemoteServiceProxyBinding.isRemoteServiceProxy(typeLiteral)) {
// We could check whether the synchronous interface has a rebind rule;
// however, the user is probably expecting us to GWT.create() a service
// interface for them. If there isn't a rebind rule, a GWT rebind error
// probably makes more sense than a Gin error.
return true;
}
return hasRebindRule(rawType);
} else {
return hasAccessibleZeroArgConstructor(rawType) || hasRebindRule(rawType);
}
}
private boolean hasRebindRule(Class> rawType) throws BindingCreationException {
String canonicalName = rawType.getCanonicalName();
if (canonicalName == null) {
throw new BindingCreationException("Cannot inject a type with no canonical name: " + rawType);
} else {
return generatorContext.checkRebindRuleAvailable(canonicalName);
}
}
/**
* 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 typeLiteral type to be checked for matching constructor
* @return true if a matching constructor is present on the passed type
*/
private boolean hasAccessibleZeroArgConstructor(Class> rawType) {
Constructor> constructor;
try {
constructor = rawType.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
return rawType.getDeclaredConstructors().length == 0;
}
return !ReflectUtil.isPrivate(constructor) || ReflectUtil.isPrivate(rawType);
}
private BindClassBinding createImplementedByBinding(Key> key, ImplementedBy implementedBy)
throws BindingCreationException {
Class> rawType = key.getTypeLiteral().getRawType();
Class> implementationType = implementedBy.value();
if (implementationType == rawType) {
throw new BindingCreationException(
"@ImplementedBy points to the same class it annotates: %s", rawType);
}
if (!rawType.isAssignableFrom(implementationType)) {
throw new BindingCreationException("%s doesn't extend %s (while resolving @ImplementedBy)",
implementationType, rawType);
}
return bindingFactory.getBindClassBinding(Key.get(implementationType), key,
Context.forText("@ImplementedBy annotation"));
}
private BindProviderBinding createProvidedByBinding(Key> key, ProvidedBy providedBy)
throws BindingCreationException {
Class> rawType = key.getTypeLiteral().getRawType();
Class extends Provider>> providerType = providedBy.value();
if (providerType == rawType) {
throw new BindingCreationException(
"@ProvidedBy points to the same class it annotates: %s", rawType);
}
return bindingFactory.getBindProviderBinding(Key.get(providerType), key,
Context.forText("@ProvidedBy annotation"));
}
private boolean isProviderKey(Key> key) {
Type keyType = key.getTypeLiteral().getType();
return keyType instanceof ParameterizedType &&
(((ParameterizedType) keyType).getRawType() == Provider.class
|| ((ParameterizedType) keyType).getRawType() == com.google.inject.Provider.class);
}
private boolean isAsyncProviderKey(Key> key) {
Type keyType = key.getTypeLiteral().getType();
return keyType instanceof ParameterizedType &&
((ParameterizedType) keyType).getRawType() == AsyncProvider.class;
}
private MethodLiteral, Constructor>> getInjectConstructor(TypeLiteral> type)
throws BindingCreationException {
Constructor>[] constructors = type.getRawType().getDeclaredConstructors();
MethodLiteral, Constructor>> injectConstructor = null;
for (Constructor> constructor : constructors) {
MethodLiteral, Constructor>> constructorLiteral = MethodLiteral.get(constructor, type);
if (GuiceUtil.hasInject(constructorLiteral)) {
if (injectConstructor != null) {
throw new BindingCreationException(
"More than one @Inject constructor found for %s; %s, %s",
type, injectConstructor, constructorLiteral);
}
injectConstructor = constructorLiteral;
}
}
return injectConstructor;
}
public interface Factory {
ImplicitBindingCreator create(TreeLogger logger);
}
}