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