• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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@google.com (Sam Berlin)
46   */
47  public class ProvidesIntoTest extends TestCase {
48  
testAnnotation()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 {
value()172      TestEnum value();
173    }
174  
175    @MapKey(unwrapValue = false)
176    @Retention(RUNTIME)
177    @interface WrappedKey {
number()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;
wrappedKeyFor(int number)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  
testDoubleScannerIsIgnored()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 {
value()213      int[] value();
214    }
215  
testArrayKeys_unwrapValuesTrue()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 {
number()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;
arrayWrappedKeyFor(int number)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  
testArrayKeys_unwrapValuesFalse()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  
testProvidesIntoSetWithMapKey()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  
testProvidesIntoOptionalWithMapKey()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  
testProvidesIntoMapWithoutMapKey()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 {
value()325      TestEnum value();
326    }
327  
testMoreThanOneMapKeyAnnotation()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  
testMapKeyMissingValueMethod()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