| /** |
| * Copyright (C) 2007 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 com.google.inject.AbstractModule; |
| import static com.google.inject.Asserts.assertContains; |
| import com.google.inject.Binder; |
| import com.google.inject.BindingAnnotation; |
| 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.Module; |
| import com.google.inject.Provider; |
| import com.google.inject.Provides; |
| import com.google.inject.Singleton; |
| import com.google.inject.internal.util.ImmutableList; |
| import com.google.inject.internal.util.ImmutableSet; |
| import com.google.inject.internal.ProviderMethod; |
| import com.google.inject.internal.ProviderMethodsModule; |
| import com.google.inject.name.Named; |
| import com.google.inject.name.Names; |
| import com.google.inject.util.Types; |
| import java.lang.annotation.ElementType; |
| import java.lang.annotation.Retention; |
| import static java.lang.annotation.RetentionPolicy.RUNTIME; |
| import java.lang.annotation.Target; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicReference; |
| import java.util.logging.Logger; |
| |
| import junit.framework.TestCase; |
| |
| /** |
| * @author crazybob@google.com (Bob Lee) |
| */ |
| public class ProviderMethodsTest extends TestCase implements Module { |
| |
| @SuppressWarnings("unchecked") |
| public void testProviderMethods() { |
| Injector injector = Guice.createInjector(this); |
| |
| Bob bob = injector.getInstance(Bob.class); |
| assertEquals("A Bob", bob.getName()); |
| |
| Bob clone = injector.getInstance(Bob.class); |
| assertEquals("A Bob", clone.getName()); |
| |
| assertNotSame(bob, clone); |
| assertSame(bob.getDaughter(), clone.getDaughter()); |
| |
| Key soleBobKey = Key.get(Bob.class, Sole.class); |
| assertSame( |
| injector.getInstance(soleBobKey), |
| injector.getInstance(soleBobKey) |
| ); |
| } |
| |
| public void configure(Binder binder) {} |
| |
| interface Bob { |
| String getName(); |
| Dagny getDaughter(); |
| } |
| |
| interface Dagny { |
| int getAge(); |
| } |
| |
| @Provides |
| Bob provideBob(final Dagny dagny) { |
| return new Bob() { |
| public String getName() { |
| return "A Bob"; |
| } |
| |
| public Dagny getDaughter() { |
| return dagny; |
| } |
| }; |
| } |
| |
| @Provides |
| @Singleton |
| @Sole |
| Bob provideSoleBob(final Dagny dagny) { |
| return new Bob() { |
| public String getName() { |
| return "Only Bob"; |
| } |
| |
| public Dagny getDaughter() { |
| return dagny; |
| } |
| }; |
| } |
| |
| @Provides |
| @Singleton |
| Dagny provideDagny() { |
| return new Dagny() { |
| public int getAge() { |
| return 1; |
| } |
| }; |
| } |
| |
| @Retention(RUNTIME) |
| @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) |
| @BindingAnnotation |
| @interface Sole {} |
| |
| |
| |
| // We'll have to make getProvider() support circular dependencies before this |
| // will work. |
| // |
| // public void testCircularDependency() { |
| // Injector injector = Guice.createInjector(new Module() { |
| // public void configure(Binder binder) { |
| // binder.install(ProviderMethods.from(ProviderMethodsTest.this)); |
| // } |
| // }); |
| // |
| // Foo foo = injector.getInstance(Foo.class); |
| // assertEquals(5, foo.getI()); |
| // assertEquals(10, foo.getBar().getI()); |
| // assertEquals(5, foo.getBar().getFoo().getI()); |
| // } |
| // |
| // interface Foo { |
| // Bar getBar(); |
| // int getI(); |
| // } |
| // |
| // interface Bar { |
| // Foo getFoo(); |
| // int getI(); |
| // } |
| // |
| // @Provides Foo newFoo(final Bar bar) { |
| // return new Foo() { |
| // |
| // public Bar getBar() { |
| // return bar; |
| // } |
| // |
| // public int getI() { |
| // return 5; |
| // } |
| // }; |
| // } |
| // |
| // @Provides Bar newBar(final Foo foo) { |
| // return new Bar() { |
| // |
| // public Foo getFoo() { |
| // return foo; |
| // } |
| // |
| // public int getI() { |
| // return 10; |
| // } |
| // }; |
| // } |
| |
| |
| public void testMultipleBindingAnnotations() { |
| try { |
| Guice.createInjector(new AbstractModule() { |
| protected void configure() {} |
| |
| @Provides @Named("A") @Blue |
| public String provideString() { |
| return "a"; |
| } |
| }); |
| fail(); |
| } catch (CreationException expected) { |
| assertContains(expected.getMessage(), |
| "more than one annotation annotated with @BindingAnnotation:", "Named", "Blue", |
| "at " + getClass().getName(), ".provideString(ProviderMethodsTest.java:"); |
| } |
| |
| } |
| |
| @Retention(RUNTIME) |
| @BindingAnnotation @interface Blue {} |
| |
| public void testGenericProviderMethods() { |
| Injector injector = Guice.createInjector( |
| new ProvideTs<String>("A", "B") {}, new ProvideTs<Integer>(1, 2) {}); |
| |
| assertEquals("A", injector.getInstance(Key.get(String.class, Names.named("First")))); |
| assertEquals("B", injector.getInstance(Key.get(String.class, Names.named("Second")))); |
| assertEquals(ImmutableSet.of("A", "B"), |
| injector.getInstance(Key.get(Types.setOf(String.class)))); |
| |
| assertEquals(1, injector.getInstance(Key.get(Integer.class, Names.named("First"))).intValue()); |
| assertEquals(2, injector.getInstance(Key.get(Integer.class, Names.named("Second"))).intValue()); |
| assertEquals(ImmutableSet.of(1, 2), |
| injector.getInstance(Key.get(Types.setOf(Integer.class)))); |
| } |
| |
| abstract class ProvideTs<T> extends AbstractModule { |
| final T first; |
| final T second; |
| |
| protected ProvideTs(T first, T second) { |
| this.first = first; |
| this.second = second; |
| } |
| |
| protected void configure() {} |
| |
| @Named("First") @Provides T provideFirst() { |
| return first; |
| } |
| |
| @Named("Second") @Provides T provideSecond() { |
| return second; |
| } |
| |
| @Provides Set<T> provideBoth(@Named("First") T first, @Named("Second") T second) { |
| return ImmutableSet.of(first, second); |
| } |
| } |
| |
| public void testAutomaticProviderMethods() { |
| Injector injector = Guice.createInjector((Module) new AbstractModule() { |
| protected void configure() { } |
| private int next = 1; |
| |
| @Provides @Named("count") |
| public Integer provideCount() { |
| return next++; |
| } |
| }); |
| |
| assertEquals(1, injector.getInstance(Key.get(Integer.class, Names.named("count"))).intValue()); |
| assertEquals(2, injector.getInstance(Key.get(Integer.class, Names.named("count"))).intValue()); |
| assertEquals(3, injector.getInstance(Key.get(Integer.class, Names.named("count"))).intValue()); |
| } |
| |
| /** |
| * If the user installs provider methods for the module manually, that shouldn't cause a double |
| * binding of the provider methods' types. |
| */ |
| public void testAutomaticProviderMethodsDoNotCauseDoubleBinding() { |
| Module installsSelf = new AbstractModule() { |
| protected void configure() { |
| install(this); |
| bind(Integer.class).toInstance(5); |
| } |
| @Provides public String provideString(Integer count) { |
| return "A" + count; |
| } |
| }; |
| |
| Injector injector = Guice.createInjector(installsSelf); |
| assertEquals("A5", injector.getInstance(String.class)); |
| } |
| |
| public void testWildcardProviderMethods() { |
| final List<String> strings = ImmutableList.of("A", "B", "C"); |
| final List<Number> numbers = ImmutableList.<Number>of(1, 2, 3); |
| |
| Injector injector = Guice.createInjector(new AbstractModule() { |
| protected void configure() { |
| @SuppressWarnings("unchecked") |
| Key<List<? super Integer>> listOfSupertypesOfInteger = (Key<List<? super Integer>>) |
| Key.get(Types.listOf(Types.supertypeOf(Integer.class))); |
| bind(listOfSupertypesOfInteger).toInstance(numbers); |
| } |
| @Provides public List<? extends CharSequence> provideCharSequences() { |
| return strings; |
| } |
| @Provides public Class<?> provideType() { |
| return Float.class; |
| } |
| }); |
| |
| assertSame(strings, injector.getInstance(HasWildcardInjection.class).charSequences); |
| assertSame(numbers, injector.getInstance(HasWildcardInjection.class).numbers); |
| assertSame(Float.class, injector.getInstance(HasWildcardInjection.class).type); |
| } |
| |
| static class HasWildcardInjection { |
| @Inject List<? extends CharSequence> charSequences; |
| @Inject List<? super Integer> numbers; |
| @Inject Class<?> type; |
| } |
| |
| public void testProviderMethodDependenciesAreExposed() { |
| Injector injector = Guice.createInjector(new AbstractModule() { |
| protected void configure() { |
| bind(Integer.class).toInstance(50); |
| bindConstant().annotatedWith(Names.named("units")).to("Kg"); |
| } |
| @Provides @Named("weight") String provideWeight(Integer count, @Named("units") String units) { |
| return count + units; |
| } |
| }); |
| |
| ProviderInstanceBinding<?> binding = (ProviderInstanceBinding<?>) injector.getBinding( |
| Key.get(String.class, Names.named("weight"))); |
| assertEquals(ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(Integer.class)), |
| Dependency.get(Key.get(String.class, Names.named("units")))), |
| binding.getDependencies()); |
| } |
| |
| public void testNonModuleProviderMethods() { |
| final Object methodsObject = new Object() { |
| @Provides @Named("foo") String provideFoo() { |
| return "foo-value"; |
| } |
| }; |
| |
| Module module = new AbstractModule() { |
| @Override protected void configure() { |
| install(ProviderMethodsModule.forObject(methodsObject)); |
| } |
| }; |
| |
| Injector injector = Guice.createInjector(module); |
| |
| Key<String> key = Key.get(String.class, Names.named("foo")); |
| assertEquals("foo-value", injector.getInstance(key)); |
| |
| // Test the provider method object itself. This makes sure getInstance works, since GIN uses it |
| List<Element> elements = Elements.getElements(module); |
| assertEquals(1, elements.size()); |
| |
| Element element = elements.get(0); |
| assertTrue(element + " instanceof ProviderInstanceBinding", |
| element instanceof ProviderInstanceBinding); |
| |
| ProviderInstanceBinding binding = (ProviderInstanceBinding) element; |
| Provider provider = binding.getProviderInstance(); |
| assertEquals(ProviderMethod.class, provider.getClass()); |
| assertEquals(methodsObject, ((ProviderMethod) provider).getInstance()); |
| } |
| |
| public void testVoidProviderMethods() { |
| try { |
| Guice.createInjector(new AbstractModule() { |
| protected void configure() {} |
| |
| @Provides void provideFoo() {} |
| }); |
| fail(); |
| } catch (CreationException expected) { |
| assertContains(expected.getMessage(), |
| "1) Provider methods must return a value. Do not return void.", |
| getClass().getName(), ".provideFoo(ProviderMethodsTest.java:"); |
| } |
| } |
| |
| public void testInjectsJustOneLogger() { |
| AtomicReference<Logger> loggerRef = new AtomicReference<Logger>(); |
| Injector injector = Guice.createInjector(new FooModule(loggerRef)); |
| |
| assertNull(loggerRef.get()); |
| injector.getInstance(Integer.class); |
| Logger lastLogger = loggerRef.getAndSet(null); |
| assertNotNull(lastLogger); |
| injector.getInstance(Integer.class); |
| assertSame(lastLogger, loggerRef.get()); |
| |
| assertEquals(FooModule.class.getName() + ".foo", lastLogger.getName()); |
| } |
| |
| private static class FooModule extends AbstractModule { |
| private final AtomicReference<Logger> loggerRef; |
| |
| public FooModule(AtomicReference<Logger> loggerRef) { |
| this.loggerRef = loggerRef; |
| } |
| |
| @Override protected void configure() {} |
| |
| @SuppressWarnings("unused") |
| @Provides Integer foo(Logger logger) { |
| loggerRef.set(logger); |
| return 42; |
| } |
| } |
| } |