xref: /aosp_15_r20/external/guice/core/test/com/google/inject/multibindings/ProvidesIntoTest.java (revision dc5640d1ceac12a29404866b9a53df952a7a6c47)
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 import java.lang.annotation.Retention;
35 import java.lang.reflect.Field;
36 import java.util.Map;
37 import java.util.Set;
38 import junit.framework.TestCase;
39 
40 /**
41  * Tests the various @ProvidesInto annotations.
42  *
43  * @author [email protected] (Sam Berlin)
44  */
45 public class ProvidesIntoTest extends TestCase {
46 
testAnnotation()47   public void testAnnotation() throws Exception {
48     Injector injector =
49         Guice.createInjector(
50             MultibindingsScanner.asModule(),
51             new AbstractModule() {
52 
53               @ProvidesIntoSet
54               @Named("foo")
55               String setFoo() {
56                 return "foo";
57               }
58 
59               @ProvidesIntoSet
60               @Named("foo")
61               String setFoo2() {
62                 return "foo2";
63               }
64 
65               @ProvidesIntoSet
66               @Named("bar")
67               String setBar() {
68                 return "bar";
69               }
70 
71               @ProvidesIntoSet
72               @Named("bar")
73               String setBar2() {
74                 return "bar2";
75               }
76 
77               @ProvidesIntoSet
78               String setNoAnnotation() {
79                 return "na";
80               }
81 
82               @ProvidesIntoSet
83               String setNoAnnotation2() {
84                 return "na2";
85               }
86 
87               @ProvidesIntoMap
88               @StringMapKey("fooKey")
89               @Named("foo")
90               String mapFoo() {
91                 return "foo";
92               }
93 
94               @ProvidesIntoMap
95               @StringMapKey("foo2Key")
96               @Named("foo")
97               String mapFoo2() {
98                 return "foo2";
99               }
100 
101               @ProvidesIntoMap
102               @ClassMapKey(String.class)
103               @Named("bar")
104               String mapBar() {
105                 return "bar";
106               }
107 
108               @ProvidesIntoMap
109               @ClassMapKey(Number.class)
110               @Named("bar")
111               String mapBar2() {
112                 return "bar2";
113               }
114 
115               @ProvidesIntoMap
116               @TestEnumKey(TestEnum.A)
117               String mapNoAnnotation() {
118                 return "na";
119               }
120 
121               @ProvidesIntoMap
122               @TestEnumKey(TestEnum.B)
123               String mapNoAnnotation2() {
124                 return "na2";
125               }
126 
127               @ProvidesIntoMap
128               @WrappedKey(number = 1)
129               Number wrapped1() {
130                 return 11;
131               }
132 
133               @ProvidesIntoMap
134               @WrappedKey(number = 2)
135               Number wrapped2() {
136                 return 22;
137               }
138 
139               @ProvidesIntoOptional(ProvidesIntoOptional.Type.DEFAULT)
140               @Named("foo")
141               String optionalDefaultFoo() {
142                 return "foo";
143               }
144 
145               @ProvidesIntoOptional(ProvidesIntoOptional.Type.ACTUAL)
146               @Named("foo")
147               String optionalActualFoo() {
148                 return "foo2";
149               }
150 
151               @ProvidesIntoOptional(ProvidesIntoOptional.Type.DEFAULT)
152               @Named("bar")
153               String optionalDefaultBar() {
154                 return "bar";
155               }
156 
157               @ProvidesIntoOptional(ProvidesIntoOptional.Type.ACTUAL)
158               String optionalActualBar() {
159                 return "na2";
160               }
161             });
162 
163     Set<String> fooSet = injector.getInstance(new Key<Set<String>>(named("foo")) {});
164     assertEquals(ImmutableSet.of("foo", "foo2"), fooSet);
165 
166     Set<String> barSet = injector.getInstance(new Key<Set<String>>(named("bar")) {});
167     assertEquals(ImmutableSet.of("bar", "bar2"), barSet);
168 
169     Set<String> noAnnotationSet = injector.getInstance(new Key<Set<String>>() {});
170     assertEquals(ImmutableSet.of("na", "na2"), noAnnotationSet);
171 
172     Map<String, String> fooMap =
173         injector.getInstance(new Key<Map<String, String>>(named("foo")) {});
174     assertEquals(ImmutableMap.of("fooKey", "foo", "foo2Key", "foo2"), fooMap);
175 
176     Map<Class<?>, String> barMap =
177         injector.getInstance(new Key<Map<Class<?>, String>>(named("bar")) {});
178     assertEquals(ImmutableMap.of(String.class, "bar", Number.class, "bar2"), barMap);
179 
180     Map<TestEnum, String> noAnnotationMap =
181         injector.getInstance(new Key<Map<TestEnum, String>>() {});
182     assertEquals(ImmutableMap.of(TestEnum.A, "na", TestEnum.B, "na2"), noAnnotationMap);
183 
184     Map<WrappedKey, Number> wrappedMap =
185         injector.getInstance(new Key<Map<WrappedKey, Number>>() {});
186     assertEquals(ImmutableMap.of(wrappedKeyFor(1), 11, wrappedKeyFor(2), 22), wrappedMap);
187 
188     Optional<String> fooOptional = injector.getInstance(new Key<Optional<String>>(named("foo")) {});
189     assertEquals("foo2", fooOptional.get());
190 
191     Optional<String> barOptional = injector.getInstance(new Key<Optional<String>>(named("bar")) {});
192     assertEquals("bar", barOptional.get());
193 
194     Optional<String> noAnnotationOptional = injector.getInstance(new Key<Optional<String>>() {});
195     assertEquals("na2", noAnnotationOptional.get());
196   }
197 
198   enum TestEnum {
199     A,
200     B
201   }
202 
203   @MapKey(unwrapValue = true)
204   @Retention(RUNTIME)
205   @interface TestEnumKey {
value()206     TestEnum value();
207   }
208 
209   @MapKey(unwrapValue = false)
210   @Retention(RUNTIME)
211   @interface WrappedKey {
number()212     int number();
213   }
214 
215   @SuppressWarnings("unused")
216   @WrappedKey(number = 1)
217   private static Object wrappedKey1Holder;
218 
219   @SuppressWarnings("unused")
220   @WrappedKey(number = 2)
221   private static Object wrappedKey2Holder;
222 
wrappedKeyFor(int number)223   WrappedKey wrappedKeyFor(int number) throws Exception {
224     Field field;
225     switch (number) {
226       case 1:
227         field = ProvidesIntoTest.class.getDeclaredField("wrappedKey1Holder");
228         break;
229       case 2:
230         field = ProvidesIntoTest.class.getDeclaredField("wrappedKey2Holder");
231         break;
232       default:
233         throw new IllegalArgumentException("only 1 or 2 supported");
234     }
235     return field.getAnnotation(WrappedKey.class);
236   }
237 
testDoubleScannerIsIgnored()238   public void testDoubleScannerIsIgnored() {
239     Injector injector =
240         Guice.createInjector(
241             MultibindingsScanner.asModule(),
242             MultibindingsScanner.asModule(),
243             new AbstractModule() {
244 
245               @ProvidesIntoSet
246               String provideFoo() {
247                 return "foo";
248               }
249             });
250     assertEquals(ImmutableSet.of("foo"), injector.getInstance(new Key<Set<String>>() {}));
251   }
252 
253   @MapKey(unwrapValue = true)
254   @Retention(RUNTIME)
255   @interface ArrayUnwrappedKey {
value()256     int[] value();
257   }
258 
testArrayKeys_unwrapValuesTrue()259   public void testArrayKeys_unwrapValuesTrue() {
260     Module m =
261         new AbstractModule() {
262 
263           @ProvidesIntoMap
264           @ArrayUnwrappedKey({1, 2})
265           String provideFoo() {
266             return "foo";
267           }
268         };
269     try {
270       Guice.createInjector(MultibindingsScanner.asModule(), m);
271       fail();
272     } catch (CreationException ce) {
273       assertEquals(1, ce.getErrorMessages().size());
274       assertContains(
275           ce.getMessage(),
276           "Array types are not allowed in a MapKey with unwrapValue=true: "
277               + ArrayUnwrappedKey.class.getName(),
278           "at " + m.getClass().getName() + ".provideFoo(");
279     }
280   }
281 
282   @MapKey(unwrapValue = false)
283   @Retention(RUNTIME)
284   @interface ArrayWrappedKey {
number()285     int[] number();
286   }
287 
288   @SuppressWarnings("unused")
289   @ArrayWrappedKey(number = {1, 2})
290   private static Object arrayWrappedKeyHolder12;
291 
292   @SuppressWarnings("unused")
293   @ArrayWrappedKey(number = {3, 4})
294   private static Object arrayWrappedKeyHolder34;
295 
arrayWrappedKeyFor(int number)296   ArrayWrappedKey arrayWrappedKeyFor(int number) throws Exception {
297     Field field;
298     switch (number) {
299       case 12:
300         field = ProvidesIntoTest.class.getDeclaredField("arrayWrappedKeyHolder12");
301         break;
302       case 34:
303         field = ProvidesIntoTest.class.getDeclaredField("arrayWrappedKeyHolder34");
304         break;
305       default:
306         throw new IllegalArgumentException("only 1 or 2 supported");
307     }
308     return field.getAnnotation(ArrayWrappedKey.class);
309   }
310 
testArrayKeys_unwrapValuesFalse()311   public void testArrayKeys_unwrapValuesFalse() throws Exception {
312     Module m =
313         new AbstractModule() {
314 
315           @ProvidesIntoMap
316           @ArrayWrappedKey(number = {1, 2})
317           String provideFoo() {
318             return "foo";
319           }
320 
321           @ProvidesIntoMap
322           @ArrayWrappedKey(number = {3, 4})
323           String provideBar() {
324             return "bar";
325           }
326         };
327     Injector injector = Guice.createInjector(MultibindingsScanner.asModule(), m);
328     Map<ArrayWrappedKey, String> map =
329         injector.getInstance(new Key<Map<ArrayWrappedKey, String>>() {});
330     ArrayWrappedKey key12 = arrayWrappedKeyFor(12);
331     ArrayWrappedKey key34 = arrayWrappedKeyFor(34);
332     assertEquals("foo", map.get(key12));
333     assertEquals("bar", map.get(key34));
334     assertEquals(2, map.size());
335   }
336 
testProvidesIntoSetWithMapKey()337   public void testProvidesIntoSetWithMapKey() {
338     Module m =
339         new AbstractModule() {
340 
341           @ProvidesIntoSet
342           @TestEnumKey(TestEnum.A)
343           String provideFoo() {
344             return "foo";
345           }
346         };
347     try {
348       Guice.createInjector(MultibindingsScanner.asModule(), m);
349       fail();
350     } catch (CreationException ce) {
351       assertEquals(1, ce.getErrorMessages().size());
352       assertContains(
353           ce.getMessage(),
354           "Found a MapKey annotation on non map binding at "
355               + m.getClass().getName()
356               + ".provideFoo");
357     }
358   }
359 
testProvidesIntoOptionalWithMapKey()360   public void testProvidesIntoOptionalWithMapKey() {
361     Module m =
362         new AbstractModule() {
363 
364           @ProvidesIntoOptional(Type.ACTUAL)
365           @TestEnumKey(TestEnum.A)
366           String provideFoo() {
367             return "foo";
368           }
369         };
370     try {
371       Guice.createInjector(MultibindingsScanner.asModule(), m);
372       fail();
373     } catch (CreationException ce) {
374       assertEquals(1, ce.getErrorMessages().size());
375       assertContains(
376           ce.getMessage(),
377           "Found a MapKey annotation on non map binding at "
378               + m.getClass().getName()
379               + ".provideFoo");
380     }
381   }
382 
testProvidesIntoMapWithoutMapKey()383   public void testProvidesIntoMapWithoutMapKey() {
384     Module m =
385         new AbstractModule() {
386 
387           @ProvidesIntoMap
388           String provideFoo() {
389             return "foo";
390           }
391         };
392     try {
393       Guice.createInjector(MultibindingsScanner.asModule(), m);
394       fail();
395     } catch (CreationException ce) {
396       assertEquals(1, ce.getErrorMessages().size());
397       assertContains(
398           ce.getMessage(),
399           "No MapKey found for map binding at " + m.getClass().getName() + ".provideFoo");
400     }
401   }
402 
403   @MapKey(unwrapValue = true)
404   @Retention(RUNTIME)
405   @interface TestEnumKey2 {
value()406     TestEnum value();
407   }
408 
testMoreThanOneMapKeyAnnotation()409   public void testMoreThanOneMapKeyAnnotation() {
410     Module m =
411         new AbstractModule() {
412 
413           @ProvidesIntoMap
414           @TestEnumKey(TestEnum.A)
415           @TestEnumKey2(TestEnum.B)
416           String provideFoo() {
417             return "foo";
418           }
419         };
420     try {
421       Guice.createInjector(MultibindingsScanner.asModule(), m);
422       fail();
423     } catch (CreationException ce) {
424       assertEquals(1, ce.getErrorMessages().size());
425       assertContains(
426           ce.getMessage(),
427           "Found more than one MapKey annotations on " + m.getClass().getName() + ".provideFoo");
428     }
429   }
430 
431   @MapKey(unwrapValue = true)
432   @Retention(RUNTIME)
433   @interface MissingValueMethod {}
434 
testMapKeyMissingValueMethod()435   public void testMapKeyMissingValueMethod() {
436     Module m =
437         new AbstractModule() {
438 
439           @ProvidesIntoMap
440           @MissingValueMethod
441           String provideFoo() {
442             return "foo";
443           }
444         };
445     try {
446       Guice.createInjector(MultibindingsScanner.asModule(), m);
447       fail();
448     } catch (CreationException ce) {
449       assertEquals(1, ce.getErrorMessages().size());
450       assertContains(
451           ce.getMessage(),
452           "No 'value' method in MapKey with unwrapValue=true: "
453               + MissingValueMethod.class.getName());
454     }
455   }
456 }
457