/**
* 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;
import static com.google.inject.Asserts.assertContains;
import static com.google.inject.Guice.createInjector;
import static com.google.inject.name.Names.named;
import com.google.inject.internal.util.Objects;
import com.google.inject.name.Named;
import com.google.inject.util.Modules;
import static java.lang.annotation.ElementType.TYPE;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
import java.util.Date;
import java.util.concurrent.atomic.AtomicReference;
import junit.framework.TestCase;
/**
* @author sberlin@gmail.com (Sam Berlin)
*/
public class OverrideModuleTest extends TestCase {
private static final Key<String> key2 = Key.get(String.class, named("2"));
private static final Key<String> key3 = Key.get(String.class, named("3"));
private static final Module EMPTY_MODULE = new Module() {
public void configure(Binder binder) {}
};
public void testOverride() {
Injector injector = createInjector(Modules.override(newModule("A")).with(newModule("B")));
assertEquals("B", injector.getInstance(String.class));
}
public void testOverrideMultiple() {
Module module = Modules.override(newModule("A"), newModule(1), newModule(0.5f))
.with(newModule("B"), newModule(2), newModule(1.5d));
Injector injector = createInjector(module);
assertEquals("B", injector.getInstance(String.class));
assertEquals(2, injector.getInstance(Integer.class).intValue());
assertEquals(0.5f, injector.getInstance(Float.class));
assertEquals(1.5d, injector.getInstance(Double.class));
}
public void testOverrideUnmatchedTolerated() {
Injector injector = createInjector(Modules.override(EMPTY_MODULE).with(newModule("B")));
assertEquals("B", injector.getInstance(String.class));
}
public void testOverrideConstant() {
Module original = new AbstractModule() {
@Override protected void configure() {
bindConstant().annotatedWith(named("Test")).to("A");
}
};
Module replacements = new AbstractModule() {
@Override protected void configure() {
bindConstant().annotatedWith(named("Test")).to("B");
}
};
Injector injector = createInjector(Modules.override(original).with(replacements));
assertEquals("B", injector.getInstance(Key.get(String.class, named("Test"))));
}
public void testGetProviderInModule() {
Module original = new AbstractModule() {
@Override protected void configure() {
bind(String.class).toInstance("A");
bind(key2).toProvider(getProvider(String.class));
}
};
Injector injector = createInjector(Modules.override(original).with(EMPTY_MODULE));
assertEquals("A", injector.getInstance(String.class));
assertEquals("A", injector.getInstance(key2));
}
public void testOverrideWhatGetProviderProvided() {
Module original = new AbstractModule() {
@Override protected void configure() {
bind(String.class).toInstance("A");
bind(key2).toProvider(getProvider(String.class));
}
};
Module replacements = newModule("B");
Injector injector = createInjector(Modules.override(original).with(replacements));
assertEquals("B", injector.getInstance(String.class));
assertEquals("B", injector.getInstance(key2));
}
public void testOverrideUsingOriginalsGetProvider() {
Module original = new AbstractModule() {
@Override protected void configure() {
bind(String.class).toInstance("A");
bind(key2).toInstance("B");
}
};
Module replacements = new AbstractModule() {
@Override protected void configure() {
bind(String.class).toProvider(getProvider(key2));
}
};
Injector injector = createInjector(Modules.override(original).with(replacements));
assertEquals("B", injector.getInstance(String.class));
assertEquals("B", injector.getInstance(key2));
}
public void testOverrideOfOverride() {
Module original = new AbstractModule() {
@Override protected void configure() {
bind(String.class).toInstance("A1");
bind(key2).toInstance("A2");
bind(key3).toInstance("A3");
}
};
Module replacements1 = new AbstractModule() {
@Override protected void configure() {
bind(String.class).toInstance("B1");
bind(key2).toInstance("B2");
}
};
Module overrides = Modules.override(original).with(replacements1);
Module replacements2 = new AbstractModule() {
@Override protected void configure() {
bind(String.class).toInstance("C1");
bind(key3).toInstance("C3");
}
};
Injector injector = createInjector(Modules.override(overrides).with(replacements2));
assertEquals("C1", injector.getInstance(String.class));
assertEquals("B2", injector.getInstance(key2));
assertEquals("C3", injector.getInstance(key3));
}
public void testOverridesTwiceFails() {
Module original = newModule("A");
Module replacements = new AbstractModule() {
@Override protected void configure() {
bind(String.class).toInstance("B");
bind(String.class).toInstance("C");
}
};
Module module = Modules.override(original).with(replacements);
try {
createInjector(module);
fail();
} catch (CreationException expected) {
assertContains(expected.getMessage(), "A binding to java.lang.String "
+ "was already configured at " + replacements.getClass().getName(),
"at " + replacements.getClass().getName());
}
}
public void testOverridesDoesntFixTwiceBoundInOriginal() {
Module original = new AbstractModule() {
@Override protected void configure() {
bind(String.class).toInstance("A");
bind(String.class).toInstance("B");
}
};
Module replacements = new AbstractModule() {
@Override protected void configure() {
bind(String.class).toInstance("C");
}
};
Module module = Modules.override(original).with(replacements);
try {
createInjector(module);
fail();
} catch (CreationException expected) {
assertContains(expected.getMessage(), "1) A binding to java.lang.String "
+ "was already configured at " + replacements.getClass().getName(),
"at " + original.getClass().getName());
}
}
public void testStandardScopeAnnotation() {
final SingleUseScope scope = new SingleUseScope();
Module module = new AbstractModule() {
protected void configure() {
bindScope(TestScopeAnnotation.class, scope);
bind(String.class).in(TestScopeAnnotation.class);
}
};
assertFalse(scope.used);
Guice.createInjector(module);
assertTrue(scope.used);
}
public void testOverrideUntargettedBinding() {
Module original = new AbstractModule() {
@Override protected void configure() {
bind(Date.class);
}
};
Module replacements = new AbstractModule() {
@Override protected void configure() {
bind(Date.class).toInstance(new Date(0));
}
};
Injector injector = createInjector(Modules.override(original).with(replacements));
assertEquals(0, injector.getInstance(Date.class).getTime());
}
public void testOverrideScopeAnnotation() {
final Scope scope = new Scope() {
public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped) {
throw new AssertionError("Should not be called");
}
};
final SingleUseScope replacementScope = new SingleUseScope();
Module original = new AbstractModule() {
@Override protected void configure() {
bindScope(TestScopeAnnotation.class, scope);
bind(Date.class).in(TestScopeAnnotation.class);
}
};
Module replacements = new AbstractModule() {
@Override protected void configure() {
bindScope(TestScopeAnnotation.class, replacementScope);
}
};
Injector injector = createInjector(Modules.override(original).with(replacements));
injector.getInstance(Date.class);
assertTrue(replacementScope.used);
}
public void testFailsIfOverridenScopeInstanceHasBeenUsed() {
final Scope scope = new Scope() {
public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped) {
return unscoped;
}
@Override public String toString() {
return "ORIGINAL SCOPE";
}
};
Module original = new AbstractModule() {
@Override protected void configure() {
bindScope(TestScopeAnnotation.class, scope);
bind(Date.class).in(scope);
}
};
Module replacements = new AbstractModule() {
@Override protected void configure() {
bindScope(TestScopeAnnotation.class, new SingleUseScope());
}
};
try {
createInjector(Modules.override(original).with(replacements));
fail("Exception expected");
} catch (CreationException e) {
assertContains(e.getMessage(),
"1) The scope for @TestScopeAnnotation is bound directly and cannot be overridden.",
"at ", getClass().getName(), ".configure(");
}
}
public void testOverrideIsLazy() {
final AtomicReference<String> value = new AtomicReference<String>("A");
Module overridden = Modules.override(new AbstractModule() {
protected void configure() {
bind(String.class).annotatedWith(named("original")).toInstance(value.get());
}
}).with(new AbstractModule() {
protected void configure() {
bind(String.class).annotatedWith(named("override")).toInstance(value.get());
}
});
// the value.get() call should be deferred until Guice.createInjector
value.set("B");
Injector injector = Guice.createInjector(overridden);
assertEquals("B", injector.getInstance(Key.get(String.class, named("original"))));
assertEquals("B", injector.getInstance(Key.get(String.class, named("override"))));
}
public void testOverridePrivateModuleOverPrivateModule() {
Module exposes5and6 = new AbstractModule() {
protected void configure() {
install(new PrivateModule() {
protected void configure() {
bind(Integer.class).toInstance(5);
expose(Integer.class);
bind(Character.class).toInstance('E');
}
});
install(new PrivateModule() {
protected void configure() {
bind(Long.class).toInstance(6L);
expose(Long.class);
bind(Character.class).toInstance('F');
}
});
}
};
AbstractModule exposes15 = new AbstractModule() {
protected void configure() {
install(new PrivateModule() {
protected void configure() {
bind(Integer.class).toInstance(15);
expose(Integer.class);
bind(Character.class).toInstance('G');
}
});
install(new PrivateModule() {
protected void configure() {
bind(Character.class).toInstance('H');
}
});
}
};
// override forwards
Injector injector = Guice.createInjector(Modules.override(exposes5and6).with(exposes15));
assertEquals(15, injector.getInstance(Integer.class).intValue());
assertEquals(6L, injector.getInstance(Long.class).longValue());
// and in reverse order
Injector reverse = Guice.createInjector(Modules.override(exposes15).with(exposes5and6));
assertEquals(5, reverse.getInstance(Integer.class).intValue());
assertEquals(6L, reverse.getInstance(Long.class).longValue());
}
public void testOverrideModuleAndPrivateModule() {
Module exposes5 = new PrivateModule() {
protected void configure() {
bind(Integer.class).toInstance(5);
expose(Integer.class);
}
};
Module binds15 = new AbstractModule() {
protected void configure() {
bind(Integer.class).toInstance(15);
}
};
Injector injector = Guice.createInjector(Modules.override(exposes5).with(binds15));
assertEquals(15, injector.getInstance(Integer.class).intValue());
Injector reverse = Guice.createInjector(Modules.override(binds15).with(exposes5));
assertEquals(5, reverse.getInstance(Integer.class).intValue());
}
public void testOverrideDeepExpose() {
final AtomicReference<Provider<Character>> charAProvider
= new AtomicReference<Provider<Character>>();
Module exposes5 = new PrivateModule() {
protected void configure() {
install(new PrivateModule() {
protected void configure() {
bind(Integer.class).toInstance(5);
expose(Integer.class);
charAProvider.set(getProvider(Character.class));
bind(Character.class).toInstance('A');
}
});
expose(Integer.class);
}
};
Injector injector = Guice.createInjector(Modules.override(exposes5).with(EMPTY_MODULE));
assertEquals(5, injector.getInstance(Integer.class).intValue());
assertEquals('A', charAProvider.getAndSet(null).get().charValue());
injector = Guice.createInjector(Modules.override(EMPTY_MODULE).with(exposes5));
assertEquals(5, injector.getInstance(Integer.class).intValue());
assertEquals('A', charAProvider.getAndSet(null).get().charValue());
final AtomicReference<Provider<Character>> charBProvider
= new AtomicReference<Provider<Character>>();
Module binds15 = new AbstractModule() {
protected void configure() {
bind(Integer.class).toInstance(15);
install(new PrivateModule() {
protected void configure() {
charBProvider.set(getProvider(Character.class));
bind(Character.class).toInstance('B');
}
});
}
};
injector = Guice.createInjector(Modules.override(binds15).with(exposes5));
assertEquals(5, injector.getInstance(Integer.class).intValue());
assertEquals('A', charAProvider.getAndSet(null).get().charValue());
assertEquals('B', charBProvider.getAndSet(null).get().charValue());
injector = Guice.createInjector(Modules.override(exposes5).with(binds15));
assertEquals(15, injector.getInstance(Integer.class).intValue());
assertEquals('A', charAProvider.getAndSet(null).get().charValue());
assertEquals('B', charBProvider.getAndSet(null).get().charValue());
}
@Retention(RUNTIME)
@Target(TYPE)
@ScopeAnnotation
private static @interface TestScopeAnnotation {}
private static class SingleUseScope implements Scope {
boolean used = false;
public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped) {
assertFalse(used);
used = true;
return unscoped;
}
}
private static <T> Module newModule(final T bound) {
return new AbstractModule() {
@Override protected void configure() {
@SuppressWarnings("unchecked")
Class<T> type = (Class<T>) bound.getClass();
bind(type).toInstance(bound);
}
};
}
private static final String RESULT = "RESULT";
private static final String PRIVATE_INPUT = "PRIVATE_INPUT";
private static final String OVERRIDDEN_INPUT = "FOO";
private static final String OVERRIDDEN_RESULT = "Size: 3";
private static final Key<String> RESULT_KEY = Key.get(String.class, named(RESULT));
private static final Key<String> INPUT_KEY = Key.get(String.class, named(PRIVATE_INPUT));
public void testExposedBindingOverride() throws Exception {
Injector inj = Guice.createInjector(
Modules.override(new ExampleModule()).with(
new AbstractModule() {
@Override protected void configure() {
bind(RESULT_KEY).toInstance(OVERRIDDEN_RESULT);
}
}));
assertEquals(inj.getInstance(RESULT_KEY), OVERRIDDEN_RESULT);
}
public void testPrivateBindingOverride() throws Exception {
Injector inj = Guice.createInjector(
Modules.override(new ExampleModule()).with(
new AbstractModule() {
@Override protected void configure() {
bind(INPUT_KEY).toInstance(OVERRIDDEN_INPUT);
}
}));
assertEquals(inj.getInstance(RESULT_KEY), OVERRIDDEN_RESULT);
}
public static class ExampleModule extends PrivateModule {
@Provides @Exposed @Named(RESULT)
public String provideResult(@Named(PRIVATE_INPUT) String input) {
return "Size: " + input.length();
}
@Provides @Named(PRIVATE_INPUT)
public String provideInput() {
return "Hello World";
}
@Override protected void configure() {
}
}
public void testEqualsNotCalledByDefaultOnInstance() {
final HashEqualsTester a = new HashEqualsTester();
a.throwOnEquals = true;
Guice.createInjector(Modules.override(new AbstractModule() {
@Override
protected void configure() {
bind(String.class);
bind(HashEqualsTester.class).toInstance(a);
}
}).with());
}
public void testEqualsNotCalledByDefaultOnProvider() {
final HashEqualsTester a = new HashEqualsTester();
a.throwOnEquals = true;
Guice.createInjector(Modules.override(new AbstractModule() {
@Override
protected void configure() {
bind(String.class);
bind(Object.class).toProvider(a);
}
}).with());
}
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(Modules.override(new AbstractModule() {
@Override
protected void configure() {
bind(String.class);
bind(HashEqualsTester.class).toInstance(a);
bind(HashEqualsTester.class).toInstance(b);
}
}).with());
}
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(Modules.override(new AbstractModule() {
@Override
protected void configure() {
bind(String.class);
bind(Object.class).toProvider(a);
bind(Object.class).toProvider(b);
}
}).with());
}
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();
}
}
}