blob: 62c7a58bac00b4616222bf99018981aac7f44ddb [file] [log] [blame]
/**
* Copyright (C) 2015 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.multibindings;
import static com.google.inject.Asserts.assertContains;
import static com.google.inject.name.Names.named;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.inject.AbstractModule;
import com.google.inject.CreationException;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.multibindings.ProvidesIntoOptional.Type;
import com.google.inject.name.Named;
import junit.framework.TestCase;
import java.lang.annotation.Retention;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Set;
/**
* Tests the various @ProvidesInto annotations.
*
* @author sameb@google.com (Sam Berlin)
*/
public class ProvidesIntoTest extends TestCase {
public void testAnnotation() throws Exception {
Injector injector = Guice.createInjector(MultibindingsScanner.asModule(), new AbstractModule() {
@Override protected void configure() {}
@ProvidesIntoSet
@Named("foo")
String setFoo() { return "foo"; }
@ProvidesIntoSet
@Named("foo")
String setFoo2() { return "foo2"; }
@ProvidesIntoSet
@Named("bar")
String setBar() { return "bar"; }
@ProvidesIntoSet
@Named("bar")
String setBar2() { return "bar2"; }
@ProvidesIntoSet
String setNoAnnotation() { return "na"; }
@ProvidesIntoSet
String setNoAnnotation2() { return "na2"; }
@ProvidesIntoMap
@StringMapKey("fooKey")
@Named("foo")
String mapFoo() { return "foo"; }
@ProvidesIntoMap
@StringMapKey("foo2Key")
@Named("foo")
String mapFoo2() { return "foo2"; }
@ProvidesIntoMap
@ClassMapKey(String.class)
@Named("bar")
String mapBar() { return "bar"; }
@ProvidesIntoMap
@ClassMapKey(Number.class)
@Named("bar")
String mapBar2() { return "bar2"; }
@ProvidesIntoMap
@TestEnumKey(TestEnum.A)
String mapNoAnnotation() { return "na"; }
@ProvidesIntoMap
@TestEnumKey(TestEnum.B)
String mapNoAnnotation2() { return "na2"; }
@ProvidesIntoMap
@WrappedKey(number = 1)
Number wrapped1() { return 11; }
@ProvidesIntoMap
@WrappedKey(number = 2)
Number wrapped2() { return 22; }
@ProvidesIntoOptional(ProvidesIntoOptional.Type.DEFAULT)
@Named("foo")
String optionalDefaultFoo() { return "foo"; }
@ProvidesIntoOptional(ProvidesIntoOptional.Type.ACTUAL)
@Named("foo")
String optionalActualFoo() { return "foo2"; }
@ProvidesIntoOptional(ProvidesIntoOptional.Type.DEFAULT)
@Named("bar")
String optionalDefaultBar() { return "bar"; }
@ProvidesIntoOptional(ProvidesIntoOptional.Type.ACTUAL)
String optionalActualBar() { return "na2"; }
});
Set<String> fooSet = injector.getInstance(new Key<Set<String>>(named("foo")) {});
assertEquals(ImmutableSet.of("foo", "foo2"), fooSet);
Set<String> barSet = injector.getInstance(new Key<Set<String>>(named("bar")) {});
assertEquals(ImmutableSet.of("bar", "bar2"), barSet);
Set<String> noAnnotationSet = injector.getInstance(new Key<Set<String>>() {});
assertEquals(ImmutableSet.of("na", "na2"), noAnnotationSet);
Map<String, String> fooMap =
injector.getInstance(new Key<Map<String, String>>(named("foo")) {});
assertEquals(ImmutableMap.of("fooKey", "foo", "foo2Key", "foo2"), fooMap);
Map<Class<?>, String> barMap =
injector.getInstance(new Key<Map<Class<?>, String>>(named("bar")) {});
assertEquals(ImmutableMap.of(String.class, "bar", Number.class, "bar2"), barMap);
Map<TestEnum, String> noAnnotationMap =
injector.getInstance(new Key<Map<TestEnum, String>>() {});
assertEquals(ImmutableMap.of(TestEnum.A, "na", TestEnum.B, "na2"), noAnnotationMap);
Map<WrappedKey, Number> wrappedMap =
injector.getInstance(new Key<Map<WrappedKey, Number>>() {});
assertEquals(ImmutableMap.of(wrappedKeyFor(1), 11, wrappedKeyFor(2), 22), wrappedMap);
Optional<String> fooOptional =
injector.getInstance(new Key<Optional<String>>(named("foo")) {});
assertEquals("foo2", fooOptional.get());
Optional<String> barOptional =
injector.getInstance(new Key<Optional<String>>(named("bar")) {});
assertEquals("bar", barOptional.get());
Optional<String> noAnnotationOptional =
injector.getInstance(new Key<Optional<String>>() {});
assertEquals("na2", noAnnotationOptional.get());
}
enum TestEnum {
A, B
}
@MapKey(unwrapValue = true)
@Retention(RUNTIME)
@interface TestEnumKey {
TestEnum value();
}
@MapKey(unwrapValue = false)
@Retention(RUNTIME)
@interface WrappedKey {
int number();
}
@SuppressWarnings("unused") @WrappedKey(number=1) private static Object wrappedKey1Holder;
@SuppressWarnings("unused") @WrappedKey(number=2) private static Object wrappedKey2Holder;
WrappedKey wrappedKeyFor(int number) throws Exception {
Field field;
switch (number) {
case 1:
field = ProvidesIntoTest.class.getDeclaredField("wrappedKey1Holder");
break;
case 2:
field = ProvidesIntoTest.class.getDeclaredField("wrappedKey2Holder");
break;
default:
throw new IllegalArgumentException("only 1 or 2 supported");
}
return field.getAnnotation(WrappedKey.class);
}
public void testDoubleScannerIsIgnored() {
Injector injector = Guice.createInjector(
MultibindingsScanner.asModule(),
MultibindingsScanner.asModule(),
new AbstractModule() {
@Override protected void configure() {}
@ProvidesIntoSet String provideFoo() { return "foo"; }
}
);
assertEquals(ImmutableSet.of("foo"), injector.getInstance(new Key<Set<String>>() {}));
}
@MapKey(unwrapValue = true)
@Retention(RUNTIME)
@interface ArrayUnwrappedKey {
int[] value();
}
public void testArrayKeys_unwrapValuesTrue() {
Module m = new AbstractModule() {
@Override protected void configure() {}
@ProvidesIntoMap @ArrayUnwrappedKey({1, 2}) String provideFoo() { return "foo"; }
};
try {
Guice.createInjector(MultibindingsScanner.asModule(), m);
fail();
} catch (CreationException ce) {
assertEquals(1, ce.getErrorMessages().size());
assertContains(ce.getMessage(),
"Array types are not allowed in a MapKey with unwrapValue=true: "
+ ArrayUnwrappedKey.class.getName(),
"at " + m.getClass().getName() + ".provideFoo(");
}
}
@MapKey(unwrapValue = false)
@Retention(RUNTIME)
@interface ArrayWrappedKey {
int[] number();
}
@SuppressWarnings("unused") @ArrayWrappedKey(number={1, 2}) private static Object arrayWrappedKeyHolder12;
@SuppressWarnings("unused") @ArrayWrappedKey(number={3, 4}) private static Object arrayWrappedKeyHolder34;
ArrayWrappedKey arrayWrappedKeyFor(int number) throws Exception {
Field field;
switch (number) {
case 12:
field = ProvidesIntoTest.class.getDeclaredField("arrayWrappedKeyHolder12");
break;
case 34:
field = ProvidesIntoTest.class.getDeclaredField("arrayWrappedKeyHolder34");
break;
default:
throw new IllegalArgumentException("only 1 or 2 supported");
}
return field.getAnnotation(ArrayWrappedKey.class);
}
public void testArrayKeys_unwrapValuesFalse() throws Exception {
Module m = new AbstractModule() {
@Override protected void configure() {}
@ProvidesIntoMap @ArrayWrappedKey(number = {1, 2}) String provideFoo() { return "foo"; }
@ProvidesIntoMap @ArrayWrappedKey(number = {3, 4}) String provideBar() { return "bar"; }
};
Injector injector = Guice.createInjector(MultibindingsScanner.asModule(), m);
Map<ArrayWrappedKey, String> map =
injector.getInstance(new Key<Map<ArrayWrappedKey, String>>() {});
ArrayWrappedKey key12 = arrayWrappedKeyFor(12);
ArrayWrappedKey key34 = arrayWrappedKeyFor(34);
assertEquals("foo", map.get(key12));
assertEquals("bar", map.get(key34));
assertEquals(2, map.size());
}
public void testProvidesIntoSetWithMapKey() {
Module m = new AbstractModule() {
@Override protected void configure() {}
@ProvidesIntoSet @TestEnumKey(TestEnum.A) String provideFoo() { return "foo"; }
};
try {
Guice.createInjector(MultibindingsScanner.asModule(), m);
fail();
} catch (CreationException ce) {
assertEquals(1, ce.getErrorMessages().size());
assertContains(ce.getMessage(), "Found a MapKey annotation on non map binding at "
+ m.getClass().getName() + ".provideFoo");
}
}
public void testProvidesIntoOptionalWithMapKey() {
Module m = new AbstractModule() {
@Override protected void configure() {}
@ProvidesIntoOptional(Type.ACTUAL)
@TestEnumKey(TestEnum.A)
String provideFoo() {
return "foo";
}
};
try {
Guice.createInjector(MultibindingsScanner.asModule(), m);
fail();
} catch (CreationException ce) {
assertEquals(1, ce.getErrorMessages().size());
assertContains(ce.getMessage(), "Found a MapKey annotation on non map binding at "
+ m.getClass().getName() + ".provideFoo");
}
}
public void testProvidesIntoMapWithoutMapKey() {
Module m = new AbstractModule() {
@Override protected void configure() {}
@ProvidesIntoMap String provideFoo() { return "foo"; }
};
try {
Guice.createInjector(MultibindingsScanner.asModule(), m);
fail();
} catch (CreationException ce) {
assertEquals(1, ce.getErrorMessages().size());
assertContains(ce.getMessage(), "No MapKey found for map binding at "
+ m.getClass().getName() + ".provideFoo");
}
}
@MapKey(unwrapValue = true)
@Retention(RUNTIME)
@interface TestEnumKey2 {
TestEnum value();
}
public void testMoreThanOneMapKeyAnnotation() {
Module m = new AbstractModule() {
@Override protected void configure() {}
@ProvidesIntoMap
@TestEnumKey(TestEnum.A)
@TestEnumKey2(TestEnum.B)
String provideFoo() {
return "foo";
}
};
try {
Guice.createInjector(MultibindingsScanner.asModule(), m);
fail();
} catch (CreationException ce) {
assertEquals(1, ce.getErrorMessages().size());
assertContains(ce.getMessage(), "Found more than one MapKey annotations on "
+ m.getClass().getName() + ".provideFoo");
}
}
@MapKey(unwrapValue = true)
@Retention(RUNTIME)
@interface MissingValueMethod {
}
public void testMapKeyMissingValueMethod() {
Module m = new AbstractModule() {
@Override protected void configure() {}
@ProvidesIntoMap
@MissingValueMethod
String provideFoo() {
return "foo";
}
};
try {
Guice.createInjector(MultibindingsScanner.asModule(), m);
fail();
} catch (CreationException ce) {
assertEquals(1, ce.getErrorMessages().size());
assertContains(ce.getMessage(), "No 'value' method in MapKey with unwrapValue=true: "
+ MissingValueMethod.class.getName());
}
}
}