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