/* * 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.collect.Iterables.getOnlyElement; import static com.google.inject.Asserts.assertContains; import static com.google.inject.Asserts.getDeclaringSourcePart; import static com.google.inject.Asserts.isIncludeStackTraceComplete; import static java.lang.annotation.RetentionPolicy.RUNTIME; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.inject.AbstractModule; import com.google.inject.Binding; import com.google.inject.BindingAnnotation; import com.google.inject.Inject; import com.google.inject.Key; import com.google.inject.MembersInjector; import com.google.inject.Module; import com.google.inject.PrivateBinder; import com.google.inject.Provider; import com.google.inject.Scope; import com.google.inject.Scopes; import com.google.inject.Singleton; 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.ConstantBindingBuilder; import com.google.inject.binder.ScopedBindingBuilder; import com.google.inject.matcher.Matcher; import com.google.inject.matcher.Matchers; import com.google.inject.name.Named; import com.google.inject.name.Names; import com.google.inject.util.Providers; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import junit.framework.TestCase; /** @author jessewilson@google.com (Jesse Wilson) */ public class ElementsTest extends TestCase { // Binder fidelity tests public void testAddMessageErrorCommand() { checkModule( new AbstractModule() { @Override protected void configure() { addError("Message %s %d %s", "A", 5, "C"); } }, new FailingElementVisitor() { @Override public Void visit(Message command) { assertEquals("Message A 5 C", command.getMessage()); assertNull(command.getCause()); assertContains( command.getSources().toString(), ElementsTest.class.getName(), getDeclaringSourcePart(ElementsTest.class)); assertContains(command.getSource(), getDeclaringSourcePart(ElementsTest.class)); return null; } }); } public void testAddThrowableErrorCommand() { checkModule( new AbstractModule() { @Override protected void configure() { addError(new Exception("A")); } }, new FailingElementVisitor() { @Override public Void visit(Message command) { assertEquals("A", command.getCause().getMessage()); assertEquals(command.getMessage(), "An exception was caught and reported. Message: A"); assertContains(command.getSource(), getDeclaringSourcePart(ElementsTest.class)); return null; } }); } public void testErrorsAddedWhenExceptionsAreThrown() { checkModule( new AbstractModule() { @Override protected void configure() { install( new AbstractModule() { @Override protected void configure() { throw new RuntimeException( "Throwing RuntimeException in AbstractModule.configure()."); } }); addError("Code after the exception still gets executed"); } }, new FailingElementVisitor() { @Override public Void visit(Message command) { assertEquals( "Throwing RuntimeException in AbstractModule.configure().", command.getCause().getMessage()); return null; } }, new FailingElementVisitor() { @Override public Void visit(Message command) { assertEquals("Code after the exception still gets executed", command.getMessage()); return null; } }); } private <T> T getInstance(Binding<T> binding) { return binding.acceptTargetVisitor(Elements.<T>getInstanceVisitor()); } public void testBindConstantAnnotations() { checkModule( new AbstractModule() { @Override protected void configure() { bindConstant().annotatedWith(SampleAnnotation.class).to("A"); bindConstant().annotatedWith(Names.named("Bee")).to("B"); } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertTrue(command instanceof InstanceBinding); assertEquals(Key.get(String.class, SampleAnnotation.class), command.getKey()); assertEquals("A", getInstance(command)); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertTrue(command instanceof InstanceBinding); assertEquals(Key.get(String.class, Names.named("Bee")), command.getKey()); assertEquals("B", getInstance(command)); return null; } }); } public void testBindConstantTypes() { checkModule( new AbstractModule() { @Override protected void configure() { bindConstant().annotatedWith(Names.named("String")).to("A"); bindConstant().annotatedWith(Names.named("int")).to(2); bindConstant().annotatedWith(Names.named("long")).to(3L); bindConstant().annotatedWith(Names.named("boolean")).to(false); bindConstant().annotatedWith(Names.named("double")).to(5.0d); bindConstant().annotatedWith(Names.named("float")).to(6.0f); bindConstant().annotatedWith(Names.named("short")).to((short) 7); bindConstant().annotatedWith(Names.named("char")).to('h'); bindConstant().annotatedWith(Names.named("byte")).to((byte) 8); bindConstant().annotatedWith(Names.named("Class")).to(Iterator.class); bindConstant().annotatedWith(Names.named("Enum")).to(CoinSide.TAILS); } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertTrue(command instanceof InstanceBinding); assertEquals(Key.get(String.class, Names.named("String")), command.getKey()); assertEquals("A", getInstance(command)); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertTrue(command instanceof InstanceBinding); assertEquals(Key.get(Integer.class, Names.named("int")), command.getKey()); assertEquals(2, getInstance(command)); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertTrue(command instanceof InstanceBinding); assertEquals(Key.get(Long.class, Names.named("long")), command.getKey()); assertEquals(3L, getInstance(command)); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertTrue(command instanceof InstanceBinding); assertEquals(Key.get(Boolean.class, Names.named("boolean")), command.getKey()); assertEquals(false, getInstance(command)); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertTrue(command instanceof InstanceBinding); assertEquals(Key.get(Double.class, Names.named("double")), command.getKey()); assertEquals(5.0d, getInstance(command)); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertTrue(command instanceof InstanceBinding); assertEquals(Key.get(Float.class, Names.named("float")), command.getKey()); assertEquals(6.0f, getInstance(command)); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertTrue(command instanceof InstanceBinding); assertEquals(Key.get(Short.class, Names.named("short")), command.getKey()); assertEquals((short) 7, getInstance(command)); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertTrue(command instanceof InstanceBinding); assertEquals(Key.get(Character.class, Names.named("char")), command.getKey()); assertEquals('h', getInstance(command)); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertTrue(command instanceof InstanceBinding); assertEquals(Key.get(Byte.class, Names.named("byte")), command.getKey()); assertEquals((byte) 8, getInstance(command)); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertTrue(command instanceof InstanceBinding); assertEquals(Key.get(Class.class, Names.named("Class")), command.getKey()); assertEquals(Iterator.class, getInstance(command)); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertTrue(command instanceof InstanceBinding); assertEquals(Key.get(CoinSide.class, Names.named("Enum")), command.getKey()); assertEquals(CoinSide.TAILS, getInstance(command)); return null; } }); } public void testBindKeysNoAnnotations() { FailingElementVisitor keyChecker = new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertEquals(Key.get(String.class), command.getKey()); return null; } }; checkModule( new AbstractModule() { @Override protected void configure() { bind(String.class).toInstance("A"); bind(new TypeLiteral<String>() {}).toInstance("B"); bind(Key.get(String.class)).toInstance("C"); } }, keyChecker, keyChecker, keyChecker); } public void testBindKeysWithAnnotationType() { FailingElementVisitor annotationChecker = new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertEquals(Key.get(String.class, SampleAnnotation.class), command.getKey()); return null; } }; checkModule( new AbstractModule() { @Override protected void configure() { bind(String.class).annotatedWith(SampleAnnotation.class).toInstance("A"); bind(new TypeLiteral<String>() {}) .annotatedWith(SampleAnnotation.class) .toInstance("B"); } }, annotationChecker, annotationChecker); } public void testBindKeysWithAnnotationInstance() { FailingElementVisitor annotationChecker = new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertEquals(Key.get(String.class, Names.named("a")), command.getKey()); return null; } }; checkModule( new AbstractModule() { @Override protected void configure() { bind(String.class).annotatedWith(Names.named("a")).toInstance("B"); bind(new TypeLiteral<String>() {}).annotatedWith(Names.named("a")).toInstance("C"); } }, annotationChecker, annotationChecker); } public void testBindToProvider() { final Provider<String> aProvider = new Provider<String>() { @Override public String get() { return "A"; } }; final javax.inject.Provider<Integer> intJavaxProvider = new javax.inject.Provider<Integer>() { @Override public Integer get() { return 42; } }; final javax.inject.Provider<Double> doubleJavaxProvider = new javax.inject.Provider<Double>() { @javax.inject.Inject String string; @Override public Double get() { return 42.42; } }; checkModule( new AbstractModule() { @Override protected void configure() { bind(String.class).toProvider(aProvider); bind(Integer.class).toProvider(intJavaxProvider); bind(Double.class).toProvider(doubleJavaxProvider); bind(List.class).toProvider(ListProvider.class); bind(Collection.class).toProvider(Key.get(ListProvider.class)); bind(Iterable.class).toProvider(new TypeLiteral<TProvider<List>>() {}); } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertTrue(command instanceof ProviderInstanceBinding); assertEquals(Key.get(String.class), command.getKey()); command.acceptTargetVisitor( new FailingTargetVisitor<T>() { @Override public Void visit(ProviderInstanceBinding<? extends T> binding) { assertSame(aProvider, binding.getUserSuppliedProvider()); assertSame(aProvider, binding.getProviderInstance()); return null; } }); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertTrue(command instanceof ProviderInstanceBinding); assertEquals(Key.get(Integer.class), command.getKey()); command.acceptTargetVisitor( new FailingTargetVisitor<T>() { @Override public Void visit(ProviderInstanceBinding<? extends T> binding) { assertSame(intJavaxProvider, binding.getUserSuppliedProvider()); assertEquals(42, binding.getProviderInstance().get()); // we don't wrap this w/ dependencies if there were none. assertFalse(binding.getProviderInstance() instanceof HasDependencies); return null; } }); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertTrue(command instanceof ProviderInstanceBinding); assertEquals(Key.get(Double.class), command.getKey()); command.acceptTargetVisitor( new FailingTargetVisitor<T>() { @Override public Void visit(ProviderInstanceBinding<? extends T> binding) { assertSame(doubleJavaxProvider, binding.getUserSuppliedProvider()); assertEquals(42.42, binding.getProviderInstance().get()); // we do wrap it with dependencies if there were some. assertTrue(binding.getProviderInstance() instanceof HasDependencies); Set<Dependency<?>> deps = ((HasDependencies) binding.getProviderInstance()).getDependencies(); assertEquals(1, deps.size()); assertEquals( String.class, deps.iterator().next().getKey().getTypeLiteral().getRawType()); return null; } }); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertTrue(command instanceof ProviderKeyBinding); assertEquals(Key.get(List.class), command.getKey()); command.acceptTargetVisitor( new FailingTargetVisitor<T>() { @Override public Void visit(ProviderKeyBinding<? extends T> binding) { assertEquals(Key.get(ListProvider.class), binding.getProviderKey()); return null; } }); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertTrue(command instanceof ProviderKeyBinding); assertEquals(Key.get(Collection.class), command.getKey()); command.acceptTargetVisitor( new FailingTargetVisitor<T>() { @Override public Void visit(ProviderKeyBinding<? extends T> binding) { assertEquals(Key.get(ListProvider.class), binding.getProviderKey()); return null; } }); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertTrue(command instanceof ProviderKeyBinding); assertEquals(Key.get(Iterable.class), command.getKey()); command.acceptTargetVisitor( new FailingTargetVisitor<T>() { @Override public Void visit(ProviderKeyBinding<? extends T> binding) { assertEquals(new Key<TProvider<List>>() {}, binding.getProviderKey()); return null; } }); return null; } }); } public void testBindToLinkedBinding() { checkModule( new AbstractModule() { @Override protected void configure() { bind(List.class).to(ArrayList.class); bind(Map.class).to(new TypeLiteral<HashMap<Integer, String>>() {}); bind(Set.class).to(Key.get(TreeSet.class, SampleAnnotation.class)); } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertTrue(command instanceof LinkedKeyBinding); assertEquals(Key.get(List.class), command.getKey()); command.acceptTargetVisitor( new FailingTargetVisitor<T>() { @Override public Void visit(LinkedKeyBinding<? extends T> binding) { assertEquals(Key.get(ArrayList.class), binding.getLinkedKey()); return null; } }); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertTrue(command instanceof LinkedKeyBinding); assertEquals(Key.get(Map.class), command.getKey()); command.acceptTargetVisitor( new FailingTargetVisitor<T>() { @Override public Void visit(LinkedKeyBinding<? extends T> binding) { assertEquals( Key.get(new TypeLiteral<HashMap<Integer, String>>() {}), binding.getLinkedKey()); return null; } }); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertTrue(command instanceof LinkedKeyBinding); assertEquals(Key.get(Set.class), command.getKey()); command.acceptTargetVisitor( new FailingTargetVisitor<T>() { @Override public Void visit(LinkedKeyBinding<? extends T> binding) { assertEquals( Key.get(TreeSet.class, SampleAnnotation.class), binding.getLinkedKey()); return null; } }); return null; } }); } public void testBindToInstance() { checkModule( new AbstractModule() { @Override protected void configure() { bind(String.class).toInstance("A"); } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertTrue(command instanceof InstanceBinding); assertEquals(Key.get(String.class), command.getKey()); assertEquals("A", getInstance(command)); return null; } }); } public void testBindInScopes() { checkModule( new AbstractModule() { @Override protected void configure() { bind(String.class); bind(List.class).to(ArrayList.class).in(Scopes.SINGLETON); bind(Map.class).to(HashMap.class).in(Singleton.class); bind(Set.class).to(TreeSet.class).asEagerSingleton(); } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertEquals(Key.get(String.class), command.getKey()); command.acceptScopingVisitor( new FailingBindingScopingVisitor() { @Override public Void visitNoScoping() { return null; } }); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertEquals(Key.get(List.class), command.getKey()); command.acceptScopingVisitor( new FailingBindingScopingVisitor() { @Override public Void visitScope(Scope scope) { assertEquals(Scopes.SINGLETON, scope); return null; } }); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertEquals(Key.get(Map.class), command.getKey()); command.acceptScopingVisitor( new FailingBindingScopingVisitor() { @Override public Void visitScopeAnnotation(Class<? extends Annotation> annotation) { assertEquals(Singleton.class, annotation); return null; } }); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { assertEquals(Key.get(Set.class), command.getKey()); command.acceptScopingVisitor( new FailingBindingScopingVisitor() { @Override public Void visitEagerSingleton() { return null; } }); return null; } }); } public void testBindToInstanceInScope() { checkModule( new AbstractModule() { @Override protected void configure() { AnnotatedBindingBuilder<String> b = bind(String.class); b.toInstance("A"); b.in(Singleton.class); } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { return null; } }, new FailingElementVisitor() { @Override public Void visit(Message command) { assertEquals( "Setting the scope is not permitted when binding to a single instance.", command.getMessage()); assertNull(command.getCause()); assertContains(command.getSource(), getDeclaringSourcePart(ElementsTest.class)); return null; } }); } public void testBindToInstanceScope() { checkModule( new AbstractModule() { @Override protected void configure() { bind(String.class).toInstance("A"); } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> binding) { assertEquals(Key.get(String.class), binding.getKey()); binding.acceptScopingVisitor( new FailingBindingScopingVisitor() { @Override public Void visitEagerSingleton() { return null; } }); return null; } }); } /*if[AOP]*/ public void testBindIntercepor() { final Matcher<Class> classMatcher = Matchers.subclassesOf(List.class); final Matcher<Object> methodMatcher = Matchers.any(); final org.aopalliance.intercept.MethodInterceptor methodInterceptor = new org.aopalliance.intercept.MethodInterceptor() { @Override public Object invoke(org.aopalliance.intercept.MethodInvocation methodInvocation) { return null; } }; checkModule( new AbstractModule() { @Override protected void configure() { bindInterceptor(classMatcher, methodMatcher, methodInterceptor); } }, new FailingElementVisitor() { @Override public Void visit(InterceptorBinding command) { assertSame(classMatcher, command.getClassMatcher()); assertSame(methodMatcher, command.getMethodMatcher()); assertEquals(Arrays.asList(methodInterceptor), command.getInterceptors()); return null; } }); } /*end[AOP]*/ public void testBindScope() { checkModule( new AbstractModule() { @Override protected void configure() { bindScope(SampleAnnotation.class, Scopes.NO_SCOPE); } }, new FailingElementVisitor() { @Override public Void visit(ScopeBinding command) { assertSame(SampleAnnotation.class, command.getAnnotationType()); assertSame(Scopes.NO_SCOPE, command.getScope()); return null; } }); } public void testBindListener() { final Matcher<Object> typeMatcher = Matchers.only(TypeLiteral.get(String.class)); final TypeListener listener = new TypeListener() { @Override public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) { throw new UnsupportedOperationException(); } }; checkModule( new AbstractModule() { @Override protected void configure() { bindListener(typeMatcher, listener); } }, new FailingElementVisitor() { @Override public Void visit(TypeListenerBinding binding) { assertSame(typeMatcher, binding.getTypeMatcher()); assertSame(listener, binding.getListener()); return null; } }); } public void testConvertToTypes() { final TypeConverter typeConverter = new TypeConverter() { @Override public Object convert(String value, TypeLiteral<?> toType) { return value; } }; checkModule( new AbstractModule() { @Override protected void configure() { convertToTypes(Matchers.any(), typeConverter); } }, new FailingElementVisitor() { @Override public Void visit(TypeConverterBinding command) { assertSame(typeConverter, command.getTypeConverter()); assertSame(Matchers.any(), command.getTypeMatcher()); return null; } }); } public void testGetProvider() { checkModule( new AbstractModule() { @Override protected void configure() { Provider<String> keyGetProvider = getProvider(Key.get(String.class, SampleAnnotation.class)); try { keyGetProvider.get(); fail("Expected IllegalStateException"); } catch (IllegalStateException e) { assertEquals( "This Provider cannot be used until the Injector has been created.", e.getMessage()); } Provider<String> typeGetProvider = getProvider(String.class); try { typeGetProvider.get(); fail("Expected IllegalStateException"); } catch (IllegalStateException e) { assertEquals( "This Provider cannot be used until the Injector has been created.", e.getMessage()); } } }, new FailingElementVisitor() { @Override public <T> Void visit(ProviderLookup<T> command) { assertEquals(Key.get(String.class, SampleAnnotation.class), command.getKey()); assertNull(command.getDelegate()); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(ProviderLookup<T> command) { assertEquals(Key.get(String.class), command.getKey()); assertNull(command.getDelegate()); return null; } }); } public void testElementInitialization() { final AtomicReference<Provider<String>> providerFromBinder = new AtomicReference<Provider<String>>(); final AtomicReference<MembersInjector<String>> membersInjectorFromBinder = new AtomicReference<MembersInjector<String>>(); final AtomicReference<String> lastInjected = new AtomicReference<String>(); final MembersInjector<String> stringInjector = new MembersInjector<String>() { @Override public void injectMembers(String instance) { lastInjected.set(instance); } }; checkModule( new AbstractModule() { @Override protected void configure() { providerFromBinder.set(getProvider(String.class)); membersInjectorFromBinder.set(getMembersInjector(String.class)); } }, new FailingElementVisitor() { @Override public <T> Void visit(ProviderLookup<T> providerLookup) { @SuppressWarnings("unchecked") // we know that T is a String here ProviderLookup<String> stringLookup = (ProviderLookup<String>) providerLookup; stringLookup.initializeDelegate(Providers.of("out")); assertEquals("out", providerFromBinder.get().get()); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(MembersInjectorLookup<T> lookup) { @SuppressWarnings("unchecked") // we know that T is a String here MembersInjectorLookup<String> stringLookup = (MembersInjectorLookup<String>) lookup; stringLookup.initializeDelegate(stringInjector); membersInjectorFromBinder.get().injectMembers("in"); assertEquals("in", lastInjected.get()); return null; } }); } public void testGetMembersInjector() { checkModule( new AbstractModule() { @Override protected void configure() { MembersInjector<A<String>> typeMembersInjector = getMembersInjector(new TypeLiteral<A<String>>() {}); try { typeMembersInjector.injectMembers(new A<String>()); fail("Expected IllegalStateException"); } catch (IllegalStateException e) { assertEquals( "This MembersInjector cannot be used until the Injector has been created.", e.getMessage()); } MembersInjector<String> classMembersInjector = getMembersInjector(String.class); try { classMembersInjector.injectMembers("hello"); fail("Expected IllegalStateException"); } catch (IllegalStateException e) { assertEquals( "This MembersInjector cannot be used until the Injector has been created.", e.getMessage()); } } }, new FailingElementVisitor() { @Override public <T> Void visit(MembersInjectorLookup<T> command) { assertEquals(new TypeLiteral<A<String>>() {}, command.getType()); assertNull(command.getDelegate()); return null; } }, new FailingElementVisitor() { @Override public <T> Void visit(MembersInjectorLookup<T> command) { assertEquals(TypeLiteral.get(String.class), command.getType()); assertNull(command.getDelegate()); return null; } }); } public void testRequestInjection() { final Object firstObject = new Object(); final Object secondObject = new Object(); checkModule( new AbstractModule() { @Override protected void configure() { requestInjection(firstObject); requestInjection(secondObject); } }, new FailingElementVisitor() { @Override public Void visit(InjectionRequest<?> command) { assertEquals(firstObject, command.getInstance()); return null; } }, new FailingElementVisitor() { @Override public Void visit(InjectionRequest<?> command) { assertEquals(secondObject, command.getInstance()); return null; } }); } public void testRequestStaticInjection() { checkModule( new AbstractModule() { @Override protected void configure() { requestStaticInjection(ArrayList.class); } }, new FailingElementVisitor() { @Override public Void visit(StaticInjectionRequest command) { assertEquals(ArrayList.class, command.getType()); return null; } }); } 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() { @Override protected void configure() { PrivateBinder one = binder().newPrivateBinder(); one.expose(ArrayList.class); one.expose(Collection.class).annotatedWith(SampleAnnotation.class); one.bind(List.class).to(ArrayList.class); PrivateBinder two = binder().withSource("1 FooBar").newPrivateBinder().withSource("2 FooBar"); two.expose(String.class).annotatedWith(Names.named("a")); two.expose(b); two.bind(List.class).to(ArrayList.class); } }, new FailingElementVisitor() { @Override public Void visit(PrivateElements one) { assertEquals(collections, one.getExposedKeys()); checkElements( one.getElements(), new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> binding) { assertEquals(Key.get(List.class), binding.getKey()); return null; } }); return null; } }, new ExternalFailureVisitor() { @Override public Void visit(PrivateElements two) { assertEquals(ab, two.getExposedKeys()); assertEquals("1 FooBar", two.getSource().toString()); checkElements( two.getElements(), new ExternalFailureVisitor() { @Override public <T> Void visit(Binding<T> binding) { assertEquals("2 FooBar", binding.getSource().toString()); assertEquals(Key.get(List.class), binding.getKey()); return null; } }); return null; } }); } public void testBindWithMultipleAnnotationsAddsError() { checkModule( new AbstractModule() { @Override protected void configure() { AnnotatedBindingBuilder<String> abb = bind(String.class); abb.annotatedWith(SampleAnnotation.class); abb.annotatedWith(Names.named("A")); } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { return null; } }, new FailingElementVisitor() { @Override public Void visit(Message command) { assertEquals( "More than one annotation is specified for this binding.", command.getMessage()); assertNull(command.getCause()); assertContains(command.getSource(), getDeclaringSourcePart(ElementsTest.class)); return null; } }); } public void testBindWithMultipleTargetsAddsError() { checkModule( new AbstractModule() { @Override protected void configure() { AnnotatedBindingBuilder<String> abb = bind(String.class); abb.toInstance("A"); abb.toInstance("B"); } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { return null; } }, new FailingElementVisitor() { @Override public Void visit(Message command) { assertEquals("Implementation is set more than once.", command.getMessage()); assertNull(command.getCause()); assertContains(command.getSource(), getDeclaringSourcePart(ElementsTest.class)); return null; } }); } public void testBindWithMultipleScopesAddsError() { checkModule( new AbstractModule() { @Override protected void configure() { ScopedBindingBuilder sbb = bind(List.class).to(ArrayList.class); sbb.in(Scopes.NO_SCOPE); sbb.asEagerSingleton(); } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { return null; } }, new FailingElementVisitor() { @Override public Void visit(Message command) { assertEquals("Scope is set more than once.", command.getMessage()); assertNull(command.getCause()); assertContains(command.getSource(), getDeclaringSourcePart(ElementsTest.class)); return null; } }); } public void testBindConstantWithMultipleAnnotationsAddsError() { checkModule( new AbstractModule() { @Override protected void configure() { AnnotatedConstantBindingBuilder cbb = bindConstant(); cbb.annotatedWith(SampleAnnotation.class).to("A"); cbb.annotatedWith(Names.named("A")); } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { return null; } }, new FailingElementVisitor() { @Override public Void visit(Message command) { assertEquals( "More than one annotation is specified for this binding.", command.getMessage()); assertNull(command.getCause()); assertContains(command.getSource(), getDeclaringSourcePart(ElementsTest.class)); return null; } }); } public void testBindConstantWithMultipleTargetsAddsError() { checkModule( new AbstractModule() { @Override protected void configure() { ConstantBindingBuilder cbb = bindConstant().annotatedWith(SampleAnnotation.class); cbb.to("A"); cbb.to("B"); } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> command) { return null; } }, new FailingElementVisitor() { @Override public Void visit(Message message) { assertEquals("Constant value is set more than once.", message.getMessage()); assertNull(message.getCause()); assertContains(message.getSource(), getDeclaringSourcePart(ElementsTest.class)); return null; } }); } public void testBindToConstructor() throws NoSuchMethodException, NoSuchFieldException { final Constructor<A> aConstructor = A.class.getDeclaredConstructor(); final Constructor<B> bConstructor = B.class.getDeclaredConstructor(Object.class); final Field field = B.class.getDeclaredField("stage"); checkModule( new AbstractModule() { @Override protected void configure() { bind(A.class).toConstructor(aConstructor); bind(B.class) .toConstructor(bConstructor, new TypeLiteral<B<Integer>>() {}) .in(Singleton.class); } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> binding) { assertEquals(new Key<A>() {}, binding.getKey()); return binding.acceptTargetVisitor( new FailingTargetVisitor<T>() { @Override public Void visit(ConstructorBinding<? extends T> constructorBinding) { InjectionPoint injectionPoint = constructorBinding.getConstructor(); assertEquals(aConstructor, injectionPoint.getMember()); assertEquals(new TypeLiteral<A>() {}, injectionPoint.getDeclaringType()); return null; } }); } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> binding) { assertEquals(new Key<B>() {}, binding.getKey()); binding.acceptScopingVisitor( new FailingBindingScopingVisitor() { @Override public Void visitScopeAnnotation(Class<? extends Annotation> annotation) { assertEquals(Singleton.class, annotation); return null; } }); binding.acceptTargetVisitor( new FailingTargetVisitor<T>() { @Override public Void visit(ConstructorBinding<? extends T> constructorBinding) { assertEquals(bConstructor, constructorBinding.getConstructor().getMember()); assertEquals( Key.get(Integer.class), getOnlyElement(constructorBinding.getConstructor().getDependencies()) .getKey()); assertEquals( field, getOnlyElement(constructorBinding.getInjectableMembers()).getMember()); assertEquals(2, constructorBinding.getDependencies().size()); /*if[AOP]*/ assertEquals(ImmutableMap.of(), constructorBinding.getMethodInterceptors()); /*end[AOP]*/ return null; } }); return null; } }); } public void testBindToMalformedConstructor() throws NoSuchMethodException, NoSuchFieldException { final Constructor<C> constructor = C.class.getDeclaredConstructor(Integer.class); checkModule( new AbstractModule() { @Override protected void configure() { bind(C.class).toConstructor(constructor); } }, new FailingElementVisitor() { @Override public <T> Void visit(Binding<T> binding) { assertEquals(Key.get(C.class), binding.getKey()); assertTrue(binding instanceof UntargettedBinding); return null; } }, new ExternalFailureVisitor() { @Override public Void visit(Message message) { assertContains( message.getMessage(), C.class.getName() + ".a has more than one annotation ", Named.class.getName(), SampleAnnotation.class.getName()); return null; } }, new ExternalFailureVisitor() { @Override public Void visit(Message message) { assertContains( message.getMessage(), C.class.getName() + ".<init>() has more than one annotation ", Named.class.getName(), SampleAnnotation.class.getName()); return null; } }); } // Business logic tests public void testModulesAreInstalledAtMostOnce() { final AtomicInteger aConfigureCount = new AtomicInteger(0); final Module a = new AbstractModule() { @Override public void configure() { aConfigureCount.incrementAndGet(); } }; Elements.getElements(a, a); assertEquals(1, aConfigureCount.get()); aConfigureCount.set(0); Module b = new AbstractModule() { @Override protected void configure() { install(a); install(a); } }; Elements.getElements(b); assertEquals(1, aConfigureCount.get()); } /** Ensures the module performs the commands consistent with {@code visitors}. */ protected void checkModule(Module module, ElementVisitor<?>... visitors) { List<Element> elements = Elements.getElements(module); assertEquals(elements.size(), visitors.length); checkElements(elements, visitors); } protected void checkElements(List<Element> elements, ElementVisitor<?>... visitors) { for (int i = 0; i < visitors.length; i++) { ElementVisitor<?> visitor = visitors[i]; Element element = elements.get(i); if (!(element instanceof Message)) { ElementSource source = (ElementSource) element.getSource(); assertFalse(source.getModuleClassNames().isEmpty()); if (isIncludeStackTraceComplete()) { assertTrue(source.getStackTrace().length > 0); } else { assertEquals(0, source.getStackTrace().length); } } if (!(visitor instanceof ExternalFailureVisitor)) { assertContains(element.getSource().toString(), getDeclaringSourcePart(ElementsTest.class)); } element.acceptVisitor(visitor); } } private static class ListProvider implements Provider<List> { @Override public List get() { return new ArrayList(); } } private static class TProvider<T> implements Provider<T> { @Override public T get() { return null; } } /** * By extending this interface rather than FailingElementVisitor, the source of the error doesn't * need to contain the string {@code ElementsTest.java}. */ abstract static class ExternalFailureVisitor extends FailingElementVisitor {} @Retention(RUNTIME) @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD}) @BindingAnnotation public @interface SampleAnnotation {} public enum CoinSide { HEADS, TAILS } static class A<T> { @Inject Stage stage; } static class B<T> { @Inject Stage stage; B(T t) {} } static class C { @Inject @Named("foo") @SampleAnnotation String a; C(@Named("bar") @SampleAnnotation Integer b) {} } }