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