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