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