    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.defaults;
    011
    012    import static org.junit.Assert.assertEquals;
    013    import static org.junit.Assert.assertNotNull;
    014    import static org.junit.Assert.assertNull;
    015
    016    import org.junit.Test;
    017    import org.picocontainer.DefaultPicoContainer;
    018    import org.picocontainer.PicoContainer;
    019    import org.picocontainer.Parameter;
    020    import org.picocontainer.ComponentAdapter;
    021    import org.picocontainer.NameBinding;
    022    import org.picocontainer.PicoCompositionException;
    023    import org.picocontainer.adapters.InstanceAdapter;
    024    import org.picocontainer.parameters.ComponentParameter;
    025    import org.picocontainer.injectors.ConstructorInjection;
    026    import org.picocontainer.injectors.ConstructorInjector;
    027
    028    import java.lang.reflect.Type;
    029    import java.lang.annotation.Annotation;
    030
    031    /**
    032     * @author Paul Hammant
    033     */
    034    public class ResolveAdapterReductionTestCase {
    035
    036        int resolveAdapterCalls;
    037        private Parameter[] parms;
    038        private ComponentAdapter[] injecteeAdapters;
    039
    040        @Test
    041        public void testThatResolveAdapterCanBeDoneOnceForASituationWhereItWasPreviouslyDoneAtLeastTwice() throws Exception {
    042            resolveAdapterCalls = 0;
    043            DefaultPicoContainer pico = new DefaultPicoContainer(new ConstructorInjection());
    044            pico.addAdapter(new CountingConstructorInjector(One.class, One.class));
    045            pico.addComponent(new Two());
    046            long start = System.currentTimeMillis();
    047            for (int x = 0; x<30000; x++) {
    048                One one = pico.getComponent(One.class);
    049                assertNotNull(one);
    050                assertNotNull(one.two);
    051                assertEquals("resolveAdapter for 'Two' should only be called once, regardless of how many getComponents there are",
    052                        1, resolveAdapterCalls);
    053            }
    054            System.out.println("GreediestConstructorTestCase elapsed: " + (System.currentTimeMillis() - start));
    055            assertNotNull(parms);
    056            assertEquals(1, parms.length);
    057            assertEquals(true, parms[0] instanceof CountingComponentParameter);
    058            assertNotNull(injecteeAdapters);
    059            assertEquals(1, injecteeAdapters.length);
    060            assertEquals(true, injecteeAdapters[0] instanceof InstanceAdapter);
    061        }
    062
    063        @Test
    064        public void testThatResolveAdapterCallsAreNotDuplicatedForMultipleConstructorsInTheSameComponent() throws Exception {
    065            resolveAdapterCalls = 0;
    066            DefaultPicoContainer pico = new DefaultPicoContainer(new ConstructorInjection());
    067            // 'Three', in addition to a 'Two', requires a string, and an int for two of the longer constructors ....
    068            pico.addAdapter(new CountingConstructorInjector(Three.class, Three.class));
    069            // .. but we ain't going to provide them, forcing the smallest constructor to be used.
    070            pico.addComponent(new Two());
    071            long start = System.currentTimeMillis();
    072            for (int x = 0; x<30000; x++) {
    073                Three three = pico.getComponent(Three.class);
    074                assertNotNull(three);
    075                assertNotNull(three.two);
    076                assertNull(three.string);
    077                assertNull(three.integer);
    078
    079                // if we did not cache the results of the longer (unsatisfiable) constructors, then we'd be doing
    080                // resolveAdapter(..) more than once.  See ConstructorInjector.ResolverKey.
    081                assertEquals("resolveAdapter for 'Two' should only be called once, regardless of how many getComponents there are",
    082                        1, resolveAdapterCalls);
    083            }
    084            System.out.println("GreediestConstructorTestCase elapsed: " + (System.currentTimeMillis() - start));
    085        }
    086
    087        public static class One {
    088            private final Two two;
    089
    090            public One(Two two) {
    091                this.two = two;
    092            }
    093        }
    094
    095        public static class Two {
    096            public Two() {
    097            }
    098        }
    099
    100        public static class Three {
    101            private final Two two;
    102            private final String string;
    103            private final Integer integer;
    104
    105            public Three(Two two, String string, Integer integer) {
    106                this.two = two;
    107                this.string = string;
    108                this.integer = integer;
    109            }
    110            public Three(Two two, String string) {
    111                this.two = two;
    112                this.string = string;
    113                integer = null;
    114            }
    115            public Three(Two two) {
    116                this.two = two;
    117                string = null;
    118                integer = null;
    119            }
    120        }
    121
    122        private class CountingConstructorInjector extends ConstructorInjector {
    123
    124            public CountingConstructorInjector(Class<?> componentKey, Class<?> componentImplementation) {
    125                super(componentKey, componentImplementation, null);
    126            }
    127
    128            protected CtorAndAdapters getGreediestSatisfiableConstructor(PicoContainer container) throws PicoCompositionException {
    129                CtorAndAdapters adapters = super.getGreediestSatisfiableConstructor(container);
    130                parms = adapters.getParameters();
    131                injecteeAdapters = adapters.getInjecteeAdapters();
    132                return adapters;
    133            }
    134
    135            protected Parameter[] createDefaultParameters(Type[] parameters) {
    136                Parameter[] componentParameters = new Parameter[parameters.length];
    137                for (int i = 0; i < parameters.length; i++) {
    138                    componentParameters[i] = new CountingComponentParameter();
    139
    140                }
    141                return componentParameters;
    142            }
    143
    144        }
    145        private class CountingComponentParameter extends ComponentParameter {
    146            public int hashCode() {
    147                return ResolveAdapterReductionTestCase.super.hashCode();
    148            }
    149
    150            public boolean equals(Object o) {
    151                return true;
    152            }
    153
    154            protected <T> ComponentAdapter<T> resolveAdapter(PicoContainer container, ComponentAdapter adapter, Class<T> expectedType, NameBinding expectedNameBinding, boolean useNames, Annotation binding) {
    155                if (expectedType == Two.class) {
    156                    resolveAdapterCalls++;
    157                }
    158                return super.resolveAdapter(container, adapter, expectedType, expectedNameBinding, useNames, binding);    //To change body of overridden methods use File | Settings | File Templates.
    159            }
    160        }
    161    }