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 }