diff options
-rw-r--r-- | build.properties | 2 | ||||
-rw-r--r-- | build.xml | 6 | ||||
-rw-r--r-- | extensions/privatemodules/build.properties | 6 | ||||
-rw-r--r-- | extensions/privatemodules/build.xml | 22 | ||||
-rw-r--r-- | extensions/privatemodules/privatemodules.iml | 15 | ||||
-rw-r--r-- | extensions/privatemodules/src/com/google/inject/privatemodules/PrivateModule.java | 260 | ||||
-rw-r--r-- | extensions/privatemodules/test/com/google/inject/privatemodules/AllTests.java | 31 | ||||
-rw-r--r-- | extensions/privatemodules/test/com/google/inject/privatemodules/PrivateModuleTest.java | 71 | ||||
-rw-r--r-- | src/com/google/inject/BindingProcessor.java | 13 | ||||
-rw-r--r-- | test/com/google/inject/ParentInjectorTest.java | 27 |
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 @@ -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 {} |