Pretty massive rewrite of PrivateModules so that they're now implemented in core Guice.
This change introduces InjectorShell, an unfortunate class to hold an in-progress injector while it's being constructed. It refactors InjectorBuilder to support building several injectors simultaneously.
Still outstanding is fixing up the docs for PrivateModule and these new APIs.
git-svn-id: https://google-guice.googlecode.com/svn/trunk@701 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProviderTest.java b/extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProviderTest.java
index ddbf72f..7f649f4 100755
--- a/extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProviderTest.java
+++ b/extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProviderTest.java
@@ -18,6 +18,7 @@
import com.google.inject.AbstractModule;
import static com.google.inject.Asserts.assertContains;
+import com.google.inject.ConfigurationException;
import com.google.inject.CreationException;
import com.google.inject.Guice;
import com.google.inject.Inject;
@@ -170,8 +171,8 @@
try {
FactoryProvider.newFactory(SummerCarFactory.class, Beetle.class);
fail();
- } catch(IllegalArgumentException e) {
- assertTrue(e.getMessage().startsWith("Constructor mismatch"));
+ } catch(ConfigurationException e) {
+ assertContains(e.getMessage(), "Constructor mismatch");
}
}
@@ -379,7 +380,7 @@
try {
FactoryProvider.newFactory(DefectiveCarFactoryWithNoExceptions.class, DefectiveCar.class);
fail();
- } catch (IllegalStateException expected) {
+ } catch (ConfigurationException expected) {
assertContains(expected.getMessage(), "no compatible exception is thrown");
}
}
diff --git a/extensions/commands/src/com/google/inject/commands/intercepting/InterceptingInjectorBuilder.java b/extensions/commands/src/com/google/inject/commands/intercepting/InterceptingInjectorBuilder.java
index b59f84b..decd8e2 100644
--- a/extensions/commands/src/com/google/inject/commands/intercepting/InterceptingInjectorBuilder.java
+++ b/extensions/commands/src/com/google/inject/commands/intercepting/InterceptingInjectorBuilder.java
@@ -31,7 +31,6 @@
import com.google.inject.ProvidedBy;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
-import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.binder.ScopedBindingBuilder;
import com.google.inject.internal.UniqueAnnotations;
import com.google.inject.name.Names;
@@ -175,8 +174,7 @@
Key<T> anonymousKey = Key.get(key.getTypeLiteral(), UniqueAnnotations.create());
binder.bind(key).toProvider(new InterceptingProvider<T>(key, anonymousKey));
- LinkedBindingBuilder<T> linkedBindingBuilder = binder.bind(anonymousKey);
- ScopedBindingBuilder scopedBindingBuilder = applyTarget(binding, linkedBindingBuilder);
+ ScopedBindingBuilder scopedBindingBuilder = bindKeyToTarget(binding, binder, anonymousKey);
// we scope the user's provider, not the interceptor. This is dangerous,
// but convenient. It means that although the user's provider will live
diff --git a/extensions/privatemodules/src/com/google/inject/privatemodules/PrivateModule.java b/extensions/privatemodules/src/com/google/inject/privatemodules/PrivateModule.java
deleted file mode 100644
index ecc2821..0000000
--- a/extensions/privatemodules/src/com/google/inject/privatemodules/PrivateModule.java
+++ /dev/null
@@ -1,393 +0,0 @@
-/**
- * Copyright (C) 2008 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.privatemodules;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-import com.google.common.collect.Sets;
-import com.google.inject.Binder;
-import com.google.inject.Binding;
-import com.google.inject.Inject;
-import com.google.inject.Injector;
-import com.google.inject.Key;
-import com.google.inject.Module;
-import com.google.inject.Provider;
-import com.google.inject.Scope;
-import com.google.inject.Stage;
-import com.google.inject.TypeLiteral;
-import com.google.inject.binder.AnnotatedBindingBuilder;
-import com.google.inject.binder.AnnotatedConstantBindingBuilder;
-import com.google.inject.binder.LinkedBindingBuilder;
-import com.google.inject.internal.ProviderMethod;
-import com.google.inject.internal.ProviderMethodsModule;
-import com.google.inject.internal.SourceProvider;
-import com.google.inject.internal.UniqueAnnotations;
-import com.google.inject.matcher.Matcher;
-import com.google.inject.spi.DefaultElementVisitor;
-import com.google.inject.spi.Element;
-import com.google.inject.spi.ElementVisitor;
-import com.google.inject.spi.Elements;
-import com.google.inject.spi.Message;
-import com.google.inject.spi.ModuleWriter;
-import com.google.inject.spi.TypeConverter;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.util.List;
-import java.util.Set;
-import org.aopalliance.intercept.MethodInterceptor;
-
-/**
- * A module whose configuration information is hidden from its environment by default. Only bindings
- * that are explicitly exposed will be available to other modules and to the users of the injector.
- * This module may expose the bindings it creates and the bindings of the modules it installs.
- *
- * <p>A private module can be nested within a regular module or within another private module using
- * {@link Binder#install install()}. Its bindings live in a new environment that inherits bindings,
- * type converters, scopes, and interceptors from the surrounding ("parent") environment. When you
- * nest multiple private modules, the result is a tree of environments where the injector's
- * environment is the root.
- *
- * <p>Guice EDSL bindings can be exposed with {@link #expose(Class) expose()}. {@literal @}{@link
- * com.google.inject.Provides Provides} bindings can be exposed with the {@literal @}{@link
- * Exposed} annotation:
- *
- * <pre>
- * public class FooBarBazModule extends PrivateModule {
- * protected void configurePrivateBindings() {
- * bind(Foo.class).to(RealFoo.class);
- * expose(Foo.class);
- *
- * install(new TransactionalBarModule());
- * expose(Bar.class).annotatedWith(Transactional.class);
- *
- * bind(SomeImplementationDetail.class);
- * install(new MoreImplementationDetailsModule());
- * }
- *
- * {@literal @}Provides {@literal @}Exposed
- * public Baz provideBaz() {
- * return new SuperBaz();
- * }
- * }
- * </pre>
- *
- * <p>Private modules are implemented using {@link Injector#createChildInjector(Module[]) parent
- * injectors}. When it can satisfy their dependencies, just-in-time bindings will be created in the
- * root environment. Such bindings are shared among all environments in the tree.
- *
- * <p>The scope of a binding is constrained to its environment. A singleton bound in a private
- * module will be unique to its environment. But a binding for the same type in a different private
- * module will yield a different instance.
- *
- * <p>A shared binding that injects the {@code Injector} gets the root injector, which only has
- * access to bindings in the root environment. An explicit binding that injects the {@code Injector}
- * gets access to all bindings in the child environment.
- *
- * <p>To promote a just-in-time binding to an explicit binding, bind it:
- * <pre>
- * bind(FooImpl.class);
- * </pre>
- *
- * @author jessewilson@google.com (Jesse Wilson)
- */
-public abstract class PrivateModule implements Module {
-
- private final SourceProvider sourceProvider
- = new SourceProvider().plusSkippedClasses(PrivateModule.class);
-
- /** When this provider returns, the private injector creation has started. */
- private Provider<Ready> readyProvider;
-
- /** Keys exposed to the public injector */
- private Set<Expose> exposes;
-
- /** Like abstract module, the binder of the current private module */
- private Binder privateBinder;
-
- /*
- * This implementation is complicated in order to satisfy two different roles in one class:
- *
- * 1. As a public module (the one that installs this), we bind only the exposed keys. This is the
- * role we play first, when configure() is called by the installing binder. It collects the
- * exposed keys and their corresponding providers by executing itself as a private module.
- *
- * 2. As a private module, we bind everything. This is performed our own indirect re-entrant call
- * to configure() via the Elements.getElements() API.
- *
- * Throwing further wrenches into the mix:
- *
- * o Provider methods. The ProviderMethodsModule class special cases modules that extend
- * PrivateModules to skip them by default. We have our own provider methods backdoor API
- * called ProviderMethodsModule.forPrivateModule so provider methods are only applied when
- * we're running as a private module. We also need to iterate through the provider methods
- * by hand to gather the ones with the @Exposed annotation
- *
- * o Injector creation time. Dependencies can flow freely between child and parent injectors.
- * When providers are being exercised, we need to make sure the child injector construction
- * has started.
- */
- public final synchronized void configure(Binder binder) {
- // when 'exposes' is null, we're being run for the public injector
- if (exposes == null) {
- configurePublicBindings(binder);
- return;
- }
-
- // otherwise we're being run for the private injector
- checkState(this.privateBinder == null, "Re-entry is not allowed.");
- privateBinder = binder.skipSources(PrivateModule.class);
- try {
- configurePrivateBindings();
-
- ProviderMethodsModule providerMethods = ProviderMethodsModule.forPrivateModule(this);
- for (ProviderMethod<?> providerMethod : providerMethods.getProviderMethods(privateBinder)) {
- providerMethod.configure(privateBinder);
- if (providerMethod.getMethod().isAnnotationPresent(Exposed.class)) {
- expose(providerMethod.getKey());
- }
- }
-
- for (Expose<?> expose : exposes) {
- expose.initPrivateProvider(binder);
- }
- } finally {
- privateBinder = null;
- }
- }
-
-
- private void configurePublicBindings(Binder publicBinder) {
- exposes = Sets.newLinkedHashSet();
- Key<Ready> readyKey = Key.get(Ready.class, UniqueAnnotations.create());
- readyProvider = publicBinder.getProvider(readyKey);
- try {
- List<Element> privateElements = Elements.getElements(this); // reentrant on configure()
- Set<Key<?>> privatelyBoundKeys = getBoundKeys(privateElements);
- final Module privateModule = new ModuleWriter().create(privateElements);
-
- for (Expose<?> expose : exposes) {
- if (!privatelyBoundKeys.contains(expose.key)) {
- publicBinder.addError("Could not expose() at %s%n %s must be explicitly bound.",
- expose.source, expose.key);
- } else {
- expose.configure(publicBinder);
- }
- }
-
- // create the private injector while the public injector is injecting its members. This is
- // necessary so the providers from getProvider() will work. We use provider injection as our
- // hook. Guice promises that initialize() will be called before a Ready is returned.
- publicBinder.bind(readyKey).toProvider(new Provider<Ready>() {
- @Inject void initialize(Injector publicInjector) {
- publicInjector.createChildInjector(privateModule);
- }
-
- public Ready get() {
- return new Ready();
- }
- });
-
- } finally {
- readyProvider = null;
- exposes = null;
- }
- }
-
- /** Marker object used to indicate the private injector has been created */
- private static class Ready {}
-
- /**
- * Creates bindings and other configurations private to this module. Use {@link #expose(Class)
- * expose()} to make the bindings in this module available externally.
- */
- protected abstract void configurePrivateBindings();
-
- /** Makes the binding for {@code key} available to other modules and the injector. */
- protected final <T> void expose(Key<T> key) {
- checkState(exposes != null, "Cannot expose %s, private module is not ready", key);
- exposes.add(new Expose<T>(sourceProvider.get(), readyProvider, key));
- }
-
- /**
- * Makes a binding for {@code type} available to other modules and the injector. Use {@link
- * ExposedKeyBuilder#annotatedWith(Class) annotatedWith()} to expose {@code type} with a binding
- * annotation.
- */
- protected final <T> ExposedKeyBuilder expose(Class<T> type) {
- checkState(exposes != null, "Cannot expose %s, private module is not ready", type);
- Expose<T> expose = new Expose<T>(sourceProvider.get(), readyProvider, Key.get(type));
- exposes.add(expose);
- return expose;
- }
-
- /**
- * Makes a binding for {@code type} available to other modules and the injector. Use {@link
- * ExposedKeyBuilder#annotatedWith(Class) annotatedWith()} to expose {@code type} with a binding
- * annotation.
- */
- protected final <T> ExposedKeyBuilder expose(TypeLiteral<T> type) {
- checkState(exposes != null, "Cannot expose %s, private module is not ready", type);
- Expose<T> expose = new Expose<T>(sourceProvider.get(), readyProvider, Key.get(type));
- exposes.add(expose);
- return expose;
- }
-
- /** Qualifies an exposed type with a binding annotation. */
- public interface ExposedKeyBuilder {
- void annotatedWith(Class<? extends Annotation> annotationType);
- void annotatedWith(Annotation annotation);
- }
-
- /** A binding from the private injector make visible to the public injector. */
- private static class Expose<T> implements ExposedKeyBuilder, Provider<T> {
- private final Object source;
- private final Provider<Ready> readyProvider;
- private Key<T> key; // mutable, a binding annotation may be assigned after exposure creation
- private Provider<T> privateProvider;
-
- private Expose(Object source, Provider<Ready> readyProvider, Key<T> key) {
- this.source = checkNotNull(source, "source");
- this.readyProvider = checkNotNull(readyProvider, "readyProvider");
- this.key = checkNotNull(key, "key");
- }
-
- public void annotatedWith(Class<? extends Annotation> annotationType) {
- checkState(key.getAnnotationType() == null, "already annotated");
- key = Key.get(key.getTypeLiteral(), annotationType);
- }
-
- public void annotatedWith(Annotation annotation) {
- checkState(key.getAnnotationType() == null, "already annotated");
- key = Key.get(key.getTypeLiteral(), annotation);
- }
-
- /** Sets the provider in the private injector, to be used by the public injector */
- private void initPrivateProvider(Binder privateBinder) {
- privateProvider = privateBinder.withSource(source).getProvider(key);
- }
-
- /** Creates a binding in the public binder */
- private void configure(Binder publicBinder) {
- publicBinder.withSource(source).bind(key).toProvider(this);
- }
-
- public T get() {
- readyProvider.get(); // force creation of the private injector
- return privateProvider.get();
- }
- }
-
- /** Returns the set of keys bound by {@code elements}. */
- private Set<Key<?>> getBoundKeys(Iterable<? extends Element> elements) {
- final Set<Key<?>> privatelyBoundKeys = Sets.newHashSet();
- ElementVisitor<Void> visitor = new DefaultElementVisitor<Void>() {
- public <T> Void visitBinding(Binding<T> command) {
- privatelyBoundKeys.add(command.getKey());
- return null;
- }
- };
-
- for (Element element : elements) {
- element.acceptVisitor(visitor);
- }
-
- return privatelyBoundKeys;
- }
-
- // prevent classes migrated from AbstractModule from implementing the wrong method.
- protected final void configure() {}
-
- // everything below is copied from AbstractModule
-
- protected final Binder binder() {
- return privateBinder;
- }
-
- protected final void bindScope(Class<? extends Annotation> scopeAnnotation, Scope scope) {
- privateBinder.bindScope(scopeAnnotation, scope);
- }
-
- protected final <T> LinkedBindingBuilder<T> bind(Key<T> key) {
- return privateBinder.bind(key);
- }
-
- protected final <T> AnnotatedBindingBuilder<T> bind(TypeLiteral<T> typeLiteral) {
- return privateBinder.bind(typeLiteral);
- }
-
- protected final <T> AnnotatedBindingBuilder<T> bind(Class<T> clazz) {
- return privateBinder.bind(clazz);
- }
-
- protected final AnnotatedConstantBindingBuilder bindConstant() {
- return privateBinder.bindConstant();
- }
-
- protected final void install(Module module) {
- privateBinder.install(module);
- }
-
- protected final void addError(String message, Object... arguments) {
- privateBinder.addError(message, arguments);
- }
-
- protected final void addError(Throwable t) {
- privateBinder.addError(t);
- }
-
- protected final void addError(Message message) {
- privateBinder.addError(message);
- }
-
- protected final void requestInjection(Object... objects) {
- privateBinder.requestInjection(objects);
- }
-
- protected final void requestStaticInjection(Class<?>... types) {
- privateBinder.requestStaticInjection(types);
- }
-
- protected final void bindInterceptor(Matcher<? super Class<?>> classMatcher,
- Matcher<? super Method> methodMatcher, MethodInterceptor... interceptors) {
- privateBinder.bindInterceptor(classMatcher, methodMatcher, interceptors);
- }
-
- protected final void requireBinding(Key<?> key) {
- privateBinder.getProvider(key);
- }
-
- protected final void requireBinding(Class<?> type) {
- privateBinder.getProvider(type);
- }
-
- protected final <T> Provider<T> getProvider(Key<T> key) {
- return privateBinder.getProvider(key);
- }
-
- protected final <T> Provider<T> getProvider(Class<T> type) {
- return privateBinder.getProvider(type);
- }
-
- protected final void convertToTypes(Matcher<? super TypeLiteral<?>> typeMatcher,
- TypeConverter converter) {
- privateBinder.convertToTypes(typeMatcher, converter);
- }
-
- protected final Stage currentStage() {
- return privateBinder.currentStage();
- }
-}
diff --git a/extensions/privatemodules/test/com/google/inject/privatemodules/AllTests.java b/extensions/privatemodules/test/com/google/inject/privatemodules/AllTests.java
index 2d947e8..ba58af0 100644
--- a/extensions/privatemodules/test/com/google/inject/privatemodules/AllTests.java
+++ b/extensions/privatemodules/test/com/google/inject/privatemodules/AllTests.java
@@ -16,6 +16,7 @@
package com.google.inject.privatemodules;
+import com.google.inject.PrivateModuleTest;
import junit.framework.Test;
import junit.framework.TestSuite;
diff --git a/src/com/google/inject/AbstractProcessor.java b/src/com/google/inject/AbstractProcessor.java
index a909f8f..f8269b9 100644
--- a/src/com/google/inject/AbstractProcessor.java
+++ b/src/com/google/inject/AbstractProcessor.java
@@ -19,7 +19,6 @@
import com.google.inject.internal.Errors;
import com.google.inject.spi.Element;
import com.google.inject.spi.ElementVisitor;
-import com.google.inject.spi.Exposure;
import com.google.inject.spi.InjectionRequest;
import com.google.inject.spi.InterceptorBinding;
import com.google.inject.spi.Message;
@@ -43,13 +42,21 @@
abstract class AbstractProcessor implements ElementVisitor<Boolean> {
protected Errors errors;
+ protected InjectorImpl injector;
protected AbstractProcessor(Errors errors) {
this.errors = errors;
}
- public void processCommands(List<Element> elements) {
+ public void process(Iterable<InjectorShell> isolatedInjectorBuilders) {
+ for (InjectorShell injectorShell : isolatedInjectorBuilders) {
+ process(injectorShell.getInjector(), injectorShell.getElements());
+ }
+ }
+
+ public void process(InjectorImpl injector, List<Element> elements) {
Errors errorsAnyElement = this.errors;
+ this.injector = injector;
try {
for (Iterator<Element> i = elements.iterator(); i.hasNext(); ) {
Element element = i.next();
@@ -61,6 +68,7 @@
}
} finally {
this.errors = errorsAnyElement;
+ this.injector = null;
}
}
@@ -96,11 +104,7 @@
return false;
}
- public Boolean visitPrivateElements(PrivateEnvironment privateEnvironment) {
- return false;
- }
-
- public Boolean visitExposure(Exposure exposure) {
+ public Boolean visitPrivateEnvironment(PrivateEnvironment privateEnvironment) {
return false;
}
}
diff --git a/src/com/google/inject/Binder.java b/src/com/google/inject/Binder.java
index c1634cf..63594f9 100644
--- a/src/com/google/inject/Binder.java
+++ b/src/com/google/inject/Binder.java
@@ -304,8 +304,7 @@
* @param converter converts values
* @since 2.0
*/
- void convertToTypes(Matcher<? super TypeLiteral<?>> typeMatcher,
- TypeConverter converter);
+ void convertToTypes(Matcher<? super TypeLiteral<?>> typeMatcher, TypeConverter converter);
/**
* Returns a binder that uses {@code source} as the reference location for
diff --git a/src/com/google/inject/BindingProcessor.java b/src/com/google/inject/BindingProcessor.java
index f51895f..b9442bf 100644
--- a/src/com/google/inject/BindingProcessor.java
+++ b/src/com/google/inject/BindingProcessor.java
@@ -18,16 +18,18 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
+import com.google.inject.ExposedBindingImpl.Factory;
import com.google.inject.internal.Annotations;
import com.google.inject.internal.Errors;
import com.google.inject.internal.ErrorsException;
import com.google.inject.spi.BindingScopingVisitor;
import com.google.inject.spi.BindingTargetVisitor;
-import com.google.inject.spi.DefaultBindingTargetVisitor;
import com.google.inject.spi.InjectionPoint;
+import com.google.inject.spi.PrivateEnvironment;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -38,14 +40,6 @@
*/
class BindingProcessor extends AbstractProcessor {
- /** Returns the class name of the bound provider, or null */
- private BindingTargetVisitor<Object, String> GET_BOUND_PROVIDER_CLASS_NAME
- = new DefaultBindingTargetVisitor<Object, String>() {
- public String visitProvider(Provider<?> provider, Set<InjectionPoint> injectionPoints) {
- return provider.getClass().getName();
- }
- };
-
private static final BindingScopingVisitor<LoadStrategy> LOAD_STRATEGY_VISITOR
= new BindingScopingVisitor<LoadStrategy>() {
public LoadStrategy visitEagerSingleton() {
@@ -65,18 +59,16 @@
}
};
- private final InjectorImpl injector;
- private final State state;
private final List<CreationListener> creationListeners = Lists.newArrayList();
private final Initializer initializer;
- private final List<Runnable> untargettedBindings = Lists.newArrayList();
+ private final List<Runnable> uninitializedBindings = Lists.newArrayList();
+ private final Map<PrivateEnvironment, InjectorImpl> environmentToInjector;
- BindingProcessor(Errors errors, InjectorImpl injector, State state,
- Initializer initializer) {
+ BindingProcessor(Errors errors, Initializer initializer,
+ Map<PrivateEnvironment, InjectorImpl> environmentToInjector) {
super(errors);
- this.injector = injector;
- this.state = state;
this.initializer = initializer;
+ this.environmentToInjector = environmentToInjector;
}
@Override public <T> Boolean visitBinding(Binding<T> command) {
@@ -108,7 +100,7 @@
}
public Scope visitScopeAnnotation(Class<? extends Annotation> scopeAnnotation) {
- Scope scope = state.getScope(scopeAnnotation);
+ Scope scope = injector.state.getScope(scopeAnnotation);
if (scope != null) {
return scope;
} else {
@@ -124,7 +116,8 @@
command.acceptTargetVisitor(new BindingTargetVisitor<T, Void>() {
public Void visitInstance(T instance, Set<InjectionPoint> injectionPoints) {
- Initializable<T> ref = initializer.requestInjection(instance, source, injectionPoints);
+ Initializable<T> ref = initializer.requestInjection(
+ injector, instance, source, injectionPoints);
ConstantFactory<? extends T> factory = new ConstantFactory<T>(ref);
InternalFactory<? extends T> scopedFactory = Scopes.scope(key, injector, factory, scope);
putBinding(new InstanceBindingImpl<T>(injector, key, source, scopedFactory, injectionPoints,
@@ -135,7 +128,7 @@
public Void visitProvider(Provider<? extends T> provider,
Set<InjectionPoint> injectionPoints) {
Initializable<Provider<? extends T>> initializable = initializer
- .<Provider<? extends T>>requestInjection(provider, source, injectionPoints);
+ .<Provider<? extends T>>requestInjection(injector, provider, source, injectionPoints);
InternalFactory<T> factory = new InternalFactoryToProviderAdapter<T>(initializable, source);
InternalFactory<? extends T> scopedFactory = Scopes.scope(key, injector, factory, scope);
putBinding(new ProviderInstanceBindingImpl<T>(injector, key, source, scopedFactory, scope,
@@ -144,8 +137,8 @@
}
public Void visitProviderKey(Key<? extends Provider<? extends T>> providerKey) {
- final BoundProviderFactory<T> boundProviderFactory =
- new BoundProviderFactory<T>(providerKey, source);
+ BoundProviderFactory<T> boundProviderFactory
+ = new BoundProviderFactory<T>(injector, providerKey, source);
creationListeners.add(boundProviderFactory);
InternalFactory<? extends T> scopedFactory = Scopes.scope(
key, injector, (InternalFactory<? extends T>) boundProviderFactory, scope);
@@ -159,7 +152,7 @@
errors.recursiveBinding();
}
- FactoryProxy<T> factory = new FactoryProxy<T>(key, targetKey, source);
+ FactoryProxy<T> factory = new FactoryProxy<T>(injector, key, targetKey, source);
creationListeners.add(factory);
InternalFactory<? extends T> scopedFactory = Scopes.scope(key, injector, factory, scope);
putBinding(new LinkedBindingImpl<T>(
@@ -189,10 +182,10 @@
return null;
}
- untargettedBindings.add(new Runnable() {
+ uninitializedBindings.add(new Runnable() {
public void run() {
try {
- injector.initializeBinding(binding, errors.withSource(source));
+ binding.injector.initializeBinding(binding, errors.withSource(source));
} catch (ErrorsException e) {
errors.merge(e.getErrors());
}
@@ -202,6 +195,13 @@
return null;
}
+ public Void visitExposed(PrivateEnvironment privateEnvironment) {
+ Factory<T> factory = new Factory<T>(key, privateEnvironment);
+ creationListeners.add(factory);
+ putBinding(new ExposedBindingImpl<T>(injector, source, factory));
+ return null;
+ }
+
public Void visitConvertedConstant(T value) {
throw new IllegalArgumentException("Cannot apply a non-module element");
}
@@ -227,15 +227,15 @@
return new InvalidBindingImpl<T>(injector, key, source);
}
- public void createUntargettedBindings() {
- for (Runnable untargettedBinding : untargettedBindings) {
- untargettedBinding.run();
+ public void initializeBindings() {
+ for (Runnable initializer : uninitializedBindings) {
+ initializer.run();
}
}
- public void runCreationListeners(InjectorImpl injector) {
+ public void runCreationListeners(Map<PrivateEnvironment, InjectorImpl> privateInjectors) {
for (CreationListener creationListener : creationListeners) {
- creationListener.notify(injector, errors);
+ creationListener.notify(privateInjectors, errors);
}
}
@@ -248,22 +248,30 @@
return;
}
- Binding<?> original = state.getExplicitBinding(key);
-
- if (original != null) {
- // the hard-coded class name is certainly lame, but it avoids an even lamer dependency...
- boolean isOkayDuplicate = original instanceof ProviderInstanceBindingImpl
- && "com.google.inject.privatemodules.PrivateModule$Expose"
- .equals(original.acceptTargetVisitor(GET_BOUND_PROVIDER_CLASS_NAME));
- if (!isOkayDuplicate) {
- errors.bindingAlreadySet(key, original.getSource());
- return;
- }
+ Binding<?> original = injector.state.getExplicitBinding(key);
+ if (original != null && !isOkayDuplicate(original, binding)) {
+ errors.bindingAlreadySet(key, original.getSource());
+ return;
}
// prevent the parent from creating a JIT binding for this key
- state.parent().blacklist(key);
- state.putBinding(key, binding);
+ injector.state.parent().blacklist(key);
+ injector.state.putBinding(key, binding);
+ }
+
+ /**
+ * We tolerate duplicate bindings only if one exposes the other.
+ *
+ * @param original the binding in the parent injector (candidate for an exposing binding)
+ * @param binding the binding to check (candidate for the exposed binding)
+ */
+ private boolean isOkayDuplicate(Binding<?> original, BindingImpl<?> binding) {
+ if (original instanceof ExposedBindingImpl) {
+ ExposedBindingImpl exposed = (ExposedBindingImpl) original;
+ InjectorImpl exposedFrom = environmentToInjector.get(exposed.getPrivateEnvironment());
+ return (exposedFrom == binding.injector);
+ }
+ return false;
}
// It's unfortunate that we have to maintain a blacklist of specific
@@ -282,6 +290,6 @@
// TODO(jessewilson): fix BuiltInModule, then add Stage
interface CreationListener {
- void notify(InjectorImpl injector, Errors errors);
+ void notify(Map<PrivateEnvironment, InjectorImpl> privateInjectors, Errors errors);
}
}
diff --git a/src/com/google/inject/BoundProviderFactory.java b/src/com/google/inject/BoundProviderFactory.java
index 705dd4e..8419653 100644
--- a/src/com/google/inject/BoundProviderFactory.java
+++ b/src/com/google/inject/BoundProviderFactory.java
@@ -20,24 +20,29 @@
import com.google.inject.internal.Errors;
import com.google.inject.internal.ErrorsException;
import com.google.inject.spi.Dependency;
+import com.google.inject.spi.PrivateEnvironment;
+import java.util.Map;
/**
* Delegates to a custom factory which is also bound in the injector.
*/
class BoundProviderFactory<T> implements InternalFactory<T>, CreationListener {
+ private final InjectorImpl injector;
final Key<? extends Provider<? extends T>> providerKey;
final Object source;
private InternalFactory<? extends Provider<? extends T>> providerFactory;
BoundProviderFactory(
+ InjectorImpl injector,
Key<? extends Provider<? extends T>> providerKey,
Object source) {
+ this.injector = injector;
this.providerKey = providerKey;
this.source = source;
}
- public void notify(final InjectorImpl injector, final Errors errors) {
+ public void notify(Map<PrivateEnvironment, InjectorImpl> privateInjectors, Errors errors) {
try {
providerFactory = injector.getInternalFactory(providerKey, errors.withSource(source));
} catch (ErrorsException e) {
diff --git a/extensions/privatemodules/src/com/google/inject/privatemodules/Exposed.java b/src/com/google/inject/Exposed.java
similarity index 95%
rename from extensions/privatemodules/src/com/google/inject/privatemodules/Exposed.java
rename to src/com/google/inject/Exposed.java
index 264af8c..685c9c3 100644
--- a/extensions/privatemodules/src/com/google/inject/privatemodules/Exposed.java
+++ b/src/com/google/inject/Exposed.java
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package com.google.inject.privatemodules;
+package com.google.inject;
+import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
-import java.lang.annotation.Documented;
/**
* Acccompanies a {@literal @}{@link com.google.inject.Provides Provides} method annotation in a
diff --git a/src/com/google/inject/ExposedBindingImpl.java b/src/com/google/inject/ExposedBindingImpl.java
new file mode 100644
index 0000000..c5bfd8a
--- /dev/null
+++ b/src/com/google/inject/ExposedBindingImpl.java
@@ -0,0 +1,80 @@
+/**
+ * Copyright (C) 2008 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.internal.Errors;
+import com.google.inject.internal.ErrorsException;
+import com.google.inject.internal.ToStringBuilder;
+import com.google.inject.spi.BindingTargetVisitor;
+import com.google.inject.spi.Dependency;
+import com.google.inject.spi.PrivateEnvironment;
+import java.util.Map;
+
+public class ExposedBindingImpl<T> extends BindingImpl<T> {
+
+ private Factory factory;
+
+ public ExposedBindingImpl(InjectorImpl injector, Object source, Factory<T> factory) {
+ super(injector, factory.key, source, factory, Scopes.NO_SCOPE, LoadStrategy.LAZY);
+ this.factory = factory;
+ }
+
+ public <V> V acceptTargetVisitor(BindingTargetVisitor<? super T, V> visitor) {
+ return visitor.visitExposed(factory.privateEnvironment);
+ }
+
+ public PrivateEnvironment getPrivateEnvironment() {
+ return factory.privateEnvironment;
+ }
+
+ @Override public String toString() {
+ return new ToStringBuilder(Binding.class)
+ .add("key", key)
+ .add("privateEnvironment", factory.privateEnvironment)
+ .add("scope", scope)
+ .add("source", source)
+ .toString();
+ }
+
+ static class Factory<T> implements InternalFactory<T>, BindingProcessor.CreationListener {
+ private final Key<T> key;
+ private final PrivateEnvironment privateEnvironment;
+ private BindingImpl<T> delegate;
+
+ Factory(Key<T> key, PrivateEnvironment privateEnvironment) {
+ this.key = key;
+ this.privateEnvironment = privateEnvironment;
+ }
+
+ public void notify(Map<PrivateEnvironment, InjectorImpl> privateInjectors, Errors errors) {
+ InjectorImpl privateInjector = privateInjectors.get(privateEnvironment);
+ BindingImpl<T> explicitBinding = privateInjector.state.getExplicitBinding(key);
+
+ if (explicitBinding.getInternalFactory() == this) {
+ errors.withSource(explicitBinding.getSource()).exposedButNotBound(key);
+ return;
+ }
+
+ this.delegate = explicitBinding;
+ }
+
+ public T get(Errors errors, InternalContext context, Dependency<?> dependency)
+ throws ErrorsException {
+ return delegate.getInternalFactory().get(errors, context, dependency);
+ }
+ }
+}
diff --git a/src/com/google/inject/FactoryProxy.java b/src/com/google/inject/FactoryProxy.java
index fef74ea..afcafb5 100644
--- a/src/com/google/inject/FactoryProxy.java
+++ b/src/com/google/inject/FactoryProxy.java
@@ -21,26 +21,29 @@
import com.google.inject.internal.ErrorsException;
import com.google.inject.internal.ToStringBuilder;
import com.google.inject.spi.Dependency;
+import com.google.inject.spi.PrivateEnvironment;
+import java.util.Map;
/**
- * A placeholder which enables us to swap in the real factory once the
- * container is created.
+ * A placeholder which enables us to swap in the real factory once the injector is created.
*/
class FactoryProxy<T> implements InternalFactory<T>, BindingProcessor.CreationListener {
+ private final InjectorImpl injector;
private final Key<T> key;
private final Key<? extends T> targetKey;
private final Object source;
private InternalFactory<? extends T> targetFactory;
- FactoryProxy(Key<T> key, Key<? extends T> targetKey, Object source) {
+ FactoryProxy(InjectorImpl injector, Key<T> key, Key<? extends T> targetKey, Object source) {
+ this.injector = injector;
this.key = key;
this.targetKey = targetKey;
this.source = source;
}
- public void notify(final InjectorImpl injector, final Errors errors) {
+ public void notify(Map<PrivateEnvironment, InjectorImpl> privateInjectors, final Errors errors) {
try {
targetFactory = injector.getInternalFactory(targetKey, errors.withSource(source));
} catch (ErrorsException e) {
diff --git a/src/com/google/inject/Initializer.java b/src/com/google/inject/Initializer.java
index 6427083..abac3f0 100644
--- a/src/com/google/inject/Initializer.java
+++ b/src/com/google/inject/Initializer.java
@@ -43,11 +43,6 @@
/** Maps instances that need injection to a source that registered them */
private final Map<Object, InjectableReference<?>> pendingInjection = Maps.newIdentityHashMap();
- private final InjectorImpl injector;
-
- Initializer(InjectorImpl injector) {
- this.injector = injector;
- }
/**
* Registers an instance for member injection when that step is performed.
@@ -56,7 +51,7 @@
* @Inject).
* @param source the source location that this injection was requested
*/
- public <T> Initializable<T> requestInjection(T instance, Object source,
+ public <T> Initializable<T> requestInjection(InjectorImpl injector, T instance, Object source,
Set<InjectionPoint> injectionPoints) {
checkNotNull(source);
@@ -65,7 +60,7 @@
return Initializables.of(instance);
}
- InjectableReference<T> initializable = new InjectableReference<T>(instance, source);
+ InjectableReference<T> initializable = new InjectableReference<T>(injector, instance, source);
pendingInjection.put(instance, initializable);
return initializable;
}
@@ -108,11 +103,13 @@
}
private class InjectableReference<T> implements Initializable<T> {
+ private final InjectorImpl injector;
private final T instance;
private final Object source;
private List<SingleMemberInjector> injectors;
- public InjectableReference(T instance, Object source) {
+ public InjectableReference(InjectorImpl injector, T instance, Object source) {
+ this.injector = injector;
this.instance = checkNotNull(instance, "instance");
this.source = checkNotNull(source, "source");
}
diff --git a/src/com/google/inject/InjectionRequestProcessor.java b/src/com/google/inject/InjectionRequestProcessor.java
index b761b33..538e437 100644
--- a/src/com/google/inject/InjectionRequestProcessor.java
+++ b/src/com/google/inject/InjectionRequestProcessor.java
@@ -36,16 +36,15 @@
class InjectionRequestProcessor extends AbstractProcessor {
private final List<StaticInjection> staticInjections = Lists.newArrayList();
- private final Initializer memberInjector;
+ private final Initializer initializer;
- InjectionRequestProcessor(Errors errors,
- Initializer memberInjector) {
+ InjectionRequestProcessor(Errors errors, Initializer initializer) {
super(errors);
- this.memberInjector = memberInjector;
+ this.initializer = initializer;
}
@Override public Boolean visitStaticInjectionRequest(StaticInjectionRequest command) {
- staticInjections.add(new StaticInjection(command.getSource(), command.getType()));
+ staticInjections.add(new StaticInjection(injector, command.getSource(), command.getType()));
return true;
}
@@ -59,34 +58,36 @@
injectionPoints = e.getPartialValue();
}
- memberInjector.requestInjection(command.getInstance(), command.getSource(), injectionPoints);
+ initializer.requestInjection(injector, command.getInstance(), command.getSource(), injectionPoints);
return true;
}
- public void validate(InjectorImpl injector) {
+ public void validate() {
for (StaticInjection staticInjection : staticInjections) {
- staticInjection.validate(injector);
+ staticInjection.validate();
}
}
- public void injectMembers(InjectorImpl injector) {
+ public void injectMembers() {
for (StaticInjection staticInjection : staticInjections) {
- staticInjection.injectMembers(injector);
+ staticInjection.injectMembers();
}
}
/** A requested static injection. */
private class StaticInjection {
+ final InjectorImpl injector;
final Object source;
final Class<?> type;
ImmutableList<SingleMemberInjector> memberInjectors;
- public StaticInjection(Object source, Class type) {
+ public StaticInjection(InjectorImpl injector, Object source, Class type) {
+ this.injector = injector;
this.source = source;
this.type = type;
}
- void validate(final InjectorImpl injector) {
+ void validate() {
Errors errorsForMember = errors.withSource(source);
Set<InjectionPoint> injectionPoints;
try {
@@ -98,7 +99,7 @@
memberInjectors = injector.getInjectors(injectionPoints, errorsForMember);
}
- void injectMembers(InjectorImpl injector) {
+ void injectMembers() {
try {
injector.callInContext(new ContextualCallable<Void>() {
public Void call(InternalContext context) {
diff --git a/src/com/google/inject/InjectorBuilder.java b/src/com/google/inject/InjectorBuilder.java
index 8932423..916200c 100644
--- a/src/com/google/inject/InjectorBuilder.java
+++ b/src/com/google/inject/InjectorBuilder.java
@@ -16,27 +16,25 @@
package com.google.inject;
-import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import static com.google.inject.Scopes.SINGLETON;
+import com.google.common.collect.Maps;
import com.google.inject.internal.Errors;
import com.google.inject.internal.ErrorsException;
-import com.google.inject.internal.SourceProvider;
import com.google.inject.internal.Stopwatch;
import com.google.inject.spi.Dependency;
-import com.google.inject.spi.Element;
-import com.google.inject.spi.Elements;
-import com.google.inject.spi.InjectionPoint;
+import com.google.inject.spi.PrivateEnvironment;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.logging.Logger;
/**
- * Builds a dependency injection {@link Injector}. Injector construction happens in two phases.
+ * Builds a tree of injectors. This is a primary injector, plus child injectors needed for each
+ * {@link Binder#newPrivateBinder() private environment}. The primary injector is not necessarily a
+ * top-level injector.
+ *
+ * <p>Injector construction happens in two phases.
* <ol>
* <li>Static building. In this phase, we interpret commands, create bindings, and inspect
* dependencies. During this phase, we hold a lock to ensure consistency with parent injectors.
@@ -53,125 +51,111 @@
class InjectorBuilder {
private final Stopwatch stopwatch = new Stopwatch();
+ private final Errors errors = new Errors();
- private InjectorImpl parent = null;
private Stage stage;
- private final List<Module> modules = Lists.newLinkedList();
- private InjectorImpl injector;
- private Errors errors = new Errors();
+ private final Initializer initializer = new Initializer();
+ private final BindingProcessor bindingProcesor;
+ private final InjectionRequestProcessor injectionRequestProcessor;
+ private final Map<PrivateEnvironment, InjectorImpl> environmentToInjector = Maps.newHashMap();
- private final List<Element> elements = Lists.newArrayList();
+ private final InjectorShell.Builder shellBuilder = new InjectorShell.Builder();
+ private List<InjectorShell> shells;
- private BindingProcessor bindCommandProcesor;
- private InjectionRequestProcessor injectionCommandProcessor;
+ InjectorBuilder() {
+ injectionRequestProcessor = new InjectionRequestProcessor(errors, initializer);
+ bindingProcesor = new BindingProcessor(errors, initializer, environmentToInjector);
+ }
/**
- * @param stage we're running in. If the stage is {@link Stage#PRODUCTION}, we will eagerly load
- * singletons.
+ * Sets the stage for the created injector. If the stage is {@link Stage#PRODUCTION}, this class
+ * will eagerly load singletons.
*/
InjectorBuilder stage(Stage stage) {
+ shellBuilder.stage(stage);
this.stage = stage;
return this;
}
+ /**
+ * Sets the parent of the injector to-be-constructed. As a side effect, this sets this injector's
+ * stage to the stage of {@code parent}.
+ */
InjectorBuilder parentInjector(InjectorImpl parent) {
- this.parent = parent;
- return this;
+ shellBuilder.parent(parent);
+ return stage(parent.getInstance(Stage.class));
}
InjectorBuilder addModules(Iterable<? extends Module> modules) {
- for (Module module : modules) {
- this.modules.add(module);
- }
+ shellBuilder.addModules(modules);
return this;
}
Injector build() {
- if (injector != null) {
+ if (shellBuilder == null) {
throw new AssertionError("Already built, builders are not reusable.");
}
- injector = new InjectorImpl(parent);
+ // Synchronize while we're building up the bindings and other injector state. This ensures that
+ // the JIT bindings in the parent injector don't change while we're being built
+ synchronized (shellBuilder.lock()) {
+ shells = shellBuilder.build(
+ initializer, bindingProcesor, environmentToInjector, stopwatch, errors);
+ stopwatch.resetAndLog("Injector construction");
- buildStatically();
+ initializeStatically();
+ }
// If we're in the tool stage, stop here. Don't eagerly inject or load anything.
if (stage == Stage.TOOL) {
- return new ToolStageInjector(injector);
+ return new ToolStageInjector(primaryInjector());
}
injectDynamically();
- return injector;
+ return primaryInjector();
+ }
+
+ /** Initialize and validate everything. */
+ private void initializeStatically() {
+ bindingProcesor.initializeBindings();
+ stopwatch.resetAndLog("Binding initialization");
+
+ for (InjectorShell shell : shells) {
+ shell.getInjector().index();
+ }
+ stopwatch.resetAndLog("Binding indexing");
+
+ injectionRequestProcessor.process(shells);
+ stopwatch.resetAndLog("Collecting injection requests");
+
+ bindingProcesor.runCreationListeners(environmentToInjector);
+ stopwatch.resetAndLog("Binding validation");
+
+ injectionRequestProcessor.validate();
+ stopwatch.resetAndLog("Static validation");
+
+ initializer.validateOustandingInjections(errors);
+ stopwatch.resetAndLog("Instance member validation");
+
+ new ProviderLookupProcessor(errors).process(shells);
+ stopwatch.resetAndLog("Provider verification");
+
+ for (InjectorShell shell : shells) {
+ if (!shell.getElements().isEmpty()) {
+ throw new AssertionError("Failed to execute " + shell.getElements());
+ }
+ }
+
+ errors.throwCreationExceptionIfErrorsExist();
}
/**
- * Synchronize while we're building up the bindings and other injector state. This ensures that
- * the JIT bindings in the parent injector don't change while we're being built
+ * Returns the injector being constructed. This is not necessarily the root injector.
*/
- void buildStatically() {
- synchronized (injector.state.lock()) {
- // bind Stage and Singleton if this is a top-level injector
- if (parent == null) {
- modules.add(0, new RootModule(stage));
- }
-
- elements.addAll(Elements.getElements(stage, modules));
- stopwatch.resetAndLog("Module execution");
-
- new MessageProcessor(errors).processCommands(elements);
-
- InterceptorBindingProcessor interceptorCommandProcessor
- = new InterceptorBindingProcessor(errors, injector.state);
- interceptorCommandProcessor.processCommands(elements);
- injector.constructionProxyFactory = interceptorCommandProcessor.createProxyFactory();
- stopwatch.resetAndLog("Interceptors creation");
-
- new ScopeBindingProcessor(errors, injector.state).processCommands(elements);
- stopwatch.resetAndLog("Scopes creation");
-
- new TypeConverterBindingProcessor(errors, injector.state).processCommands(elements);
- stopwatch.resetAndLog("Converters creation");
-
- bindInjector();
- bindLogger();
- bindCommandProcesor = new BindingProcessor(errors,
- injector, injector.state, injector.initializer);
- bindCommandProcesor.processCommands(elements);
- bindCommandProcesor.createUntargettedBindings();
- stopwatch.resetAndLog("Binding creation");
-
- injector.index();
- stopwatch.resetAndLog("Binding indexing");
-
- injectionCommandProcessor = new InjectionRequestProcessor(errors, injector.initializer);
- injectionCommandProcessor.processCommands(elements);
- stopwatch.resetAndLog("Static injection");
-
- validateStatically();
-
- if (!elements.isEmpty()) {
- throw new AssertionError("Failed to execute " + elements);
- }
- }
- }
-
- /** Validate everything that we can validate now that the injector is ready for use. */
- private void validateStatically() {
- bindCommandProcesor.runCreationListeners(injector);
- stopwatch.resetAndLog("Validation");
-
- injectionCommandProcessor.validate(injector);
- stopwatch.resetAndLog("Static validation");
-
- injector.initializer.validateOustandingInjections(errors);
- stopwatch.resetAndLog("Instance member validation");
-
- new ProviderLookupProcessor(errors, injector).processCommands(elements);
- stopwatch.resetAndLog("Provider verification");
-
- errors.throwCreationExceptionIfErrorsExist();
+ private Injector primaryInjector() {
+ return shells.get(0).getInjector();
}
/**
@@ -180,27 +164,31 @@
* code build a just-in-time binding from another thread.
*/
private void injectDynamically() {
- injectionCommandProcessor.injectMembers(injector);
+ injectionRequestProcessor.injectMembers();
stopwatch.resetAndLog("Static member injection");
- injector.initializer.injectAll(errors);
+ initializer.injectAll(errors);
stopwatch.resetAndLog("Instance injection");
errors.throwCreationExceptionIfErrorsExist();
- loadEagerSingletons();
- stopwatch.resetAndLog("Preloading");
+ for (InjectorShell shell : shells) {
+ loadEagerSingletons(shell.getInjector(), stage, errors);
+ }
+ stopwatch.resetAndLog("Preloading singletons");
errors.throwCreationExceptionIfErrorsExist();
}
- public void loadEagerSingletons() {
- // load eager singletons, or all singletons if we're in Stage.PRODUCTION.
- // Bindings discovered while we're binding these singletons are not be eager.
+ /**
+ * Loads eager singletons, or all singletons if we're in Stage.PRODUCTION. Bindings discovered
+ * while we're binding these singletons are not be eager.
+ */
+ public void loadEagerSingletons(InjectorImpl injector, Stage stage, final Errors errors) {
@SuppressWarnings("unchecked") // casting Collection<Binding> to Collection<BindingImpl> is safe
Set<BindingImpl<?>> candidateBindings = ImmutableSet.copyOf(Iterables.concat(
(Collection) injector.state.getExplicitBindingsThisLevel().values(),
injector.jitBindings.values()));
for (final BindingImpl<?> binding : candidateBindings) {
- if ((stage == Stage.PRODUCTION && binding.getScope() == SINGLETON)
+ if ((stage == Stage.PRODUCTION && binding.getScope() == com.google.inject.Scopes.SINGLETON)
|| binding.getLoadStrategy() == LoadStrategy.EAGER) {
try {
injector.callInContext(new ContextualCallable<Void>() {
@@ -226,84 +214,6 @@
}
}
- private static class RootModule implements Module {
- final Stage stage;
-
- private RootModule(Stage stage) {
- this.stage = checkNotNull(stage, "stage");
- }
-
- public void configure(Binder binder) {
- binder = binder.withSource(SourceProvider.UNKNOWN_SOURCE);
- binder.bind(Stage.class).toInstance(stage);
- binder.bindScope(Singleton.class, SINGLETON);
- }
- }
-
- /**
- * The Injector is a special case because we allow both parent and child injectors to both have
- * a binding for that key.
- */
- private void bindInjector() {
- Key<Injector> key = Key.get(Injector.class);
- InjectorFactory injectorFactory = new InjectorFactory(injector);
- injector.state.putBinding(key,
- new ProviderInstanceBindingImpl<Injector>(injector, key, SourceProvider.UNKNOWN_SOURCE,
- injectorFactory, Scopes.NO_SCOPE, injectorFactory, LoadStrategy.LAZY,
- ImmutableSet.<InjectionPoint>of()));
- }
-
- static class InjectorFactory implements InternalFactory<Injector>, Provider<Injector> {
- private final Injector injector;
-
- private InjectorFactory(Injector injector) {
- this.injector = injector;
- }
-
- public Injector get(Errors errors, InternalContext context, Dependency<?> dependency)
- throws ErrorsException {
- return injector;
- }
-
- public Injector get() {
- return injector;
- }
-
- public String toString() {
- return "Provider<Injector>";
- }
- }
-
- /**
- * The Logger is a special case because it knows the injection point of the injected member. It's
- * the only binding that does this.
- */
- private void bindLogger() {
- Key<Logger> key = Key.get(Logger.class);
- LoggerFactory loggerFactory = new LoggerFactory();
- injector.state.putBinding(key,
- new ProviderInstanceBindingImpl<Logger>(injector, key,
- SourceProvider.UNKNOWN_SOURCE, loggerFactory, Scopes.NO_SCOPE,
- loggerFactory, LoadStrategy.LAZY, ImmutableSet.<InjectionPoint>of()));
- }
-
- static class LoggerFactory implements InternalFactory<Logger>, Provider<Logger> {
- public Logger get(Errors errors, InternalContext context, Dependency<?> dependency) {
- InjectionPoint injectionPoint = dependency.getInjectionPoint();
- return injectionPoint == null
- ? Logger.getAnonymousLogger()
- : Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
- }
-
- public Logger get() {
- return Logger.getAnonymousLogger();
- }
-
- public String toString() {
- return "Provider<Logger>";
- }
- }
-
/** {@link Injector} exposed to users in {@link Stage#TOOL}. */
static class ToolStageInjector implements Injector {
private final Injector delegateInjector;
diff --git a/src/com/google/inject/InjectorImpl.java b/src/com/google/inject/InjectorImpl.java
index f58cb90..b3918cf 100644
--- a/src/com/google/inject/InjectorImpl.java
+++ b/src/com/google/inject/InjectorImpl.java
@@ -61,12 +61,13 @@
final State state;
final InjectorImpl parent;
final BindingsMultimap bindingsMultimap = new BindingsMultimap();
- final Initializer initializer = new Initializer(this);
+ final Initializer initializer;
ConstructionProxyFactory constructionProxyFactory;
- InjectorImpl(@Nullable InjectorImpl parent) {
+ InjectorImpl(@Nullable InjectorImpl parent, State state, Initializer initializer) {
this.parent = parent;
- this.state = new InheritingState(parent != null ? parent.state : State.NONE);
+ this.state = state;
+ this.initializer = initializer;
if (parent != null) {
localContext = parent.localContext;
@@ -135,7 +136,6 @@
public Injector createChildInjector(Iterable<? extends Module> modules) {
return new InjectorBuilder()
.parentInjector(this)
- .stage(getInstance(Stage.class))
.addModules(modules)
.build();
}
diff --git a/src/com/google/inject/InjectorShell.java b/src/com/google/inject/InjectorShell.java
new file mode 100644
index 0000000..844fe7c
--- /dev/null
+++ b/src/com/google/inject/InjectorShell.java
@@ -0,0 +1,252 @@
+/**
+ * Copyright (C) 2008 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 static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import static com.google.inject.Scopes.SINGLETON;
+import com.google.inject.internal.Errors;
+import com.google.inject.internal.ErrorsException;
+import com.google.inject.internal.SourceProvider;
+import com.google.inject.internal.Stopwatch;
+import com.google.inject.spi.Dependency;
+import com.google.inject.spi.Element;
+import com.google.inject.spi.Elements;
+import com.google.inject.spi.InjectionPoint;
+import com.google.inject.spi.PrivateEnvironment;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Logger;
+
+/**
+ * A partially-initialized injector. See {@link InjectorBuilder}, which uses this to build a tree
+ * of injectors in batch.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+class InjectorShell {
+
+ private final List<Element> elements;
+ private final InjectorImpl injector;
+ private final PrivateEnvironment privateEnvironment;
+
+ private InjectorShell(Builder builder, List<Element> elements, InjectorImpl injector) {
+ this.privateEnvironment = builder.privateEnvironment;
+ this.elements = elements;
+ this.injector = injector;
+ }
+
+ PrivateEnvironment getPrivateEnvironment() {
+ return privateEnvironment;
+ }
+
+ InjectorImpl getInjector() {
+ return injector;
+ }
+
+ List<Element> getElements() {
+ return elements;
+ }
+
+ static class Builder {
+ private final List<Element> elements = Lists.newArrayList();
+ private final List<Module> modules = Lists.newArrayList();
+
+ /** lazily constructed */
+ private State state;
+
+ private InjectorImpl parent;
+ private Stage stage;
+
+ /** null unless this exists in a {@link Binder#newPrivateBinder private environment} */
+ private PrivateEnvironment privateEnvironment;
+
+ Builder parent(InjectorImpl parent) {
+ this.parent = parent;
+ this.state = new InheritingState(parent.state);
+ return this;
+ }
+
+ Builder stage(Stage stage) {
+ this.stage = stage;
+ return this;
+ }
+
+ Builder privateEnvironment(PrivateEnvironment privateEnvironment) {
+ this.privateEnvironment = privateEnvironment;
+ this.elements.addAll(privateEnvironment.getElements());
+ return this;
+ }
+
+ void addModules(Iterable<? extends Module> modules) {
+ for (Module module : modules) {
+ this.modules.add(module);
+ }
+ }
+
+ /** Synchronize on this before calling {@link #build}. */
+ Object lock() {
+ return getState().lock();
+ }
+
+ /**
+ * Creates and returns the injector shells for the current modules. Multiple shells will be
+ * returned if any modules contain {@link Binder#newPrivateBinder private environments}. The
+ * primary injector will be first in the returned list.
+ */
+ List<InjectorShell> build(Initializer initializer, BindingProcessor bindingProcessor,
+ Map<PrivateEnvironment, InjectorImpl> environmentToInjector,
+ Stopwatch stopwatch, Errors errors) {
+ checkState(stage != null, "Stage not initialized");
+ checkState(privateEnvironment == null || parent != null, "PrivateEnvironment with no parent");
+ checkState(state != null, "no state. Did you remember to lock() ?");
+
+ InjectorImpl injector = new InjectorImpl(parent, state, initializer);
+ if (privateEnvironment != null) {
+ environmentToInjector.put(privateEnvironment, injector);
+ }
+
+ // bind Stage and Singleton if this is a top-level injector
+ if (parent == null) {
+ modules.add(0, new RootModule(stage));
+ new TypeConverterBindingProcessor(errors).prepareBuiltInConverters(injector);
+ }
+
+ elements.addAll(Elements.getElements(stage, modules));
+ stopwatch.resetAndLog("Module execution");
+
+ new MessageProcessor(errors).process(injector, elements);
+
+ InterceptorBindingProcessor interceptors = new InterceptorBindingProcessor(errors);
+ interceptors.process(injector, elements);
+ interceptors.setupProxyFactory(injector);
+ stopwatch.resetAndLog("Interceptors creation");
+
+ new ScopeBindingProcessor(errors).process(injector, elements);
+ stopwatch.resetAndLog("Scopes creation");
+
+ new TypeConverterBindingProcessor(errors).process(injector, elements);
+ stopwatch.resetAndLog("Converters creation");
+
+ bindInjector(injector);
+ bindLogger(injector);
+ bindingProcessor.process(injector, elements);
+ stopwatch.resetAndLog("Binding creation");
+
+ List<InjectorShell> injectorShells = Lists.newArrayList();
+ injectorShells.add(new InjectorShell(this, elements, injector));
+
+ // recursively build child shells
+ PrivateEnvironmentProcessor processor = new PrivateEnvironmentProcessor(errors, stage);
+ processor.process(injector, elements);
+ for (Builder builder : processor.getInjectorShellBuilders()) {
+ injectorShells.addAll(
+ builder.build(initializer, bindingProcessor, environmentToInjector, stopwatch, errors));
+ }
+ stopwatch.resetAndLog("Private environment creation");
+
+ return injectorShells;
+ }
+
+ private State getState() {
+ if (state == null) {
+ state = new InheritingState(State.NONE);
+ }
+ return state;
+ }
+ }
+
+ /**
+ * The Injector is a special case because we allow both parent and child injectors to both have
+ * a binding for that key.
+ */
+ private static void bindInjector(InjectorImpl injector) {
+ Key<Injector> key = Key.get(Injector.class);
+ InjectorFactory injectorFactory = new InjectorFactory(injector);
+ injector.state.putBinding(key,
+ new ProviderInstanceBindingImpl<Injector>(injector, key, SourceProvider.UNKNOWN_SOURCE,
+ injectorFactory, Scopes.NO_SCOPE, injectorFactory, LoadStrategy.LAZY,
+ ImmutableSet.<InjectionPoint>of()));
+ }
+
+ private static class InjectorFactory implements InternalFactory<Injector>, Provider<Injector> {
+ private final Injector injector;
+
+ private InjectorFactory(Injector injector) {
+ this.injector = injector;
+ }
+
+ public Injector get(Errors errors, InternalContext context, Dependency<?> dependency)
+ throws ErrorsException {
+ return injector;
+ }
+
+ public Injector get() {
+ return injector;
+ }
+
+ public String toString() {
+ return "Provider<Injector>";
+ }
+ }
+
+ /**
+ * The Logger is a special case because it knows the injection point of the injected member. It's
+ * the only binding that does this.
+ */
+ private static void bindLogger(InjectorImpl injector) {
+ Key<Logger> key = Key.get(Logger.class);
+ LoggerFactory loggerFactory = new LoggerFactory();
+ injector.state.putBinding(key,
+ new ProviderInstanceBindingImpl<Logger>(injector, key,
+ SourceProvider.UNKNOWN_SOURCE, loggerFactory, Scopes.NO_SCOPE,
+ loggerFactory, LoadStrategy.LAZY, ImmutableSet.<InjectionPoint>of()));
+ }
+
+ private static class LoggerFactory implements InternalFactory<Logger>, Provider<Logger> {
+ public Logger get(Errors errors, InternalContext context, Dependency<?> dependency) {
+ InjectionPoint injectionPoint = dependency.getInjectionPoint();
+ return injectionPoint == null
+ ? Logger.getAnonymousLogger()
+ : Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
+ }
+
+ public Logger get() {
+ return Logger.getAnonymousLogger();
+ }
+
+ public String toString() {
+ return "Provider<Logger>";
+ }
+ }
+
+ private static class RootModule implements Module {
+ final Stage stage;
+
+ private RootModule(Stage stage) {
+ this.stage = checkNotNull(stage, "stage");
+ }
+
+ public void configure(Binder binder) {
+ binder = binder.withSource(SourceProvider.UNKNOWN_SOURCE);
+ binder.bind(Stage.class).toInstance(stage);
+ binder.bindScope(Singleton.class, SINGLETON);
+ }
+ }
+}
diff --git a/src/com/google/inject/InterceptorBindingProcessor.java b/src/com/google/inject/InterceptorBindingProcessor.java
index ef1c2f9..3967355 100644
--- a/src/com/google/inject/InterceptorBindingProcessor.java
+++ b/src/com/google/inject/InterceptorBindingProcessor.java
@@ -27,20 +27,17 @@
*/
class InterceptorBindingProcessor extends AbstractProcessor {
- private final State state;
-
- InterceptorBindingProcessor(Errors errors, State state) {
+ InterceptorBindingProcessor(Errors errors) {
super(errors);
- this.state = state;
}
@Override public Boolean visitInterceptorBinding(InterceptorBinding command) {
- state.addMethodAspect(new MethodAspect(
+ injector.state.addMethodAspect(new MethodAspect(
command.getClassMatcher(), command.getMethodMatcher(), command.getInterceptors()));
return true;
}
- ProxyFactory createProxyFactory() {
- return new ProxyFactory(state.getMethodAspects());
+ void setupProxyFactory(InjectorImpl injector) {
+ injector.constructionProxyFactory = new ProxyFactory(injector.state.getMethodAspects());
}
}
diff --git a/src/com/google/inject/PrivateEnvironmentProcessor.java b/src/com/google/inject/PrivateEnvironmentProcessor.java
new file mode 100644
index 0000000..cfccab7
--- /dev/null
+++ b/src/com/google/inject/PrivateEnvironmentProcessor.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright (C) 2008 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.common.collect.Lists;
+import com.google.inject.internal.Errors;
+import com.google.inject.spi.PrivateEnvironment;
+import java.util.List;
+
+/**
+ * Handles {@link Binder#newPrivateBinder()} elements.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public class PrivateEnvironmentProcessor extends AbstractProcessor {
+
+ private final Stage stage;
+ private final List<InjectorShell.Builder> injectorShellBuilders = Lists.newArrayList();
+
+ PrivateEnvironmentProcessor(Errors errors, Stage stage) {
+ super(errors);
+ this.stage = stage;
+ }
+
+ @Override public Boolean visitPrivateEnvironment(PrivateEnvironment privateEnvironment) {
+ InjectorShell.Builder builder = new InjectorShell.Builder()
+ .parent(injector)
+ .stage(stage)
+ .privateEnvironment(privateEnvironment);
+ injectorShellBuilders.add(builder);
+ return true;
+ }
+
+ public List<InjectorShell.Builder> getInjectorShellBuilders() {
+ return injectorShellBuilders;
+ }
+}
diff --git a/src/com/google/inject/PrivateModule.java b/src/com/google/inject/PrivateModule.java
new file mode 100644
index 0000000..277acba
--- /dev/null
+++ b/src/com/google/inject/PrivateModule.java
@@ -0,0 +1,213 @@
+/**
+ * Copyright (C) 2008 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 static com.google.common.base.Preconditions.checkState;
+import com.google.inject.binder.AnnotatedBindingBuilder;
+import com.google.inject.binder.AnnotatedConstantBindingBuilder;
+import com.google.inject.binder.AnnotatedElementBuilder;
+import com.google.inject.binder.LinkedBindingBuilder;
+import com.google.inject.binder.PrivateBinder;
+import com.google.inject.matcher.Matcher;
+import com.google.inject.spi.Message;
+import com.google.inject.spi.TypeConverter;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import org.aopalliance.intercept.MethodInterceptor;
+
+/**
+ * A module whose configuration information is hidden from its environment by default. Only bindings
+ * that are explicitly exposed will be available to other modules and to the users of the injector.
+ * This module may expose the bindings it creates and the bindings of the modules it installs.
+ *
+ * <p>A private module can be nested within a regular module or within another private module using
+ * {@link Binder#install install()}. Its bindings live in a new environment that inherits bindings,
+ * type converters, scopes, and interceptors from the surrounding ("parent") environment. When you
+ * nest multiple private modules, the result is a tree of environments where the injector's
+ * environment is the root.
+ *
+ * <p>Guice EDSL bindings can be exposed with {@link #expose(Class) expose()}. {@literal @}{@link
+ * com.google.inject.Provides Provides} bindings can be exposed with the {@literal @}{@link
+ * Exposed} annotation:
+ *
+ * <pre>
+ * public class FooBarBazModule extends PrivateModule {
+ * protected void configurePrivateBindings() {
+ * bind(Foo.class).to(RealFoo.class);
+ * expose(Foo.class);
+ *
+ * install(new TransactionalBarModule());
+ * expose(Bar.class).annotatedWith(Transactional.class);
+ *
+ * bind(SomeImplementationDetail.class);
+ * install(new MoreImplementationDetailsModule());
+ * }
+ *
+ * {@literal @}Provides {@literal @}Exposed
+ * public Baz provideBaz() {
+ * return new SuperBaz();
+ * }
+ * }
+ * </pre>
+ *
+ * <p>Private modules are implemented using {@link Injector#createChildInjector(Module[]) parent
+ * injectors}. When it can satisfy their dependencies, just-in-time bindings will be created in the
+ * root environment. Such bindings are shared among all environments in the tree.
+ *
+ * <p>The scope of a binding is constrained to its environment. A singleton bound in a private
+ * module will be unique to its environment. But a binding for the same type in a different private
+ * module will yield a different instance.
+ *
+ * <p>A shared binding that injects the {@code Injector} gets the root injector, which only has
+ * access to bindings in the root environment. An explicit binding that injects the {@code Injector}
+ * gets access to all bindings in the child environment.
+ *
+ * <p>To promote a just-in-time binding to an explicit binding, bind it:
+ * <pre>
+ * bind(FooImpl.class);
+ * </pre>
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public abstract class PrivateModule implements Module {
+
+ /** Like abstract module, the binder of the current private module */
+ private PrivateBinder binder;
+
+ public final synchronized void configure(Binder binder) {
+ checkState(this.binder == null, "Re-entry is not allowed.");
+
+ this.binder = (PrivateBinder) binder.skipSources(PrivateModule.class);
+ try {
+ configurePrivateBindings();
+ } finally {
+ this.binder = null;
+ }
+ }
+
+ /**
+ * Creates bindings and other configurations private to this module. Use {@link #expose(Class)
+ * expose()} to make the bindings in this module available externally.
+ */
+ protected abstract void configurePrivateBindings();
+
+ /** Makes the binding for {@code key} available to other modules and the injector. */
+ protected final <T> void expose(Key<T> key) {
+ binder.expose(key);
+ }
+
+ /**
+ * Makes a binding for {@code type} available to other modules and the injector. Use {@link
+ * AnnotatedElementBuilder#annotatedWith(Class) annotatedWith()} to expose {@code type} with a
+ * binding annotation.
+ */
+ protected final AnnotatedElementBuilder expose(Class<?> type) {
+ return binder.expose(type);
+ }
+
+ /**
+ * Makes a binding for {@code type} available to other modules and the injector. Use {@link
+ * AnnotatedElementBuilder#annotatedWith(Class) annotatedWith()} to expose {@code type} with a
+ * binding annotation.
+ */
+ protected final AnnotatedElementBuilder expose(TypeLiteral<?> type) {
+ return binder.expose(type);
+ }
+
+ // prevent classes migrated from AbstractModule from implementing the wrong method.
+ protected final void configure() {}
+
+ // everything below is copied from AbstractModule
+
+ protected final Binder binder() {
+ return binder;
+ }
+
+ protected final void bindScope(Class<? extends Annotation> scopeAnnotation, Scope scope) {
+ binder.bindScope(scopeAnnotation, scope);
+ }
+
+ protected final <T> LinkedBindingBuilder<T> bind(Key<T> key) {
+ return binder.bind(key);
+ }
+
+ protected final <T> AnnotatedBindingBuilder<T> bind(TypeLiteral<T> typeLiteral) {
+ return binder.bind(typeLiteral);
+ }
+
+ protected final <T> AnnotatedBindingBuilder<T> bind(Class<T> clazz) {
+ return binder.bind(clazz);
+ }
+
+ protected final AnnotatedConstantBindingBuilder bindConstant() {
+ return binder.bindConstant();
+ }
+
+ protected final void install(Module module) {
+ binder.install(module);
+ }
+
+ protected final void addError(String message, Object... arguments) {
+ binder.addError(message, arguments);
+ }
+
+ protected final void addError(Throwable t) {
+ binder.addError(t);
+ }
+
+ protected final void addError(Message message) {
+ binder.addError(message);
+ }
+
+ protected final void requestInjection(Object... objects) {
+ binder.requestInjection(objects);
+ }
+
+ protected final void requestStaticInjection(Class<?>... types) {
+ binder.requestStaticInjection(types);
+ }
+
+ protected final void bindInterceptor(Matcher<? super Class<?>> classMatcher,
+ Matcher<? super Method> methodMatcher, MethodInterceptor... interceptors) {
+ binder.bindInterceptor(classMatcher, methodMatcher, interceptors);
+ }
+
+ protected final void requireBinding(Key<?> key) {
+ binder.getProvider(key);
+ }
+
+ protected final void requireBinding(Class<?> type) {
+ binder.getProvider(type);
+ }
+
+ protected final <T> Provider<T> getProvider(Key<T> key) {
+ return binder.getProvider(key);
+ }
+
+ protected final <T> Provider<T> getProvider(Class<T> type) {
+ return binder.getProvider(type);
+ }
+
+ protected final void convertToTypes(Matcher<? super TypeLiteral<?>> typeMatcher,
+ TypeConverter converter) {
+ binder.convertToTypes(typeMatcher, converter);
+ }
+
+ protected final Stage currentStage() {
+ return binder.currentStage();
+ }
+}
diff --git a/src/com/google/inject/ProviderLookupProcessor.java b/src/com/google/inject/ProviderLookupProcessor.java
index f50d3ed..8c06228 100644
--- a/src/com/google/inject/ProviderLookupProcessor.java
+++ b/src/com/google/inject/ProviderLookupProcessor.java
@@ -28,11 +28,8 @@
*/
class ProviderLookupProcessor extends AbstractProcessor {
- private final InjectorImpl injector;
-
- ProviderLookupProcessor(Errors errors, InjectorImpl injector) {
+ ProviderLookupProcessor(Errors errors) {
super(errors);
- this.injector = injector;
}
@Override public <T> Boolean visitProviderLookup(ProviderLookup<T> command) {
diff --git a/src/com/google/inject/ScopeBindingProcessor.java b/src/com/google/inject/ScopeBindingProcessor.java
index 5936fe9..5c6fb39 100644
--- a/src/com/google/inject/ScopeBindingProcessor.java
+++ b/src/com/google/inject/ScopeBindingProcessor.java
@@ -30,12 +30,8 @@
*/
class ScopeBindingProcessor extends AbstractProcessor {
- private final State state;
-
- ScopeBindingProcessor(Errors errors,
- State state) {
+ ScopeBindingProcessor(Errors errors) {
super(errors);
- this.state = state;
}
@Override public Boolean visitScopeBinding(ScopeBinding command) {
@@ -53,11 +49,11 @@
// Go ahead and bind anyway so we don't get collateral errors.
}
- Scope existing = state.getScope(checkNotNull(annotationType, "annotation type"));
+ Scope existing = injector.state.getScope(checkNotNull(annotationType, "annotation type"));
if (existing != null) {
errors.duplicateScopes(existing, annotationType, scope);
} else {
- state.putAnnotation(annotationType, checkNotNull(scope, "scope"));
+ injector.state.putAnnotation(annotationType, checkNotNull(scope, "scope"));
}
return true;
diff --git a/src/com/google/inject/TypeConverterBindingProcessor.java b/src/com/google/inject/TypeConverterBindingProcessor.java
index b9d1fd3..b2e5885 100644
--- a/src/com/google/inject/TypeConverterBindingProcessor.java
+++ b/src/com/google/inject/TypeConverterBindingProcessor.java
@@ -38,76 +38,79 @@
*/
class TypeConverterBindingProcessor extends AbstractProcessor {
- private final State state;
-
- TypeConverterBindingProcessor(Errors errors, State state) {
+ TypeConverterBindingProcessor(Errors errors) {
super(errors);
- this.state = state;
-
- // 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);
-
- convertToClass(Character.class, 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>";
- }
- });
-
- 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<?>>";
- }
- }
- );
}
- private <T> void convertToPrimitiveType(Class<T> primitiveType,
- final Class<T> wrapperType) {
+ /** Installs default converters for primitives, enums, and class literals. */
+ public void prepareBuiltInConverters(InjectorImpl injector) {
+ this.injector = injector;
+ try {
+ // 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);
+
+ convertToClass(Character.class, 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>";
+ }
+ });
+
+ 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<?>>";
+ }
+ }
+ );
+ } finally {
+ this.injector = null;
+ }
+ }
+
+ private <T> void convertToPrimitiveType(Class<T> primitiveType, final Class<T> wrapperType) {
try {
final Method parser = wrapperType.getMethod(
"parse" + Strings.capitalize(primitiveType.getName()), String.class);
@@ -117,11 +120,9 @@
public Object convert(String value, TypeLiteral<?> toType) {
try {
return parser.invoke(null, value);
- }
- catch (IllegalAccessException e) {
+ } catch (IllegalAccessException e) {
throw new AssertionError(e);
- }
- catch (InvocationTargetException e) {
+ } catch (InvocationTargetException e) {
throw new RuntimeException(e.getTargetException().getMessage());
}
}
@@ -161,12 +162,12 @@
private void internalConvertToTypes(Matcher<? super TypeLiteral<?>> typeMatcher,
TypeConverter converter) {
- state.addConverter(
+ injector.state.addConverter(
new MatcherAndConverter(typeMatcher, converter, SourceProvider.UNKNOWN_SOURCE));
}
@Override public Boolean visitTypeConverterBinding(TypeConverterBinding command) {
- state.addConverter(new MatcherAndConverter(
+ injector.state.addConverter(new MatcherAndConverter(
command.getTypeMatcher(), command.getTypeConverter(), command.getSource()));
return true;
}
diff --git a/src/com/google/inject/internal/Errors.java b/src/com/google/inject/internal/Errors.java
index 1c6861d..b8de568 100644
--- a/src/com/google/inject/internal/Errors.java
+++ b/src/com/google/inject/internal/Errors.java
@@ -262,6 +262,10 @@
return errorInUserCode("Error in custom provider, %s", runtimeException);
}
+ public void exposedButNotBound(Key<?> key) {
+ addMessage("Could not expose() %s, it must be explicitly bound.", key);
+ }
+
public static Collection<Message> getMessagesFromThrowable(Throwable throwable) {
if (throwable instanceof ProvisionException) {
return ((ProvisionException) throwable).getErrorMessages();
diff --git a/src/com/google/inject/internal/FailableCache.java b/src/com/google/inject/internal/FailableCache.java
index b252b31..3e3250f 100644
--- a/src/com/google/inject/internal/FailableCache.java
+++ b/src/com/google/inject/internal/FailableCache.java
@@ -45,7 +45,9 @@
errors.merge((Errors) resultOrError);
throw errors.toException();
} else {
- return (V) resultOrError;
+ @SuppressWarnings("unchecked") // create returned a non-error result, so this is safe
+ V result = (V) resultOrError;
+ return result;
}
}
}
diff --git a/src/com/google/inject/internal/ModuleBinding.java b/src/com/google/inject/internal/ModuleBinding.java
index a714b68..6887359 100644
--- a/src/com/google/inject/internal/ModuleBinding.java
+++ b/src/com/google/inject/internal/ModuleBinding.java
@@ -27,6 +27,7 @@
import com.google.inject.TypeLiteral;
import com.google.inject.binder.AnnotatedBindingBuilder;
import com.google.inject.binder.AnnotatedConstantBindingBuilder;
+import com.google.inject.binder.AnnotatedElementBuilder;
import com.google.inject.binder.ConstantBindingBuilder;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.binder.ScopedBindingBuilder;
@@ -36,6 +37,7 @@
import com.google.inject.spi.ElementVisitor;
import com.google.inject.spi.InjectionPoint;
import com.google.inject.spi.Message;
+import com.google.inject.spi.PrivateEnvironment;
import com.google.inject.util.Providers;
import java.lang.annotation.Annotation;
import java.util.Set;
@@ -153,6 +155,23 @@
return new RegularBuilder(binder);
}
+
+ /** @param binder the binder where errors will be reported. */
+ public ExposureBuilder<T> exposedKeyBuilder(Binder binder,
+ final PrivateEnvironment privateEnvironment) {
+ if (target != EMPTY_TARGET) {
+ throw new AssertionError();
+ }
+
+ target = new Target<T>() {
+ public <V> V acceptTargetVisitor(BindingTargetVisitor<? super T, V> visitor) {
+ return visitor.visitExposed(privateEnvironment);
+ }
+ };
+
+ return new ExposureBuilder<T>(this, binder);
+ }
+
/**
* Write access to the internal state of this element. Not for use by the public API.
*/
@@ -163,8 +182,7 @@
this.binder = binder.skipSources(RegularBuilder.class);
}
- public LinkedBindingBuilder<T> annotatedWith(
- Class<? extends Annotation> annotationType) {
+ public LinkedBindingBuilder<T> annotatedWith(Class<? extends Annotation> annotationType) {
checkNotNull(annotationType, "annotationType");
checkNotAnnotated();
key = Key.get(key.getTypeLiteral(), annotationType);
@@ -352,8 +370,7 @@
/**
* Package-private write access to the internal state of this element.
*/
- class ConstantBuilder
- implements AnnotatedConstantBindingBuilder, ConstantBindingBuilder {
+ class ConstantBuilder implements AnnotatedConstantBindingBuilder, ConstantBindingBuilder {
private final Binder binder;
ConstantBuilder(Binder binder) {
@@ -455,6 +472,45 @@
}
}
+ /**
+ * For private binder's expose() method.
+ */
+ public static class ExposureBuilder<T> implements AnnotatedElementBuilder {
+ private ModuleBinding<T> binding;
+ private final Binder binder;
+
+ public ExposureBuilder(ModuleBinding<T> binding, Binder binder) {
+ this.binding = binding;
+ this.binder = binder;
+ }
+
+ public void annotatedWith(Class<? extends Annotation> annotationType) {
+ checkNotNull(annotationType, "annotationType");
+ checkNotAnnotated();
+ binding.key = Key.get(binding.key.getTypeLiteral(), annotationType);
+ }
+
+ public void annotatedWith(Annotation annotation) {
+ checkNotNull(annotation, "annotation");
+ checkNotAnnotated();
+ binding.key = Key.get(binding.key.getTypeLiteral(), annotation);
+ }
+
+ public Key<?> getKey() {
+ return binding.key;
+ }
+
+ private void checkNotAnnotated() {
+ if (binding.key.getAnnotationType() != null) {
+ binder.addError(ANNOTATION_ALREADY_SPECIFIED);
+ }
+ }
+
+ @Override public String toString() {
+ return "AnnotatedElementBuilder";
+ }
+ }
+
/** A binding target, which provides instances from a specific key. */
private interface Target<T> {
<V> V acceptTargetVisitor(BindingTargetVisitor<? super T, V> visitor);
diff --git a/src/com/google/inject/internal/ProviderMethod.java b/src/com/google/inject/internal/ProviderMethod.java
index 659e4d0..6514d2c 100644
--- a/src/com/google/inject/internal/ProviderMethod.java
+++ b/src/com/google/inject/internal/ProviderMethod.java
@@ -16,12 +16,14 @@
package com.google.inject.internal;
-import com.google.inject.Provider;
-import com.google.inject.Key;
import com.google.inject.Binder;
+import com.google.inject.Exposed;
+import com.google.inject.Key;
+import com.google.inject.Provider;
+import com.google.inject.binder.PrivateBinder;
import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.List;
/**
@@ -37,6 +39,7 @@
private final Object instance;
private final Method method;
private final List<Provider<?>> parameterProviders;
+ private final boolean exposed;
/**
* @param method the method to invoke. It's return type must be the same type as {@code key}.
@@ -48,6 +51,7 @@
this.instance = instance;
this.method = method;
this.parameterProviders = parameterProviders;
+ this.exposed = method.isAnnotationPresent(Exposed.class);
method.setAccessible(true);
}
@@ -61,10 +65,16 @@
}
public void configure(Binder binder) {
+ binder = binder.withSource(method);
+
if (scopeAnnotation != null) {
- binder.withSource(method).bind(key).toProvider(this).in(scopeAnnotation);
+ binder.bind(key).toProvider(this).in(scopeAnnotation);
} else {
- binder.withSource(method).bind(key).toProvider(this);
+ binder.bind(key).toProvider(this);
+ }
+
+ if (exposed) {
+ ((PrivateBinder) binder).expose(key);
}
}
diff --git a/src/com/google/inject/internal/ProviderMethodsModule.java b/src/com/google/inject/internal/ProviderMethodsModule.java
index 486abd9..1b873cb 100644
--- a/src/com/google/inject/internal/ProviderMethodsModule.java
+++ b/src/com/google/inject/internal/ProviderMethodsModule.java
@@ -16,7 +16,6 @@
package com.google.inject.internal;
-import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.Lists;
import com.google.inject.Binder;
@@ -57,31 +56,9 @@
return Modules.EMPTY_MODULE;
}
- // don't install provider methods for private modules, they take care of that manually
- if (isPrivateModule(module)) {
- return Modules.EMPTY_MODULE;
- }
-
return new ProviderMethodsModule(module);
}
- private static boolean isPrivateModule(Module module) {
- // use the ugly class name to avoid an even uglier dependency. If private modules ever get
- // incorporated into core, we could use a single instanceof instead of this loop
- for (Class<?> c = module.getClass(); c != Object.class; c = c.getSuperclass()) {
- if (c.getName().equals("com.google.inject.privatemodules.PrivateModule")) {
- return true;
- }
- }
- return false;
- }
-
- /** See {@link com.google.inject.privatemodules.PrivateModule}. */
- public static ProviderMethodsModule forPrivateModule(Module privateModule) {
- checkArgument(isPrivateModule(privateModule));
- return new ProviderMethodsModule(privateModule);
- }
-
public synchronized void configure(Binder binder) {
for (ProviderMethod<?> providerMethod : getProviderMethods(binder)) {
providerMethod.configure(binder);
diff --git a/src/com/google/inject/spi/BindingTargetVisitor.java b/src/com/google/inject/spi/BindingTargetVisitor.java
index 3775246..bf69009 100644
--- a/src/com/google/inject/spi/BindingTargetVisitor.java
+++ b/src/com/google/inject/spi/BindingTargetVisitor.java
@@ -105,4 +105,9 @@
* com.google.inject.Injector#getBinding(Key) Injector.getBinding(provided)}
*/
V visitProviderBinding(Key<?> provided);
+
+ /**
+ * Visit a binding to a key exposed by a private environment.
+ */
+ V visitExposed(PrivateEnvironment privateEnvironment);
}
diff --git a/src/com/google/inject/spi/DefaultBindingTargetVisitor.java b/src/com/google/inject/spi/DefaultBindingTargetVisitor.java
index 245a3b8..c213dbc 100644
--- a/src/com/google/inject/spi/DefaultBindingTargetVisitor.java
+++ b/src/com/google/inject/spi/DefaultBindingTargetVisitor.java
@@ -69,4 +69,8 @@
public V visitProviderBinding(Key<?> provided) {
return visitOther();
}
+
+ public V visitExposed(PrivateEnvironment privateEnvironment) {
+ return visitOther();
+ }
}
diff --git a/src/com/google/inject/spi/DefaultElementVisitor.java b/src/com/google/inject/spi/DefaultElementVisitor.java
index 1d2e21d..9ab49ab 100644
--- a/src/com/google/inject/spi/DefaultElementVisitor.java
+++ b/src/com/google/inject/spi/DefaultElementVisitor.java
@@ -69,11 +69,7 @@
return visitElement(staticInjectionRequest);
}
- public V visitPrivateElements(PrivateEnvironment privateEnvironment) {
+ public V visitPrivateEnvironment(PrivateEnvironment privateEnvironment) {
return visitElement(privateEnvironment);
}
-
- public V visitExposure(Exposure exposure) {
- return visitElement(exposure);
- }
}
diff --git a/src/com/google/inject/spi/ElementVisitor.java b/src/com/google/inject/spi/ElementVisitor.java
index b091a37..19b1e8a 100644
--- a/src/com/google/inject/spi/ElementVisitor.java
+++ b/src/com/google/inject/spi/ElementVisitor.java
@@ -72,10 +72,5 @@
/**
* Visit a collection of configuration elements for a private environment.
*/
- V visitPrivateElements(PrivateEnvironment privateEnvironment);
-
- /**
- * Visit the exposure of a binding to its enclosing environment.
- */
- V visitExposure(Exposure exposure);
+ V visitPrivateEnvironment(PrivateEnvironment privateEnvironment);
}
diff --git a/src/com/google/inject/spi/Elements.java b/src/com/google/inject/spi/Elements.java
index ce2a0b0..6d1ae25 100644
--- a/src/com/google/inject/spi/Elements.java
+++ b/src/com/google/inject/spi/Elements.java
@@ -25,6 +25,7 @@
import com.google.inject.Binder;
import com.google.inject.Key;
import com.google.inject.Module;
+import com.google.inject.PrivateModule;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.Stage;
@@ -35,6 +36,7 @@
import com.google.inject.binder.PrivateBinder;
import com.google.inject.internal.Errors;
import com.google.inject.internal.ModuleBinding;
+import com.google.inject.internal.ModuleBinding.ExposureBuilder;
import com.google.inject.internal.ProviderMethodsModule;
import com.google.inject.internal.SourceProvider;
import com.google.inject.matcher.Matcher;
@@ -109,6 +111,9 @@
private final List<Element> elements;
private final Object source;
private final SourceProvider sourceProvider;
+
+ /** The binder where exposed bindings will be created */
+ private final RecordingBinder parent;
private final PrivateEnvironment privateEnvironment;
private RecordingBinder(Stage stage) {
@@ -118,28 +123,32 @@
this.source = null;
this.sourceProvider = new SourceProvider()
.plusSkippedClasses(Elements.class, RecordingBinder.class, AbstractModule.class);
+ this.parent = null;
this.privateEnvironment = null;
}
- /** Creates a recording binder that's backed by {@code backingBinder}. */
- private RecordingBinder(RecordingBinder parent, Object source, SourceProvider sourceProvider) {
+ /** Creates a recording binder that's backed by {@code prototype}. */
+ private RecordingBinder(
+ RecordingBinder prototype, Object source, SourceProvider sourceProvider) {
checkArgument(source == null ^ sourceProvider == null);
- this.stage = parent.stage;
- this.modules = parent.modules;
- this.elements = parent.elements;
+ this.stage = prototype.stage;
+ this.modules = prototype.modules;
+ this.elements = prototype.elements;
this.source = source;
this.sourceProvider = sourceProvider;
- this.privateEnvironment = parent.privateEnvironment;
+ this.parent = prototype.parent;
+ this.privateEnvironment = prototype.privateEnvironment;
}
/** Creates a private recording binder. */
private RecordingBinder(RecordingBinder parent, PrivateEnvironment privateEnvironment) {
this.stage = parent.stage;
this.modules = Sets.newHashSet();
- this.elements = privateEnvironment.elementsMutable;
+ this.elements = privateEnvironment.getElementsMutable();
this.source = parent.source;
this.sourceProvider = parent.sourceProvider;
+ this.parent = parent;
this.privateEnvironment = privateEnvironment;
}
@@ -168,8 +177,13 @@
public void install(Module module) {
if (modules.add(module)) {
+ Binder binder = this;
+ if (module instanceof PrivateModule) {
+ binder = binder.newPrivateBinder();
+ }
+
try {
- module.configure(this);
+ module.configure(binder);
} catch (RuntimeException e) {
Collection<Message> messages = Errors.getMessagesFromThrowable(e);
if (!messages.isEmpty()) {
@@ -178,7 +192,7 @@
addError(e);
}
}
- install(ProviderMethodsModule.forModule(module));
+ binder.install(ProviderMethodsModule.forModule(module));
}
}
@@ -277,15 +291,18 @@
return exposeInternal(Key.get(type));
}
- private AnnotatedElementBuilder exposeInternal(Key<?> key) {
+ private <T> AnnotatedElementBuilder exposeInternal(Key<T> key) {
if (privateEnvironment == null) {
throw new UnsupportedOperationException("expose() only supported on PrivateBinder. "
+ "Avoid using 'instanceof PrivateBinder', it's unsafe.");
}
- Exposure exposure = new Exposure(getSource(), privateEnvironment, key);
- elements.add(exposure);
- return exposure.annotatedElementBuilder(this);
+ ModuleBinding<T> exposeBinding = new ModuleBinding<T>(getSource(), key);
+ parent.elements.add(exposeBinding);
+
+ ExposureBuilder<T> builder = exposeBinding.exposedKeyBuilder(this, privateEnvironment);
+ privateEnvironment.addExposureBuilder(builder);
+ return builder;
}
protected Object getSource() {
diff --git a/src/com/google/inject/spi/Exposure.java b/src/com/google/inject/spi/Exposure.java
index 07a6039..d9d0370 100644
--- a/src/com/google/inject/spi/Exposure.java
+++ b/src/com/google/inject/spi/Exposure.java
@@ -1,88 +1,88 @@
-/**
- * Copyright (C) 2008 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.spi;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import com.google.inject.Binder;
-import com.google.inject.Key;
-import com.google.inject.binder.AnnotatedElementBuilder;
-import com.google.inject.internal.ModuleBinding;
-import java.lang.annotation.Annotation;
-
-/**
- * Exposes a binding to its enclosing environment.
- *
- * @author jessewilson@google.com (Jesse Wilson)
- * @since 2.0
- */
-public final class Exposure implements Element {
- private final Object source;
- private final PrivateEnvironment privateEnvironment;
- private Key<?> key;
-
- Exposure(Object source, PrivateEnvironment privateEnvironment, Key<?> key) {
- this.source = checkNotNull(source, "source");
- this.privateEnvironment = privateEnvironment;
- this.key = checkNotNull(key, "key");
- }
-
- public Object getSource() {
- return source;
- }
-
- /**
- * Returns the environment that owns this binding. Its enclosing environment gains access to the
- * binding via this exposure.
- */
- public PrivateEnvironment getPrivateEnvironment() {
- return privateEnvironment;
- }
-
- /**
- * Returns the exposed key.
- */
- public Key<?> getKey() {
- return key;
- }
-
- public <T> T acceptVisitor(ElementVisitor<T> visitor) {
- return visitor.visitExposure(this);
- }
-
- /** Returns a builder that applies annotations to this exposed key. */
- AnnotatedElementBuilder annotatedElementBuilder(final Binder binder) {
- return new AnnotatedElementBuilder() {
- public void annotatedWith(Class<? extends Annotation> annotationType) {
- checkNotNull(annotationType, "annotationType");
- checkNotAnnotated();
- key = Key.get(key.getTypeLiteral(), annotationType);
- }
-
- public void annotatedWith(Annotation annotation) {
- checkNotNull(annotation, "annotation");
- checkNotAnnotated();
- key = Key.get(key.getTypeLiteral(), annotation);
- }
-
- private void checkNotAnnotated() {
- if (key.getAnnotationType() != null) {
- binder.addError(ModuleBinding.ANNOTATION_ALREADY_SPECIFIED);
- }
- }
- };
- }
-}
+///**
+// * Copyright (C) 2008 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.spi;
+//
+//import static com.google.common.base.Preconditions.checkNotNull;
+//import com.google.inject.Binder;
+//import com.google.inject.Key;
+//import com.google.inject.binder.AnnotatedElementBuilder;
+//import com.google.inject.internal.ModuleBinding;
+//import java.lang.annotation.Annotation;
+//
+///**
+// * Exposes a binding to its enclosing environment.
+// *
+// * @author jessewilson@google.com (Jesse Wilson)
+// * @since 2.0
+// */
+//public final class Exposure implements Element {
+// private final Object source;
+// private final PrivateEnvironment privateEnvironment;
+// private Key<?> key;
+//
+// Exposure(Object source, PrivateEnvironment privateEnvironment, Key<?> key) {
+// this.source = checkNotNull(source, "source");
+// this.privateEnvironment = privateEnvironment;
+// this.key = checkNotNull(key, "key");
+// }
+//
+// public Object getSource() {
+// return source;
+// }
+//
+// /**
+// * Returns the environment that owns this binding. Its enclosing environment gains access to the
+// * binding via this exposure.
+// */
+// public PrivateEnvironment getPrivateEnvironment() {
+// return privateEnvironment;
+// }
+//
+// /**
+// * Returns the exposed key.
+// */
+// public Key<?> getKey() {
+// return key;
+// }
+//
+// public <T> T acceptVisitor(ElementVisitor<T> visitor) {
+// return visitor.visitExposure(this);
+// }
+//
+// /** Returns a builder that applies annotations to this exposed key. */
+// AnnotatedElementBuilder annotatedElementBuilder(final Binder binder) {
+// return new AnnotatedElementBuilder() {
+// public void annotatedWith(Class<? extends Annotation> annotationType) {
+// checkNotNull(annotationType, "annotationType");
+// checkNotAnnotated();
+// key = Key.get(key.getTypeLiteral(), annotationType);
+// }
+//
+// public void annotatedWith(Annotation annotation) {
+// checkNotNull(annotation, "annotation");
+// checkNotAnnotated();
+// key = Key.get(key.getTypeLiteral(), annotation);
+// }
+//
+// private void checkNotAnnotated() {
+// if (key.getAnnotationType() != null) {
+// binder.addError(ModuleBinding.ANNOTATION_ALREADY_SPECIFIED);
+// }
+// }
+// };
+// }
+//}
diff --git a/src/com/google/inject/spi/ModuleWriter.java b/src/com/google/inject/spi/ModuleWriter.java
index 2ce1e3c..a9a4cd0 100644
--- a/src/com/google/inject/spi/ModuleWriter.java
+++ b/src/com/google/inject/spi/ModuleWriter.java
@@ -16,19 +16,21 @@
package com.google.inject.spi;
+import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.common.collect.Maps;
import com.google.inject.Binder;
import com.google.inject.Binding;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.Scope;
-import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.binder.PrivateBinder;
import com.google.inject.binder.ScopedBindingBuilder;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import org.aopalliance.intercept.MethodInterceptor;
@@ -40,9 +42,10 @@
*/
public class ModuleWriter {
+ private final Map<PrivateEnvironment, PrivateBinder> environmentToBinder = Maps.newHashMap();
+
/**
- * Returns a module that executes the specified elements
- * using this executing visitor.
+ * Returns a module that executes the specified elements using this executing visitor.
*/
public final Module create(final Iterable<? extends Element> elements) {
return new Module() {
@@ -101,15 +104,10 @@
return null;
}
- public Void visitPrivateElements(PrivateEnvironment privateEnvironment) {
+ public Void visitPrivateEnvironment(PrivateEnvironment privateEnvironment) {
writePrivateElements(binder, privateEnvironment);
return null;
}
-
- public Void visitExposure(Exposure expose) {
- writeExpose(binder, expose);
- return null;
- }
};
for (Element element : elements) {
@@ -148,47 +146,55 @@
}
protected <T> void writeBind(Binder binder, Binding<T> element) {
- LinkedBindingBuilder<T> lbb = binder.withSource(element.getSource()).bind(element.getKey());
- ScopedBindingBuilder sbb = applyTarget(element, lbb);
+ ScopedBindingBuilder sbb
+ = bindKeyToTarget(element, binder.withSource(element.getSource()), element.getKey());
applyScoping(element, sbb);
}
+ /**
+ * Writes all the elements bound in the private environment to new private binder that's enclosed
+ * by the current binder. The private binder will be associated with its environment, so exposed
+ * bindings from the main elements list can be exposed from the corresponding private binder.
+ */
protected void writePrivateElements(Binder binder, PrivateEnvironment element) {
PrivateBinder privateBinder = binder.withSource(element.getSource()).newPrivateBinder();
+ setPrivateBinder(element, privateBinder);
apply(privateBinder, element.getElements());
}
- protected void writeExpose(Binder binder, Exposure exposure) {
- PrivateBinder privateBinder = (PrivateBinder) binder;
- privateBinder.withSource(exposure.getSource()).expose(exposure.getKey());
- }
-
/**
* Execute this target against the linked binding builder.
*/
- protected <T> ScopedBindingBuilder applyTarget(Binding<T> binding,
- final LinkedBindingBuilder<T> linkedBindingBuilder) {
+ protected <T> ScopedBindingBuilder bindKeyToTarget(
+ final Binding<T> binding, final Binder binder, final Key<T> key) {
return binding.acceptTargetVisitor(new BindingTargetVisitor<T, ScopedBindingBuilder>() {
public ScopedBindingBuilder visitInstance(T instance, Set<InjectionPoint> injectionPoints) {
- linkedBindingBuilder.toInstance(instance);
+ binder.bind(key).toInstance(instance);
return null;
}
public ScopedBindingBuilder visitProvider(Provider<? extends T> provider,
Set<InjectionPoint> injectionPoints) {
- return linkedBindingBuilder.toProvider(provider);
+ return binder.bind(key).toProvider(provider);
}
- public ScopedBindingBuilder visitProviderKey(Key<? extends Provider<? extends T>> providerKey) {
- return linkedBindingBuilder.toProvider(providerKey);
+ public ScopedBindingBuilder visitProviderKey(
+ Key<? extends Provider<? extends T>> providerKey) {
+ return binder.bind(key).toProvider(providerKey);
}
- public ScopedBindingBuilder visitKey(Key<? extends T> key) {
- return linkedBindingBuilder.to(key);
+ public ScopedBindingBuilder visitKey(Key<? extends T> targetKey) {
+ return binder.bind(key).to(targetKey);
}
public ScopedBindingBuilder visitUntargetted() {
- return linkedBindingBuilder;
+ return binder.bind(key);
+ }
+
+ public ScopedBindingBuilder visitExposed(PrivateEnvironment privateEnvironment) {
+ PrivateBinder privateBinder = getPrivateBinder(privateEnvironment);
+ privateBinder.withSource(binding.getSource()).expose(key);
+ return null;
}
public ScopedBindingBuilder visitConvertedConstant(T value) {
@@ -206,6 +212,26 @@
});
}
+ /**
+ * Associates {@code binder} with {@code privateEnvironment}. This can later be used to lookup the
+ * binder for its environment.
+ */
+ protected void setPrivateBinder(PrivateEnvironment privateEnvironment, PrivateBinder binder) {
+ checkArgument(!environmentToBinder.containsKey(privateEnvironment),
+ "A private binder already exists for %s", privateEnvironment);
+ environmentToBinder.put(privateEnvironment, binder);
+ }
+
+ /**
+ * Returns the {@code binder} accociated with {@code privateEnvironment}. This can be used to
+ * expose bindings from {@code privateEnvironment} from the corresponding private binder.
+ */
+ protected PrivateBinder getPrivateBinder(PrivateEnvironment privateEnvironment) {
+ PrivateBinder privateBinder = environmentToBinder.get(privateEnvironment);
+ checkArgument(privateBinder != null, "No private binder for %s", privateEnvironment);
+ return privateBinder;
+ }
+
protected void applyScoping(Binding<?> binding, final ScopedBindingBuilder scopedBindingBuilder) {
binding.acceptScopingVisitor(new BindingScopingVisitor<Void>() {
public Void visitEagerSingleton() {
diff --git a/src/com/google/inject/spi/PrivateEnvironment.java b/src/com/google/inject/spi/PrivateEnvironment.java
index 546624b..c757102 100644
--- a/src/com/google/inject/spi/PrivateEnvironment.java
+++ b/src/com/google/inject/spi/PrivateEnvironment.java
@@ -22,6 +22,7 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.inject.Key;
+import com.google.inject.internal.ModuleBinding.ExposureBuilder;
import java.util.List;
import java.util.Set;
@@ -33,8 +34,16 @@
*/
public final class PrivateEnvironment implements Element {
+ /*
+ * This class acts as both a value object and as a builder. When getElements() is called, an
+ * immutable collection of elements is constructed and the original mutable list is nulled out.
+ * Similarly, the exposed keys are made immutable on access.
+ */
+
private final Object source;
- List<Element> elementsMutable = Lists.newArrayList();
+
+ private List<Element> elementsMutable = Lists.newArrayList();
+ private List<ExposureBuilder<?>> exposureBuilders = Lists.newArrayList();
/** lazily instantiated */
private ImmutableList<Element> elements;
@@ -51,8 +60,7 @@
}
/**
- * Returns the configuration information in this private environment, including the {@link
- * Exposure} elements that make configuration available to the enclosing environment.
+ * Returns the configuration information in this private environment.
*/
public List<Element> getElements() {
if (elements == null) {
@@ -69,18 +77,25 @@
public Set<Key<?>> getExposedKeys() {
if (exposedKeys == null) {
Set<Key<?>> exposedKeysMutable = Sets.newLinkedHashSet();
- for (Element element : getElements()) {
- if (element instanceof Exposure) {
- exposedKeysMutable.add(((Exposure) element).getKey());
- }
+ for (ExposureBuilder<?> exposureBuilder : exposureBuilders) {
+ exposedKeysMutable.add(exposureBuilder.getKey());
}
exposedKeys = ImmutableSet.copyOf(exposedKeysMutable);
+ exposureBuilders = null;
}
return exposedKeys;
}
public <T> T acceptVisitor(ElementVisitor<T> visitor) {
- return visitor.visitPrivateElements(this);
+ return visitor.visitPrivateEnvironment(this);
+ }
+
+ List<Element> getElementsMutable() {
+ return elementsMutable;
+ }
+
+ void addExposureBuilder(ExposureBuilder<?> exposureBuilder) {
+ exposureBuilders.add(exposureBuilder);
}
}
diff --git a/test/com/google/inject/AllTests.java b/test/com/google/inject/AllTests.java
index e61332c..d6fb5dc 100644
--- a/test/com/google/inject/AllTests.java
+++ b/test/com/google/inject/AllTests.java
@@ -66,6 +66,7 @@
suite.addTestSuite(OptionalBindingTest.class);
suite.addTestSuite(OverrideModuleTest.class);
suite.addTestSuite(ParentInjectorTest.class);
+ suite.addTestSuite(PrivateModuleTest.class);
suite.addTestSuite(ProviderInjectionTest.class);
suite.addTestSuite(ProviderMethodsTest.class);
suite.addTestSuite(ProvisionExceptionTest.class);
diff --git a/extensions/privatemodules/test/com/google/inject/privatemodules/PrivateModuleTest.java b/test/com/google/inject/PrivateModuleTest.java
similarity index 84%
rename from extensions/privatemodules/test/com/google/inject/privatemodules/PrivateModuleTest.java
rename to test/com/google/inject/PrivateModuleTest.java
index 031a3af..39e32a0 100644
--- a/extensions/privatemodules/test/com/google/inject/privatemodules/PrivateModuleTest.java
+++ b/test/com/google/inject/PrivateModuleTest.java
@@ -14,18 +14,10 @@
* limitations under the License.
*/
-package com.google.inject.privatemodules;
+package com.google.inject;
-import com.google.inject.AbstractModule;
import static com.google.inject.Asserts.assertContains;
-import com.google.inject.CreationException;
-import com.google.inject.Guice;
-import com.google.inject.Inject;
-import com.google.inject.Injector;
-import com.google.inject.Key;
-import com.google.inject.Provider;
-import com.google.inject.Provides;
-import com.google.inject.ProvisionException;
+import com.google.inject.binder.PrivateBinder;
import com.google.inject.name.Named;
import static com.google.inject.name.Names.named;
import junit.framework.TestCase;
@@ -68,6 +60,25 @@
assertEquals("public", ab2.a);
assertEquals("ii", ab2.b);
}
+
+ public void testWithoutPrivateModules() {
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ PrivateBinder bindA = binder().newPrivateBinder();
+ bindA.bind(String.class).annotatedWith(named("a")).toInstance("i");
+ bindA.expose(String.class).annotatedWith(named("a"));
+ bindA.bind(String.class).annotatedWith(named("c")).toInstance("private to A");
+
+ PrivateBinder bindB = binder().newPrivateBinder();
+ bindB.bind(String.class).annotatedWith(named("b")).toInstance("ii");
+ bindB.expose(String.class).annotatedWith(named("b"));
+ bindB.bind(String.class).annotatedWith(named("c")).toInstance("private to B");
+ }
+ });
+
+ assertEquals("i", injector.getInstance(Key.get(String.class, named("a"))));
+ assertEquals("ii", injector.getInstance(Key.get(String.class, named("b"))));
+ }
public void testPrivateModulesAndProvidesMethods() {
Injector injector = Guice.createInjector(new AbstractModule() {
@@ -89,11 +100,11 @@
install(new PrivateModule() {
public void configurePrivateBindings() {}
- @Provides @Named("a") String providePrivateA() {
+ @Provides @Named("c") String providePrivateC() {
return "private";
}
- @Provides @Exposed @Named("b") String providePublicB() {
+ @Provides @Exposed @Named("d") String providePublicD() {
return "ii";
}
});
@@ -101,7 +112,7 @@
});
assertEquals("i", injector.getInstance(Key.get(String.class, named("a"))));
- assertEquals("ii", injector.getInstance(Key.get(String.class, named("b"))));
+ assertEquals("ii", injector.getInstance(Key.get(String.class, named("d"))));
}
public void testCannotBindAKeyExportedByASibling() {
@@ -122,9 +133,12 @@
});
}
});
- fail("KNOWN ISSUE: Binding to 'private' should conflict with binding to 'public'");
+ fail();
} catch (CreationException expected) {
- assertContains(expected.getMessage(), "Cannot bind String");
+ assertContains(expected.getMessage(),
+ "A binding to java.lang.String was already configured at ",
+ getClass().getName(), ".configurePrivateBindings(PrivateModuleTest.java:",
+ " at " + getClass().getName(), ".configurePrivateBindings(PrivateModuleTest.java:");
}
}
@@ -144,9 +158,9 @@
});
fail("AB was exposed but not bound");
} catch (CreationException expected) {
- assertContains(expected.getMessage(), "Could not expose() at ",
- PrivateModuleTest.class.getName(), ".configurePrivateBindings(PrivateModuleTest.java:",
- Key.get(AB.class).toString(), " must be explicitly bound.");
+ assertContains(expected.getMessage(),
+ "Could not expose() " + AB.class.getName() + ", it must be explicitly bound",
+ ".configurePrivateBindings(PrivateModuleTest.java:");
}
}
@@ -232,21 +246,21 @@
}
});
- assertEquals("boeo", injector.getInstance(
- Key.get(String.class, named("bound outer, exposed outer"))));
- assertEquals("bieb", injector.getInstance(
- Key.get(String.class, named("bound inner, exposed both"))));
+ assertEquals("boeo",
+ injector.getInstance(Key.get(String.class, named("bound outer, exposed outer"))));
+ assertEquals("bieb",
+ injector.getInstance(Key.get(String.class, named("bound inner, exposed both"))));
try {
injector.getInstance(Key.get(String.class, named("bound outer, exposed none")));
fail();
- } catch (ProvisionException expected) {
+ } catch (ConfigurationException expected) {
}
try {
injector.getInstance(Key.get(String.class, named("bound inner, exposed none")));
fail();
- } catch (ProvisionException expected) {
+ } catch (ConfigurationException expected) {
}
}
diff --git a/test/com/google/inject/spi/ElementsTest.java b/test/com/google/inject/spi/ElementsTest.java
index 09b51d1..08bd863 100644
--- a/test/com/google/inject/spi/ElementsTest.java
+++ b/test/com/google/inject/spi/ElementsTest.java
@@ -672,6 +672,14 @@
}
public void testNewPrivateBinder() {
+ final Key<Collection> collection = Key.get(Collection.class, SampleAnnotation.class);
+ final Key<ArrayList> arrayList = Key.get(ArrayList.class);
+ final ImmutableSet<Key<?>> collections = ImmutableSet.<Key<?>>of(arrayList, collection);
+
+ final Key<?> a = Key.get(String.class, Names.named("a"));
+ final Key<?> b = Key.get(String.class, Names.named("b"));
+ final ImmutableSet<Key<?>> ab = ImmutableSet.of(a, b);
+
checkModule(
new AbstractModule() {
protected void configure() {
@@ -683,30 +691,16 @@
PrivateBinder two = binder().withSource("1 ElementsTest.java")
.newPrivateBinder().withSource("2 ElementsTest.java");
two.expose(String.class).annotatedWith(Names.named("a"));
- two.expose(Key.get(String.class, Names.named("b")));
+ two.expose(b);
two.bind(List.class).to(ArrayList.class);
}
},
new FailingElementVisitor() {
- @Override public Void visitPrivateElements(PrivateEnvironment one) {
- assertEquals(ImmutableSet.<Key<?>>of(Key.get(ArrayList.class),
- Key.get(Collection.class, SampleAnnotation.class)), one.getExposedKeys());
+ @Override public Void visitPrivateEnvironment(PrivateEnvironment one) {
+ assertEquals(collections, one.getExposedKeys());
checkElements(one.getElements(),
new FailingElementVisitor() {
- @Override public Void visitExposure(Exposure exposure) {
- assertEquals(Key.get(ArrayList.class), exposure.getKey());
- return null;
- }
- },
- new FailingElementVisitor() {
- @Override public Void visitExposure(Exposure exposure) {
- assertEquals(Key.get(Collection.class, SampleAnnotation.class),
- exposure.getKey());
- return null;
- }
- },
- new FailingElementVisitor() {
@Override public <T> Void visitBinding(Binding<T> binding) {
assertEquals(Key.get(List.class), binding.getKey());
return null;
@@ -718,26 +712,37 @@
},
new FailingElementVisitor() {
- @Override public Void visitPrivateElements(PrivateEnvironment two) {
- assertEquals(ImmutableSet.<Key<?>>of(Key.get(String.class, Names.named("a")),
- Key.get(String.class, Names.named("b"))), two.getExposedKeys());
+ @Override public <T> Void visitBinding(Binding<T> binding) {
+ assertEquals(arrayList, binding.getKey());
+ binding.acceptTargetVisitor(new FailingTargetVisitor<T>() {
+ @Override public Void visitExposed(PrivateEnvironment privateEnvironment) {
+ assertEquals(collections, privateEnvironment.getExposedKeys());
+ return null;
+ }
+ });
+ return null;
+ }
+ },
+
+ new FailingElementVisitor() {
+ @Override public <T> Void visitBinding(Binding<T> binding) {
+ assertEquals(collection, binding.getKey());
+ binding.acceptTargetVisitor(new FailingTargetVisitor<T>() {
+ @Override public Void visitExposed(PrivateEnvironment privateEnvironment) {
+ assertEquals(collections, privateEnvironment.getExposedKeys());
+ return null;
+ }
+ });
+ return null;
+ }
+ },
+
+ new FailingElementVisitor() {
+ @Override public Void visitPrivateEnvironment(PrivateEnvironment two) {
+ assertEquals(ab, two.getExposedKeys());
assertEquals("1 ElementsTest.java", two.getSource());
checkElements(two.getElements(),
new FailingElementVisitor() {
- @Override public Void visitExposure(Exposure exposure) {
- assertEquals("2 ElementsTest.java", exposure.getSource());
- assertEquals(Key.get(String.class, Names.named("a")), exposure.getKey());
- return null;
- }
- },
- new FailingElementVisitor() {
- @Override public Void visitExposure(Exposure exposure) {
- assertEquals("2 ElementsTest.java", exposure.getSource());
- assertEquals(Key.get(String.class, Names.named("b")), exposure.getKey());
- return null;
- }
- },
- new FailingElementVisitor() {
@Override public <T> Void visitBinding(Binding<T> binding) {
assertEquals("2 ElementsTest.java", binding.getSource());
assertEquals(Key.get(List.class), binding.getKey());
@@ -747,6 +752,34 @@
);
return null;
}
+ },
+
+ new FailingElementVisitor() {
+ @Override public <T> Void visitBinding(Binding<T> binding) {
+ assertEquals(a, binding.getKey());
+ assertEquals("2 ElementsTest.java", binding.getSource());
+ binding.acceptTargetVisitor(new FailingTargetVisitor<T>() {
+ @Override public Void visitExposed(PrivateEnvironment privateEnvironment) {
+ assertEquals(ab, privateEnvironment.getExposedKeys());
+ return null;
+ }
+ });
+ return null;
+ }
+ },
+
+ new FailingElementVisitor() {
+ @Override public <T> Void visitBinding(Binding<T> binding) {
+ assertEquals(b, binding.getKey());
+ assertEquals("2 ElementsTest.java", binding.getSource());
+ binding.acceptTargetVisitor(new FailingTargetVisitor<T>() {
+ @Override public Void visitExposed(PrivateEnvironment privateEnvironment) {
+ assertEquals(ab, privateEnvironment.getExposedKeys());
+ return null;
+ }
+ });
+ return null;
+ }
}
);
}