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.injectors;
011    
012    import java.awt.event.ActionEvent;
013    import java.awt.event.ActionListener;
014    import java.lang.reflect.Constructor;
015    import java.lang.reflect.InvocationTargetException;
016    import java.util.HashMap;
017    import java.util.Map;
018    
019    import org.jmock.Mock;
020    import org.jmock.core.Constraint;
021    import org.picocontainer.ComponentAdapter;
022    import org.picocontainer.ComponentMonitor;
023    import org.picocontainer.MutablePicoContainer;
024    import org.picocontainer.Parameter;
025    import org.picocontainer.PicoCompositionException;
026    import org.picocontainer.DefaultPicoContainer;
027    import org.picocontainer.lifecycle.NullLifecycleStrategy;
028    import org.picocontainer.injectors.AbstractInjector;
029    import org.picocontainer.injectors.ConstructorInjector;
030    import org.picocontainer.monitors.AbstractComponentMonitor;
031    import org.picocontainer.monitors.NullComponentMonitor;
032    import org.picocontainer.parameters.ComponentParameter;
033    import org.picocontainer.parameters.ConstantParameter;
034    import org.picocontainer.tck.AbstractComponentAdapterTestCase;
035    import org.picocontainer.testmodel.DependsOnTouchable;
036    import org.picocontainer.testmodel.NullLifecycle;
037    import org.picocontainer.testmodel.SimpleTouchable;
038    import org.picocontainer.testmodel.Touchable;
039    
040    import javax.swing.*;
041    
042    
043    public class ConstructorInjectorTestCase extends AbstractComponentAdapterTestCase {
044    
045        protected Class getComponentAdapterType() {
046            return ConstructorInjector.class;
047        }
048    
049        protected ComponentAdapter prepDEF_verifyWithoutDependencyWorks(MutablePicoContainer picoContainer) {
050            return new ConstructorInjector("foo", A.class, null, new NullComponentMonitor(), new NullLifecycleStrategy());
051        }
052    
053        public static class A {
054            public A() {
055                fail("verification should not instantiate");
056            }
057        }
058    
059        public static class B {
060            public B(A a) {
061                fail("verification should not instantiate");
062            }
063        }
064    
065        protected ComponentAdapter prepDEF_verifyDoesNotInstantiate(MutablePicoContainer picoContainer) {
066            picoContainer.addComponent(A.class);
067            return new ConstructorInjector(B.class, B.class, null, new NullComponentMonitor(), new NullLifecycleStrategy());
068        }
069    
070        protected ComponentAdapter prepDEF_visitable() {
071            return new ConstructorInjector("bar", B.class, new Parameter[] {ComponentParameter.DEFAULT} , new NullComponentMonitor(), new NullLifecycleStrategy());
072        }
073    
074        protected ComponentAdapter prepDEF_isAbleToTakeParameters(MutablePicoContainer picoContainer) {
075            picoContainer.addComponent(SimpleTouchable.class);
076            return new ConstructorInjector(
077                    NamedDependsOnTouchable.class, NamedDependsOnTouchable.class,
078                    new Parameter[] {ComponentParameter.DEFAULT, new ConstantParameter("Name")} , new NullComponentMonitor(), new NullLifecycleStrategy());
079        }
080    
081        protected ComponentAdapter prepSER_isSerializable(MutablePicoContainer picoContainer) {
082            return new ConstructorInjector(SimpleTouchable.class, SimpleTouchable.class, null, new NullComponentMonitor(), new NullLifecycleStrategy());
083        }
084    
085        protected ComponentAdapter prepSER_isXStreamSerializable(final MutablePicoContainer picoContainer) {
086            return prepSER_isSerializable(picoContainer);
087        }
088    
089        public static class NamedDependsOnTouchable extends DependsOnTouchable {
090            public NamedDependsOnTouchable(Touchable t, String name) {
091                super(t);
092            }
093        }
094    
095        protected ComponentAdapter prepVER_verificationFails(MutablePicoContainer picoContainer) {
096            return new ConstructorInjector(DependsOnTouchable.class, DependsOnTouchable.class, null, new NullComponentMonitor(), new NullLifecycleStrategy());
097        }
098    
099        protected ComponentAdapter prepINS_createsNewInstances(MutablePicoContainer picoContainer) {
100            return new ConstructorInjector(SimpleTouchable.class, SimpleTouchable.class, null, new NullComponentMonitor(), new NullLifecycleStrategy());
101        }
102    
103        public static class Erroneous {
104            public Erroneous() {
105                throw new VerifyError("test");
106            }
107        }
108    
109        protected ComponentAdapter prepINS_errorIsRethrown(MutablePicoContainer picoContainer) {
110            return new ConstructorInjector(Erroneous.class, Erroneous.class, null, new NullComponentMonitor(), new NullLifecycleStrategy());
111        }
112    
113        public static class RuntimeThrowing {
114            public RuntimeThrowing() {
115                throw new RuntimeException("test");
116            }
117        }
118    
119        protected ComponentAdapter prepINS_runtimeExceptionIsRethrown(MutablePicoContainer picoContainer) {
120            return new ConstructorInjector(RuntimeThrowing.class, RuntimeThrowing.class, null, new NullComponentMonitor(), new NullLifecycleStrategy());
121        }
122    
123        public static class NormalExceptionThrowing {
124            public NormalExceptionThrowing() throws Exception {
125                throw new Exception("test");
126            }
127        }
128    
129        protected ComponentAdapter prepINS_normalExceptionIsRethrownInsidePicoInitializationException(
130                MutablePicoContainer picoContainer) {
131            return new ConstructorInjector(NormalExceptionThrowing.class, NormalExceptionThrowing.class, null, new NullComponentMonitor(), new NullLifecycleStrategy());
132        }
133    
134        protected ComponentAdapter prepRES_dependenciesAreResolved(MutablePicoContainer picoContainer) {
135            picoContainer.addComponent(SimpleTouchable.class);
136            return new ConstructorInjector(DependsOnTouchable.class, DependsOnTouchable.class, null, new NullComponentMonitor(), new NullLifecycleStrategy());
137        }
138    
139        public static class C1 {
140            public C1(C2 c2) {
141                fail("verification should not instantiate");
142            }
143        }
144    
145        public static class C2 {
146            public C2(C1 c1) {
147                fail("verification should not instantiate");
148            }
149        }
150    
151        protected ComponentAdapter prepRES_failingVerificationWithCyclicDependencyException(MutablePicoContainer picoContainer) {
152            final ComponentAdapter componentAdapter = new ConstructorInjector(C1.class, C1.class, null, new NullComponentMonitor(), new NullLifecycleStrategy());
153            picoContainer.addAdapter(componentAdapter);
154            picoContainer.addComponent(C2.class, C2.class);
155            return componentAdapter;
156        }
157    
158        protected ComponentAdapter prepRES_failingInstantiationWithCyclicDependencyException(MutablePicoContainer picoContainer) {
159            final ComponentAdapter componentAdapter = new ConstructorInjector(C1.class, C1.class, null, new NullComponentMonitor(), new NullLifecycleStrategy());
160            picoContainer.addAdapter(componentAdapter);
161            picoContainer.addComponent(C2.class, C2.class);
162            return componentAdapter;
163        }
164    
165        public void testNormalExceptionThrownInCtorIsRethrownInsideInvocationTargetExeption() {
166            DefaultPicoContainer picoContainer = new DefaultPicoContainer();
167            picoContainer.addComponent(NormalExceptionThrowing.class);
168            try {
169                picoContainer.getComponent(NormalExceptionThrowing.class);
170                fail();
171            } catch (PicoCompositionException e) {
172                assertEquals("test", e.getCause().getMessage());
173            }
174        }
175    
176        public static class InstantiationExceptionThrowing {
177            public InstantiationExceptionThrowing() {
178                throw new RuntimeException("Barf");
179            }
180        }
181    
182        public void testInstantiationExceptionThrownInCtorIsRethrownInsideInvocationTargetExeption() {
183            DefaultPicoContainer picoContainer = new DefaultPicoContainer();
184            try {
185                picoContainer.addComponent(InstantiationExceptionThrowing.class);
186                picoContainer.getComponent(InstantiationExceptionThrowing.class);
187                fail();
188            } catch (RuntimeException e) {
189                assertEquals("Barf", e.getMessage());
190            }
191        }
192    
193        public class IllegalAccessExceptionThrowing {
194            private IllegalAccessExceptionThrowing() {
195            }
196        }
197    
198        // TODO test fails currently, since non accessible ctors are filtered out, because of
199        // PICO-201.
200        // Maybe we can activate it again with some kind of SecurityManager & Policy combination?
201        public void XXXtestIllegalAccessExceptionThrownInCtorIsRethrownInsideInvocationTargetExeption() {
202            DefaultPicoContainer picoContainer = new DefaultPicoContainer();
203            try {
204                picoContainer.addComponent(IllegalAccessExceptionThrowing.class);
205                picoContainer.getComponent(IllegalAccessExceptionThrowing.class);
206                fail();
207            } catch (PicoCompositionException e) {
208                assertTrue(e.getCause().getMessage().indexOf(IllegalAccessExceptionThrowing.class.getName()) > 0);
209            }
210        }
211    
212        public void testPicoInitializationExceptionThrownBecauseOfFilteredConstructors() {
213            DefaultPicoContainer picoContainer = new DefaultPicoContainer();
214            try {
215                picoContainer.addComponent(IllegalAccessExceptionThrowing.class);
216                picoContainer.getComponent(IllegalAccessExceptionThrowing.class);
217                fail();
218            } catch (PicoCompositionException e) {
219                assertTrue(e.getMessage().indexOf(IllegalAccessExceptionThrowing.class.getName()) > 0);
220            }
221        }
222    
223        public void testRegisterInterfaceShouldFail() throws PicoCompositionException {
224            MutablePicoContainer pico = new DefaultPicoContainer();
225    
226            try {
227                pico.addComponent(Runnable.class);
228                fail("Shouldn't be allowed to register abstract classes or interfaces.");
229            } catch (AbstractInjector.NotConcreteRegistrationException e) {
230                assertEquals(Runnable.class, e.getComponentImplementation());
231                assertTrue(e.getMessage().indexOf(Runnable.class.getName()) > 0);
232            }
233        }
234    
235        public void testRegisterAbstractShouldFail() throws PicoCompositionException {
236            MutablePicoContainer pico = new DefaultPicoContainer();
237    
238            try {
239                pico.addComponent(AbstractButton.class);
240                fail("Shouldn't be allowed to register abstract classes or interfaces.");
241            } catch (AbstractInjector.NotConcreteRegistrationException e) {
242                assertEquals(AbstractButton.class, e.getComponentImplementation());
243                assertTrue(e.getMessage().indexOf(AbstractButton.class.getName()) > 0);
244            }
245        }
246    
247        private static class Private {
248            private Private() {
249            }
250        }
251    
252        private static class NotYourBusiness {
253            private NotYourBusiness(Private aPrivate) {
254                assertNotNull(aPrivate);
255            }
256        }
257    
258        static public class Component201 {
259            public Component201(final String s) {
260            }
261    
262            protected Component201(final Integer i, final Boolean b) {
263                fail("Wrong constructor taken.");
264            }
265        }
266    
267        // http://jira.codehaus.org/browse/PICO-201
268        public void testShouldNotConsiderNonPublicConstructors() {
269            DefaultPicoContainer pico = new DefaultPicoContainer();
270            pico.addComponent(Component201.class);
271            pico.addComponent(new Integer(2));
272            pico.addComponent(Boolean.TRUE);
273            pico.addComponent("Hello");
274            assertNotNull(pico.getComponent(Component201.class));
275        }
276    
277        public void testMonitoringHappensBeforeAndAfterInstantiation() throws NoSuchMethodException {
278            Mock monitor = mock(ComponentMonitor.class);
279            Constructor emptyHashMapCtor = HashMap.class.getConstructor();
280            monitor.expects(once()).method("instantiating").with(NULL, isA(ConstructorInjector.class),eq(emptyHashMapCtor)).will(returnValue(emptyHashMapCtor));
281            Constraint durationIsGreaterThanOrEqualToZero = new Constraint() {
282                public boolean eval(Object o) {
283                    Long duration = (Long)o;
284                    return 0 <= duration;
285                }
286    
287                public StringBuffer describeTo(StringBuffer stringBuffer) {
288                    return stringBuffer.append("The endTime wasn't after the startTime");
289                }
290            };
291            Constraint isAHashMapThatWozCreated = new Constraint() {
292                public boolean eval(Object o) {
293                    return o instanceof HashMap;
294                }
295    
296                public StringBuffer describeTo(StringBuffer stringBuffer) {
297                    return stringBuffer.append("Should have been a hashmap");
298                }
299            };
300    
301            Constraint injectedIsEmptyArray = new Constraint() {
302                public boolean eval(Object o) {
303                    Object[] injected = (Object[])o;
304                    return 0 == injected.length;
305                }
306    
307                public StringBuffer describeTo(StringBuffer stringBuffer) {
308                    return stringBuffer.append("Should have had nothing injected into it");
309                }
310            };
311    
312            monitor.expects(once()).method("instantiated").with(new Constraint[] {NULL, isA(ComponentAdapter.class), eq(emptyHashMapCtor), isAHashMapThatWozCreated, injectedIsEmptyArray, durationIsGreaterThanOrEqualToZero});
313            ConstructorInjector cica = new ConstructorInjector(
314                    Map.class, HashMap.class, new Parameter[0], (ComponentMonitor)monitor.proxy(), new NullLifecycleStrategy());
315            cica.getComponentInstance(null);
316        }
317    
318        public void testMonitoringHappensBeforeAndOnFailOfImpossibleComponentsInstantiation() throws NoSuchMethodException {
319            Mock monitor = mock(ComponentMonitor.class);
320            Constructor barfingActionListenerCtor = BarfingActionListener.class.getConstructor();
321            monitor.expects(once()).method("instantiating").with(new Constraint[] {NULL,isA(ComponentAdapter.class), eq(barfingActionListenerCtor)}).will(returnValue(barfingActionListenerCtor));
322    
323            Constraint isITE = new Constraint() {
324                public boolean eval(Object o) {
325                    Exception ex = (Exception)o;
326                    return ex instanceof InvocationTargetException;
327                }
328    
329                public StringBuffer describeTo(StringBuffer stringBuffer) {
330                    return stringBuffer.append("Should have been unable to instantiate");
331                }
332            };
333    
334            monitor.expects(once()).method("instantiationFailed").with(NULL,isA(ComponentAdapter.class), eq(barfingActionListenerCtor), isITE);
335            ConstructorInjector cica = new ConstructorInjector(
336                    ActionListener.class, BarfingActionListener.class, new Parameter[0], (ComponentMonitor)monitor.proxy(), new NullLifecycleStrategy());
337            try {
338                cica.getComponentInstance(null);
339                fail("Should barf");
340            } catch (RuntimeException e) {
341                assertEquals("Barf!", e.getMessage());
342            }
343        }
344    
345        private static class BarfingActionListener implements ActionListener {
346            public BarfingActionListener() {
347                throw new RuntimeException("Barf!");
348            }
349    
350            public void actionPerformed(ActionEvent e) {
351            }
352        }
353    
354        public void testCustomLifecycleCanBeInjected() throws NoSuchMethodException {
355            RecordingLifecycleStrategy strategy = new RecordingLifecycleStrategy(new StringBuffer());
356            ConstructorInjector cica = new ConstructorInjector(
357                    NullLifecycle.class, NullLifecycle.class, new Parameter[0],
358                    new AbstractComponentMonitor(), strategy);
359            Touchable touchable = new SimpleTouchable();
360            cica.start(touchable);
361            cica.stop(touchable);
362            cica.dispose(touchable);
363            assertEquals("<start<stop<dispose", strategy.recording());
364        }
365    }