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    }