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 *****************************************************************************/
010package org.picocontainer.defaults;
011
012import static org.junit.Assert.assertEquals;
013import static org.junit.Assert.assertNotNull;
014import static org.junit.Assert.assertNull;
015
016import org.junit.Test;
017import org.picocontainer.DefaultPicoContainer;
018import org.picocontainer.PicoContainer;
019import org.picocontainer.Parameter;
020import org.picocontainer.ComponentAdapter;
021import org.picocontainer.NameBinding;
022import org.picocontainer.PicoCompositionException;
023import org.picocontainer.testmodel.Touchable;
024import org.picocontainer.testmodel.SimpleTouchable;
025import org.picocontainer.adapters.InstanceAdapter;
026import org.picocontainer.adapters.NullCA;
027import org.picocontainer.parameters.ComponentParameter;
028import org.picocontainer.injectors.ConstructorInjection;
029import org.picocontainer.injectors.ConstructorInjector;
030
031import java.lang.reflect.Type;
032import java.lang.annotation.Annotation;
033
034/**
035 * @author Paul Hammant
036 */
037public 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(int length) {
143            Parameter[] componentParameters = new Parameter[length];
144            for (int i = 0; i < 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}