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