Home | History | Annotate | Download | only in multibindings
      1 /**
      2  * Copyright (C) 2015 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.inject.multibindings;
     18 
     19 import static com.google.inject.Asserts.assertContains;
     20 import static com.google.inject.name.Names.named;
     21 import static java.lang.annotation.RetentionPolicy.RUNTIME;
     22 
     23 import com.google.common.base.Optional;
     24 import com.google.common.collect.ImmutableMap;
     25 import com.google.common.collect.ImmutableSet;
     26 import com.google.inject.AbstractModule;
     27 import com.google.inject.CreationException;
     28 import com.google.inject.Guice;
     29 import com.google.inject.Injector;
     30 import com.google.inject.Key;
     31 import com.google.inject.Module;
     32 import com.google.inject.multibindings.ProvidesIntoOptional.Type;
     33 import com.google.inject.name.Named;
     34 
     35 import junit.framework.TestCase;
     36 
     37 import java.lang.annotation.Retention;
     38 import java.lang.reflect.Field;
     39 import java.util.Map;
     40 import java.util.Set;
     41 
     42 /**
     43  * Tests the various @ProvidesInto annotations.
     44  *
     45  * @author sameb (at) google.com (Sam Berlin)
     46  */
     47 public class ProvidesIntoTest extends TestCase {
     48 
     49   public void testAnnotation() throws Exception {
     50     Injector injector = Guice.createInjector(MultibindingsScanner.asModule(), new AbstractModule() {
     51       @Override protected void configure() {}
     52 
     53       @ProvidesIntoSet
     54       @Named("foo")
     55       String setFoo() { return "foo"; }
     56 
     57       @ProvidesIntoSet
     58       @Named("foo")
     59       String setFoo2() { return "foo2"; }
     60 
     61       @ProvidesIntoSet
     62       @Named("bar")
     63       String setBar() { return "bar"; }
     64 
     65       @ProvidesIntoSet
     66       @Named("bar")
     67       String setBar2() { return "bar2"; }
     68 
     69       @ProvidesIntoSet
     70       String setNoAnnotation() { return "na"; }
     71 
     72       @ProvidesIntoSet
     73       String setNoAnnotation2() { return "na2"; }
     74 
     75       @ProvidesIntoMap
     76       @StringMapKey("fooKey")
     77       @Named("foo")
     78       String mapFoo() { return "foo"; }
     79 
     80       @ProvidesIntoMap
     81       @StringMapKey("foo2Key")
     82       @Named("foo")
     83       String mapFoo2() { return "foo2"; }
     84 
     85       @ProvidesIntoMap
     86       @ClassMapKey(String.class)
     87       @Named("bar")
     88       String mapBar() { return "bar"; }
     89 
     90       @ProvidesIntoMap
     91       @ClassMapKey(Number.class)
     92       @Named("bar")
     93       String mapBar2() { return "bar2"; }
     94 
     95       @ProvidesIntoMap
     96       @TestEnumKey(TestEnum.A)
     97       String mapNoAnnotation() { return "na"; }
     98 
     99       @ProvidesIntoMap
    100       @TestEnumKey(TestEnum.B)
    101       String mapNoAnnotation2() { return "na2"; }
    102 
    103       @ProvidesIntoMap
    104       @WrappedKey(number = 1)
    105       Number wrapped1() { return 11; }
    106 
    107       @ProvidesIntoMap
    108       @WrappedKey(number = 2)
    109       Number wrapped2() { return 22; }
    110 
    111       @ProvidesIntoOptional(ProvidesIntoOptional.Type.DEFAULT)
    112       @Named("foo")
    113       String optionalDefaultFoo() { return "foo"; }
    114 
    115       @ProvidesIntoOptional(ProvidesIntoOptional.Type.ACTUAL)
    116       @Named("foo")
    117       String optionalActualFoo() { return "foo2"; }
    118 
    119       @ProvidesIntoOptional(ProvidesIntoOptional.Type.DEFAULT)
    120       @Named("bar")
    121       String optionalDefaultBar() { return "bar"; }
    122 
    123       @ProvidesIntoOptional(ProvidesIntoOptional.Type.ACTUAL)
    124       String optionalActualBar() { return "na2"; }
    125     });
    126 
    127     Set<String> fooSet = injector.getInstance(new Key<Set<String>>(named("foo")) {});
    128     assertEquals(ImmutableSet.of("foo", "foo2"), fooSet);
    129 
    130     Set<String> barSet = injector.getInstance(new Key<Set<String>>(named("bar")) {});
    131     assertEquals(ImmutableSet.of("bar", "bar2"), barSet);
    132 
    133     Set<String> noAnnotationSet = injector.getInstance(new Key<Set<String>>() {});
    134     assertEquals(ImmutableSet.of("na", "na2"), noAnnotationSet);
    135 
    136     Map<String, String> fooMap =
    137         injector.getInstance(new Key<Map<String, String>>(named("foo")) {});
    138     assertEquals(ImmutableMap.of("fooKey", "foo", "foo2Key", "foo2"), fooMap);
    139 
    140     Map<Class<?>, String> barMap =
    141         injector.getInstance(new Key<Map<Class<?>, String>>(named("bar")) {});
    142     assertEquals(ImmutableMap.of(String.class, "bar", Number.class, "bar2"), barMap);
    143 
    144     Map<TestEnum, String> noAnnotationMap =
    145         injector.getInstance(new Key<Map<TestEnum, String>>() {});
    146     assertEquals(ImmutableMap.of(TestEnum.A, "na", TestEnum.B, "na2"), noAnnotationMap);
    147 
    148     Map<WrappedKey, Number> wrappedMap =
    149         injector.getInstance(new Key<Map<WrappedKey, Number>>() {});
    150     assertEquals(ImmutableMap.of(wrappedKeyFor(1), 11, wrappedKeyFor(2), 22), wrappedMap);
    151 
    152     Optional<String> fooOptional =
    153         injector.getInstance(new Key<Optional<String>>(named("foo")) {});
    154     assertEquals("foo2", fooOptional.get());
    155 
    156     Optional<String> barOptional =
    157         injector.getInstance(new Key<Optional<String>>(named("bar")) {});
    158     assertEquals("bar", barOptional.get());
    159 
    160     Optional<String> noAnnotationOptional =
    161         injector.getInstance(new Key<Optional<String>>() {});
    162     assertEquals("na2", noAnnotationOptional.get());
    163   }
    164 
    165   enum TestEnum {
    166     A, B
    167   }
    168 
    169   @MapKey(unwrapValue = true)
    170   @Retention(RUNTIME)
    171   @interface TestEnumKey {
    172     TestEnum value();
    173   }
    174 
    175   @MapKey(unwrapValue = false)
    176   @Retention(RUNTIME)
    177   @interface WrappedKey {
    178     int number();
    179   }
    180 
    181   @SuppressWarnings("unused") @WrappedKey(number=1) private static Object wrappedKey1Holder;
    182   @SuppressWarnings("unused") @WrappedKey(number=2) private static Object wrappedKey2Holder;
    183   WrappedKey wrappedKeyFor(int number) throws Exception {
    184     Field field;
    185     switch (number) {
    186       case 1:
    187         field = ProvidesIntoTest.class.getDeclaredField("wrappedKey1Holder");
    188         break;
    189       case 2:
    190         field = ProvidesIntoTest.class.getDeclaredField("wrappedKey2Holder");
    191         break;
    192       default:
    193         throw new IllegalArgumentException("only 1 or 2 supported");
    194     }
    195     return field.getAnnotation(WrappedKey.class);
    196   }
    197 
    198   public void testDoubleScannerIsIgnored() {
    199     Injector injector = Guice.createInjector(
    200         MultibindingsScanner.asModule(),
    201         MultibindingsScanner.asModule(),
    202         new AbstractModule() {
    203           @Override protected void configure() {}
    204           @ProvidesIntoSet String provideFoo() { return "foo"; }
    205         }
    206     );
    207     assertEquals(ImmutableSet.of("foo"), injector.getInstance(new Key<Set<String>>() {}));
    208   }
    209 
    210   @MapKey(unwrapValue = true)
    211   @Retention(RUNTIME)
    212   @interface ArrayUnwrappedKey {
    213     int[] value();
    214   }
    215 
    216   public void testArrayKeys_unwrapValuesTrue() {
    217     Module m = new AbstractModule() {
    218       @Override protected void configure() {}
    219       @ProvidesIntoMap @ArrayUnwrappedKey({1, 2}) String provideFoo() { return "foo"; }
    220     };
    221     try {
    222       Guice.createInjector(MultibindingsScanner.asModule(), m);
    223       fail();
    224     } catch (CreationException ce) {
    225       assertEquals(1, ce.getErrorMessages().size());
    226       assertContains(ce.getMessage(),
    227           "Array types are not allowed in a MapKey with unwrapValue=true: "
    228               + ArrayUnwrappedKey.class.getName(),
    229           "at " + m.getClass().getName() + ".provideFoo(");
    230     }
    231   }
    232 
    233   @MapKey(unwrapValue = false)
    234   @Retention(RUNTIME)
    235   @interface ArrayWrappedKey {
    236     int[] number();
    237   }
    238 
    239   @SuppressWarnings("unused") @ArrayWrappedKey(number={1, 2}) private static Object arrayWrappedKeyHolder12;
    240   @SuppressWarnings("unused") @ArrayWrappedKey(number={3, 4}) private static Object arrayWrappedKeyHolder34;
    241   ArrayWrappedKey arrayWrappedKeyFor(int number) throws Exception {
    242     Field field;
    243     switch (number) {
    244       case 12:
    245         field = ProvidesIntoTest.class.getDeclaredField("arrayWrappedKeyHolder12");
    246         break;
    247       case 34:
    248         field = ProvidesIntoTest.class.getDeclaredField("arrayWrappedKeyHolder34");
    249         break;
    250       default:
    251         throw new IllegalArgumentException("only 1 or 2 supported");
    252     }
    253     return field.getAnnotation(ArrayWrappedKey.class);
    254   }
    255 
    256   public void testArrayKeys_unwrapValuesFalse() throws Exception {
    257     Module m = new AbstractModule() {
    258       @Override protected void configure() {}
    259       @ProvidesIntoMap @ArrayWrappedKey(number = {1, 2}) String provideFoo() { return "foo"; }
    260       @ProvidesIntoMap @ArrayWrappedKey(number = {3, 4}) String provideBar() { return "bar"; }
    261     };
    262     Injector injector = Guice.createInjector(MultibindingsScanner.asModule(), m);
    263     Map<ArrayWrappedKey, String> map =
    264         injector.getInstance(new Key<Map<ArrayWrappedKey, String>>() {});
    265     ArrayWrappedKey key12 = arrayWrappedKeyFor(12);
    266     ArrayWrappedKey key34 = arrayWrappedKeyFor(34);
    267     assertEquals("foo", map.get(key12));
    268     assertEquals("bar", map.get(key34));
    269     assertEquals(2, map.size());
    270   }
    271 
    272   public void testProvidesIntoSetWithMapKey() {
    273     Module m = new AbstractModule() {
    274       @Override protected void configure() {}
    275       @ProvidesIntoSet @TestEnumKey(TestEnum.A) String provideFoo() { return "foo"; }
    276     };
    277     try {
    278       Guice.createInjector(MultibindingsScanner.asModule(), m);
    279       fail();
    280     } catch (CreationException ce) {
    281       assertEquals(1, ce.getErrorMessages().size());
    282       assertContains(ce.getMessage(), "Found a MapKey annotation on non map binding at "
    283           + m.getClass().getName() + ".provideFoo");
    284     }
    285   }
    286 
    287   public void testProvidesIntoOptionalWithMapKey() {
    288     Module m = new AbstractModule() {
    289       @Override protected void configure() {}
    290 
    291       @ProvidesIntoOptional(Type.ACTUAL)
    292       @TestEnumKey(TestEnum.A)
    293       String provideFoo() {
    294         return "foo";
    295       }
    296     };
    297     try {
    298       Guice.createInjector(MultibindingsScanner.asModule(), m);
    299       fail();
    300     } catch (CreationException ce) {
    301       assertEquals(1, ce.getErrorMessages().size());
    302       assertContains(ce.getMessage(), "Found a MapKey annotation on non map binding at "
    303           + m.getClass().getName() + ".provideFoo");
    304     }
    305   }
    306 
    307   public void testProvidesIntoMapWithoutMapKey() {
    308     Module m = new AbstractModule() {
    309       @Override protected void configure() {}
    310       @ProvidesIntoMap String provideFoo() { return "foo"; }
    311     };
    312     try {
    313       Guice.createInjector(MultibindingsScanner.asModule(), m);
    314       fail();
    315     } catch (CreationException ce) {
    316       assertEquals(1, ce.getErrorMessages().size());
    317       assertContains(ce.getMessage(), "No MapKey found for map binding at "
    318           + m.getClass().getName() + ".provideFoo");
    319     }
    320   }
    321 
    322   @MapKey(unwrapValue = true)
    323   @Retention(RUNTIME)
    324   @interface TestEnumKey2 {
    325     TestEnum value();
    326   }
    327 
    328   public void testMoreThanOneMapKeyAnnotation() {
    329     Module m = new AbstractModule() {
    330       @Override protected void configure() {}
    331 
    332       @ProvidesIntoMap
    333       @TestEnumKey(TestEnum.A)
    334       @TestEnumKey2(TestEnum.B)
    335       String provideFoo() {
    336         return "foo";
    337       }
    338     };
    339     try {
    340       Guice.createInjector(MultibindingsScanner.asModule(), m);
    341       fail();
    342     } catch (CreationException ce) {
    343       assertEquals(1, ce.getErrorMessages().size());
    344       assertContains(ce.getMessage(), "Found more than one MapKey annotations on "
    345           + m.getClass().getName() + ".provideFoo");
    346     }
    347   }
    348 
    349   @MapKey(unwrapValue = true)
    350   @Retention(RUNTIME)
    351   @interface MissingValueMethod {
    352   }
    353 
    354   public void testMapKeyMissingValueMethod() {
    355     Module m = new AbstractModule() {
    356       @Override protected void configure() {}
    357 
    358       @ProvidesIntoMap
    359       @MissingValueMethod
    360       String provideFoo() {
    361         return "foo";
    362       }
    363     };
    364     try {
    365       Guice.createInjector(MultibindingsScanner.asModule(), m);
    366       fail();
    367     } catch (CreationException ce) {
    368       assertEquals(1, ce.getErrorMessages().size());
    369       assertContains(ce.getMessage(), "No 'value' method in MapKey with unwrapValue=true: "
    370           + MissingValueMethod.class.getName());
    371     }
    372   }
    373 }
    374