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