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     * Idea by Rachel Davies, Original code by Aslak Hellesoy and Paul Hammant   *
009     *****************************************************************************/
010    package org.picocontainer.defaults;
011    
012    import org.picocontainer.ComponentAdapter;
013    import org.picocontainer.ComponentMonitor;
014    import org.picocontainer.MutablePicoContainer;
015    import org.picocontainer.Parameter;
016    import org.picocontainer.PicoContainer;
017    import org.picocontainer.PicoInitializationException;
018    import org.picocontainer.PicoIntrospectionException;
019    import org.picocontainer.PicoVisitor;
020    import org.picocontainer.Startable;
021    import org.picocontainer.alternatives.EmptyPicoContainer;
022    import org.picocontainer.monitors.DefaultComponentMonitor;
023    import org.picocontainer.monitors.WriterComponentMonitor;
024    import org.picocontainer.tck.AbstractPicoContainerTestCase;
025    import org.picocontainer.testmodel.DecoratedTouchable;
026    import org.picocontainer.testmodel.DependsOnTouchable;
027    import org.picocontainer.testmodel.SimpleTouchable;
028    import org.picocontainer.testmodel.Touchable;
029    
030    import java.io.Serializable;
031    import java.io.StringWriter;
032    import java.lang.reflect.Method;
033    import java.util.ArrayList;
034    import java.util.Collection;
035    import java.util.HashMap;
036    import java.util.LinkedList;
037    import java.util.List;
038    
039    /**
040     * @author Aslak Hellesøy
041     * @author Paul Hammant
042     * @author Ward Cunningham
043     * @author Mauro Talevi
044     * @version $Revision: 3116 $
045     */
046    public class DefaultPicoContainerTestCase extends AbstractPicoContainerTestCase {
047        protected MutablePicoContainer createPicoContainer(PicoContainer parent) {
048            return new DefaultPicoContainer(parent);
049        }
050    
051        public void testInstantiationWithNullComponentAdapterFactory(){
052            try {
053                new DefaultPicoContainer((ComponentAdapterFactory)null, (PicoContainer)null);
054                fail("NPE expected");
055            } catch (NullPointerException e) {
056                // expected
057            }
058        }
059        public void testUpDownDependenciesCannotBeFollowed() {
060            MutablePicoContainer parent = createPicoContainer(null);
061            MutablePicoContainer child = createPicoContainer(parent);
062    
063            // ComponentF -> ComponentA -> ComponentB+C
064            child.registerComponentImplementation(ComponentF.class);
065            parent.registerComponentImplementation(ComponentA.class);
066            child.registerComponentImplementation(ComponentB.class);
067            child.registerComponentImplementation(ComponentC.class);
068    
069            try {
070                child.getComponentInstance(ComponentF.class);
071                fail("Thrown " + UnsatisfiableDependenciesException.class.getName() + " expected");
072            } catch (final UnsatisfiableDependenciesException e) {
073                assertEquals(ComponentB.class, e.getUnsatisfiedDependencyType());
074            }
075        }
076    
077        public void testComponentsCanBeRemovedByInstance() {
078            MutablePicoContainer pico = createPicoContainer(null);
079            pico.registerComponentImplementation(HashMap.class);
080            pico.registerComponentImplementation(ArrayList.class);
081            List list = (List) pico.getComponentInstanceOfType(List.class);
082            pico.unregisterComponentByInstance(list);
083            assertEquals(1, pico.getComponentAdapters().size());
084            assertEquals(1, pico.getComponentInstances().size());
085            assertEquals(HashMap.class, pico.getComponentInstanceOfType(Serializable.class).getClass());
086        }
087    
088        public void testComponentInstancesListIsReturnedForNullType(){
089            MutablePicoContainer pico = createPicoContainer(null);
090            List componentInstances = pico.getComponentInstancesOfType(null);
091            assertNotNull(componentInstances);
092            assertEquals(0, componentInstances.size());
093        }
094        
095        public void testComponentsWithCommonSupertypeWhichIsAConstructorArgumentCanBeLookedUpByConcreteType() {
096            MutablePicoContainer pico = createPicoContainer(null);
097            pico.registerComponentImplementation(LinkedList.class, LinkedList.class, new Parameter[0]);
098            pico.registerComponentImplementation(ArrayList.class);
099            assertEquals(ArrayList.class, pico.getComponentInstanceOfType(ArrayList.class).getClass());
100        }
101    
102        /*
103         When pico tries to resolve DecoratedTouchable it find as dependency itself and SimpleTouchable.
104         Problem is basically the same as above. Pico should not consider self as solution.
105         
106         JS
107         fixed it ( PICO-222 )
108         KP
109         */
110        public void testUnambiguouSelfDependency() {
111            MutablePicoContainer pico = createPicoContainer(null);
112            pico.registerComponentImplementation(SimpleTouchable.class);
113            pico.registerComponentImplementation(DecoratedTouchable.class);
114            Touchable t = (Touchable) pico.getComponentInstance(DecoratedTouchable.class);
115            assertNotNull(t);
116        }
117    
118        public static class Thingie {
119            public Thingie(List c) {
120                assertNotNull(c);
121            }
122        }
123    
124        public void testThangCanBeInstantiatedWithArrayList() {
125            MutablePicoContainer pico = new DefaultPicoContainer();
126            pico.registerComponentImplementation(Thingie.class);
127            pico.registerComponentImplementation(ArrayList.class);
128            assertNotNull(pico.getComponentInstance(Thingie.class));
129        }
130    
131        public void testGetComponentAdaptersOfTypeNullReturnsEmptyList() {
132            DefaultPicoContainer pico = new DefaultPicoContainer();
133            List adapters = pico.getComponentAdaptersOfType(null);
134            assertNotNull(adapters);
135            assertEquals(0, adapters.size());
136        }
137        
138    
139        public static class Service {
140        }
141    
142        public static class TransientComponent {
143            private Service service;
144    
145            public TransientComponent(Service service) {
146                this.service = service;
147            }
148        }
149    
150        public void testDefaultPicoContainerReturnsNewInstanceForEachCallWhenUsingTransientComponentAdapter() {
151            DefaultPicoContainer picoContainer = new DefaultPicoContainer();
152            picoContainer.registerComponentImplementation(Service.class);
153            picoContainer.registerComponent(new ConstructorInjectionComponentAdapter(TransientComponent.class, TransientComponent.class));
154            TransientComponent c1 = (TransientComponent) picoContainer.getComponentInstance(TransientComponent.class);
155            TransientComponent c2 = (TransientComponent) picoContainer.getComponentInstance(TransientComponent.class);
156            assertNotSame(c1, c2);
157            assertSame(c1.service, c2.service);
158        }
159    
160        public static class DependsOnCollection {
161            public DependsOnCollection(Collection c) {
162            }
163        }
164    
165        public void testShouldProvideInfoAboutDependingWhenAmbiguityHappens() {
166            MutablePicoContainer pico = this.createPicoContainer(null);
167            pico.registerComponentInstance(new ArrayList());
168            pico.registerComponentInstance(new LinkedList());
169            pico.registerComponentImplementation(DependsOnCollection.class);
170            try {
171                pico.getComponentInstanceOfType(DependsOnCollection.class);
172                fail();
173            } catch (AmbiguousComponentResolutionException expected) {
174                String doc = DependsOnCollection.class.getName();
175                assertEquals("class " + doc + " has ambiguous dependency on interface java.util.Collection, resolves to multiple classes: [class java.util.ArrayList, class java.util.LinkedList]", expected.getMessage());
176            }
177        }
178    
179        public void testInstantiationWithMonitorAndParent() {
180            StringWriter writer = new StringWriter();
181            ComponentMonitor monitor = new WriterComponentMonitor(writer);
182            DefaultPicoContainer parent = new DefaultPicoContainer();
183            DefaultPicoContainer child = new DefaultPicoContainer(monitor, parent);
184            parent.registerComponentImplementation("st", SimpleTouchable.class);
185            child.registerComponentImplementation("dot", DependsOnTouchable.class);
186            DependsOnTouchable dot = (DependsOnTouchable) child.getComponentInstance("dot");
187            assertNotNull(dot);
188            assertTrue("writer not empty", writer.toString().length() > 0);
189        }
190        
191        public void testStartCapturedByMonitor() {
192            final StringBuffer sb = new StringBuffer();
193            DefaultPicoContainer dpc = new DefaultPicoContainer(new DefaultComponentMonitor() {
194                public void invoking(Method method, Object instance) {
195                    sb.append(method.toString());
196                }
197            });
198            dpc.registerComponentImplementation(DefaultPicoContainer.class);
199            dpc.start();
200            assertEquals("ComponentMonitor should have been notified that the component had been started",
201                    "public abstract void org.picocontainer.Startable.start()", sb.toString());
202        }
203    
204        public void testCanChangeMonitor() {
205            StringWriter writer1 = new StringWriter();
206            ComponentMonitor monitor1 = new WriterComponentMonitor(writer1);
207            DefaultPicoContainer pico = new DefaultPicoContainer(monitor1);
208            pico.registerComponentImplementation("t1", SimpleTouchable.class);
209            pico.registerComponentImplementation("t3", SimpleTouchable.class);
210            Touchable t1 = (Touchable) pico.getComponentInstance("t1");
211            assertNotNull(t1);
212            assertTrue("writer not empty", writer1.toString().length() > 0);
213            StringWriter writer2 = new StringWriter();
214            ComponentMonitor monitor2 = new WriterComponentMonitor(writer2);
215            pico.changeMonitor(monitor2);
216            pico.registerComponentImplementation("t2", SimpleTouchable.class);
217            Touchable t2 = (Touchable) pico.getComponentInstance("t2");
218            assertNotNull(t2);
219            assertTrue("writer not empty", writer2.toString().length() > 0);
220            assertTrue("writers of same length", writer1.toString().length() == writer2.toString().length());
221            Touchable t3 = (Touchable) pico.getComponentInstance("t3");
222            assertNotNull(t3);
223            assertTrue("old writer was used", writer1.toString().length() < writer2.toString().length());
224        }
225    
226        public void testCanChangeMonitorOfChildContainers() {
227            StringWriter writer1 = new StringWriter();
228            ComponentMonitor monitor1 = new WriterComponentMonitor(writer1);
229            DefaultPicoContainer parent = new DefaultPicoContainer();
230            DefaultPicoContainer child = new DefaultPicoContainer(monitor1);
231            parent.addChildContainer(child);
232            child.registerComponentImplementation("t1", SimpleTouchable.class);
233            child.registerComponentImplementation("t3", SimpleTouchable.class);
234            Touchable t1 = (Touchable) child.getComponentInstance("t1");
235            assertNotNull(t1);
236            assertTrue("writer not empty", writer1.toString().length() > 0);
237            StringWriter writer2 = new StringWriter();
238            ComponentMonitor monitor2 = new WriterComponentMonitor(writer2);
239            parent.changeMonitor(monitor2);
240            child.registerComponentImplementation("t2", SimpleTouchable.class);
241            Touchable t2 = (Touchable) child.getComponentInstance("t2");
242            assertNotNull(t2);
243            assertTrue("writer not empty", writer2.toString().length() > 0);
244            String s1 = writer1.toString();
245            String s2 = writer2.toString();
246            assertTrue("writers of same length", s1.length() == s2.length());
247            Touchable t3 = (Touchable) child.getComponentInstance("t3");
248            assertNotNull(t3);
249            assertTrue("old writer was used", writer1.toString().length() < writer2.toString().length());
250        }
251    
252        public void testChangeMonitorIsIgnoredIfNotSupportingStrategy(){
253            StringWriter writer = new StringWriter();
254            ComponentMonitor monitor = new WriterComponentMonitor(writer);
255            DefaultPicoContainer parent = new DefaultPicoContainer(new ComponentAdapterFactoryWithNoMonitor(new ComponentAdapterWithNoMonitor(new SimpleTouchable())));
256            parent.addChildContainer(new EmptyPicoContainer());
257            parent.registerComponentImplementation("t1", SimpleTouchable.class);
258            parent.changeMonitor(monitor);
259            assertTrue("writer empty", writer.toString().length() == 0);
260        }
261        
262        public void testCanReturnCurrentMonitorFromComponentAdapterFactory() {
263            StringWriter writer1 = new StringWriter();
264            ComponentMonitor monitor1 = new WriterComponentMonitor(writer1);
265            DefaultPicoContainer pico = new DefaultPicoContainer(monitor1);
266            assertEquals(monitor1, pico.currentMonitor());
267            StringWriter writer2 = new StringWriter();
268            ComponentMonitor monitor2 = new WriterComponentMonitor(writer2);
269            pico.changeMonitor(monitor2);
270            assertEquals(monitor2, pico.currentMonitor());
271        }
272    
273        public void testCanReturnCurrentMonitorFromComponentAdapter() {
274            StringWriter writer1 = new StringWriter();
275            ComponentMonitor monitor1 = new WriterComponentMonitor(writer1);
276            InstanceComponentAdapter adapterWithMonitor = new InstanceComponentAdapter(SimpleTouchable.class.getName(), new SimpleTouchable());
277            adapterWithMonitor.changeMonitor(monitor1);
278            DefaultPicoContainer pico = new DefaultPicoContainer(new ComponentAdapterFactoryWithNoMonitor(adapterWithMonitor));
279            pico.registerComponentImplementation("t1", SimpleTouchable.class);
280            assertEquals(monitor1, pico.currentMonitor());
281            StringWriter writer2 = new StringWriter();
282            ComponentMonitor monitor2 = new WriterComponentMonitor(writer2);
283            pico.changeMonitor(monitor2);
284            assertEquals(monitor2, pico.currentMonitor());
285        }
286    
287        public void testCanReturnCurrentMonitorFromChildContainer() {
288            StringWriter writer1 = new StringWriter();
289            ComponentMonitor monitor1 = new WriterComponentMonitor(writer1);
290            DefaultPicoContainer pico = new DefaultPicoContainer(new ComponentAdapterFactoryWithNoMonitor(new ComponentAdapterWithNoMonitor(new SimpleTouchable())));
291            pico.registerComponentImplementation("t1", SimpleTouchable.class);
292            // first child does not support ComponentMonitorStrategy
293            pico.addChildContainer(new EmptyPicoContainer());
294            // second child does support ComponentMonitorStrategy
295            pico.addChildContainer(new DefaultPicoContainer(monitor1));
296            assertEquals(monitor1, pico.currentMonitor());
297            StringWriter writer2 = new StringWriter();
298            ComponentMonitor monitor2 = new WriterComponentMonitor(writer2);
299            pico.changeMonitor(monitor2);
300            assertEquals(monitor2, pico.currentMonitor());
301        }
302        
303        public void testCannotReturnCurrentMonitor() {
304            DefaultPicoContainer pico = new DefaultPicoContainer(new ComponentAdapterFactoryWithNoMonitor(null));
305            try {
306                pico.currentMonitor();
307                fail("PicoIntrospectionException expected");
308            } catch (PicoIntrospectionException e) {
309                assertEquals("No component monitor found in container or its children", e.getMessage());
310            }
311        }
312    
313        private static class ComponentAdapterFactoryWithNoMonitor implements ComponentAdapterFactory {
314            private ComponentAdapter adapter;
315            public ComponentAdapterFactoryWithNoMonitor(ComponentAdapter adapter){
316                this.adapter = adapter;
317            }
318            public ComponentAdapter createComponentAdapter(Object componentKey, Class componentImplementation, Parameter[] parameters) throws PicoIntrospectionException, AssignabilityRegistrationException, NotConcreteRegistrationException {
319                return adapter;
320            }        
321        }
322        
323        private static class ComponentAdapterWithNoMonitor implements ComponentAdapter {
324            private Object instance;
325            public ComponentAdapterWithNoMonitor(Object instance){
326                this.instance = instance;
327            }
328            public Object getComponentKey() {
329                return instance.getClass();
330            }
331            public Class getComponentImplementation() {
332                return instance.getClass();
333            }
334            public Object getComponentInstance(PicoContainer container) throws PicoInitializationException, PicoIntrospectionException {
335                return instance;
336            }
337            public void verify(PicoContainer container) throws PicoIntrospectionException {
338            }
339            public void accept(PicoVisitor visitor) {
340            }
341        }
342        
343        public void testMakeChildContainer() {
344            MutablePicoContainer parent = new DefaultPicoContainer();
345            parent.registerComponentImplementation("t1", SimpleTouchable.class);
346            MutablePicoContainer child = parent.makeChildContainer();
347            Object t1 = child.getParent().getComponentInstance("t1");
348            assertNotNull(t1);
349            assertTrue(t1 instanceof SimpleTouchable);
350        }
351    
352        public void testCanUseCustomLifecycleStrategyForClassRegistrations() {
353            DefaultPicoContainer dpc = new DefaultPicoContainer(new FailingLifecycleStrategy(), null);
354            dpc.registerComponentImplementation(Startable.class, MyStartable.class);
355            try {
356                dpc.start();
357                fail("should have barfed");
358            } catch (RuntimeException e) {
359                assertEquals("foo", e.getMessage());
360            }
361        }
362    
363        public void testCanUseCustomLifecycleStrategyForInstanceRegistrations() {
364            DefaultPicoContainer dpc = new DefaultPicoContainer(new FailingLifecycleStrategy(), null);
365            Startable myStartable = new MyStartable();
366            dpc.registerComponentInstance(Startable.class, myStartable);
367            try {
368                dpc.start();
369                fail("should have barfed");
370            } catch (RuntimeException e) {
371                assertEquals("foo", e.getMessage());
372            }
373        }
374    
375        public static class FailingLifecycleStrategy implements LifecycleStrategy {
376                public void start(Object component) {
377                    throw new RuntimeException("foo");
378                }
379    
380                public void stop(Object component) {
381                }
382    
383                public void dispose(Object component) {
384                }
385    
386                public boolean hasLifecycle(Class type) {
387                    return true;
388                }
389    
390        }
391        public static class MyStartable implements Startable {
392            public MyStartable() {
393            }
394    
395            public void start() {
396            }
397    
398            public void stop() {
399            }
400        }
401    
402        public static interface A {
403    
404        }
405    
406        public static class SimpleA implements A
407        {
408    
409        }
410    
411        public static class WrappingA implements A
412        {
413            private final A wrapped;
414    
415            public WrappingA(A wrapped) {
416                this.wrapped = wrapped;
417            }
418        }
419    
420        public void testCanRegisterTwoComponentsImplementingSameInterfaceOneWithInterfaceAsKey() throws Exception {
421            MutablePicoContainer container = createPicoContainer(null);
422    
423            container.registerComponentImplementation(SimpleA.class);
424            container.registerComponentImplementation(A.class, WrappingA.class);
425    
426            container.start();
427    
428            assertEquals(WrappingA.class, container.getComponentInstance(A.class).getClass());
429        }
430    
431        public void testCanRegisterTwoComponentsWithSameImplementionAndDifferentKey() throws Exception {
432            MutablePicoContainer container = createPicoContainer(null);
433    
434            container.registerComponentImplementation(SimpleA.class);
435            container.registerComponentImplementation("A", SimpleA.class);
436    
437            container.start();
438    
439            assertNotNull(container.getComponentInstance("A"));
440            assertNotNull(container.getComponentInstance(SimpleA.class));
441            assertNotSame(container.getComponentInstance("A"), container.getComponentInstance(SimpleA.class));
442        }
443        
444        public static class MyPicoContainer extends DefaultPicoContainer {
445    
446            public ComponentAdapter registerComponent(ComponentAdapter componentAdapter) {
447                return super.registerComponent(new SynchronizedComponentAdapter(componentAdapter));
448            }
449            
450        }
451        
452        public void testDerivedPicoContainerCanOverloadRegisterComponentForAllCreatedComponentAdapters() {
453            MutablePicoContainer mpc = new MyPicoContainer();
454            assertEquals(SynchronizedComponentAdapter.class, mpc.registerComponent(new InstanceComponentAdapter("foo", "bar")).getClass());
455            assertEquals(SynchronizedComponentAdapter.class, mpc.registerComponentInstance("foobar").getClass());
456            assertEquals(SynchronizedComponentAdapter.class, mpc.registerComponentImplementation(SimpleA.class).getClass());
457        }
458    }