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 }