/**
* Copyright (C) 2010 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.inject.Asserts.assertContains;
import static com.google.inject.name.Names.named;
import com.google.inject.internal.util.Lists;
import com.google.inject.internal.util.Objects;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.logging.Logger;
import junit.framework.TestCase;
import com.google.inject.spi.Element;
import com.google.inject.spi.Elements;
import com.google.inject.util.Providers;
/**
* A suite of tests for duplicate bindings.
*
* @author sameb@google.com (Sam Berlin)
*/
public class DuplicateBindingsTest extends TestCase {
private FooImpl foo = new FooImpl();
private Provider<Foo> pFoo = Providers.<Foo>of(new FooImpl());
private Class<? extends Provider<? extends Foo>> pclFoo = FooProvider.class;
private Class<? extends Foo> clFoo = FooImpl.class;
private Constructor<FooImpl> cFoo = FooImpl.cxtor();
public void testDuplicateBindingsAreIgnored() {
Injector injector = Guice.createInjector(
new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo),
new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo)
);
List<Key<?>> bindings = Lists.newArrayList(injector.getAllBindings().keySet());
removeBasicBindings(bindings);
// Ensure only one binding existed for each type.
assertTrue(bindings.remove(Key.get(Foo.class, named("instance"))));
assertTrue(bindings.remove(Key.get(Foo.class, named("pInstance"))));
assertTrue(bindings.remove(Key.get(Foo.class, named("pKey"))));
assertTrue(bindings.remove(Key.get(Foo.class, named("linkedKey"))));
assertTrue(bindings.remove(Key.get(FooImpl.class)));
assertTrue(bindings.remove(Key.get(Foo.class, named("constructor"))));
assertTrue(bindings.remove(Key.get(FooProvider.class))); // JIT binding
assertEquals(bindings.toString(), 0, bindings.size());
}
public void testElementsDeduplicate() {
List<Element> elements = Elements.getElements(
new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo),
new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo)
);
assertEquals(12, elements.size());
assertEquals(6, new LinkedHashSet<Element>(elements).size());
}
public void testSameScopeInstanceIgnored() {
Guice.createInjector(
new ScopedModule(Scopes.SINGLETON, foo, pFoo, pclFoo, clFoo, cFoo),
new ScopedModule(Scopes.SINGLETON, foo, pFoo, pclFoo, clFoo, cFoo)
);
Guice.createInjector(
new ScopedModule(Scopes.NO_SCOPE, foo, pFoo, pclFoo, clFoo, cFoo),
new ScopedModule(Scopes.NO_SCOPE, foo, pFoo, pclFoo, clFoo, cFoo)
);
}
public void testSameScopeAnnotationIgnored() {
Guice.createInjector(
new AnnotatedScopeModule(Singleton.class, foo, pFoo, pclFoo, clFoo, cFoo),
new AnnotatedScopeModule(Singleton.class, foo, pFoo, pclFoo, clFoo, cFoo)
);
}
public void testMixedAnnotationAndScopeForSingletonIgnored() {
Guice.createInjector(
new ScopedModule(Scopes.SINGLETON, foo, pFoo, pclFoo, clFoo, cFoo),
new AnnotatedScopeModule(Singleton.class, foo, pFoo, pclFoo, clFoo, cFoo)
);
}
public void testMixedScopeAndUnscopedIgnored() {
Guice.createInjector(
new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo),
new ScopedModule(Scopes.NO_SCOPE, foo, pFoo, pclFoo, clFoo, cFoo)
);
}
public void testMixedScopeFails() {
try {
Guice.createInjector(
new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo),
new ScopedModule(Scopes.SINGLETON, foo, pFoo, pclFoo, clFoo, cFoo)
);
fail("expected exception");
} catch(CreationException ce) {
assertContains(ce.getMessage(),
"A binding to " + Foo.class.getName() + " annotated with " + named("pInstance") + " was already configured at " + SimpleModule.class.getName(),
"at " + ScopedModule.class.getName(),
"A binding to " + Foo.class.getName() + " annotated with " + named("pKey") + " was already configured at " + SimpleModule.class.getName(),
"at " + ScopedModule.class.getName(),
"A binding to " + Foo.class.getName() + " annotated with " + named("linkedKey") + " was already configured at " + SimpleModule.class.getName(),
"at " + ScopedModule.class.getName(),
"A binding to " + FooImpl.class.getName() + " was already configured at " + SimpleModule.class.getName(),
"at " + ScopedModule.class.getName(),
"A binding to " + Foo.class.getName() + " annotated with " + named("constructor") + " was already configured at " + SimpleModule.class.getName(),
"at " + ScopedModule.class.getName());
}
}
@SuppressWarnings("unchecked")
public void testMixedTargetsFails() {
try {
Guice.createInjector(
new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo),
new SimpleModule(new FooImpl(), Providers.<Foo>of(new FooImpl()),
(Class)BarProvider.class, (Class)Bar.class, (Constructor)Bar.cxtor())
);
fail("expected exception");
} catch(CreationException ce) {
assertContains(ce.getMessage(),
"A binding to " + Foo.class.getName() + " annotated with " + named("pInstance") + " was already configured at " + SimpleModule.class.getName(),
"at " + SimpleModule.class.getName(),
"A binding to " + Foo.class.getName() + " annotated with " + named("pKey") + " was already configured at " + SimpleModule.class.getName(),
"at " + SimpleModule.class.getName(),
"A binding to " + Foo.class.getName() + " annotated with " + named("linkedKey") + " was already configured at " + SimpleModule.class.getName(),
"at " + SimpleModule.class.getName(),
"A binding to " + Foo.class.getName() + " annotated with " + named("constructor") + " was already configured at " + SimpleModule.class.getName(),
"at " + SimpleModule.class.getName());
}
}
public void testExceptionInEqualsThrowsCreationException() {
try {
Guice.createInjector(new ThrowingModule(), new ThrowingModule());
fail("expected exception");
} catch(CreationException ce) {
assertContains(ce.getMessage(),
"A binding to " + Foo.class.getName() + " was already configured at " + ThrowingModule.class.getName(),
"and an error was thrown while checking duplicate bindings. Error: java.lang.RuntimeException: Boo!",
"at " + ThrowingModule.class.getName());
}
}
public void testChildInjectorDuplicateParentFail() {
Injector injector = Guice.createInjector(
new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo)
);
try {
injector.createChildInjector(
new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo)
);
fail("expected exception");
} catch(CreationException ce) {
assertContains(ce.getMessage(),
"A binding to " + Foo.class.getName() + " annotated with " + named("pInstance") + " was already configured at " + SimpleModule.class.getName(),
"at " + SimpleModule.class.getName(),
"A binding to " + Foo.class.getName() + " annotated with " + named("pKey") + " was already configured at " + SimpleModule.class.getName(),
"at " + SimpleModule.class.getName(),
"A binding to " + Foo.class.getName() + " annotated with " + named("linkedKey") + " was already configured at " + SimpleModule.class.getName(),
"at " + SimpleModule.class.getName(),
"A binding to " + Foo.class.getName() + " annotated with " + named("constructor") + " was already configured at " + SimpleModule.class.getName(),
"at " + SimpleModule.class.getName());
}
}
public void testDuplicatesSolelyInChildIgnored() {
Injector injector = Guice.createInjector();
injector.createChildInjector(
new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo),
new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo)
);
}
public void testDifferentBindingTypesFail() {
List<Element> elements = Elements.getElements(
new FailedModule(foo, pFoo, pclFoo, clFoo, cFoo)
);
// Make sure every combination of the elements with another element fails.
// This ensures that duplication checks the kind of binding also.
for(Element e1 : elements) {
for(Element e2: elements) {
// if they're the same, this shouldn't fail.
try {
Guice.createInjector(Elements.getModule(Arrays.asList(e1, e2)));
if(e1 != e2) {
fail("must fail!");
}
} catch(CreationException expected) {
if(e1 != e2) {
assertContains(expected.getMessage(),
"A binding to " + Foo.class.getName() + " was already configured at " + FailedModule.class.getName(),
"at " + FailedModule.class.getName());
} else {
throw expected;
}
}
}
}
}
public void testJitBindingsAreCheckedAfterConversions() {
Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(A.class);
bind(A.class).to(RealA.class);
}
});
}
public void testEqualsNotCalledByDefaultOnInstance() {
final HashEqualsTester a = new HashEqualsTester();
a.throwOnEquals = true;
Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(String.class);
bind(HashEqualsTester.class).toInstance(a);
}
});
}
public void testEqualsNotCalledByDefaultOnProvider() {
final HashEqualsTester a = new HashEqualsTester();
a.throwOnEquals = true;
Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(String.class);
bind(Object.class).toProvider(a);
}
});
}
public void testHashcodeNeverCalledOnInstance() {
final HashEqualsTester a = new HashEqualsTester();
a.throwOnHashcode = true;
a.equality = "test";
final HashEqualsTester b = new HashEqualsTester();
b.throwOnHashcode = true;
b.equality = "test";
Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(String.class);
bind(HashEqualsTester.class).toInstance(a);
bind(HashEqualsTester.class).toInstance(b);
}
});
}
public void testHashcodeNeverCalledOnProviderInstance() {
final HashEqualsTester a = new HashEqualsTester();
a.throwOnHashcode = true;
a.equality = "test";
final HashEqualsTester b = new HashEqualsTester();
b.throwOnHashcode = true;
b.equality = "test";
Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(String.class);
bind(Object.class).toProvider(a);
bind(Object.class).toProvider(b);
}
});
}
private static class RealA extends A {}
@ImplementedBy(RealA.class) private static class A {}
private void removeBasicBindings(Collection<Key<?>> bindings) {
bindings.remove(Key.get(Injector.class));
bindings.remove(Key.get(Logger.class));
bindings.remove(Key.get(Stage.class));
}
private static class ThrowingModule extends AbstractModule {
@Override
protected void configure() {
bind(Foo.class).toInstance(new Foo() {
@Override
public boolean equals(Object obj) {
throw new RuntimeException("Boo!");
}
});
}
}
private static abstract class FooModule extends AbstractModule {
protected final FooImpl foo;
protected final Provider<Foo> pFoo;
protected final Class<? extends Provider<? extends Foo>> pclFoo;
protected final Class<? extends Foo> clFoo;
protected final Constructor<FooImpl> cFoo;
FooModule(FooImpl foo, Provider<Foo> pFoo, Class<? extends Provider<? extends Foo>> pclFoo,
Class<? extends Foo> clFoo, Constructor<FooImpl> cFoo) {
this.foo = foo;
this.pFoo = pFoo;
this.pclFoo = pclFoo;
this.clFoo = clFoo;
this.cFoo = cFoo;
}
}
private static class FailedModule extends FooModule {
FailedModule(FooImpl foo, Provider<Foo> pFoo, Class<? extends Provider<? extends Foo>> pclFoo,
Class<? extends Foo> clFoo, Constructor<FooImpl> cFoo) {
super(foo, pFoo, pclFoo, clFoo, cFoo);
}
protected void configure() {
// InstanceBinding
bind(Foo.class).toInstance(foo);
// ProviderInstanceBinding
bind(Foo.class).toProvider(pFoo);
// ProviderKeyBinding
bind(Foo.class).toProvider(pclFoo);
// LinkedKeyBinding
bind(Foo.class).to(clFoo);
// ConstructorBinding
bind(Foo.class).toConstructor(cFoo);
}
}
private static class SimpleModule extends FooModule {
SimpleModule(FooImpl foo, Provider<Foo> pFoo, Class<? extends Provider<? extends Foo>> pclFoo,
Class<? extends Foo> clFoo, Constructor<FooImpl> cFoo) {
super(foo, pFoo, pclFoo, clFoo, cFoo);
}
protected void configure() {
// InstanceBinding
bind(Foo.class).annotatedWith(named("instance")).toInstance(foo);
// ProviderInstanceBinding
bind(Foo.class).annotatedWith(named("pInstance")).toProvider(pFoo);
// ProviderKeyBinding
bind(Foo.class).annotatedWith(named("pKey")).toProvider(pclFoo);
// LinkedKeyBinding
bind(Foo.class).annotatedWith(named("linkedKey")).to(clFoo);
// UntargettedBinding / ConstructorBinding
bind(FooImpl.class);
// ConstructorBinding
bind(Foo.class).annotatedWith(named("constructor")).toConstructor(cFoo);
}
}
private static class ScopedModule extends FooModule {
private final Scope scope;
ScopedModule(Scope scope, FooImpl foo, Provider<Foo> pFoo,
Class<? extends Provider<? extends Foo>> pclFoo, Class<? extends Foo> clFoo,
Constructor<FooImpl> cFoo) {
super(foo, pFoo, pclFoo, clFoo, cFoo);
this.scope = scope;
}
protected void configure() {
// ProviderInstanceBinding
bind(Foo.class).annotatedWith(named("pInstance")).toProvider(pFoo).in(scope);
// ProviderKeyBinding
bind(Foo.class).annotatedWith(named("pKey")).toProvider(pclFoo).in(scope);
// LinkedKeyBinding
bind(Foo.class).annotatedWith(named("linkedKey")).to(clFoo).in(scope);
// UntargettedBinding / ConstructorBinding
bind(FooImpl.class).in(scope);
// ConstructorBinding
bind(Foo.class).annotatedWith(named("constructor")).toConstructor(cFoo).in(scope);
}
}
private static class AnnotatedScopeModule extends FooModule {
private final Class<? extends Annotation> scope;
AnnotatedScopeModule(Class<? extends Annotation> scope, FooImpl foo, Provider<Foo> pFoo,
Class<? extends Provider<? extends Foo>> pclFoo, Class<? extends Foo> clFoo,
Constructor<FooImpl> cFoo) {
super(foo, pFoo, pclFoo, clFoo, cFoo);
this.scope = scope;
}
protected void configure() {
// ProviderInstanceBinding
bind(Foo.class).annotatedWith(named("pInstance")).toProvider(pFoo).in(scope);
// ProviderKeyBinding
bind(Foo.class).annotatedWith(named("pKey")).toProvider(pclFoo).in(scope);
// LinkedKeyBinding
bind(Foo.class).annotatedWith(named("linkedKey")).to(clFoo).in(scope);
// UntargettedBinding / ConstructorBinding
bind(FooImpl.class).in(scope);
// ConstructorBinding
bind(Foo.class).annotatedWith(named("constructor")).toConstructor(cFoo).in(scope);
}
}
private static interface Foo {}
private static class FooImpl implements Foo {
@Inject public FooImpl() {}
private static Constructor<FooImpl> cxtor() {
try {
return FooImpl.class.getConstructor();
} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}
private static class FooProvider implements Provider<Foo> {
public Foo get() {
return new FooImpl();
}
}
private static class Bar implements Foo {
@Inject public Bar() {}
private static Constructor<Bar> cxtor() {
try {
return Bar.class.getConstructor();
} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}
private static class BarProvider implements Provider<Foo> {
public Foo get() {
return new Bar();
}
}
private static class HashEqualsTester implements Provider<Object> {
private String equality;
private boolean throwOnEquals;
private boolean throwOnHashcode;
@Override
public boolean equals(Object obj) {
if (throwOnEquals) {
throw new RuntimeException();
} else if (obj instanceof HashEqualsTester) {
HashEqualsTester o = (HashEqualsTester)obj;
if(o.throwOnEquals) {
throw new RuntimeException();
}
if(equality == null && o.equality == null) {
return this == o;
} else {
return Objects.equal(equality, o.equality);
}
} else {
return false;
}
}
@Override
public int hashCode() {
if(throwOnHashcode) {
throw new RuntimeException();
} else {
return super.hashCode();
}
}
public Object get() {
return new Object();
}
}
}