• Home
  • 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