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