aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.properties2
-rw-r--r--build.xml6
-rw-r--r--extensions/privatemodules/build.properties6
-rw-r--r--extensions/privatemodules/build.xml22
-rw-r--r--extensions/privatemodules/privatemodules.iml15
-rw-r--r--extensions/privatemodules/src/com/google/inject/privatemodules/PrivateModule.java260
-rw-r--r--extensions/privatemodules/test/com/google/inject/privatemodules/AllTests.java31
-rw-r--r--extensions/privatemodules/test/com/google/inject/privatemodules/PrivateModuleTest.java71
-rw-r--r--src/com/google/inject/BindingProcessor.java13
-rw-r--r--test/com/google/inject/ParentInjectorTest.java27
10 files changed, 452 insertions, 1 deletions
diff --git a/build.properties b/build.properties
index ca5f2d9a..6f120595 100644
--- a/build.properties
+++ b/build.properties
@@ -7,6 +7,7 @@ assistedinject.src.dir=extensions/assistedinject/src
commands.src.dir=extensions/commands/src
throwingproviders.src.dir=extensions/throwingproviders/src
multibindings.src.dir=extensions/multibindings/src
+privatemodules.src.dir=extensions/privatemodules/src
build.dir=build
javadoc.packagenames=com.google.inject,com.google.inject.spi,\
com.google.inject.matcher,com.google.inject.servlet,com.google.inject.name,\
@@ -15,6 +16,7 @@ javadoc.packagenames=com.google.inject,com.google.inject.spi,\
com.google.inject.assistedinject,\
com.google.inject.throwingproviders,\
com.google.inject.multibindings,\
+ com.google.inject.privatemodules,\
com.google.inject.commands
test.class=com.google.inject.AllTests
module=com.google.inject
diff --git a/build.xml b/build.xml
index e092ccf9..dcea5ea7 100644
--- a/build.xml
+++ b/build.xml
@@ -35,6 +35,7 @@
<ant antfile="extensions/assistedinject/build.xml" target="distjars" inheritAll="false"/>
<ant antfile="extensions/throwingproviders/build.xml" target="distjars" inheritAll="false"/>
<ant antfile="extensions/multibindings/build.xml" target="distjars" inheritAll="false"/>
+ <ant antfile="extensions/privatemodules/build.xml" target="distjars" inheritAll="false"/>
<ant antfile="extensions/commands/build.xml" target="distjars" inheritAll="false"/>
<copy toDir="${build.dir}/dist">
@@ -56,6 +57,9 @@
<fileset dir="extensions/multibindings/build" includes="*.jar"/>
</copy>
<copy toDir="${build.dir}/dist">
+ <fileset dir="extensions/privatemodules/build" includes="*.jar"/>
+ </copy>
+ <copy toDir="${build.dir}/dist">
<fileset dir="extensions/commands/build" includes="*.jar"/>
</copy>
@@ -115,6 +119,7 @@
<pathelement location="${assistedinject.src.dir}"/>
<pathelement location="${throwingproviders.src.dir}"/>
<pathelement location="${multibindings.src.dir}"/>
+ <pathelement location="${privatemodules.src.dir}"/>
<pathelement location="${commands.src.dir}"/>
</sourcepath>
<classpath refid="compile.classpath"/>
@@ -136,6 +141,7 @@
<ant dir="extensions/assistedinject" antfile="build.xml" target="clean"/>
<ant dir="extensions/throwingproviders" antfile="build.xml" target="clean"/>
<ant dir="extensions/multibindings" antfile="build.xml" target="clean"/>
+ <ant dir="extensions/privatemodules" antfile="build.xml" target="clean"/>
<ant dir="extensions/commands" antfile="build.xml" target="clean"/>
</target>
diff --git a/extensions/privatemodules/build.properties b/extensions/privatemodules/build.properties
new file mode 100644
index 00000000..677b88f5
--- /dev/null
+++ b/extensions/privatemodules/build.properties
@@ -0,0 +1,6 @@
+lib.dir=../../lib
+src.dir=src
+test.dir=test
+build.dir=build
+test.class=com.google.inject.privatemodules.AllTests
+module=com.google.inject.privatemodules
diff --git a/extensions/privatemodules/build.xml b/extensions/privatemodules/build.xml
new file mode 100644
index 00000000..99c76209
--- /dev/null
+++ b/extensions/privatemodules/build.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+
+<project name="guice-privatemodules" basedir="." default="jar">
+
+ <import file="../../common.xml"/>
+
+ <path id="compile.classpath">
+ <fileset dir="${lib.dir}" includes="*.jar"/>
+ <fileset dir="${lib.dir}/build" includes="*.jar"/>
+ <fileset dir="../../build/dist" includes="*.jar"/>
+ </path>
+
+ <target name="jar" depends="compile, manifest"
+ description="Build jar.">
+ <mkdir dir="${build.dir}"/>
+ <jar destfile="${build.dir}/${ant.project.name}-${version}.jar"
+ manifest="${build.dir}/META-INF/MANIFEST.MF">
+ <fileset dir="${build.dir}/classes"/>
+ </jar>
+ </target>
+
+</project>
diff --git a/extensions/privatemodules/privatemodules.iml b/extensions/privatemodules/privatemodules.iml
new file mode 100644
index 00000000..bc42261f
--- /dev/null
+++ b/extensions/privatemodules/privatemodules.iml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="guice" />
+ <orderEntryProperties />
+ </component>
+</module>
+
diff --git a/extensions/privatemodules/src/com/google/inject/privatemodules/PrivateModule.java b/extensions/privatemodules/src/com/google/inject/privatemodules/PrivateModule.java
new file mode 100644
index 00000000..e6660b1f
--- /dev/null
+++ b/extensions/privatemodules/src/com/google/inject/privatemodules/PrivateModule.java
@@ -0,0 +1,260 @@
+/**
+ * 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.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.Scopes;
+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.SourceProvider;
+import com.google.inject.internal.UniqueAnnotations;
+import com.google.inject.matcher.Matcher;
+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.Set;
+import org.aopalliance.intercept.MethodInterceptor;
+
+/**
+ * @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 is ready. */
+ 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;
+
+ public final synchronized void configure(Binder binder) {
+ // when exposes is null, we're being run for the public injector
+ if (exposes == null) {
+ configurePublicBindings(binder);
+
+ // otherwise we're being run for the private injector
+ } else {
+ checkState(this.privateBinder == null, "Re-entry is not allowed.");
+ this.privateBinder = binder.skipSources(PrivateModule.class);
+ try {
+ configurePrivateBindings();
+
+ for (Expose<?> expose : exposes) {
+ expose.initPrivateProvider(binder);
+ }
+ } finally {
+ this.privateBinder = null;
+ }
+ }
+ }
+
+ private void configurePublicBindings(Binder publicBinder) {
+ exposes = Sets.newLinkedHashSet();
+ Key<Ready> readyKey = Key.get(Ready.class, UniqueAnnotations.create());
+ readyProvider = publicBinder.getProvider(readyKey);
+ try {
+ // gather elements and exposes from the private module by being re-entrant on configure()
+ final Module privateModule = new ModuleWriter().create(Elements.getElements(this));
+ for (Expose<?> expose : exposes) {
+ expose.configure(publicBinder);
+ }
+
+ // create the private injector while the public injector is injecting its members
+ publicBinder.bind(readyKey).toProvider(new Provider<Ready>() {
+ @Inject Injector publicInjector;
+ public Ready get() {
+ // this is necessary so the providers from getProvider() will work
+ publicInjector.createChildInjector(privateModule);
+ return new Ready();
+ }
+ }).in(Scopes.SINGLETON);
+
+ } finally {
+ readyProvider = null;
+ exposes = null;
+ }
+ }
+
+ private static class Ready {}
+
+ public abstract void configurePrivateBindings();
+
+ protected <T> void expose(Key<T> key) {
+ checkState(exposes != null, "Cannot expose %s, private module is not ready");
+ exposes.add(new Expose<T>(sourceProvider.get(), readyProvider, key));
+ }
+
+ protected <T> ExposedKeyBuilder expose(Class<T> type) {
+ checkState(exposes != null, "Cannot expose %s, private module is not ready");
+ Expose<T> expose = new Expose<T>(sourceProvider.get(), readyProvider, Key.get(type));
+ exposes.add(expose);
+ return expose;
+ }
+
+ protected <T> ExposedKeyBuilder expose(TypeLiteral<T> type) {
+ checkState(exposes != null, "Cannot expose %s, private module is not ready");
+ Expose<T> expose = new Expose<T>(sourceProvider.get(), readyProvider, Key.get(type));
+ exposes.add(expose);
+ return expose;
+ }
+
+ public interface ExposedKeyBuilder {
+ void annotatedWith(Class<? extends Annotation> annotationType);
+ void annotatedWith(Annotation annotation);
+ }
+
+ /**
+ * A binding from the private injector exposed 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 Expose 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.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();
+ }
+ }
+
+ // everything below is copied from AbstractModule
+
+ protected Binder binder() {
+ return privateBinder;
+ }
+
+ protected void bindScope(Class<? extends Annotation> scopeAnnotation, Scope scope) {
+ privateBinder.bindScope(scopeAnnotation, scope);
+ }
+
+ protected <T> LinkedBindingBuilder<T> bind(Key<T> key) {
+ return privateBinder.bind(key);
+ }
+
+ protected <T> AnnotatedBindingBuilder<T> bind(TypeLiteral<T> typeLiteral) {
+ return privateBinder.bind(typeLiteral);
+ }
+
+ protected <T> AnnotatedBindingBuilder<T> bind(Class<T> clazz) {
+ return privateBinder.bind(clazz);
+ }
+
+ protected AnnotatedConstantBindingBuilder bindConstant() {
+ return privateBinder.bindConstant();
+ }
+
+ protected void install(Module module) {
+ privateBinder.install(module);
+ }
+
+ protected void addError(String message, Object... arguments) {
+ privateBinder.addError(message, arguments);
+ }
+
+ protected void addError(Throwable t) {
+ privateBinder.addError(t);
+ }
+
+ protected void addError(Message message) {
+ privateBinder.addError(message);
+ }
+
+ protected void requestInjection(Object... objects) {
+ privateBinder.requestInjection(objects);
+ }
+
+ protected void requestStaticInjection(Class<?>... types) {
+ privateBinder.requestStaticInjection(types);
+ }
+
+ protected void bindInterceptor(Matcher<? super Class<?>> classMatcher,
+ Matcher<? super Method> methodMatcher, MethodInterceptor... interceptors) {
+ privateBinder.bindInterceptor(classMatcher, methodMatcher, interceptors);
+ }
+
+ protected void requireBinding(Key<?> key) {
+ privateBinder.getProvider(key);
+ }
+
+ protected void requireBinding(Class<?> type) {
+ privateBinder.getProvider(type);
+ }
+
+ protected <T> Provider<T> getProvider(Key<T> key) {
+ return privateBinder.getProvider(key);
+ }
+
+ protected <T> Provider<T> getProvider(Class<T> type) {
+ return privateBinder.getProvider(type);
+ }
+
+ protected void convertToTypes(Matcher<? super TypeLiteral<?>> typeMatcher,
+ TypeConverter converter) {
+ privateBinder.convertToTypes(typeMatcher, converter);
+ }
+
+ protected 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
new file mode 100644
index 00000000..2d947e81
--- /dev/null
+++ b/extensions/privatemodules/test/com/google/inject/privatemodules/AllTests.java
@@ -0,0 +1,31 @@
+/**
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public class AllTests {
+ public static Test suite() {
+ TestSuite suite = new TestSuite();
+ suite.addTestSuite(PrivateModuleTest.class);
+ return suite;
+ }
+}
diff --git a/extensions/privatemodules/test/com/google/inject/privatemodules/PrivateModuleTest.java b/extensions/privatemodules/test/com/google/inject/privatemodules/PrivateModuleTest.java
new file mode 100644
index 00000000..913f7a82
--- /dev/null
+++ b/extensions/privatemodules/test/com/google/inject/privatemodules/PrivateModuleTest.java
@@ -0,0 +1,71 @@
+/**
+ * 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 com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.name.Named;
+import static com.google.inject.name.Names.named;
+import junit.framework.TestCase;
+
+/**
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public class PrivateModuleTest extends TestCase {
+
+ public void testBasicUsage() {
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ protected void configure() {
+ bind(String.class).annotatedWith(named("a")).toInstance("public");
+
+ install(new PrivateModule() {
+ public void configurePrivateBindings() {
+ bind(String.class).annotatedWith(named("b")).toInstance("i");
+
+ bind(AB.class).annotatedWith(named("one")).to(AB.class);
+ expose(AB.class).annotatedWith(named("one"));
+ }
+ });
+
+ install(new PrivateModule() {
+ public void configurePrivateBindings() {
+ bind(String.class).annotatedWith(named("b")).toInstance("ii");
+
+ bind(AB.class).annotatedWith(named("two")).to(AB.class);
+ expose(AB.class).annotatedWith(named("two"));
+ }
+ });
+ }
+ });
+
+ AB ab1 = injector.getInstance(Key.get(AB.class, named("one")));
+ assertEquals("public", ab1.a);
+ assertEquals("i", ab1.b);
+
+ AB ab2 = injector.getInstance(Key.get(AB.class, named("two")));
+ assertEquals("public", ab2.a);
+ assertEquals("ii", ab2.b);
+ }
+
+ static class AB {
+ @Inject @Named("a") String a;
+ @Inject @Named("b") String b;
+ }
+}
diff --git a/src/com/google/inject/BindingProcessor.java b/src/com/google/inject/BindingProcessor.java
index a9f03860..18d1617a 100644
--- a/src/com/google/inject/BindingProcessor.java
+++ b/src/com/google/inject/BindingProcessor.java
@@ -23,6 +23,7 @@ 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 java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
@@ -38,6 +39,14 @@ import java.util.Set;
*/
class BindingProcessor extends AbstractProcessor {
+ private BindingTargetVisitor<Object, Object> GET_BINDING_PROVIDER
+ = new DefaultBindingTargetVisitor<Object, Object>() {
+ public Object visitProvider(
+ Provider<?> provider, Set<InjectionPoint> injectionPoints) {
+ return provider;
+ }
+ };
+
private static final BindingScopingVisitor<LoadStrategy> LOAD_STRATEGY_VISITOR
= new BindingScopingVisitor<LoadStrategy>() {
public LoadStrategy visitEagerSingleton() {
@@ -247,7 +256,9 @@ class BindingProcessor extends AbstractProcessor {
}
Binding<?> original = state.getExplicitBinding(key);
- if (original != null) {
+ if (original != null && !"com.google.inject.privatemodules.PrivateModule$Expose"
+ .equals(original.acceptTargetVisitor(GET_BINDING_PROVIDER).getClass().getName())) {
+ // the hard-coded class name is certainly lame, but it avoids an even lamer dependency...
errors.bindingAlreadySet(key, original.getSource());
return;
}
diff --git a/test/com/google/inject/ParentInjectorTest.java b/test/com/google/inject/ParentInjectorTest.java
index 953549b2..f9aeaab4 100644
--- a/test/com/google/inject/ParentInjectorTest.java
+++ b/test/com/google/inject/ParentInjectorTest.java
@@ -152,6 +152,33 @@ public class ParentInjectorTest extends TestCase {
assertSame(e.injector, parent);
}
+ public void testSeveralLayersOfHierarchy() {
+ Injector top = Guice.createInjector(bindsA);
+ Injector left = top.createChildInjector();
+ Injector leftLeft = left.createChildInjector(bindsD);
+ Injector right = top.createChildInjector(bindsD);
+
+ assertSame(leftLeft, leftLeft.getInstance(D.class).injector);
+ assertSame(right, right.getInstance(D.class).injector);
+ assertSame(top, leftLeft.getInstance(E.class).injector);
+ assertSame(top.getInstance(A.class), leftLeft.getInstance(A.class));
+
+ Injector leftRight = left.createChildInjector(bindsD);
+ assertSame(leftRight, leftRight.getInstance(D.class).injector);
+
+ try {
+ top.getInstance(D.class);
+ fail();
+ } catch (ProvisionException expected) {
+ }
+
+ try {
+ left.getInstance(D.class);
+ fail();
+ } catch (ProvisionException expected) {
+ }
+ }
+
@Singleton
static class A {}