    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(), new NullLifecycleStrategy(), 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(), new NullLifecycleStrategy(), false);
    081        }
    082
    083        protected ComponentAdapter prepDEF_visitable() {
    084            return new ConstructorInjector("bar", B.class, new Parameter[] {ComponentParameter.DEFAULT} , new NullComponentMonitor(), new NullLifecycleStrategy(), 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(), new NullLifecycleStrategy(), false);
    092        }
    093
    094        protected ComponentAdapter prepSER_isSerializable(MutablePicoContainer picoContainer) {
    095            return new ConstructorInjector(SimpleTouchable.class, SimpleTouchable.class, null, new NullComponentMonitor(), new NullLifecycleStrategy(), 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(), new NullLifecycleStrategy(), false);
    110        }
    111
    112        protected ComponentAdapter prepINS_createsNewInstances(MutablePicoContainer picoContainer) {
    113            return new ConstructorInjector(SimpleTouchable.class, SimpleTouchable.class, null, new NullComponentMonitor(), new NullLifecycleStrategy(), 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(), new NullLifecycleStrategy(), 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(), new NullLifecycleStrategy(), 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(), new NullLifecycleStrategy(), 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(), new NullLifecycleStrategy(), 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(), new NullLifecycleStrategy(), 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(), new NullLifecycleStrategy(), 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, new NullLifecycleStrategy(), 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, new NullLifecycleStrategy(), 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        @Test public void testCustomLifecycleCanBeInjected() throws NoSuchMethodException {
    368            RecordingLifecycleStrategy strategy = new RecordingLifecycleStrategy(new StringBuffer());
    369            ConstructorInjector cica = new ConstructorInjector(
    370                    NullLifecycle.class, NullLifecycle.class, new Parameter[0],
    371                    new AbstractComponentMonitor(), strategy, false);
    372            Touchable touchable = new SimpleTouchable();
    373            cica.start(touchable);
    374            cica.stop(touchable);
    375            cica.dispose(touchable);
    376            assertEquals("<start<stop<dispose", strategy.recording());
    377        }
    378
    379        public static class One {
    380            public One(Two two) {
    381                two.inc();
    382            }
    383        }
    384        public static class Two {
    385            private int inc;
    386            public void inc() {
    387                inc++;
    388            }
    389
    390            public long howMany() {
    391                return inc;
    392            }
    393        }
    394
    395        @Test public void testSpeedOfRememberedConstructor()  {
    396            long with, without;
    397            injectionFactory = new ForgetfulConstructorInjection();
    398            timeIt(); // discard
    399            timeIt(); // discard
    400            timeIt(); // discard
    401            without = timeIt();
    402            injectionFactory = new ConstructorInjection();
    403            with = timeIt();
    404            assertTrue("'with' should be less than 'without' but they were in fact: " + with + ", and " + without, with < without);
    405        }
    406
    407        InjectionFactory injectionFactory;
    408        private long timeIt() {
    409            int iterations = 20000;
    410            DefaultPicoContainer dpc = new DefaultPicoContainer(injectionFactory);
    411            Two two = new Two();
    412            dpc.addComponent(two);
    413            dpc.addComponent(One.class);
    414            long start = System.currentTimeMillis();
    415            for (int x = 0; x < iterations; x++) {
    416                    dpc.getComponent(One.class);
    417                }
    418            long end = System.currentTimeMillis();
    419            assertEquals(iterations, two.howMany());
    420            return end-start;
    421        }
    422
    423    }