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 org.picocontainer.injectors.IterativeInjector;
022    import org.picocontainer.parameters.ComponentParameter;
023    
024    import org.picocontainer.Converting;
025    
026    import java.io.ByteArrayInputStream;
027    import java.io.ByteArrayOutputStream;
028    import java.io.IOException;
029    import java.io.ObjectInputStream;
030    import java.io.ObjectOutputStream;
031    import java.io.Serializable;
032    import java.lang.reflect.Type;
033    import java.util.ArrayList;
034    import java.util.Arrays;
035    import java.util.Collection;
036    import java.util.HashMap;
037    import java.util.HashSet;
038    import java.util.Iterator;
039    import java.util.LinkedList;
040    import java.util.List;
041    import java.util.Map;
042    import java.util.Properties;
043    import java.util.Set;
044    import org.junit.Test;
045    import org.picocontainer.Behavior;
046    import org.picocontainer.Characteristics;
047    import org.picocontainer.ComponentAdapter;
048    import org.picocontainer.ComponentFactory;
049    import org.picocontainer.DefaultPicoContainer;
050    import org.picocontainer.Disposable;
051    import org.picocontainer.MutablePicoContainer;
052    import org.picocontainer.NameBinding;
053    import org.picocontainer.Parameter;
054    import org.picocontainer.PicoCompositionException;
055    import org.picocontainer.PicoContainer;
056    import org.picocontainer.PicoException;
057    import org.picocontainer.PicoVerificationException;
058    import org.picocontainer.PicoVisitor;
059    import org.picocontainer.Startable;
060    import org.picocontainer.adapters.InstanceAdapter;
061    import org.picocontainer.behaviors.AbstractBehavior;
062    import org.picocontainer.behaviors.AdaptingBehavior;
063    import org.picocontainer.injectors.AbstractInjector;
064    import org.picocontainer.injectors.ConstructorInjector;
065    import org.picocontainer.injectors.AbstractInjector.UnsatisfiableDependenciesException;
066    import org.picocontainer.injectors.SingleMemberInjector.ParameterCannotBeNullException;
067    import org.picocontainer.lifecycle.NullLifecycleStrategy;
068    import org.picocontainer.monitors.NullComponentMonitor;
069    import org.picocontainer.parameters.BasicComponentParameter;
070    import org.picocontainer.parameters.ConstantParameter;
071    import org.picocontainer.parameters.NullParameter;
072    import org.picocontainer.testmodel.DependsOnTouchable;
073    import org.picocontainer.testmodel.SimpleTouchable;
074    import org.picocontainer.testmodel.Touchable;
075    import org.picocontainer.testmodel.Washable;
076    import org.picocontainer.testmodel.WashableTouchable;
077    import org.picocontainer.visitors.AbstractPicoVisitor;
078    import org.picocontainer.visitors.TraversalCheckingVisitor;
079    import org.picocontainer.visitors.VerifyingVisitor;
080    
081    /** This test tests (at least it should) all the methods in MutablePicoContainer. */
082    @SuppressWarnings("serial")
083    public abstract class AbstractPicoContainerTest {
084    
085        protected abstract MutablePicoContainer createPicoContainer(PicoContainer parent);
086    
087        protected final MutablePicoContainer createPicoContainerWithDependsOnTouchableOnly() throws PicoCompositionException {
088            MutablePicoContainer pico = createPicoContainer(null);
089            pico.addComponent(DependsOnTouchable.class);
090            return pico;
091        }
092    
093        protected final MutablePicoContainer createPicoContainerWithTouchableAndDependsOnTouchable() throws PicoCompositionException {
094            MutablePicoContainer pico = createPicoContainerWithDependsOnTouchableOnly();
095            pico.as(Characteristics.CACHE).addComponent(Touchable.class, SimpleTouchable.class);
096            return pico;
097        }
098    
099        @Test public void testBasicInstantiationAndContainment() throws PicoException {
100            PicoContainer pico = createPicoContainerWithTouchableAndDependsOnTouchable();
101            assertTrue("Component should be instance of Touchable",
102                       Touchable.class.isAssignableFrom(pico.getComponentAdapter(Touchable.class, (NameBinding) null).getComponentImplementation()));
103        }
104    
105        @Test public void testRegisteredComponentsExistAndAreTheCorrectTypes() throws PicoException {
106            PicoContainer pico = createPicoContainerWithTouchableAndDependsOnTouchable();
107            assertNotNull("Container should have Touchable addComponent",
108                          pico.getComponentAdapter(Touchable.class, (NameBinding) null));
109            assertNotNull("Container should have DependsOnTouchable addComponent",
110                          pico.getComponentAdapter(DependsOnTouchable.class, (NameBinding) null));
111            assertTrue("Component should be instance of Touchable",
112                       pico.getComponent(Touchable.class) != null);
113            assertTrue("Component should be instance of DependsOnTouchable",
114                       pico.getComponent(DependsOnTouchable.class) != null);
115            assertNull("should not have non existent addComponent", pico.getComponentAdapter(Map.class, (NameBinding) null));
116        }
117    
118        @Test public void testRegistersSingleInstance() throws PicoException {
119            MutablePicoContainer pico = createPicoContainer(null);
120            StringBuffer sb = new StringBuffer();
121            pico.addComponent(sb);
122            assertSame(sb, pico.getComponent(StringBuffer.class));
123        }
124    
125        @Test public void testContainerIsSerializable() throws PicoException,
126                                                         IOException, ClassNotFoundException
127        {
128    
129            getTouchableFromSerializedContainer();
130    
131        }
132    
133        private Touchable getTouchableFromSerializedContainer() throws IOException, ClassNotFoundException {
134            MutablePicoContainer pico = createPicoContainerWithTouchableAndDependsOnTouchable();
135            // Add a list too, using a constant parameter
136            pico.addComponent("list", ArrayList.class, new ConstantParameter(10));
137    
138            ByteArrayOutputStream baos = new ByteArrayOutputStream();
139            ObjectOutputStream oos = new ObjectOutputStream(baos);
140    
141            oos.writeObject(pico);
142            ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
143    
144            pico = (MutablePicoContainer)ois.readObject();
145    
146            DependsOnTouchable dependsOnTouchable = pico.getComponent(DependsOnTouchable.class);
147            assertNotNull(dependsOnTouchable);
148            return pico.getComponent(Touchable.class);
149        }
150    
151        @Test public void testSerializedContainerCanRetrieveImplementation() throws PicoException,
152                                                                              IOException, ClassNotFoundException
153        {
154    
155            Touchable touchable = getTouchableFromSerializedContainer();
156    
157            SimpleTouchable simpleTouchable = (SimpleTouchable)touchable;
158    
159            assertTrue(simpleTouchable.wasTouched);
160        }
161    
162    
163        @Test public void testGettingComponentWithMissingDependencyFails() throws PicoException {
164            MutablePicoContainer picoContainer = (MutablePicoContainer) createPicoContainerWithDependsOnTouchableOnly();
165            picoContainer.setName("parent");
166            try {
167                picoContainer.getComponent(DependsOnTouchable.class);
168                fail("should need a Touchable");
169            } catch (AbstractInjector.UnsatisfiableDependenciesException e) {
170                String message = e.getMessage().replace("org.picocontainer.testmodel.", "");
171                assertEquals("DependsOnTouchable has unsatisfied dependency 'interface Touchable' for constructor 'public DependsOnTouchable(Touchable)' from parent:1<|", message);
172    
173            }
174        }
175    
176        @Test public void testDuplicateRegistration() {
177            try {
178                MutablePicoContainer pico = createPicoContainer(null);
179                pico.addComponent(Object.class);
180                pico.addComponent(Object.class);
181                fail("Should have failed with duplicate registration");
182            } catch (PicoCompositionException e) {
183                assertTrue("Wrong key", e.getMessage().indexOf(Object.class.toString()) > -1);
184            }
185        }
186    
187        @Test public void testExternallyInstantiatedObjectsCanBeRegisteredAndLookedUp() throws PicoException {
188            MutablePicoContainer pico = createPicoContainer(null);
189            final HashMap map = new HashMap();
190            pico.as(getProperties()).addComponent(Map.class, map);
191            assertSame(map, pico.getComponent(Map.class));
192        }
193    
194        @Test public void testAmbiguousResolution() throws PicoCompositionException {
195            MutablePicoContainer pico = createPicoContainer(null);
196            pico.addComponent("ping", String.class);
197            pico.addComponent("pong", "pang");
198            try {
199                pico.getComponent(String.class);
200            } catch (AbstractInjector.AmbiguousComponentResolutionException e) {
201                assertTrue(e.getMessage().indexOf("java.lang.String") != -1);
202                assertTrue(e.getMessage().indexOf("<no-component>") != -1);
203                assertTrue(e.getMessage().indexOf("<unknown>") != -1);
204            }
205        }
206    
207        @Test public void testLookupWithUnregisteredKeyReturnsNull() throws PicoCompositionException {
208            MutablePicoContainer pico = createPicoContainer(null);
209            assertNull(pico.getComponent(String.class));
210        }
211    
212        @Test public void testLookupWithUnregisteredTypeReturnsNull() throws PicoCompositionException {
213            MutablePicoContainer pico = createPicoContainer(null);
214            assertNull(pico.getComponent(String.class));
215        }
216    
217        public static class ListAdder {
218            public ListAdder(Collection<String> list) {
219                list.add("something");
220            }
221        }
222    
223        @Test public void testUnsatisfiableDependenciesExceptionGivesVerboseEnoughErrorMessage() {
224            MutablePicoContainer pico = createPicoContainer(null);
225            pico.setName("parent");
226            pico.addComponent(ComponentD.class);
227    
228            try {
229                pico.getComponent(ComponentD.class);
230            } catch (AbstractInjector.UnsatisfiableDependenciesException e) {
231                String msg = e.getMessage().replace("org.picocontainer.tck.AbstractPicoContainerTest$Component", "");
232                assertEquals("D has unsatisfied dependency 'class B' for constructor 'public D(E,B)' from parent:1<|", msg);
233            }
234        }
235    
236        @Test public void testUnsatisfiableDependenciesExceptionGivesUnsatisfiedDependencyTypes() {
237            MutablePicoContainer pico = (MutablePicoContainer) createPicoContainer(null);
238            pico.setName("parent");
239            // D depends on E and B
240            pico.addComponent(ComponentD.class);
241    
242            // first - do not register any dependency
243            // should yield first unsatisfied dependency
244            try {
245                pico.getComponent(ComponentD.class);
246            } catch (AbstractInjector.UnsatisfiableDependenciesException e) {
247    
248                String message = e.getMessage().replace("org.picocontainer.tck.AbstractPicoContainerTest$Component", "");
249                assertEquals("D has unsatisfied dependency 'class B' for constructor 'public D(E,B)' from parent:1<|", message);
250            }
251    
252            // now register only first dependency
253            // should yield second unsatisfied dependency
254            pico.addComponent(ComponentE.class);
255            try {
256                pico.getComponent(ComponentD.class);
257            } catch (AbstractInjector.UnsatisfiableDependenciesException e) {
258                String message = e.getMessage().replace("org.picocontainer.tck.AbstractPicoContainerTest$Component", "");
259                assertEquals("D has unsatisfied dependency 'class B' for constructor 'public D(E,B)' from parent:2<|", message);
260            }
261        }
262    
263        @Test public void testCyclicDependencyThrowsCyclicDependencyException() {
264            assertCyclicDependencyThrowsCyclicDependencyException(createPicoContainer(null));
265        }
266    
267        private static void assertCyclicDependencyThrowsCyclicDependencyException(MutablePicoContainer pico) {
268            pico.addComponent(ComponentB.class);
269            pico.addComponent(ComponentD.class);
270            pico.addComponent(ComponentE.class);
271    
272            try {
273                pico.getComponent(ComponentD.class);
274                fail("CyclicDependencyException expected");
275            } catch (AbstractInjector.CyclicDependencyException e) {
276                // CyclicDependencyException reports now the stack.
277                //final List dependencies = Arrays.asList(ComponentD.class.getConstructors()[0].getParameterTypes());
278                final List<Class> dependencies = Arrays.<Class>asList(ComponentD.class, ComponentE.class, ComponentD.class);
279                final List<Class> reportedDependencies = Arrays.asList(e.getDependencies());
280                assertEquals(dependencies, reportedDependencies);
281            } catch (StackOverflowError e) {
282                fail();
283            }
284        }
285    
286        @Test public void testCyclicDependencyThrowsCyclicDependencyExceptionWithParentContainer() {
287            MutablePicoContainer pico = createPicoContainer(createPicoContainer(null));
288            assertCyclicDependencyThrowsCyclicDependencyException(pico);
289        }
290    
291        @Test public void testRemovalNonRegisteredComponentAdapterWorksAndReturnsNull() {
292            final MutablePicoContainer picoContainer = createPicoContainer(null);
293            assertNull(picoContainer.removeComponent("COMPONENT DOES NOT EXIST"));
294        }
295    
296        /** Important! Nanning really, really depends on this! */
297        @Test public void testComponentAdapterRegistrationOrderIsMaintained() throws NoSuchMethodException {
298    
299            ConstructorInjector c1 = new ConstructorInjector("1", Object.class, null, new NullComponentMonitor(), false);
300            ConstructorInjector c2 = new ConstructorInjector("2", String.class, null, new NullComponentMonitor(), false);
301    
302            MutablePicoContainer picoContainer = createPicoContainer(null);
303            picoContainer.addAdapter(c1).addAdapter(c2);
304            Collection<ComponentAdapter<?>> list2 = picoContainer.getComponentAdapters();
305            //registration order should be maintained
306            assertEquals(2, list2.size());
307            assertEquals(c1.getComponentKey(), ((ComponentAdapter)list2.toArray()[0]).getComponentKey());
308            assertEquals(c2.getComponentKey(), ((ComponentAdapter)list2.toArray()[1]).getComponentKey());
309    
310            picoContainer.getComponents(); // create all the instances at once
311            assertFalse("instances should be created in same order as adapters are created",
312                        picoContainer.getComponents().get(0) instanceof String);
313            assertTrue("instances should be created in same order as adapters are created",
314                       picoContainer.getComponents().get(1) instanceof String);
315    
316            MutablePicoContainer reversedPicoContainer = createPicoContainer(null);
317            reversedPicoContainer.addAdapter(c2);
318            reversedPicoContainer.addAdapter(c1);
319            //registration order should be maintained
320            list2 = reversedPicoContainer.getComponentAdapters();
321            assertEquals(2, list2.size());
322            assertEquals(c2.getComponentKey(), ((ComponentAdapter)list2.toArray()[0]).getComponentKey());
323            assertEquals(c1.getComponentKey(), ((ComponentAdapter)list2.toArray()[1]).getComponentKey());
324    
325            reversedPicoContainer.getComponents(); // create all the instances at once
326            assertTrue("instances should be created in same order as adapters are created",
327                       reversedPicoContainer.getComponents().get(0) instanceof String);
328            assertFalse("instances should be created in same order as adapters are created",
329                        reversedPicoContainer.getComponents().get(1) instanceof String);
330        }
331    
332        public static final class NeedsTouchable {
333            public final Touchable touchable;
334    
335            public NeedsTouchable(Touchable touchable) {
336                this.touchable = touchable;
337            }
338        }
339    
340        public static final class NeedsWashable {
341            public final Washable washable;
342    
343            public NeedsWashable(Washable washable) {
344                this.washable = washable;
345            }
346        }
347    
348        @Test public void testSameInstanceCanBeUsedAsDifferentTypeWhenCaching() {
349            MutablePicoContainer pico = createPicoContainer(null);
350            pico.as(Characteristics.CACHE).addComponent("wt", WashableTouchable.class);
351            pico.addComponent("nw", NeedsWashable.class);
352            pico.as(Characteristics.CACHE).addComponent("nt", NeedsTouchable.class);
353    
354            NeedsWashable nw = (NeedsWashable)pico.getComponent("nw");
355            NeedsTouchable nt = (NeedsTouchable)pico.getComponent("nt");
356            assertSame(nw.washable, nt.touchable);
357        }
358    
359        @Test public void testRegisterComponentWithObjectBadType() throws PicoCompositionException {
360            MutablePicoContainer pico = createPicoContainer(null);
361    
362            try {
363                pico.addComponent(Serializable.class, new Object());
364                fail("Shouldn't be able to register an Object.class as Serializable because it is not, " +
365                     "it does not implement it, Object.class does not implement much.");
366            } catch (ClassCastException e) {
367                assertNotNull(e.getMessage());
368            }
369    
370        }
371    
372        public static class JMSService {
373            public final String serverid;
374            public final String path;
375    
376            public JMSService(String serverid, String path) {
377                this.serverid = serverid;
378                this.path = path;
379            }
380        }
381    
382        // http://jira.codehaus.org/secure/ViewIssue.jspa?key=PICO-52
383        @Test public void testPico52() {
384            MutablePicoContainer pico = createPicoContainer(null);
385    
386            pico.addComponent("foo", JMSService.class, new ConstantParameter("0"), new ConstantParameter("something"));
387            JMSService jms = (JMSService)pico.getComponent("foo");
388            assertEquals("0", jms.serverid);
389            assertEquals("something", jms.path);
390        }
391    
392        public static class ComponentA {
393            public final ComponentC c;
394    
395            public ComponentA(ComponentB b, ComponentC c) {
396                this.c = c;
397                assertNotNull(b);
398                assertNotNull(c);
399            }
400        }
401    
402        public static class ComponentB {
403            //Does nothing.
404        }
405    
406        public static class ComponentC {
407            //Does nothing.
408        }
409    
410        public static class ComponentD {
411            public ComponentD(ComponentE e, ComponentB b) {
412                assertNotNull(e);
413                assertNotNull(b);
414            }
415        }
416    
417        public static class ComponentE {
418            public ComponentE(ComponentD d) {
419                assertNotNull(d);
420            }
421        }
422    
423        public static class ComponentF {
424            public ComponentF(ComponentA a) {
425                assertNotNull(a);
426            }
427        }
428    
429        @Test 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    //    @Test 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    //    @Test 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        @Test public void testShouldReturnNullWhenUnregistereingUnmanagedComponent() {
471            final MutablePicoContainer pico = createPicoContainer(null);
472            assertNull(pico.removeComponentByInstance("yo"));
473        }
474    
475        @Test public void testShouldReturnNullForComponentAdapterOfUnregisteredType() {
476            final MutablePicoContainer pico = createPicoContainer(null);
477            assertNull(pico.getComponent(List.class));
478        }
479    
480        @Test 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        @Test 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        @Test 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        @Test 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", "Cannot start.  Current container state was: 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", "Cannot stop.  Current container state was: STOPPED", e.getMessage());
555            }
556            parent.dispose();
557            try {
558                child.dispose();
559                fail("IllegalStateException expected");
560            } catch (IllegalStateException e) {
561                assertEquals("child already disposed", "Cannot dispose.  Current lifecycle state is: DISPOSED", e.getMessage());
562            }
563    
564        }
565    
566        @Test public void testMakingOfChildContainer() {
567            final MutablePicoContainer parent = createPicoContainer(null);
568            MutablePicoContainer child = parent.makeChildContainer();
569            assertNotNull(child);
570        }
571    
572        @Test 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", "Cannot start.  Current container state was: 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        @SuppressWarnings("unused") 
591        public static final class TestBehavior extends AbstractBehavior implements Behavior {
592    
593            public final ArrayList<PicoContainer> started = new ArrayList<PicoContainer>();
594    
595            public TestBehavior(ComponentAdapter delegate) {
596                super(delegate);
597            }
598    
599            @Override
600            public void start(PicoContainer node) {
601                started.add(node);
602            }
603    
604            @Override
605            public void stop(PicoContainer node) {
606                //Does nothing.
607            }
608    
609            @Override
610            public void dispose(PicoContainer node) {
611                //Does nothing.
612            }
613    
614            @Override
615            public boolean componentHasLifecycle() {
616                return true;
617            }
618    
619            public String getDescriptor() {
620                return null;
621            }
622        }
623    
624        public static class TestLifecycleComponent implements Startable {
625            public boolean started;
626    
627            public void start() {
628                started = true;
629            }
630    
631            public void stop() {
632                //Does nothing.
633            }
634        }
635    
636        @Test public void testStartStopAndDisposeNotCascadedtoRemovedChildren() {
637            final MutablePicoContainer parent = createPicoContainer(null);
638            parent.addComponent(new StringBuffer());
639            StringBuffer sb = parent.getComponents(StringBuffer.class).get(0);
640    
641            final MutablePicoContainer child = createPicoContainer(parent);
642            assertEquals(parent, parent.addChildContainer(child));
643            child.addComponent(LifeCycleMonitoring.class);
644            assertTrue(parent.removeChildContainer(child));
645            parent.start();
646            assertTrue(sb.toString().indexOf("-started") == -1);
647            parent.stop();
648            assertTrue(sb.toString().indexOf("-stopped") == -1);
649            parent.dispose();
650            assertTrue(sb.toString().indexOf("-disposed") == -1);
651        }
652    
653        @Test public void testShouldCascadeStartStopAndDisposeToChild() {
654    
655            StringBuffer sb = new StringBuffer();
656            final MutablePicoContainer parent = createPicoContainer(null);
657            parent.addComponent(sb);
658            parent.addComponent(Map.class, HashMap.class);
659    
660            final MutablePicoContainer child = parent.makeChildContainer();
661            child.addComponent(LifeCycleMonitoring.class);
662    
663            Map map = parent.getComponent(Map.class);
664            assertNotNull(map);
665            parent.start();
666            try {
667                child.start();
668                fail("IllegalStateException expected");
669            } catch (IllegalStateException e) {
670                assertEquals("child already started", "Cannot start.  Current container state was: STARTED", e.getMessage());
671            }
672            parent.stop();
673            try {
674                child.stop();
675                fail("IllegalStateException expected");
676            } catch (IllegalStateException e) {
677                assertEquals("child not started", "Cannot stop.  Current container state was: STOPPED", e.getMessage());
678            }
679            parent.dispose();
680            try {
681                child.dispose();
682                fail("IllegalStateException expected");
683            } catch (IllegalStateException e) {
684                assertEquals("child already disposed", "Cannot dispose.  Current lifecycle state is: DISPOSED", e.getMessage());
685            }
686        }
687    
688        public static final class LifeCycleMonitoring implements Startable, Disposable {
689            final StringBuffer sb;
690    
691            public LifeCycleMonitoring(StringBuffer sb) {
692                this.sb = sb;
693                sb.append("-instantiated");
694            }
695    
696            public void start() {
697                sb.append("-started");
698            }
699    
700            public void stop() {
701                sb.append("-stopped");
702            }
703    
704            public void dispose() {
705                sb.append("-disposed");
706            }
707        }
708    
709        public static class RecordingStrategyVisitor extends AbstractPicoVisitor {
710    
711            private final List<Object> list;
712    
713            public RecordingStrategyVisitor(List<Object> list) {
714                this.list = list;
715            }
716    
717            public boolean visitContainer(PicoContainer pico) {
718                list.add(pico.getClass());
719                return CONTINUE_TRAVERSAL;
720            }
721    
722            public void visitComponentAdapter(ComponentAdapter componentAdapter) {
723                list.add(componentAdapter.getClass());
724            }
725    
726            public void visitComponentFactory(ComponentFactory componentFactory) {
727                list.add(componentFactory.getClass());
728            }
729    
730            public void visitParameter(Parameter parameter) {
731                list.add(parameter.getClass());
732            }
733    
734        }
735    
736        protected abstract Properties[] getProperties();
737    
738        @Test public void testAcceptImplementsBreadthFirstStrategy() {
739            final MutablePicoContainer parent = createPicoContainer(null);
740            final MutablePicoContainer child = parent.makeChildContainer();
741            ComponentAdapter hashMapAdapter =
742                parent.as(getProperties()).addAdapter(new ConstructorInjector(HashMap.class, HashMap.class, null, new NullComponentMonitor(), false))
743                    .getComponentAdapter(HashMap.class, (NameBinding) null);
744            ComponentAdapter hashSetAdapter =
745                parent.as(getProperties()).addAdapter(new ConstructorInjector(HashSet.class, HashSet.class, null, new NullComponentMonitor(), false))
746                    .getComponentAdapter(HashSet.class, (NameBinding) null);
747            InstanceAdapter instanceAdapter = new InstanceAdapter(String.class, "foo",
748                                                                  new NullLifecycleStrategy(),
749                                                                  new NullComponentMonitor());
750            ComponentAdapter stringAdapter = parent.as(getProperties()).addAdapter(instanceAdapter).getComponentAdapter(instanceAdapter.getComponentKey());
751            ComponentAdapter arrayListAdapter =
752                child.as(getProperties()).addAdapter(new ConstructorInjector(ArrayList.class, ArrayList.class, null, new NullComponentMonitor(), false))
753                    .getComponentAdapter(ArrayList.class, (NameBinding) null);
754            Parameter componentParameter = BasicComponentParameter.BASIC_DEFAULT;
755            Parameter throwableParameter = new ConstantParameter(new Throwable("bar"));
756            ConstructorInjector ci = new ConstructorInjector(Exception.class, Exception.class, new Parameter[] {componentParameter,
757                                                             throwableParameter}, new NullComponentMonitor(), false);
758            ComponentAdapter exceptionAdapter = child.as(getProperties()).addAdapter(ci).getComponentAdapter(Exception.class, (NameBinding) null);
759    
760            List<Class> expectedList = new ArrayList<Class>();
761    
762            addContainers(expectedList);
763            addDefaultComponentFactories(expectedList);
764            expectedList.add(hashMapAdapter.getClass());
765            expectedList.add(hashSetAdapter.getClass());
766            expectedList.add(stringAdapter.getClass());
767            addContainers(expectedList);
768            addDefaultComponentFactories(expectedList);
769            expectedList.add(arrayListAdapter.getClass());
770            expectedList.add(exceptionAdapter.getClass());
771            expectedList.add(componentParameter.getClass());
772            expectedList.add(throwableParameter.getClass());
773            List<Object> visitedList = new LinkedList<Object>();
774            PicoVisitor visitor = new RecordingStrategyVisitor(visitedList);
775            visitor.traverse(parent);
776            assertEquals(expectedList.size(), visitedList.size());
777            for (Class c : expectedList) {
778                assertTrue(visitedList.remove(c));
779            }
780            assertEquals(0, visitedList.size());
781        }
782        
783        /**
784         * Verifies that you can halt a container traversal.
785         */
786        @Test
787        public void testAcceptIsAbortable() {
788            final MutablePicoContainer parent = createPicoContainer(null);
789            final MutablePicoContainer child = parent.makeChildContainer();
790            child.addComponent("This is a test");
791            
792            TraversalCheckingVisitor parentComponentCountingVisitor = new TraversalCheckingVisitor() {
793                    private int containerCount = 0;
794                    
795                            @Override
796                @SuppressWarnings("unused") 
797                            public void visitComponentAdapter(ComponentAdapter<?> componentAdapter) {
798                                    if (containerCount == 0) {
799                                            fail("Should have visited a container first");
800                                    }
801                                    fail("Should never have visited an adapter.");
802                            }
803    
804                            @Override
805                    @SuppressWarnings("unused") 
806                            public boolean visitContainer(PicoContainer pico) {
807                                    containerCount++;
808                                    if (containerCount > 1) {
809                                            return ABORT_TRAVERSAL;
810                                    }
811                                    
812                                    return CONTINUE_TRAVERSAL;
813                            }
814                    
815            };
816            
817            parentComponentCountingVisitor.traverse(parent);        
818        }
819    
820        protected void addContainers(List expectedList) {
821            expectedList.add(DefaultPicoContainer.class);
822        }
823        
824        protected void addDefaultComponentFactories(List expectedList) {
825            expectedList.add(AdaptingBehavior.class);
826        }
827    
828        @Test public void testAmbiguousDependencies() throws PicoCompositionException {
829    
830            MutablePicoContainer pico = this.createPicoContainer(null);
831    
832            // Register two Touchables that Fred will be confused about
833            pico.addComponent(SimpleTouchable.class);
834            pico.addComponent(DerivedTouchable.class);
835    
836            // Register a confused DependsOnTouchable
837            pico.addComponent(DependsOnTouchable.class);
838    
839            try {
840                pico.getComponent(DependsOnTouchable.class);
841                fail("DependsOnTouchable should have been confused about the two Touchables");
842            } catch (AbstractInjector.AmbiguousComponentResolutionException e) {
843                List componentImplementations = Arrays.asList(e.getAmbiguousComponentKeys());
844                assertTrue(componentImplementations.contains(DerivedTouchable.class));
845                assertTrue(componentImplementations.contains(SimpleTouchable.class));
846    
847                assertTrue(e.getMessage().indexOf(DerivedTouchable.class.getName()) != -1);
848                assertTrue(e.getMessage().indexOf("public org.picocontainer.testmodel.DependsOnTouchable(org.picocontainer.testmodel.Touchable)") != -1);
849            }
850        }
851    
852    
853        public static class DerivedTouchable extends SimpleTouchable {
854            public DerivedTouchable() {
855                //Does nothing.
856            }
857        }
858    
859    
860        public static final class NonGreedyClass {
861    
862            public final int value = 0;
863    
864            public NonGreedyClass() {
865                //Do nothing.
866            }
867    
868            public NonGreedyClass(ComponentA component) {
869                fail("Greedy Constructor should never have been called.  Instead got: " + component);
870            }
871    
872    
873        }
874    
875        @Test public void testNoArgConstructorToBeSelected() {
876            MutablePicoContainer pico = this.createPicoContainer(null);
877            pico.addComponent(ComponentA.class);
878            pico.addComponent(NonGreedyClass.class, NonGreedyClass.class, Parameter.ZERO);
879    
880    
881            NonGreedyClass instance = pico.getComponent(NonGreedyClass.class);
882            assertNotNull(instance);
883        }
884    
885        public static class ConstantParameterTestService {
886            private final String arg;
887            
888            public ConstantParameterTestService(String arg) {
889                            this.arg = arg;                 
890            }
891    
892                    public String getArg() {
893                            return arg;
894                    }
895        }
896        
897        
898        /**
899         * Currently failing
900         */
901        @Test
902        public void testNullConstantParameter() {
903            MutablePicoContainer pico = createPicoContainer(null);
904            pico.addComponent(ConstantParameterTestService.class, ConstantParameterTestService.class, NullParameter.INSTANCE);
905            ConstantParameterTestService service = (ConstantParameterTestService) pico.getComponent(ConstantParameterTestService.class);
906            assertNotNull(service);
907            assertNull(service.getArg());
908        }
909        
910        
911        public static class PrimitiveConstructor {
912            @SuppressWarnings("unused")
913            public PrimitiveConstructor(int number) {
914                //does nothing.
915            }
916        }
917    
918        @Test(expected=ParameterCannotBeNullException.class)
919        public void testNullConstantParametersDoNotInjectOnPrimitives() {
920            MutablePicoContainer pico = createPicoContainer(null);
921            pico.addComponent(PrimitiveConstructor.class, PrimitiveConstructor.class, NullParameter.INSTANCE);
922            
923            //Should throw exception here.
924            pico.getComponent(PrimitiveConstructor.class);
925         }
926    
927        
928        @Test
929        public void testNullValuesDoNotInject() {
930            MutablePicoContainer pico = createPicoContainer(null);
931            pico.addComponent(ConstantParameterTestService.class, ConstantParameterTestService.class, new ConstantParameter(null));
932            try {
933                ConstantParameterTestService service = (ConstantParameterTestService) pico.getComponent(ConstantParameterTestService.class);
934                fail("Should have thrown unsatisfiable dependencies exception.  Instead got " + service + " as a return value");
935            } catch (UnsatisfiableDependenciesException e) {
936                assertNotNull(e.getMessage());
937            }
938        }
939        
940        @Test
941        public void testNullComponentsDoNotInject() {
942            MutablePicoContainer pico = createPicoContainer(null)
943                .addComponent(ComponentA.class)
944                .addComponent(ComponentB.class);
945            
946            
947            try {
948                pico.addComponent(ComponentC.class, null);
949                fail("Pico should not have been able to register null component instance");
950            } catch (NullPointerException e) {
951                assertNotNull(e.getMessage());
952            }
953        }
954    
955        
956        public static class ConverterSample {
957            public final int value;
958    
959            public ConverterSample(Integer value) {
960                this.value = value;
961            }
962        }
963        
964        @Test
965        public void testIntegrationWithConverters() {        
966            MutablePicoContainer mpc = new DefaultPicoContainer();
967            if ( !(mpc instanceof Converting)) {
968                System.out.println("Skipping 'testIntegrationWithConverters' " +
969                            "because pico implementation is not Converting");
970                return;
971            }
972            
973            mpc.addComponent("converterParameter", "42")
974                .addComponent(ConverterSample.class, ConverterSample.class, 
975                        new ComponentParameter("converterParameter"));
976            ConverterSample result = mpc.getComponent(ConverterSample.class);
977            assertEquals(42, result.value);
978        }
979        
980    }