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