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