ProviderMethodsTest.java revision 2e39ef748a1c4e4dcab506ccfcdb14ca6e01c9c6
1/** 2 * Copyright (C) 2007 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 17package com.google.inject.spi; 18 19import static com.google.inject.Asserts.assertContains; 20import static java.lang.annotation.RetentionPolicy.RUNTIME; 21 22import com.google.common.collect.ImmutableList; 23import com.google.common.collect.ImmutableSet; 24import com.google.inject.AbstractModule; 25import com.google.inject.Binder; 26import com.google.inject.BindingAnnotation; 27import com.google.inject.CreationException; 28import com.google.inject.Guice; 29import com.google.inject.Inject; 30import com.google.inject.Injector; 31import com.google.inject.Key; 32import com.google.inject.Module; 33import com.google.inject.Provider; 34import com.google.inject.Provides; 35import com.google.inject.Singleton; 36import com.google.inject.internal.ProviderMethod; 37import com.google.inject.internal.ProviderMethodsModule; 38import com.google.inject.name.Named; 39import com.google.inject.name.Names; 40import com.google.inject.util.Types; 41 42import junit.framework.TestCase; 43 44import java.lang.annotation.ElementType; 45import java.lang.annotation.Retention; 46import java.lang.annotation.Target; 47import java.util.List; 48import java.util.Set; 49import java.util.concurrent.atomic.AtomicReference; 50import java.util.logging.Logger; 51 52/** 53 * @author crazybob@google.com (Bob Lee) 54 */ 55public class ProviderMethodsTest extends TestCase implements Module { 56 57 @SuppressWarnings("unchecked") 58 public void testProviderMethods() { 59 Injector injector = Guice.createInjector(this); 60 61 Bob bob = injector.getInstance(Bob.class); 62 assertEquals("A Bob", bob.getName()); 63 64 Bob clone = injector.getInstance(Bob.class); 65 assertEquals("A Bob", clone.getName()); 66 67 assertNotSame(bob, clone); 68 assertSame(bob.getDaughter(), clone.getDaughter()); 69 70 Key soleBobKey = Key.get(Bob.class, Sole.class); 71 assertSame( 72 injector.getInstance(soleBobKey), 73 injector.getInstance(soleBobKey) 74 ); 75 } 76 77 public void configure(Binder binder) {} 78 79 interface Bob { 80 String getName(); 81 Dagny getDaughter(); 82 } 83 84 interface Dagny { 85 int getAge(); 86 } 87 88 @Provides 89 Bob provideBob(final Dagny dagny) { 90 return new Bob() { 91 public String getName() { 92 return "A Bob"; 93 } 94 95 public Dagny getDaughter() { 96 return dagny; 97 } 98 }; 99 } 100 101 @Provides 102 @Singleton 103 @Sole 104 Bob provideSoleBob(final Dagny dagny) { 105 return new Bob() { 106 public String getName() { 107 return "Only Bob"; 108 } 109 110 public Dagny getDaughter() { 111 return dagny; 112 } 113 }; 114 } 115 116 @Provides 117 @Singleton 118 Dagny provideDagny() { 119 return new Dagny() { 120 public int getAge() { 121 return 1; 122 } 123 }; 124 } 125 126 @Retention(RUNTIME) 127 @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) 128 @BindingAnnotation 129 @interface Sole {} 130 131 132 133// We'll have to make getProvider() support circular dependencies before this 134// will work. 135// 136// public void testCircularDependency() { 137// Injector injector = Guice.createInjector(new Module() { 138// public void configure(Binder binder) { 139// binder.install(ProviderMethods.from(ProviderMethodsTest.this)); 140// } 141// }); 142// 143// Foo foo = injector.getInstance(Foo.class); 144// assertEquals(5, foo.getI()); 145// assertEquals(10, foo.getBar().getI()); 146// assertEquals(5, foo.getBar().getFoo().getI()); 147// } 148// 149// interface Foo { 150// Bar getBar(); 151// int getI(); 152// } 153// 154// interface Bar { 155// Foo getFoo(); 156// int getI(); 157// } 158// 159// @Provides Foo newFoo(final Bar bar) { 160// return new Foo() { 161// 162// public Bar getBar() { 163// return bar; 164// } 165// 166// public int getI() { 167// return 5; 168// } 169// }; 170// } 171// 172// @Provides Bar newBar(final Foo foo) { 173// return new Bar() { 174// 175// public Foo getFoo() { 176// return foo; 177// } 178// 179// public int getI() { 180// return 10; 181// } 182// }; 183// } 184 185 186 public void testMultipleBindingAnnotations() { 187 try { 188 Guice.createInjector(new AbstractModule() { 189 @Override protected void configure() {} 190 191 @Provides @Named("A") @Blue 192 public String provideString() { 193 return "a"; 194 } 195 }); 196 fail(); 197 } catch (CreationException expected) { 198 assertContains(expected.getMessage(), 199 "more than one annotation annotated with @BindingAnnotation:", "Named", "Blue", 200 "at " + getClass().getName(), ".provideString(ProviderMethodsTest.java:"); 201 } 202 203 } 204 205 @Retention(RUNTIME) 206 @BindingAnnotation @interface Blue {} 207 208 public void testGenericProviderMethods() { 209 Injector injector = Guice.createInjector( 210 new ProvideTs<String>("A", "B") {}, new ProvideTs<Integer>(1, 2) {}); 211 212 assertEquals("A", injector.getInstance(Key.get(String.class, Names.named("First")))); 213 assertEquals("B", injector.getInstance(Key.get(String.class, Names.named("Second")))); 214 assertEquals(ImmutableSet.of("A", "B"), 215 injector.getInstance(Key.get(Types.setOf(String.class)))); 216 217 assertEquals(1, injector.getInstance(Key.get(Integer.class, Names.named("First"))).intValue()); 218 assertEquals(2, injector.getInstance(Key.get(Integer.class, Names.named("Second"))).intValue()); 219 assertEquals(ImmutableSet.of(1, 2), 220 injector.getInstance(Key.get(Types.setOf(Integer.class)))); 221 } 222 223 abstract class ProvideTs<T> extends AbstractModule { 224 final T first; 225 final T second; 226 227 protected ProvideTs(T first, T second) { 228 this.first = first; 229 this.second = second; 230 } 231 232 @Override protected void configure() {} 233 234 @Named("First") @Provides T provideFirst() { 235 return first; 236 } 237 238 @Named("Second") @Provides T provideSecond() { 239 return second; 240 } 241 242 @Provides Set<T> provideBoth(@Named("First") T first, @Named("Second") T second) { 243 return ImmutableSet.of(first, second); 244 } 245 } 246 247 public void testAutomaticProviderMethods() { 248 Injector injector = Guice.createInjector((Module) new AbstractModule() { 249 @Override protected void configure() { } 250 private int next = 1; 251 252 @Provides @Named("count") 253 public Integer provideCount() { 254 return next++; 255 } 256 }); 257 258 assertEquals(1, injector.getInstance(Key.get(Integer.class, Names.named("count"))).intValue()); 259 assertEquals(2, injector.getInstance(Key.get(Integer.class, Names.named("count"))).intValue()); 260 assertEquals(3, injector.getInstance(Key.get(Integer.class, Names.named("count"))).intValue()); 261 } 262 263 /** 264 * If the user installs provider methods for the module manually, that shouldn't cause a double 265 * binding of the provider methods' types. 266 */ 267 public void testAutomaticProviderMethodsDoNotCauseDoubleBinding() { 268 Module installsSelf = new AbstractModule() { 269 @Override protected void configure() { 270 install(this); 271 bind(Integer.class).toInstance(5); 272 } 273 @Provides public String provideString(Integer count) { 274 return "A" + count; 275 } 276 }; 277 278 Injector injector = Guice.createInjector(installsSelf); 279 assertEquals("A5", injector.getInstance(String.class)); 280 } 281 282 public void testWildcardProviderMethods() { 283 final List<String> strings = ImmutableList.of("A", "B", "C"); 284 final List<Number> numbers = ImmutableList.<Number>of(1, 2, 3); 285 286 Injector injector = Guice.createInjector(new AbstractModule() { 287 @Override protected void configure() { 288 @SuppressWarnings("unchecked") 289 Key<List<? super Integer>> listOfSupertypesOfInteger = (Key<List<? super Integer>>) 290 Key.get(Types.listOf(Types.supertypeOf(Integer.class))); 291 bind(listOfSupertypesOfInteger).toInstance(numbers); 292 } 293 @Provides public List<? extends CharSequence> provideCharSequences() { 294 return strings; 295 } 296 @Provides public Class<?> provideType() { 297 return Float.class; 298 } 299 }); 300 301 assertSame(strings, injector.getInstance(HasWildcardInjection.class).charSequences); 302 assertSame(numbers, injector.getInstance(HasWildcardInjection.class).numbers); 303 assertSame(Float.class, injector.getInstance(HasWildcardInjection.class).type); 304 } 305 306 static class HasWildcardInjection { 307 @Inject List<? extends CharSequence> charSequences; 308 @Inject List<? super Integer> numbers; 309 @Inject Class<?> type; 310 } 311 312 public void testProviderMethodDependenciesAreExposed() { 313 Injector injector = Guice.createInjector(new AbstractModule() { 314 @Override protected void configure() { 315 bind(Integer.class).toInstance(50); 316 bindConstant().annotatedWith(Names.named("units")).to("Kg"); 317 } 318 @Provides @Named("weight") String provideWeight(Integer count, @Named("units") String units) { 319 return count + units; 320 } 321 }); 322 323 ProviderInstanceBinding<?> binding = (ProviderInstanceBinding<?>) injector.getBinding( 324 Key.get(String.class, Names.named("weight"))); 325 assertEquals(ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(Integer.class)), 326 Dependency.get(Key.get(String.class, Names.named("units")))), 327 binding.getDependencies()); 328 } 329 330 public void testNonModuleProviderMethods() { 331 final Object methodsObject = new Object() { 332 @Provides @Named("foo") String provideFoo() { 333 return "foo-value"; 334 } 335 }; 336 337 Module module = new AbstractModule() { 338 @Override protected void configure() { 339 install(ProviderMethodsModule.forObject(methodsObject)); 340 } 341 }; 342 343 Injector injector = Guice.createInjector(module); 344 345 Key<String> key = Key.get(String.class, Names.named("foo")); 346 assertEquals("foo-value", injector.getInstance(key)); 347 348 // Test the provider method object itself. This makes sure getInstance works, since GIN uses it 349 List<Element> elements = Elements.getElements(module); 350 assertEquals(1, elements.size()); 351 352 Element element = elements.get(0); 353 assertTrue(element + " instanceof ProviderInstanceBinding", 354 element instanceof ProviderInstanceBinding); 355 356 ProviderInstanceBinding binding = (ProviderInstanceBinding) element; 357 Provider provider = binding.getProviderInstance(); 358 assertEquals(ProviderMethod.class, provider.getClass()); 359 assertEquals(methodsObject, ((ProviderMethod) provider).getInstance()); 360 } 361 362 public void testVoidProviderMethods() { 363 try { 364 Guice.createInjector(new AbstractModule() { 365 @Override protected void configure() {} 366 367 @Provides void provideFoo() {} 368 }); 369 fail(); 370 } catch (CreationException expected) { 371 assertContains(expected.getMessage(), 372 "1) Provider methods must return a value. Do not return void.", 373 getClass().getName(), ".provideFoo(ProviderMethodsTest.java:"); 374 } 375 } 376 377 public void testInjectsJustOneLogger() { 378 AtomicReference<Logger> loggerRef = new AtomicReference<Logger>(); 379 Injector injector = Guice.createInjector(new FooModule(loggerRef)); 380 381 assertNull(loggerRef.get()); 382 injector.getInstance(Integer.class); 383 Logger lastLogger = loggerRef.getAndSet(null); 384 assertNotNull(lastLogger); 385 injector.getInstance(Integer.class); 386 assertSame(lastLogger, loggerRef.get()); 387 388 assertEquals(FooModule.class.getName() + ".foo", lastLogger.getName()); 389 } 390 391 private static class FooModule extends AbstractModule { 392 private final AtomicReference<Logger> loggerRef; 393 394 public FooModule(AtomicReference<Logger> loggerRef) { 395 this.loggerRef = loggerRef; 396 } 397 398 @Override protected void configure() {} 399 400 @SuppressWarnings("unused") 401 @Provides Integer foo(Logger logger) { 402 loggerRef.set(logger); 403 return 42; 404 } 405 } 406}