blob: 6e58b43e1cab011ed51ab1682380bbd2f3ea8915 [file] [log] [blame]
/**
* Copyright (C) 2006 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;
import com.google.inject.InjectorImpl.SingleMemberInjector;
import static com.google.inject.Scopes.SINGLETON;
import com.google.inject.internal.Annotations;
import static com.google.inject.internal.Objects.nonNull;
import com.google.inject.internal.StackTraceElements;
import com.google.inject.internal.Stopwatch;
import com.google.inject.internal.Objects;
import com.google.inject.internal.Strings;
import com.google.inject.matcher.Matcher;
import com.google.inject.matcher.Matchers;
import com.google.inject.matcher.AbstractMatcher;
import com.google.inject.spi.Message;
import com.google.inject.spi.SourceProviders;
import com.google.inject.spi.TypeConverter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.aopalliance.intercept.MethodInterceptor;
/**
* Builds a dependency injection {@link Injector}. Binds {@link Key}s to
* implementations.
*
* @author crazybob@google.com (Bob Lee)
*/
class BinderImpl implements Binder {
static {
SourceProviders.skip(BinderImpl.class);
}
private static final Logger logger
= Logger.getLogger(BinderImpl.class.getName());
final LinkedList<BindingBuilderImpl<?>> bindingBuilders
= new LinkedList<BindingBuilderImpl<?>>();
final List<ConstantBindingBuilderImpl> constantBindingBuilders
= new ArrayList<ConstantBindingBuilderImpl>();
final Map<Class<? extends Annotation>, Scope> scopes =
new HashMap<Class<? extends Annotation>, Scope>();
final List<StaticInjection> staticInjections
= new ArrayList<StaticInjection>();
final Set<Module> modulesInstalled = new HashSet<Module>();
InjectorImpl injector;
final Stage stage;
final Collection<Message> errorMessages = new ArrayList<Message>();
final ProxyFactoryBuilder proxyFactoryBuilder;
/**
* Constructs a new builder.
*
* @param stage we're running in. If the stage is {@link Stage#PRODUCTION},
* we will eagerly load singletons.
*/
public BinderImpl(Stage stage) {
bindScope(Singleton.class, SINGLETON);
bind(Logger.class, SourceProviders.UNKNOWN_SOURCE)
.toInternalFactory(new LoggerFactory());
bind(Stage.class, SourceProviders.UNKNOWN_SOURCE).toInstance(stage);
this.proxyFactoryBuilder = new ProxyFactoryBuilder();
this.stage = stage;
// Configure type converters.
convertToPrimitiveType(int.class, Integer.class);
convertToPrimitiveType(long.class, Long.class);
convertToPrimitiveType(boolean.class, Boolean.class);
convertToPrimitiveType(byte.class, Byte.class);
convertToPrimitiveType(short.class, Short.class);
convertToPrimitiveType(float.class, Float.class);
convertToPrimitiveType(double.class, Double.class);
TypeConverter characterConverter = new TypeConverter() {
public Object convert(String value, TypeLiteral<?> toType) {
value = value.trim();
if (value.length() != 1) {
throw new RuntimeException("Length != 1.");
}
return value.charAt(0);
}
@Override
public String toString() {
return "TypeConverter<Character>";
}
};
convertToClass(char.class, characterConverter);
convertToClass(Character.class, characterConverter);
convertToClasses(Matchers.subclassesOf(Enum.class), new TypeConverter() {
@SuppressWarnings("unchecked")
public Object convert(String value, TypeLiteral<?> toType) {
return Enum.valueOf((Class) toType.getRawType(), value);
}
@Override
public String toString() {
return "TypeConverter<E extends Enum<E>>";
}
});
internalConvertToTypes(
new AbstractMatcher<TypeLiteral<?>>() {
public boolean matches(TypeLiteral<?> typeLiteral) {
return typeLiteral.getRawType() == Class.class;
}
@Override
public String toString() {
return "Class<?>";
}
},
new TypeConverter() {
@SuppressWarnings("unchecked")
public Object convert(String value, TypeLiteral<?> toType) {
try {
return Class.forName(value);
}
catch (ClassNotFoundException e) {
throw new RuntimeException(e.getMessage());
}
}
@Override
public String toString() {
return "TypeConverter<Class<?>>";
}
}
);
}
/**
* Constructs a new builder for a development environment (see
* {@link Stage#DEVELOPMENT}).
*/
public BinderImpl() {
this(Stage.DEVELOPMENT);
}
final List<MatcherAndConverter<?>> converters
= new ArrayList<MatcherAndConverter<?>>();
<T> void convertToType(TypeLiteral<T> type,
TypeConverter converter) {
convertToClasses(Matchers.identicalTo(type), converter);
}
<T> void convertToClass(Class<T> type,
TypeConverter converter) {
convertToClasses(Matchers.identicalTo(type), converter);
}
void convertToClasses(final Matcher<? super Class<?>> typeMatcher,
TypeConverter converter) {
internalConvertToTypes(new AbstractMatcher<TypeLiteral<?>>() {
public boolean matches(TypeLiteral<?> typeLiteral) {
Type type = typeLiteral.getType();
if (!(type instanceof Class)) {
return false;
}
Class<?> clazz = (Class<?>) type;
return typeMatcher.matches(clazz);
}
public String toString() {
return typeMatcher.toString();
}
}, converter);
}
public void internalConvertToTypes(Matcher<? super TypeLiteral<?>> typeMatcher,
TypeConverter converter) {
converters.add(MatcherAndConverter.newInstance(typeMatcher, converter,
SourceProviders.UNKNOWN_SOURCE));
}
public void convertToTypes(Matcher<? super TypeLiteral<?>> typeMatcher,
TypeConverter converter) {
converters.add(MatcherAndConverter.newInstance(typeMatcher, converter));
}
<T> void convertToPrimitiveType(Class<T> primitiveType,
final Class<T> wrapperType) {
try {
final Method parser = wrapperType.getMethod(
"parse" + Strings.capitalize(primitiveType.getName()), String.class);
TypeConverter typeConverter = new TypeConverter() {
@SuppressWarnings("unchecked")
public Object convert(String value, TypeLiteral<?> toType) {
try {
return parser.invoke(null, value);
}
catch (IllegalAccessException e) {
throw new AssertionError(e);
}
catch (InvocationTargetException e) {
throw new RuntimeException(e.getTargetException().getMessage());
}
}
@Override
public String toString() {
return "TypeConverter<" + wrapperType.getSimpleName() + ">";
}
};
convertToClass(primitiveType, typeConverter);
convertToClass(wrapperType, typeConverter);
}
catch (NoSuchMethodException e) {
throw new AssertionError(e);
}
}
public Stage currentStage() {
return stage;
}
final List<CreationListener> creationListeners
= new ArrayList<CreationListener>();
interface CreationListener {
void notify(InjectorImpl injector);
}
final List<MembersInjector> membersInjectors
= new ArrayList<MembersInjector>();
static class MembersInjector {
final Object o;
MembersInjector(Object o) {
this.o = o;
}
void checkDependencies(InjectorImpl injector) {
injector.injectors.get(o.getClass());
}
void injectMembers(InjectorImpl injector) {
injector.injectMembers(o);
}
}
public void bindInterceptor(Matcher<? super Class<?>> classMatcher,
Matcher<? super Method> methodMatcher, MethodInterceptor... interceptors) {
proxyFactoryBuilder.intercept(classMatcher, methodMatcher, interceptors);
}
public void bindScope(Class<? extends Annotation> annotationType,
Scope scope) {
if (!Scopes.isScopeAnnotation(annotationType)) {
addError(StackTraceElements.forType(annotationType),
ErrorMessages.MISSING_SCOPE_ANNOTATION);
// Go ahead and bind anyway so we don't get collateral errors.
}
if (!Annotations.isRetainedAtRuntime(annotationType)) {
addError(StackTraceElements.forType(annotationType),
ErrorMessages.MISSING_RUNTIME_RETENTION, source());
// Go ahead and bind anyway so we don't get collateral errors.
}
Scope existing = scopes.get(nonNull(annotationType, "annotation type"));
if (existing != null) {
addError(source(), ErrorMessages.DUPLICATE_SCOPES, existing,
annotationType, scope);
}
else {
scopes.put(annotationType, nonNull(scope, "scope"));
}
}
static boolean inGuiceNamespace(Class<?> clazz) {
return clazz == Provider.class;
}
public <T> BindingBuilderImpl<T> bind(Key<T> key) {
Object source = source();
BindingBuilderImpl<T> builder =
new BindingBuilderImpl<T>(this, key, source);
Class<? super T> rawType = key.getTypeLiteral().getRawType();
if (rawType == Provider.class) {
addError(source, ErrorMessages.BINDING_TO_PROVIDER);
} else if (Logger.class == rawType) {
addError(source, ErrorMessages.LOGGER_ALREADY_BOUND);
} else {
bindingBuilders.add(builder);
}
return builder;
}
public <T> BindingBuilderImpl<T> bind(TypeLiteral<T> typeLiteral) {
return bind(Key.get(typeLiteral));
}
public <T> BindingBuilderImpl<T> bind(Class<T> clazz) {
return bind(Key.get(clazz));
}
/**
* Internal use only.
*/
<T> BindingBuilderImpl<T> bind(Class<T> clazz, Object source) {
Key<T> key = Key.get(clazz);
BindingBuilderImpl<T> builder =
new BindingBuilderImpl<T>(this, key, source);
// Put Guice's bindings in front of user's bindings.
bindingBuilders.addFirst(builder);
return builder;
}
public ConstantBindingBuilderImpl bindConstant() {
ConstantBindingBuilderImpl constantBuilder
= new ConstantBindingBuilderImpl(this, source());
constantBindingBuilders.add(constantBuilder);
return constantBuilder;
}
public void requestStaticInjection(Class<?>... types) {
staticInjections.add(new StaticInjection(source(), types));
}
public void install(Module module) {
if (modulesInstalled.add(module)) {
module.configure(this);
}
}
public void addError(String message, Object... arguments) {
configurationErrorHandler.handle(source(), message, arguments);
}
public void addError(Throwable t) {
Object source = source();
String message = ErrorMessages.getRootMessage(t);
String logMessage = String.format(
ErrorMessages.EXCEPTION_REPORTED_BY_MODULE, message);
logger.log(Level.INFO, logMessage, t);
addError(source, ErrorMessages.EXCEPTION_REPORTED_BY_MODULE_SEE_LOG,
message);
}
void addError(Object source, String message, Object... arguments) {
configurationErrorHandler.handle(source, message, arguments);
}
void addError(Object source, String message) {
configurationErrorHandler.handle(source, message);
}
/**
* Adds an error message to be reported at creation time.
*/
void add(Message errorMessage) {
errorMessages.add(errorMessage);
}
final Stopwatch stopwatch = new Stopwatch();
/**
* Creates a {@link Injector} instance. Injects static members for classes
* which were registered using {@link #requestStaticInjection(Class...)}.
*
* @throws CreationException if configuration errors are found. The
* expectation is that the application will log this exception and exit.
* @throws IllegalStateException if called more than once
*/
Injector createInjector() throws CreationException {
stopwatch.resetAndLog(logger, "Configuration (running the modules)");
Map<Key<?>, BindingImpl<?>> bindings
= new HashMap<Key<?>, BindingImpl<?>>();
injector = new InjectorImpl(
proxyFactoryBuilder.create(), bindings, scopes, converters);
// Create default bindings.
// We use toProvider() instead of toInstance() to avoid infinite recursion
// in toString().
bind(Injector.class, SourceProviders.UNKNOWN_SOURCE)
.toProvider(new InjectorProvider(injector));
injector.setErrorHandler(configurationErrorHandler);
createConstantBindings();
// Commands to execute before returning the Injector instance.
final List<ContextualCallable<Void>> preloaders
= new ArrayList<ContextualCallable<Void>>();
createBindings(preloaders);
stopwatch.resetAndLog(logger, "Binding creation");
injector.index();
stopwatch.resetAndLog(logger, "Binding indexing");
for (CreationListener creationListener : creationListeners) {
creationListener.notify(injector);
}
stopwatch.resetAndLog(logger, "Validation");
for (StaticInjection staticInjection : staticInjections) {
staticInjection.createMemberInjectors(injector);
}
stopwatch.resetAndLog(logger, "Static validation");
for (MembersInjector membersInjector : membersInjectors) {
membersInjector.checkDependencies(injector);
}
stopwatch.resetAndLog(logger, "Instance member validation");
// Blow up if we encountered errors.
if (!errorMessages.isEmpty()) {
throw new CreationException(errorMessages);
}
// Switch to runtime error handling.
injector.setErrorHandler(new RuntimeErrorHandler());
// If we're in the tool stage, stop here. Don't eagerly inject or load
// anything.
if (stage == Stage.TOOL) {
// TODO: Wrap this and prevent usage of anything besides getBindings().
return injector;
}
// Inject static members.
for (StaticInjection staticInjection : staticInjections) {
staticInjection.runMemberInjectors(injector);
}
stopwatch.resetAndLog(logger, "Static member injection");
// Inject pre-existing instances.
for (MembersInjector membersInjector : membersInjectors) {
membersInjector.injectMembers(injector);
}
stopwatch.resetAndLog(logger, "Instance injection");
// Run preloading commands.
runPreloaders(injector, preloaders);
stopwatch.resetAndLog(logger, "Preloading");
return injector;
}
private void runPreloaders(InjectorImpl injector,
final List<ContextualCallable<Void>> preloaders) {
injector.callInContext(new ContextualCallable<Void>() {
public Void call(InternalContext context) {
for (ContextualCallable<Void> preloader : preloaders) {
preloader.call(context);
}
return null;
}
});
}
private void createBindings(List<ContextualCallable<Void>> preloaders) {
for (BindingBuilderImpl<?> builder : bindingBuilders) {
createBinding(builder, preloaders);
}
}
private <T> void createBinding(BindingBuilderImpl<T> builder,
List<ContextualCallable<Void>> preloaders) {
BindingImpl<T> binding = builder.build(injector);
putBinding(binding);
// Register to preload if necessary.
boolean preload = stage == Stage.PRODUCTION;
if (binding.getScope() == Scopes.SINGLETON) {
if (preload || builder.shouldPreload()) {
preloaders.add(
new BindingPreloader(binding.key, binding.internalFactory));
}
}
else {
if (builder.shouldPreload()) {
addError(builder.getSource(), ErrorMessages.PRELOAD_NOT_ALLOWED);
}
}
}
private void createConstantBindings() {
for (ConstantBindingBuilderImpl builder : constantBindingBuilders) {
createConstantBinding(builder);
}
}
private void createConstantBinding(ConstantBindingBuilderImpl builder) {
if (builder.hasValue()) {
putBinding(builder.createBinding(injector));
}
else {
addError(builder.getSource(), ErrorMessages.MISSING_CONSTANT_VALUE);
}
}
private static Set<Class<?>> FORBIDDEN_TYPES = forbiddenTypes();
@SuppressWarnings("unchecked") // For generic array creation.
private static Set<Class<?>> forbiddenTypes() {
Set<Class<?>> set = new HashSet<Class<?>>();
Collections.addAll(set,
// It's unfortunate that we have to maintain a blacklist of specific
// classes, but we can't easily block the whole package because of
// all our unit tests.
AbstractModule.class,
Binder.class,
Binding.class,
Key.class,
Module.class,
Provider.class,
Scope.class,
TypeLiteral.class);
return Collections.unmodifiableSet(set);
}
void putBinding(BindingImpl<?> binding) {
Key<?> key = binding.getKey();
Map<Key<?>, BindingImpl<?>> bindings = injector.internalBindings();
Binding<?> original = bindings.get(key);
Class<?> rawType = key.getRawType();
if (FORBIDDEN_TYPES.contains(rawType)) {
addError(binding.getSource(), ErrorMessages.CANNOT_BIND_TO_GUICE_TYPE,
rawType.getSimpleName());
return;
}
if (bindings.containsKey(key)) {
addError(binding.getSource(), ErrorMessages.BINDING_ALREADY_SET, key,
original.getSource());
}
else {
bindings.put(key, binding);
}
}
/**
* Gets the current source.
*/
Object source() {
return SourceProviders.defaultSource();
}
ErrorHandler configurationErrorHandler = new AbstractErrorHandler() {
public void handle(Object source, String message) {
add(new Message(source, message));
}
};
/**
* Handles errors after the injector is created.
*/
static class RuntimeErrorHandler extends AbstractErrorHandler {
static ErrorHandler INSTANCE = new RuntimeErrorHandler();
public void handle(Object source, String message) {
throw new ConfigurationException("Error at " + source + " " + message);
}
}
/**
* A requested static injection.
*/
class StaticInjection {
final Object source;
final Class<?>[] types;
final List<SingleMemberInjector> memberInjectors
= new ArrayList<SingleMemberInjector>();
public StaticInjection(Object source, Class<?>[] types) {
this.source = source;
this.types = types;
}
void createMemberInjectors(final InjectorImpl injector) {
injector.withDefaultSource(source,
new Runnable() {
public void run() {
for (Class<?> clazz : types) {
injector.addSingleInjectorsForFields(
clazz.getDeclaredFields(), true, memberInjectors);
injector.addSingleInjectorsForMethods(
clazz.getDeclaredMethods(), true, memberInjectors);
}
}
});
}
void runMemberInjectors(InjectorImpl injector) {
injector.callInContext(new ContextualCallable<Void>() {
public Void call(InternalContext context) {
for (SingleMemberInjector injector : memberInjectors) {
injector.inject(context, null);
}
return null;
}
});
}
}
static class BindingPreloader implements ContextualCallable<Void> {
private final Key<?> key;
private final InternalFactory<?> factory;
public BindingPreloader(Key<?> key, InternalFactory<?> factory) {
this.key = key;
this.factory = Objects.nonNull(factory, "factory");
}
public Void call(InternalContext context) {
InjectionPoint<?> injectionPoint
= InjectionPoint.newInstance(key, context.getInjectorImpl());
context.setInjectionPoint(injectionPoint);
try {
factory.get(context, injectionPoint);
return null;
}
catch(ProvisionException provisionException) {
provisionException.addContext(injectionPoint);
throw provisionException;
}
finally {
context.setInjectionPoint(null);
}
}
}
public <T> Provider<T> getProvider(Key<T> key) {
ProviderProxy<T> providerProxy = new ProviderProxy<T>(source(), key);
creationListeners.add(providerProxy);
return providerProxy;
}
public <T> Provider<T> getProvider(Class<T> type) {
return getProvider(Key.get(type));
}
/**
* A reference to a provider which can be filled in once the Injector has
* been created.
*/
static class ProviderProxy<T> implements Provider<T>, CreationListener {
final Object source;
final Key<T> key;
// We don't have to synchronize access to this field because it doesn't
// change after the Injector has been created.
Provider<T> delegate = illegalProvider();
public ProviderProxy(Object source, Key<T> key) {
this.source = source;
this.key = key;
}
public void notify(final InjectorImpl injector) {
injector.withDefaultSource(source, new Runnable() {
public void run() {
try {
delegate = injector.getProvider(key);
}
catch (ConfigurationException e) {
ErrorMessages.handleMissingBinding(injector, source, key);
}
}
});
}
public T get() {
return delegate.get();
}
}
static final Provider<Object> ILLEGAL_PROVIDER = new Provider<Object>() {
public Object get() {
throw new IllegalStateException("This provider cannot be used until the"
+ " Injector has been created.");
}
};
@SuppressWarnings("unchecked")
static <T> Provider<T> illegalProvider() {
return (Provider<T>) ILLEGAL_PROVIDER;
}
static class LoggerFactory implements InternalFactory<Logger> {
public Logger get(InternalContext context, InjectionPoint<?> injectionPoint) {
Member member = injectionPoint.getMember();
return member == null
? Logger.getAnonymousLogger()
: Logger.getLogger(member.getDeclaringClass().getName());
}
public String toString() {
return "Provider<Logger>";
}
}
static class InjectorProvider implements Provider<Injector> {
final Injector injector;
InjectorProvider(Injector injector) {
this.injector = injector;
}
public Injector get() {
return injector;
}
public String toString() {
return "Provider<Injector>";
}
}
}