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