    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    }