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