1 /* 2 * Copyright (C) 2009 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; 18 19 import static com.google.inject.Asserts.assertContains; 20 21 import com.google.inject.internal.Annotations; 22 import com.google.inject.name.Names; 23 import com.google.inject.util.Providers; 24 import java.util.concurrent.Callable; 25 import java.util.concurrent.Executors; 26 import java.util.concurrent.TimeUnit; 27 import java.util.concurrent.TimeoutException; 28 import java.util.concurrent.atomic.AtomicReference; 29 import javax.inject.Inject; 30 import junit.framework.AssertionFailedError; 31 import junit.framework.TestCase; 32 33 /** @author jessewilson@google.com (Jesse Wilson) */ 34 public class MembersInjectorTest extends TestCase { 35 36 private static final long DEADLOCK_TIMEOUT_SECONDS = 1; 37 38 private static final A<C> uninjectableA = 39 new A<C>() { 40 @Inject 41 @Override 42 void doNothing() { 43 throw new AssertionFailedError(); 44 } 45 }; 46 47 private static final B uninjectableB = 48 new B() { 49 @Inject 50 @Override 51 void doNothing() { 52 throw new AssertionFailedError(); 53 } 54 }; 55 56 private static final C myFavouriteC = new C(); 57 testMembersInjectorFromBinder()58 public void testMembersInjectorFromBinder() { 59 final AtomicReference<MembersInjector<A<C>>> aMembersInjectorReference = 60 new AtomicReference<MembersInjector<A<C>>>(); 61 final AtomicReference<MembersInjector<B>> bMembersInjectorReference = 62 new AtomicReference<MembersInjector<B>>(); 63 64 Guice.createInjector( 65 new AbstractModule() { 66 @Override 67 protected void configure() { 68 MembersInjector<A<C>> aMembersInjector = getMembersInjector(new TypeLiteral<A<C>>() {}); 69 try { 70 aMembersInjector.injectMembers(uninjectableA); 71 fail(); 72 } catch (IllegalStateException expected) { 73 assertContains( 74 expected.getMessage(), 75 "This MembersInjector cannot be used until the Injector has been created."); 76 } 77 78 MembersInjector<B> bMembersInjector = getMembersInjector(B.class); 79 try { 80 bMembersInjector.injectMembers(uninjectableB); 81 fail(); 82 } catch (IllegalStateException expected) { 83 assertContains( 84 expected.getMessage(), 85 "This MembersInjector cannot be used until the Injector has been created."); 86 } 87 88 aMembersInjectorReference.set(aMembersInjector); 89 bMembersInjectorReference.set(bMembersInjector); 90 91 assertEquals( 92 "MembersInjector<java.lang.String>", getMembersInjector(String.class).toString()); 93 94 bind(C.class).toInstance(myFavouriteC); 95 } 96 }); 97 98 A<C> injectableA = new A<>(); 99 aMembersInjectorReference.get().injectMembers(injectableA); 100 assertSame(myFavouriteC, injectableA.t); 101 assertSame(myFavouriteC, injectableA.b.c); 102 103 B injectableB = new B(); 104 bMembersInjectorReference.get().injectMembers(injectableB); 105 assertSame(myFavouriteC, injectableB.c); 106 107 B anotherInjectableB = new B(); 108 bMembersInjectorReference.get().injectMembers(anotherInjectableB); 109 assertSame(myFavouriteC, anotherInjectableB.c); 110 } 111 testMembersInjectorFromInjector()112 public void testMembersInjectorFromInjector() { 113 Injector injector = 114 Guice.createInjector( 115 new AbstractModule() { 116 @Override 117 protected void configure() { 118 bind(C.class).toInstance(myFavouriteC); 119 } 120 }); 121 122 MembersInjector<A<C>> aMembersInjector = 123 injector.getMembersInjector(new TypeLiteral<A<C>>() {}); 124 MembersInjector<B> bMembersInjector = injector.getMembersInjector(B.class); 125 126 A<C> injectableA = new A<>(); 127 aMembersInjector.injectMembers(injectableA); 128 assertSame(myFavouriteC, injectableA.t); 129 assertSame(myFavouriteC, injectableA.b.c); 130 131 B injectableB = new B(); 132 bMembersInjector.injectMembers(injectableB); 133 assertSame(myFavouriteC, injectableB.c); 134 135 B anotherInjectableB = new B(); 136 bMembersInjector.injectMembers(anotherInjectableB); 137 assertSame(myFavouriteC, anotherInjectableB.c); 138 139 assertEquals( 140 "MembersInjector<java.lang.String>", injector.getMembersInjector(String.class).toString()); 141 } 142 testMembersInjectorWithNonInjectedTypes()143 public void testMembersInjectorWithNonInjectedTypes() { 144 Injector injector = Guice.createInjector(); 145 146 MembersInjector<NoInjectedMembers> membersInjector = 147 injector.getMembersInjector(NoInjectedMembers.class); 148 149 membersInjector.injectMembers(new NoInjectedMembers()); 150 membersInjector.injectMembers(new NoInjectedMembers()); 151 } 152 testInjectionFailure()153 public void testInjectionFailure() { 154 Injector injector = Guice.createInjector(); 155 156 MembersInjector<InjectionFailure> membersInjector = 157 injector.getMembersInjector(InjectionFailure.class); 158 159 try { 160 membersInjector.injectMembers(new InjectionFailure()); 161 fail(); 162 } catch (ProvisionException expected) { 163 assertContains( 164 expected.getMessage(), 165 "1) Error injecting method, java.lang.ClassCastException: whoops, failure #1"); 166 } 167 } 168 testInjectionAppliesToSpecifiedType()169 public void testInjectionAppliesToSpecifiedType() { 170 Injector injector = Guice.createInjector(); 171 172 MembersInjector<Object> membersInjector = injector.getMembersInjector(Object.class); 173 membersInjector.injectMembers(new InjectionFailure()); 174 } 175 testInjectingMembersInjector()176 public void testInjectingMembersInjector() { 177 InjectsMembersInjector injectsMembersInjector = 178 Guice.createInjector( 179 new AbstractModule() { 180 @Override 181 protected void configure() { 182 bind(C.class).toInstance(myFavouriteC); 183 } 184 }) 185 .getInstance(InjectsMembersInjector.class); 186 187 A<C> a = new A<>(); 188 injectsMembersInjector.aMembersInjector.injectMembers(a); 189 assertSame(myFavouriteC, a.t); 190 assertSame(myFavouriteC, a.b.c); 191 } 192 testCannotBindMembersInjector()193 public void testCannotBindMembersInjector() { 194 try { 195 Guice.createInjector( 196 new AbstractModule() { 197 @Override 198 protected void configure() { 199 bind(MembersInjector.class).toProvider(Providers.<MembersInjector>of(null)); 200 } 201 }); 202 fail(); 203 } catch (CreationException expected) { 204 assertContains( 205 expected.getMessage(), 206 "1) Binding to core guice framework type is not allowed: MembersInjector."); 207 } 208 209 try { 210 Guice.createInjector( 211 new AbstractModule() { 212 @Override 213 protected void configure() { 214 bind(new TypeLiteral<MembersInjector<A<C>>>() {}) 215 .toProvider(Providers.<MembersInjector<A<C>>>of(null)); 216 } 217 }); 218 fail(); 219 } catch (CreationException expected) { 220 assertContains( 221 expected.getMessage(), 222 "1) Binding to core guice framework type is not allowed: MembersInjector."); 223 } 224 } 225 testInjectingMembersInjectorWithErrorsInDependencies()226 public void testInjectingMembersInjectorWithErrorsInDependencies() { 227 try { 228 Guice.createInjector().getInstance(InjectsBrokenMembersInjector.class); 229 fail(); 230 } catch (ConfigurationException expected) { 231 assertContains( 232 expected.getMessage(), 233 "1) No implementation for " + Unimplemented.class.getName() + " was bound.", 234 "while locating " + Unimplemented.class.getName(), 235 "for field at " + A.class.getName() + ".t(MembersInjectorTest.java:", 236 "while locating com.google.inject.MembersInjector<", 237 "for field at " + InjectsBrokenMembersInjector.class.getName() + ".aMembersInjector(", 238 "while locating " + InjectsBrokenMembersInjector.class.getName()); 239 } 240 } 241 testLookupMembersInjectorBinding()242 public void testLookupMembersInjectorBinding() { 243 Injector injector = 244 Guice.createInjector( 245 new AbstractModule() { 246 @Override 247 protected void configure() { 248 bind(C.class).toInstance(myFavouriteC); 249 } 250 }); 251 MembersInjector<A<C>> membersInjector = 252 injector.getInstance(new Key<MembersInjector<A<C>>>() {}); 253 254 A<C> a = new A<>(); 255 membersInjector.injectMembers(a); 256 assertSame(myFavouriteC, a.t); 257 assertSame(myFavouriteC, a.b.c); 258 259 assertEquals( 260 "MembersInjector<java.lang.String>", 261 injector.getInstance(new Key<MembersInjector<String>>() {}).toString()); 262 } 263 testGettingRawMembersInjector()264 public void testGettingRawMembersInjector() { 265 Injector injector = Guice.createInjector(); 266 try { 267 injector.getInstance(MembersInjector.class); 268 fail(); 269 } catch (ConfigurationException expected) { 270 assertContains( 271 expected.getMessage(), "Cannot inject a MembersInjector that has no type parameter"); 272 } 273 } 274 testGettingAnnotatedMembersInjector()275 public void testGettingAnnotatedMembersInjector() { 276 Injector injector = Guice.createInjector(); 277 try { 278 injector.getInstance(new Key<MembersInjector<String>>(Names.named("foo")) {}); 279 fail(); 280 } catch (ConfigurationException expected) { 281 assertContains( 282 expected.getMessage(), 283 "1) No implementation for com.google.inject.MembersInjector<java.lang.String> " 284 + "annotated with @com.google.inject.name.Named(value=" 285 + Annotations.memberValueString("foo") 286 + ") was bound."); 287 } 288 } 289 290 /** Callback for member injection. Uses a static type to be referable by getInstance(). */ 291 abstract static class AbstractParallelMemberInjectionCallback { 292 293 volatile boolean called = false; 294 295 private final Thread mainThread; 296 private final Class<? extends AbstractParallelMemberInjectionCallback> otherCallbackClass; 297 AbstractParallelMemberInjectionCallback( Class<? extends AbstractParallelMemberInjectionCallback> otherCallbackClass)298 AbstractParallelMemberInjectionCallback( 299 Class<? extends AbstractParallelMemberInjectionCallback> otherCallbackClass) { 300 this.mainThread = Thread.currentThread(); 301 this.otherCallbackClass = otherCallbackClass; 302 } 303 304 @Inject callback(final Injector injector)305 void callback(final Injector injector) throws Exception { 306 called = true; 307 if (mainThread != Thread.currentThread()) { 308 // only execute logic on the main thread 309 return; 310 } 311 // verify that other callback can be finished on a separate thread 312 AbstractParallelMemberInjectionCallback otherCallback = 313 Executors.newSingleThreadExecutor() 314 .submit( 315 new Callable<AbstractParallelMemberInjectionCallback>() { 316 @Override 317 public AbstractParallelMemberInjectionCallback call() throws Exception { 318 return injector.getInstance(otherCallbackClass); 319 } 320 }) 321 .get(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS); 322 assertTrue(otherCallback.called); 323 324 try { 325 // other thread would wait for callback to finish on this thread first 326 Executors.newSingleThreadExecutor() 327 .submit( 328 new Callable<Object>() { 329 @Override 330 public Object call() throws Exception { 331 return injector.getInstance( 332 AbstractParallelMemberInjectionCallback.this.getClass()); 333 } 334 }) 335 .get(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS); 336 fail(); 337 } catch (TimeoutException expected) { 338 // recursive call from another thread should time out 339 // as it would be waiting for this thread to finish 340 } 341 } 342 } 343 344 static class ParallelMemberInjectionCallback1 extends AbstractParallelMemberInjectionCallback { 345 ParallelMemberInjectionCallback1()346 ParallelMemberInjectionCallback1() { 347 super(ParallelMemberInjectionCallback2.class); 348 } 349 } 350 351 static class ParallelMemberInjectionCallback2 extends AbstractParallelMemberInjectionCallback { 352 ParallelMemberInjectionCallback2()353 ParallelMemberInjectionCallback2() { 354 super(ParallelMemberInjectionCallback1.class); 355 } 356 } 357 358 /** 359 * Tests that member injections could happen in parallel. 360 * 361 * <p>Additional check that when member injection happen other threads would wait for it to finish 362 * to provide proper resolution order semantics. 363 */ 364 testMemberInjectorParallelization()365 public void testMemberInjectorParallelization() throws Exception { 366 final ParallelMemberInjectionCallback1 c1 = new ParallelMemberInjectionCallback1(); 367 final ParallelMemberInjectionCallback2 c2 = new ParallelMemberInjectionCallback2(); 368 Guice.createInjector( 369 new AbstractModule() { 370 @Override 371 protected void configure() { 372 bind(ParallelMemberInjectionCallback1.class).toInstance(c1); 373 bind(ParallelMemberInjectionCallback2.class).toInstance(c2); 374 } 375 }); 376 assertTrue(c1.called); 377 assertTrue(c2.called); 378 } 379 380 /** Member injection callback that injects itself. */ 381 static class RecursiveMemberInjection { 382 boolean called = false; 383 384 @Inject callback(RecursiveMemberInjection recursiveMemberInjection)385 void callback(RecursiveMemberInjection recursiveMemberInjection) { 386 if (called) { 387 fail("Should not be called twice"); 388 } 389 called = true; 390 } 391 } 392 393 /** Verifies that member injection injecting itself would get a non initialized instance. */ testRecursiveMemberInjector()394 public void testRecursiveMemberInjector() throws Exception { 395 final RecursiveMemberInjection rmi = new RecursiveMemberInjection(); 396 Guice.createInjector( 397 new AbstractModule() { 398 @Override 399 protected void configure() { 400 bind(RecursiveMemberInjection.class).toInstance(rmi); 401 } 402 }); 403 assertTrue("Member injection should happen", rmi.called); 404 } 405 406 static class A<T> { 407 @Inject B b; 408 @Inject T t; 409 410 @Inject doNothing()411 void doNothing() {} 412 } 413 414 static class B { 415 @Inject C c; 416 417 @Inject doNothing()418 void doNothing() {} 419 } 420 421 static class C {} 422 423 static class NoInjectedMembers {} 424 425 static class InjectionFailure { 426 int failures = 0; 427 428 @Inject fail()429 void fail() { 430 throw new ClassCastException("whoops, failure #" + (++failures)); 431 } 432 } 433 434 static class InjectsMembersInjector { 435 @Inject MembersInjector<A<C>> aMembersInjector; 436 @Inject A<B> ab; 437 } 438 439 static class InjectsBrokenMembersInjector { 440 @Inject MembersInjector<A<Unimplemented>> aMembersInjector; 441 } 442 443 static interface Unimplemented {} 444 } 445