blob: 8d2bad40ed8ef724dd61a5a09ca98c46213f9f48 [file] [log] [blame]
/**
* 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;
}
}
}