001/***************************************************************************** 002 * Copyright (C) PicoContainer Organization. All rights reserved. * 003 * ------------------------------------------------------------------------- * 004 * The software in this package is published under the terms of the BSD * 005 * style license a copy of which has been included with this distribution in * 006 * the LICENSE.txt file. * 007 * * 008 * Original code by * 009 *****************************************************************************/ 010package org.picocontainer.tck; 011 012import org.junit.Test; 013import org.picocontainer.Behavior; 014import org.picocontainer.Characteristics; 015import org.picocontainer.ComponentAdapter; 016import org.picocontainer.ComponentFactory; 017import org.picocontainer.Converting; 018import org.picocontainer.DefaultPicoContainer; 019import org.picocontainer.Disposable; 020import org.picocontainer.MutablePicoContainer; 021import org.picocontainer.NameBinding; 022import org.picocontainer.Parameter; 023import org.picocontainer.PicoCompositionException; 024import org.picocontainer.PicoContainer; 025import org.picocontainer.PicoException; 026import org.picocontainer.PicoVerificationException; 027import org.picocontainer.PicoVisitor; 028import org.picocontainer.Startable; 029import org.picocontainer.adapters.InstanceAdapter; 030import org.picocontainer.behaviors.AbstractBehavior; 031import org.picocontainer.behaviors.AdaptingBehavior; 032import org.picocontainer.injectors.AbstractInjector; 033import org.picocontainer.injectors.AbstractInjector.UnsatisfiableDependenciesException; 034import org.picocontainer.injectors.ConstructorInjector; 035import org.picocontainer.injectors.SingleMemberInjector.ParameterCannotBeNullException; 036import org.picocontainer.lifecycle.NullLifecycleStrategy; 037import org.picocontainer.monitors.NullComponentMonitor; 038import org.picocontainer.parameters.BasicComponentParameter; 039import org.picocontainer.parameters.ComponentParameter; 040import org.picocontainer.parameters.ConstantParameter; 041import org.picocontainer.parameters.NullParameter; 042import org.picocontainer.testmodel.DependsOnTouchable; 043import org.picocontainer.testmodel.SimpleTouchable; 044import org.picocontainer.testmodel.Touchable; 045import org.picocontainer.testmodel.Washable; 046import org.picocontainer.testmodel.WashableTouchable; 047import org.picocontainer.visitors.AbstractPicoVisitor; 048import org.picocontainer.visitors.TraversalCheckingVisitor; 049import org.picocontainer.visitors.VerifyingVisitor; 050 051import java.io.ByteArrayInputStream; 052import java.io.ByteArrayOutputStream; 053import java.io.IOException; 054import java.io.ObjectInputStream; 055import java.io.ObjectOutputStream; 056import java.io.Serializable; 057import java.util.ArrayList; 058import java.util.Arrays; 059import java.util.Collection; 060import java.util.HashMap; 061import java.util.HashSet; 062import java.util.LinkedList; 063import java.util.List; 064import java.util.Map; 065import java.util.Properties; 066 067import static org.junit.Assert.assertEquals; 068import static org.junit.Assert.assertFalse; 069import static org.junit.Assert.assertNotNull; 070import static org.junit.Assert.assertNotSame; 071import static org.junit.Assert.assertNull; 072import static org.junit.Assert.assertSame; 073import static org.junit.Assert.assertTrue; 074import static org.junit.Assert.fail; 075 076/** This test tests (at least it should) all the methods in MutablePicoContainer. */ 077@SuppressWarnings("serial") 078public abstract class AbstractPicoContainerTest { 079 080 protected abstract MutablePicoContainer createPicoContainer(PicoContainer parent); 081 082 protected final MutablePicoContainer createPicoContainerWithDependsOnTouchableOnly() throws PicoCompositionException { 083 MutablePicoContainer pico = createPicoContainer(null); 084 pico.addComponent(DependsOnTouchable.class); 085 return pico; 086 } 087 088 protected final MutablePicoContainer createPicoContainerWithTouchableAndDependsOnTouchable() throws PicoCompositionException { 089 MutablePicoContainer pico = createPicoContainerWithDependsOnTouchableOnly(); 090 pico.as(Characteristics.CACHE).addComponent(Touchable.class, SimpleTouchable.class); 091 return pico; 092 } 093 094 @Test public void testBasicInstantiationAndContainment() throws PicoException { 095 PicoContainer pico = createPicoContainerWithTouchableAndDependsOnTouchable(); 096 assertTrue("Component should be instance of Touchable", 097 Touchable.class.isAssignableFrom(pico.getComponentAdapter(Touchable.class, (NameBinding) null).getComponentImplementation())); 098 } 099 100 @Test public void testRegisteredComponentsExistAndAreTheCorrectTypes() throws PicoException { 101 PicoContainer pico = createPicoContainerWithTouchableAndDependsOnTouchable(); 102 assertNotNull("Container should have Touchable addComponent", 103 pico.getComponentAdapter(Touchable.class, (NameBinding) null)); 104 assertNotNull("Container should have DependsOnTouchable addComponent", 105 pico.getComponentAdapter(DependsOnTouchable.class, (NameBinding) null)); 106 assertTrue("Component should be instance of Touchable", 107 pico.getComponent(Touchable.class) != null); 108 assertTrue("Component should be instance of DependsOnTouchable", 109 pico.getComponent(DependsOnTouchable.class) != null); 110 assertNull("should not have non existent addComponent", pico.getComponentAdapter(Map.class, (NameBinding) null)); 111 } 112 113 @Test public void testRegistersSingleInstance() throws PicoException { 114 MutablePicoContainer pico = createPicoContainer(null); 115 StringBuffer sb = new StringBuffer(); 116 pico.addComponent(sb); 117 assertSame(sb, pico.getComponent(StringBuffer.class)); 118 } 119 120 @Test public void testContainerIsSerializable() throws PicoException, 121 IOException, ClassNotFoundException 122 { 123 124 getTouchableFromSerializedContainer(); 125 126 } 127 128 private Touchable getTouchableFromSerializedContainer() throws IOException, ClassNotFoundException { 129 MutablePicoContainer pico = createPicoContainerWithTouchableAndDependsOnTouchable(); 130 // Add a list too, using a constant parameter 131 pico.addComponent("list", ArrayList.class, new ConstantParameter(10)); 132 133 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 134 ObjectOutputStream oos = new ObjectOutputStream(baos); 135 136 oos.writeObject(pico); 137 ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray())); 138 139 pico = (MutablePicoContainer)ois.readObject(); 140 141 DependsOnTouchable dependsOnTouchable = pico.getComponent(DependsOnTouchable.class); 142 assertNotNull(dependsOnTouchable); 143 return pico.getComponent(Touchable.class); 144 } 145 146 @Test public void testSerializedContainerCanRetrieveImplementation() throws PicoException, 147 IOException, ClassNotFoundException 148 { 149 150 Touchable touchable = getTouchableFromSerializedContainer(); 151 152 SimpleTouchable simpleTouchable = (SimpleTouchable)touchable; 153 154 assertTrue(simpleTouchable.wasTouched); 155 } 156 157 158 @Test public void testGettingComponentWithMissingDependencyFails() throws PicoException { 159 MutablePicoContainer picoContainer = (MutablePicoContainer) createPicoContainerWithDependsOnTouchableOnly(); 160 picoContainer.setName("parent"); 161 try { 162 picoContainer.getComponent(DependsOnTouchable.class); 163 fail("should need a Touchable"); 164 } catch (AbstractInjector.UnsatisfiableDependenciesException e) { 165 String message = e.getMessage().replace("org.picocontainer.testmodel.", ""); 166 assertEquals("DependsOnTouchable has unsatisfied dependency 'interface Touchable' for constructor 'public DependsOnTouchable(Touchable)' from parent:1<|", message); 167 168 } 169 } 170 171 @Test public void testDuplicateRegistration() { 172 try { 173 MutablePicoContainer pico = createPicoContainer(null); 174 pico.addComponent(Object.class); 175 pico.addComponent(Object.class); 176 fail("Should have failed with duplicate registration"); 177 } catch (PicoCompositionException e) { 178 assertTrue("Wrong key", e.getMessage().indexOf(Object.class.toString()) > -1); 179 } 180 } 181 182 @Test public void testExternallyInstantiatedObjectsCanBeRegisteredAndLookedUp() throws PicoException { 183 MutablePicoContainer pico = createPicoContainer(null); 184 final HashMap map = new HashMap(); 185 pico.as(getProperties()).addComponent(Map.class, map); 186 assertSame(map, pico.getComponent(Map.class)); 187 } 188 189 @Test public void testAmbiguousResolution() throws PicoCompositionException { 190 MutablePicoContainer pico = createPicoContainer(null); 191 pico.addComponent("ping", String.class); 192 pico.addComponent("pong", "pang"); 193 try { 194 pico.getComponent(String.class); 195 } catch (AbstractInjector.AmbiguousComponentResolutionException e) { 196 assertTrue(e.getMessage().indexOf("java.lang.String") != -1); 197 assertTrue(e.getMessage().indexOf("<no-component>") != -1); 198 assertTrue(e.getMessage().indexOf("<unknown>") != -1); 199 } 200 } 201 202 @Test public void testLookupWithUnregisteredKeyReturnsNull() throws PicoCompositionException { 203 MutablePicoContainer pico = createPicoContainer(null); 204 assertNull(pico.getComponent(String.class)); 205 } 206 207 @Test public void testLookupWithUnregisteredTypeReturnsNull() throws PicoCompositionException { 208 MutablePicoContainer pico = createPicoContainer(null); 209 assertNull(pico.getComponent(String.class)); 210 } 211 212 public static class ListAdder { 213 public ListAdder(Collection<String> list) { 214 list.add("something"); 215 } 216 } 217 218 @Test public void testUnsatisfiableDependenciesExceptionGivesVerboseEnoughErrorMessage() { 219 MutablePicoContainer pico = createPicoContainer(null); 220 pico.setName("parent"); 221 pico.addComponent(ComponentD.class); 222 223 try { 224 pico.getComponent(ComponentD.class); 225 } catch (AbstractInjector.UnsatisfiableDependenciesException e) { 226 String msg = e.getMessage().replace("org.picocontainer.tck.AbstractPicoContainerTest$Component", ""); 227 assertEquals("D has unsatisfied dependency 'class B' for constructor 'public D(E,B)' from parent:1<|", msg); 228 } 229 } 230 231 @Test public void testUnsatisfiableDependenciesExceptionGivesUnsatisfiedDependencyTypes() { 232 MutablePicoContainer pico = (MutablePicoContainer) createPicoContainer(null); 233 pico.setName("parent"); 234 // D depends on E and B 235 pico.addComponent(ComponentD.class); 236 237 // first - do not register any dependency 238 // should yield first unsatisfied dependency 239 try { 240 pico.getComponent(ComponentD.class); 241 } catch (AbstractInjector.UnsatisfiableDependenciesException e) { 242 243 String message = e.getMessage().replace("org.picocontainer.tck.AbstractPicoContainerTest$Component", ""); 244 assertEquals("D has unsatisfied dependency 'class B' for constructor 'public D(E,B)' from parent:1<|", message); 245 } 246 247 // now register only first dependency 248 // should yield second unsatisfied dependency 249 pico.addComponent(ComponentE.class); 250 try { 251 pico.getComponent(ComponentD.class); 252 } catch (AbstractInjector.UnsatisfiableDependenciesException e) { 253 String message = e.getMessage().replace("org.picocontainer.tck.AbstractPicoContainerTest$Component", ""); 254 assertEquals("D has unsatisfied dependency 'class B' for constructor 'public D(E,B)' from parent:2<|", message); 255 } 256 } 257 258 @Test public void testCyclicDependencyThrowsCyclicDependencyException() { 259 assertCyclicDependencyThrowsCyclicDependencyException(createPicoContainer(null)); 260 } 261 262 private static void assertCyclicDependencyThrowsCyclicDependencyException(MutablePicoContainer pico) { 263 pico.addComponent(ComponentB.class); 264 pico.addComponent(ComponentD.class); 265 pico.addComponent(ComponentE.class); 266 267 try { 268 pico.getComponent(ComponentD.class); 269 fail("CyclicDependencyException expected"); 270 } catch (AbstractInjector.CyclicDependencyException e) { 271 // CyclicDependencyException reports now the stack. 272 //final List dependencies = Arrays.asList(ComponentD.class.getConstructors()[0].getParameterTypes()); 273 final List<Class> dependencies = Arrays.<Class>asList(ComponentD.class, ComponentE.class, ComponentD.class); 274 final List<Class> reportedDependencies = Arrays.asList(e.getDependencies()); 275 assertEquals(dependencies, reportedDependencies); 276 } catch (StackOverflowError e) { 277 fail(); 278 } 279 } 280 281 @Test public void testCyclicDependencyThrowsCyclicDependencyExceptionWithParentContainer() { 282 MutablePicoContainer pico = createPicoContainer(createPicoContainer(null)); 283 assertCyclicDependencyThrowsCyclicDependencyException(pico); 284 } 285 286 @Test public void testRemovalNonRegisteredComponentAdapterWorksAndReturnsNull() { 287 final MutablePicoContainer picoContainer = createPicoContainer(null); 288 assertNull(picoContainer.removeComponent("COMPONENT DOES NOT EXIST")); 289 } 290 291 /** Important! Nanning really, really depends on this! */ 292 @Test public void testComponentAdapterRegistrationOrderIsMaintained() throws NoSuchMethodException { 293 294 ConstructorInjector c1 = new ConstructorInjector("1", Object.class, null, new NullComponentMonitor(), false); 295 ConstructorInjector c2 = new ConstructorInjector("2", String.class, null, new NullComponentMonitor(), false); 296 297 MutablePicoContainer picoContainer = createPicoContainer(null); 298 picoContainer.addAdapter(c1).addAdapter(c2); 299 Collection<ComponentAdapter<?>> list2 = picoContainer.getComponentAdapters(); 300 //registration order should be maintained 301 assertEquals(2, list2.size()); 302 assertEquals(c1.getComponentKey(), ((ComponentAdapter)list2.toArray()[0]).getComponentKey()); 303 assertEquals(c2.getComponentKey(), ((ComponentAdapter)list2.toArray()[1]).getComponentKey()); 304 305 picoContainer.getComponents(); // create all the instances at once 306 assertFalse("instances should be created in same order as adapters are created", 307 picoContainer.getComponents().get(0) instanceof String); 308 assertTrue("instances should be created in same order as adapters are created", 309 picoContainer.getComponents().get(1) instanceof String); 310 311 MutablePicoContainer reversedPicoContainer = createPicoContainer(null); 312 reversedPicoContainer.addAdapter(c2); 313 reversedPicoContainer.addAdapter(c1); 314 //registration order should be maintained 315 list2 = reversedPicoContainer.getComponentAdapters(); 316 assertEquals(2, list2.size()); 317 assertEquals(c2.getComponentKey(), ((ComponentAdapter)list2.toArray()[0]).getComponentKey()); 318 assertEquals(c1.getComponentKey(), ((ComponentAdapter)list2.toArray()[1]).getComponentKey()); 319 320 reversedPicoContainer.getComponents(); // create all the instances at once 321 assertTrue("instances should be created in same order as adapters are created", 322 reversedPicoContainer.getComponents().get(0) instanceof String); 323 assertFalse("instances should be created in same order as adapters are created", 324 reversedPicoContainer.getComponents().get(1) instanceof String); 325 } 326 327 public static final class NeedsTouchable { 328 public final Touchable touchable; 329 330 public NeedsTouchable(Touchable touchable) { 331 this.touchable = touchable; 332 } 333 } 334 335 public static final class NeedsWashable { 336 public final Washable washable; 337 338 public NeedsWashable(Washable washable) { 339 this.washable = washable; 340 } 341 } 342 343 @Test public void testSameInstanceCanBeUsedAsDifferentTypeWhenCaching() { 344 MutablePicoContainer pico = createPicoContainer(null); 345 pico.as(Characteristics.CACHE).addComponent("wt", WashableTouchable.class); 346 pico.addComponent("nw", NeedsWashable.class); 347 pico.as(Characteristics.CACHE).addComponent("nt", NeedsTouchable.class); 348 349 NeedsWashable nw = (NeedsWashable)pico.getComponent("nw"); 350 NeedsTouchable nt = (NeedsTouchable)pico.getComponent("nt"); 351 assertSame(nw.washable, nt.touchable); 352 } 353 354 @Test public void testRegisterComponentWithObjectBadType() throws PicoCompositionException { 355 MutablePicoContainer pico = createPicoContainer(null); 356 357 try { 358 pico.addComponent(Serializable.class, new Object()); 359 fail("Shouldn't be able to register an Object.class as Serializable because it is not, " + 360 "it does not implement it, Object.class does not implement much."); 361 } catch (ClassCastException e) { 362 assertNotNull(e.getMessage()); 363 } 364 365 } 366 367 public static class JMSService { 368 public final String serverid; 369 public final String path; 370 371 public JMSService(String serverid, String path) { 372 this.serverid = serverid; 373 this.path = path; 374 } 375 } 376 377 // http://jira.codehaus.org/secure/ViewIssue.jspa?key=PICO-52 378 @Test public void testPico52() { 379 MutablePicoContainer pico = createPicoContainer(null); 380 381 pico.addComponent("foo", JMSService.class, new ConstantParameter("0"), new ConstantParameter("something")); 382 JMSService jms = (JMSService)pico.getComponent("foo"); 383 assertEquals("0", jms.serverid); 384 assertEquals("something", jms.path); 385 } 386 387 public static class ComponentA { 388 public final ComponentC c; 389 390 public ComponentA(ComponentB b, ComponentC c) { 391 this.c = c; 392 assertNotNull(b); 393 assertNotNull(c); 394 } 395 } 396 397 public static class ComponentB { 398 //Does nothing. 399 } 400 401 public static class ComponentC { 402 //Does nothing. 403 } 404 405 public static class ComponentD { 406 public ComponentD(ComponentE e, ComponentB b) { 407 assertNotNull(e); 408 assertNotNull(b); 409 } 410 } 411 412 public static class ComponentE { 413 public ComponentE(ComponentD d) { 414 assertNotNull(d); 415 } 416 } 417 418 public static class ComponentF { 419 public ComponentF(ComponentA a) { 420 assertNotNull(a); 421 } 422 } 423 424 @Test public void testAggregatedVerificationException() { 425 MutablePicoContainer pico = createPicoContainer(null); 426 pico.addComponent(ComponentA.class); 427 pico.addComponent(ComponentE.class); 428 try { 429 new VerifyingVisitor().traverse(pico); 430 fail("we expect a PicoVerificationException"); 431 } catch (PicoVerificationException e) { 432 List nested = e.getNestedExceptions(); 433 assertEquals(2, nested.size()); 434 assertTrue(-1 != e.getMessage().indexOf(ComponentA.class.getName())); 435 assertTrue(-1 != e.getMessage().indexOf(ComponentE.class.getName())); 436 } 437 } 438 439 // An adapter has no longer a hosting container. 440 441// @Test public void testRegistrationOfAdapterSetsHostingContainerAsSelf() { 442// final InstanceAdapter componentAdapter = new InstanceAdapter("", new Object()); 443// final MutablePicoContainer picoContainer = createPicoContainer(null); 444// picoContainer.addAdapter(componentAdapter); 445// assertSame(picoContainer, componentAdapter.getContainer()); 446// } 447 448 public static class ContainerDependency { 449 public ContainerDependency(PicoContainer container) { 450 assertNotNull(container); 451 } 452 } 453 454 // ImplicitPicoContainer injection is bad. It is an open door for hackers. Developers with 455 // special PicoContainer needs should specifically register() a comtainer they want components to 456 // be able to pick up on. 457 458// @Test public void testImplicitPicoContainerInjection() { 459// MutablePicoContainer pico = createPicoContainer(null); 460// pico.addAdapter(ContainerDependency.class); 461// ContainerDependency dep = (ContainerDependency) pico.getComponent(ContainerDependency.class); 462// assertSame(pico, dep.pico); 463// } 464 465 @Test public void testShouldReturnNullWhenUnregistereingUnmanagedComponent() { 466 final MutablePicoContainer pico = createPicoContainer(null); 467 assertNull(pico.removeComponentByInstance("yo")); 468 } 469 470 @Test public void testShouldReturnNullForComponentAdapterOfUnregisteredType() { 471 final MutablePicoContainer pico = createPicoContainer(null); 472 assertNull(pico.getComponent(List.class)); 473 } 474 475 @Test public void testShouldReturnNonMutableParent() { 476 DefaultPicoContainer parent = new DefaultPicoContainer(); 477 final MutablePicoContainer picoContainer = createPicoContainer(parent); 478 assertNotSame(parent, picoContainer.getParent()); 479 assertFalse(picoContainer.getParent() instanceof MutablePicoContainer); 480 } 481 482 class Foo implements Startable, Disposable { 483 public boolean started; 484 public boolean stopped; 485 public boolean disposed; 486 487 public void start() { 488 started = true; 489 } 490 491 public void stop() { 492 stopped = true; 493 } 494 495 public void dispose() { 496 disposed = true; 497 } 498 499 } 500 501 @Test public void testContainerCascadesDefaultLifecycle() { 502 final MutablePicoContainer picoContainer = createPicoContainer(null); 503 Foo foo = new Foo(); 504 picoContainer.addComponent(foo); 505 picoContainer.start(); 506 assertEquals(true, foo.started); 507 picoContainer.stop(); 508 assertEquals(true, foo.stopped); 509 picoContainer.dispose(); 510 assertEquals(true, foo.disposed); 511 } 512 513 @Test public void testComponentInstancesFromParentsAreNotDirectlyAccessible2() { 514 final MutablePicoContainer a = createPicoContainer(null); 515 final MutablePicoContainer b = createPicoContainer(a); 516 final MutablePicoContainer c = createPicoContainer(b); 517 518 Object ao = new Object(); 519 Object bo = new Object(); 520 Object co = new Object(); 521 522 a.addComponent("a", ao); 523 b.addComponent("b", bo); 524 c.addComponent("c", co); 525 526 assertEquals(1, a.getComponents().size()); 527 assertEquals(1, b.getComponents().size()); 528 assertEquals(1, c.getComponents().size()); 529 } 530 531 @Test public void testStartStopAndDisposeCascadedtoChildren() { 532 final MutablePicoContainer parent = createPicoContainer(null); 533 parent.addComponent(new StringBuffer()); 534 final MutablePicoContainer child = createPicoContainer(parent); 535 parent.addChildContainer(child); 536 child.addComponent(LifeCycleMonitoring.class); 537 parent.start(); 538 try { 539 child.start(); 540 fail("IllegalStateException expected"); 541 } catch (IllegalStateException e) { 542 assertEquals("child already started", "Cannot start. Current container state was: STARTED", e.getMessage()); 543 } 544 parent.stop(); 545 try { 546 child.stop(); 547 fail("IllegalStateException expected"); 548 } catch (IllegalStateException e) { 549 assertEquals("child not started", "Cannot stop. Current container state was: STOPPED", e.getMessage()); 550 } 551 parent.dispose(); 552 try { 553 child.dispose(); 554 fail("IllegalStateException expected"); 555 } catch (IllegalStateException e) { 556 assertEquals("child already disposed", "Cannot dispose. Current lifecycle state is: DISPOSED", e.getMessage()); 557 } 558 559 } 560 561 @Test public void testMakingOfChildContainer() { 562 final MutablePicoContainer parent = createPicoContainer(null); 563 MutablePicoContainer child = parent.makeChildContainer(); 564 assertNotNull(child); 565 } 566 567 @Test public void testMakingOfChildContainerPercolatesLifecycleManager() { 568 final MutablePicoContainer parent = createPicoContainer(null); 569 parent.addComponent("one", TestLifecycleComponent.class); 570 MutablePicoContainer child = parent.makeChildContainer(); 571 assertNotNull(child); 572 child.addComponent("two", TestLifecycleComponent.class); 573 parent.start(); 574 try { 575 child.start(); 576 } catch (IllegalStateException e) { 577 assertEquals("child already started", "Cannot start. Current container state was: STARTED", e.getMessage()); 578 } 579 //TODO - The Behavior reference in child containers is not used. Thus is is almost pointless 580 // The reason is because DefaultPicoContainer's accept() method visits child containers' on its own. 581 // This may be file for visiting components in a tree for general cases, but for lifecycle, we 582 // should hand to each Behavior's start(..) at each appropriate node. See mail-list discussion. 583 } 584 585 @SuppressWarnings("unused") 586 public static final class TestBehavior extends AbstractBehavior implements Behavior { 587 588 public final ArrayList<PicoContainer> started = new ArrayList<PicoContainer>(); 589 590 public TestBehavior(ComponentAdapter delegate) { 591 super(delegate); 592 } 593 594 @Override 595 public void start(PicoContainer node) { 596 started.add(node); 597 } 598 599 @Override 600 public void stop(PicoContainer node) { 601 //Does nothing. 602 } 603 604 @Override 605 public void dispose(PicoContainer node) { 606 //Does nothing. 607 } 608 609 @Override 610 public boolean componentHasLifecycle() { 611 return true; 612 } 613 614 public String getDescriptor() { 615 return null; 616 } 617 } 618 619 public static class TestLifecycleComponent implements Startable { 620 public boolean started; 621 622 public void start() { 623 started = true; 624 } 625 626 public void stop() { 627 //Does nothing. 628 } 629 } 630 631 @Test public void testStartStopAndDisposeNotCascadedtoRemovedChildren() { 632 final MutablePicoContainer parent = createPicoContainer(null); 633 parent.addComponent(new StringBuffer()); 634 StringBuffer sb = parent.getComponents(StringBuffer.class).get(0); 635 636 final MutablePicoContainer child = createPicoContainer(parent); 637 assertEquals(parent, parent.addChildContainer(child)); 638 child.addComponent(LifeCycleMonitoring.class); 639 assertTrue(parent.removeChildContainer(child)); 640 parent.start(); 641 assertTrue(sb.toString().indexOf("-started") == -1); 642 parent.stop(); 643 assertTrue(sb.toString().indexOf("-stopped") == -1); 644 parent.dispose(); 645 assertTrue(sb.toString().indexOf("-disposed") == -1); 646 } 647 648 @Test public void testShouldCascadeStartStopAndDisposeToChild() { 649 650 StringBuffer sb = new StringBuffer(); 651 final MutablePicoContainer parent = createPicoContainer(null); 652 parent.addComponent(sb); 653 parent.addComponent(Map.class, HashMap.class); 654 655 final MutablePicoContainer child = parent.makeChildContainer(); 656 child.addComponent(LifeCycleMonitoring.class); 657 658 Map map = parent.getComponent(Map.class); 659 assertNotNull(map); 660 parent.start(); 661 try { 662 child.start(); 663 fail("IllegalStateException expected"); 664 } catch (IllegalStateException e) { 665 assertEquals("child already started", "Cannot start. Current container state was: STARTED", e.getMessage()); 666 } 667 parent.stop(); 668 try { 669 child.stop(); 670 fail("IllegalStateException expected"); 671 } catch (IllegalStateException e) { 672 assertEquals("child not started", "Cannot stop. Current container state was: STOPPED", e.getMessage()); 673 } 674 parent.dispose(); 675 try { 676 child.dispose(); 677 fail("IllegalStateException expected"); 678 } catch (IllegalStateException e) { 679 assertEquals("child already disposed", "Cannot dispose. Current lifecycle state is: DISPOSED", e.getMessage()); 680 } 681 } 682 683 public static final class LifeCycleMonitoring implements Startable, Disposable { 684 final StringBuffer sb; 685 686 public LifeCycleMonitoring(StringBuffer sb) { 687 this.sb = sb; 688 sb.append("-instantiated"); 689 } 690 691 public void start() { 692 sb.append("-started"); 693 } 694 695 public void stop() { 696 sb.append("-stopped"); 697 } 698 699 public void dispose() { 700 sb.append("-disposed"); 701 } 702 } 703 704 public static class RecordingStrategyVisitor extends AbstractPicoVisitor { 705 706 private final List<Object> list; 707 708 public RecordingStrategyVisitor(List<Object> list) { 709 this.list = list; 710 } 711 712 public boolean visitContainer(PicoContainer pico) { 713 list.add(pico.getClass()); 714 return CONTINUE_TRAVERSAL; 715 } 716 717 public void visitComponentAdapter(ComponentAdapter componentAdapter) { 718 list.add(componentAdapter.getClass()); 719 } 720 721 public void visitComponentFactory(ComponentFactory componentFactory) { 722 list.add(componentFactory.getClass()); 723 } 724 725 public void visitParameter(Parameter parameter) { 726 list.add(parameter.getClass()); 727 } 728 729 } 730 731 protected abstract Properties[] getProperties(); 732 733 @Test public void testAcceptImplementsBreadthFirstStrategy() { 734 final MutablePicoContainer parent = createPicoContainer(null); 735 final MutablePicoContainer child = parent.makeChildContainer(); 736 ComponentAdapter hashMapAdapter = 737 parent.as(getProperties()).addAdapter(new ConstructorInjector(HashMap.class, HashMap.class, null, new NullComponentMonitor(), false)) 738 .getComponentAdapter(HashMap.class, (NameBinding) null); 739 ComponentAdapter hashSetAdapter = 740 parent.as(getProperties()).addAdapter(new ConstructorInjector(HashSet.class, HashSet.class, null, new NullComponentMonitor(), false)) 741 .getComponentAdapter(HashSet.class, (NameBinding) null); 742 InstanceAdapter instanceAdapter = new InstanceAdapter(String.class, "foo", 743 new NullLifecycleStrategy(), 744 new NullComponentMonitor()); 745 ComponentAdapter stringAdapter = parent.as(getProperties()).addAdapter(instanceAdapter).getComponentAdapter(instanceAdapter.getComponentKey()); 746 ComponentAdapter arrayListAdapter = 747 child.as(getProperties()).addAdapter(new ConstructorInjector(ArrayList.class, ArrayList.class, null, new NullComponentMonitor(), false)) 748 .getComponentAdapter(ArrayList.class, (NameBinding) null); 749 Parameter componentParameter = BasicComponentParameter.BASIC_DEFAULT; 750 Parameter throwableParameter = new ConstantParameter(new Throwable("bar")); 751 ConstructorInjector ci = new ConstructorInjector(Exception.class, Exception.class, new Parameter[] {componentParameter, 752 throwableParameter}, new NullComponentMonitor(), false); 753 ComponentAdapter exceptionAdapter = child.as(getProperties()).addAdapter(ci).getComponentAdapter(Exception.class, (NameBinding) null); 754 755 List<Class> expectedList = new ArrayList<Class>(); 756 757 addContainers(expectedList); 758 addDefaultComponentFactories(expectedList); 759 expectedList.add(hashMapAdapter.getClass()); 760 expectedList.add(hashSetAdapter.getClass()); 761 expectedList.add(stringAdapter.getClass()); 762 addContainers(expectedList); 763 addDefaultComponentFactories(expectedList); 764 expectedList.add(arrayListAdapter.getClass()); 765 expectedList.add(exceptionAdapter.getClass()); 766 expectedList.add(componentParameter.getClass()); 767 expectedList.add(throwableParameter.getClass()); 768 List<Object> visitedList = new LinkedList<Object>(); 769 PicoVisitor visitor = new RecordingStrategyVisitor(visitedList); 770 visitor.traverse(parent); 771 assertEquals(expectedList.size(), visitedList.size()); 772 for (Class c : expectedList) { 773 assertTrue(visitedList.remove(c)); 774 } 775 assertEquals(0, visitedList.size()); 776 } 777 778 /** 779 * Verifies that you can halt a container traversal. 780 */ 781 @Test 782 public void testAcceptIsAbortable() { 783 final MutablePicoContainer parent = createPicoContainer(null); 784 final MutablePicoContainer child = parent.makeChildContainer(); 785 child.addComponent("This is a test"); 786 787 TraversalCheckingVisitor parentComponentCountingVisitor = new TraversalCheckingVisitor() { 788 private int containerCount = 0; 789 790 @Override 791 @SuppressWarnings("unused") 792 public void visitComponentAdapter(ComponentAdapter<?> componentAdapter) { 793 if (containerCount == 0) { 794 fail("Should have visited a container first"); 795 } 796 fail("Should never have visited an adapter."); 797 } 798 799 @Override 800 @SuppressWarnings("unused") 801 public boolean visitContainer(PicoContainer pico) { 802 containerCount++; 803 if (containerCount > 1) { 804 return ABORT_TRAVERSAL; 805 } 806 807 return CONTINUE_TRAVERSAL; 808 } 809 810 }; 811 812 parentComponentCountingVisitor.traverse(parent); 813 } 814 815 protected void addContainers(List expectedList) { 816 expectedList.add(DefaultPicoContainer.class); 817 } 818 819 protected void addDefaultComponentFactories(List expectedList) { 820 expectedList.add(AdaptingBehavior.class); 821 } 822 823 @Test public void testAmbiguousDependencies() throws PicoCompositionException { 824 825 MutablePicoContainer pico = this.createPicoContainer(null); 826 827 // Register two Touchables that Fred will be confused about 828 pico.addComponent(SimpleTouchable.class); 829 pico.addComponent(DerivedTouchable.class); 830 831 // Register a confused DependsOnTouchable 832 pico.addComponent(DependsOnTouchable.class); 833 834 try { 835 pico.getComponent(DependsOnTouchable.class); 836 fail("DependsOnTouchable should have been confused about the two Touchables"); 837 } catch (AbstractInjector.AmbiguousComponentResolutionException e) { 838 List componentImplementations = Arrays.asList(e.getAmbiguousComponentKeys()); 839 assertTrue(componentImplementations.contains("ConstructorInjector-" + DerivedTouchable.class)); 840 assertTrue(componentImplementations.contains("ConstructorInjector-" + SimpleTouchable.class)); 841 842 assertTrue(e.getMessage().indexOf(DerivedTouchable.class.getName()) != -1); 843 assertTrue(e.getMessage().indexOf("public org.picocontainer.testmodel.DependsOnTouchable(org.picocontainer.testmodel.Touchable)") != -1); 844 } 845 } 846 847 848 public static class DerivedTouchable extends SimpleTouchable { 849 public DerivedTouchable() { 850 //Does nothing. 851 } 852 } 853 854 855 public static final class NonGreedyClass { 856 857 public final int value = 0; 858 859 public NonGreedyClass() { 860 //Do nothing. 861 } 862 863 public NonGreedyClass(ComponentA component) { 864 fail("Greedy Constructor should never have been called. Instead got: " + component); 865 } 866 867 868 } 869 870 @Test public void testNoArgConstructorToBeSelected() { 871 MutablePicoContainer pico = this.createPicoContainer(null); 872 pico.addComponent(ComponentA.class); 873 pico.addComponent(NonGreedyClass.class, NonGreedyClass.class, Parameter.ZERO); 874 875 876 NonGreedyClass instance = pico.getComponent(NonGreedyClass.class); 877 assertNotNull(instance); 878 } 879 880 public static class ConstantParameterTestService { 881 private final String arg; 882 883 public ConstantParameterTestService(String arg) { 884 this.arg = arg; 885 } 886 887 public String getArg() { 888 return arg; 889 } 890 } 891 892 893 /** 894 * Currently failing 895 */ 896 @Test 897 public void testNullConstantParameter() { 898 MutablePicoContainer pico = createPicoContainer(null); 899 pico.addComponent(ConstantParameterTestService.class, ConstantParameterTestService.class, NullParameter.INSTANCE); 900 ConstantParameterTestService service = (ConstantParameterTestService) pico.getComponent(ConstantParameterTestService.class); 901 assertNotNull(service); 902 assertNull(service.getArg()); 903 } 904 905 906 public static class PrimitiveConstructor { 907 @SuppressWarnings("unused") 908 public PrimitiveConstructor(int number) { 909 //does nothing. 910 } 911 } 912 913 @Test(expected=ParameterCannotBeNullException.class) 914 public void testNullConstantParametersDoNotInjectOnPrimitives() { 915 MutablePicoContainer pico = createPicoContainer(null); 916 pico.addComponent(PrimitiveConstructor.class, PrimitiveConstructor.class, NullParameter.INSTANCE); 917 918 //Should throw exception here. 919 pico.getComponent(PrimitiveConstructor.class); 920 } 921 922 923 @Test 924 public void testNullValuesDoNotInject() { 925 MutablePicoContainer pico = createPicoContainer(null); 926 pico.addComponent(ConstantParameterTestService.class, ConstantParameterTestService.class, new ConstantParameter(null)); 927 try { 928 ConstantParameterTestService service = (ConstantParameterTestService) pico.getComponent(ConstantParameterTestService.class); 929 fail("Should have thrown unsatisfiable dependencies exception. Instead got " + service + " as a return value"); 930 } catch (UnsatisfiableDependenciesException e) { 931 assertNotNull(e.getMessage()); 932 } 933 } 934 935 @Test 936 public void testNullComponentsDoNotInject() { 937 MutablePicoContainer pico = createPicoContainer(null) 938 .addComponent(ComponentA.class) 939 .addComponent(ComponentB.class); 940 941 942 try { 943 pico.addComponent(ComponentC.class, null); 944 fail("Pico should not have been able to register null component instance"); 945 } catch (NullPointerException e) { 946 assertNotNull(e.getMessage()); 947 } 948 } 949 950 951 public static class ConverterSample { 952 public final int value; 953 954 public ConverterSample(Integer value) { 955 this.value = value; 956 } 957 } 958 959 @Test 960 public void testIntegrationWithConverters() { 961 MutablePicoContainer mpc = new DefaultPicoContainer(); 962 if ( !(mpc instanceof Converting)) { 963 System.out.println("Skipping 'testIntegrationWithConverters' " + 964 "because pico implementation is not Converting"); 965 return; 966 } 967 968 mpc.addComponent("converterParameter", "42") 969 .addComponent(ConverterSample.class, ConverterSample.class, 970 new ComponentParameter("converterParameter")); 971 ConverterSample result = mpc.getComponent(ConverterSample.class); 972 assertEquals(42, result.value); 973 } 974 975}