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