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