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 * Idea by Rachel Davies, Original code by Aslak Hellesoy and Paul Hammant * 009 *****************************************************************************/ 010 011 package org.picocontainer.injectors; 012 013 import org.picocontainer.ComponentMonitor; 014 import org.picocontainer.LifecycleStrategy; 015 import org.picocontainer.Parameter; 016 import org.picocontainer.PicoCompositionException; 017 import org.picocontainer.PicoContainer; 018 import org.picocontainer.lifecycle.NullLifecycleStrategy; 019 import org.picocontainer.monitors.NullComponentMonitor; 020 021 import java.lang.reflect.Constructor; 022 import java.lang.reflect.InvocationTargetException; 023 import java.lang.reflect.Modifier; 024 import java.lang.reflect.Type; 025 import java.lang.annotation.Annotation; 026 import java.security.AccessController; 027 import java.security.PrivilegedAction; 028 import java.util.ArrayList; 029 import java.util.Arrays; 030 import java.util.Collections; 031 import java.util.Comparator; 032 import java.util.HashSet; 033 import java.util.List; 034 import java.util.Set; 035 036 /** 037 * Injection will happen through a constructor for the component. 038 * 039 * @author Paul Hammant 040 * @author Aslak Hellesøy 041 * @author Jon Tirsén 042 * @author Zohar Melamed 043 * @author Jörg Schaible 044 * @author Mauro Talevi 045 */ 046 @SuppressWarnings("serial") 047 public class ConstructorInjector<T> extends SingleMemberInjector<T> { 048 049 private transient List<Constructor<T>> sortedMatchingConstructors; 050 private transient ThreadLocalCyclicDependencyGuard<T> instantiationGuard; 051 private boolean rememberChosenConstructor = true; 052 private transient Constructor<T> chosenConstructor; 053 054 /** 055 * Constructor injector that uses no monitor and no lifecycle adapter. This is a more 056 * convenient constructor for use when instantiating a constructor injector directly. 057 * @param componentKey the search key for this implementation 058 * @param componentImplementation the concrete implementation 059 * @param parameters the parameters used for initialization 060 */ 061 public ConstructorInjector(final Object componentKey, final Class<?> componentImplementation, Parameter... parameters) { 062 this(componentKey, componentImplementation, parameters, new NullComponentMonitor(), new NullLifecycleStrategy(), false); 063 } 064 065 /** 066 * Creates a ConstructorInjector 067 * 068 * @param componentKey the search key for this implementation 069 * @param componentImplementation the concrete implementation 070 * @param parameters the parameters to use for the initialization 071 * @param monitor the component monitor used by this addAdapter 072 * @param lifecycleStrategy the component lifecycle strategy used by this addAdapter 073 * @param useNames use argument names when looking up dependencies 074 * @throws org.picocontainer.injectors.AbstractInjector.NotConcreteRegistrationException 075 * if the implementation is not a concrete class. 076 * @throws NullPointerException if one of the parameters is <code>null</code> 077 */ 078 public ConstructorInjector(final Object componentKey, final Class componentImplementation, Parameter[] parameters, ComponentMonitor monitor, 079 LifecycleStrategy lifecycleStrategy, boolean useNames) throws NotConcreteRegistrationException { 080 super(componentKey, componentImplementation, parameters, monitor, lifecycleStrategy, useNames); 081 } 082 083 /** 084 * Creates a ConstructorInjector 085 * 086 * @param componentKey the search key for this implementation 087 * @param componentImplementation the concrete implementation 088 * @param parameters the parameters to use for the initialization 089 * @param monitor the component monitor used by this addAdapter 090 * @param lifecycleStrategy the component lifecycle strategy used by this addAdapter 091 * @param useNames use argument names when looking up dependencies 092 * @param rememberChosenCtor remember the chosen constructor (to speed up second/subsequent calls) 093 * @throws org.picocontainer.injectors.AbstractInjector.NotConcreteRegistrationException 094 * if the implementation is not a concrete class. 095 * @throws NullPointerException if one of the parameters is <code>null</code> 096 */ 097 public ConstructorInjector(final Object componentKey, final Class componentImplementation, Parameter[] parameters, ComponentMonitor monitor, 098 LifecycleStrategy lifecycleStrategy, boolean useNames, boolean rememberChosenCtor) throws NotConcreteRegistrationException { 099 super(componentKey, componentImplementation, parameters, monitor, lifecycleStrategy, useNames); 100 this.rememberChosenConstructor = rememberChosenCtor; 101 } 102 103 protected Constructor<T> getGreediestSatisfiableConstructor(PicoContainer container) throws PicoCompositionException { 104 final Set<Constructor> conflicts = new HashSet<Constructor>(); 105 final Set<List<Class>> unsatisfiableDependencyTypes = new HashSet<List<Class>>(); 106 if (sortedMatchingConstructors == null) { 107 sortedMatchingConstructors = getSortedMatchingConstructors(); 108 } 109 Constructor<T> greediestConstructor = null; 110 int lastSatisfiableConstructorSize = -1; 111 Class<?> unsatisfiedDependencyType = null; 112 for (final Constructor<T> sortedMatchingConstructor : sortedMatchingConstructors) { 113 boolean failedDependency = false; 114 Class[] parameterTypes = sortedMatchingConstructor.getParameterTypes(); 115 Annotation[] bindings = getBindings(sortedMatchingConstructor.getParameterAnnotations()); 116 Parameter[] currentParameters = parameters != null ? parameters : createDefaultParameters(parameterTypes); 117 118 // remember: all constructors with less arguments than the given parameters are filtered out already 119 for (int j = 0; j < currentParameters.length; j++) { 120 // check whether this constructor is statisfiable 121 Class<?> boxed = box(parameterTypes[j]); 122 boolean un = useNames(); 123 if (currentParameters[j].isResolvable(container, this, boxed, 124 new ParameterNameBinding(getParanamer(), getComponentImplementation(), sortedMatchingConstructor, j), 125 un, bindings[j])) { 126 continue; 127 } 128 unsatisfiableDependencyTypes.add(Arrays.asList(parameterTypes)); 129 unsatisfiedDependencyType = box(parameterTypes[j]); 130 failedDependency = true; 131 break; 132 } 133 134 if (greediestConstructor != null && parameterTypes.length != lastSatisfiableConstructorSize) { 135 if (conflicts.isEmpty()) { 136 // we found our match [aka. greedy and satisfied] 137 return greediestConstructor; 138 } else { 139 // fits although not greedy 140 conflicts.add(sortedMatchingConstructor); 141 } 142 } else if (!failedDependency && lastSatisfiableConstructorSize == parameterTypes.length) { 143 // satisfied and same size as previous one? 144 conflicts.add(sortedMatchingConstructor); 145 conflicts.add(greediestConstructor); 146 } else if (!failedDependency) { 147 greediestConstructor = sortedMatchingConstructor; 148 lastSatisfiableConstructorSize = parameterTypes.length; 149 } 150 } 151 if (!conflicts.isEmpty()) { 152 throw new PicoCompositionException(conflicts.size() + " satisfiable constructors is too many for '"+getComponentImplementation()+"'. Constructor List:" + conflicts.toString().replace(getComponentImplementation().getName(),"<init>").replace("public <i","<i")); 153 } else if (greediestConstructor == null && !unsatisfiableDependencyTypes.isEmpty()) { 154 throw new UnsatisfiableDependenciesException(this, unsatisfiedDependencyType, unsatisfiableDependencyTypes, container); 155 } else if (greediestConstructor == null) { 156 // be nice to the user, show all constructors that were filtered out 157 final Set<Constructor> nonMatching = new HashSet<Constructor>(); 158 for (Constructor constructor : getConstructors()) { 159 nonMatching.add(constructor); 160 } 161 throw new PicoCompositionException("Either the specified parameters do not match any of the following constructors: " + nonMatching.toString() + "; OR the constructors were not accessible for '" + getComponentImplementation().getName() + "'"); 162 } 163 return greediestConstructor; 164 } 165 166 public T getComponentInstance(final PicoContainer container, Type into) throws PicoCompositionException { 167 if (instantiationGuard == null) { 168 instantiationGuard = new ThreadLocalCyclicDependencyGuard<T>() { 169 public T run() { 170 Constructor<T> ctor = null; 171 try { 172 if (chosenConstructor == null) { 173 ctor = getGreediestSatisfiableConstructor(guardedContainer); 174 } 175 if (rememberChosenConstructor) { 176 if (chosenConstructor == null) { 177 chosenConstructor = ctor; 178 } else { 179 ctor = chosenConstructor; 180 } 181 } 182 } catch (AmbiguousComponentResolutionException e) { 183 e.setComponent(getComponentImplementation()); 184 throw e; 185 } 186 ComponentMonitor componentMonitor = currentMonitor(); 187 try { 188 Object[] parameters = getMemberArguments(guardedContainer, ctor); 189 ctor = componentMonitor.instantiating(container, ConstructorInjector.this, ctor); 190 if(ctor == null) { 191 throw new NullPointerException("Component Monitor " + componentMonitor 192 + " returned a null constructor from method 'instantiating' after passing in " + ctor); 193 } 194 long startTime = System.currentTimeMillis(); 195 T inst = instantiate(ctor, parameters); 196 componentMonitor.instantiated(container, 197 ConstructorInjector.this, 198 ctor, inst, parameters, System.currentTimeMillis() - startTime); 199 return inst; 200 } catch (InvocationTargetException e) { 201 componentMonitor.instantiationFailed(container, ConstructorInjector.this, ctor, e); 202 if (e.getTargetException() instanceof RuntimeException) { 203 throw (RuntimeException) e.getTargetException(); 204 } else if (e.getTargetException() instanceof Error) { 205 throw (Error) e.getTargetException(); 206 } 207 throw new PicoCompositionException(e.getTargetException()); 208 } catch (InstantiationException e) { 209 return caughtInstantiationException(componentMonitor, ctor, e, container); 210 } catch (IllegalAccessException e) { 211 return caughtIllegalAccessException(componentMonitor, ctor, e, container); 212 213 } 214 } 215 }; 216 } 217 instantiationGuard.setGuardedContainer(container); 218 return instantiationGuard.observe(getComponentImplementation()); 219 } 220 221 protected T instantiate(Constructor<T> constructor, Object[] parameters) throws InstantiationException, IllegalAccessException, InvocationTargetException { 222 T inst = newInstance(constructor, parameters); 223 return inst; 224 } 225 226 public void decorateComponentInstance(PicoContainer container, Type into, T instance) { 227 } 228 229 protected Object[] getMemberArguments(PicoContainer container, final Constructor ctor) { 230 return super.getMemberArguments(container, ctor, ctor.getParameterTypes(), getBindings(ctor.getParameterAnnotations())); 231 } 232 233 private List<Constructor<T>> getSortedMatchingConstructors() { 234 List<Constructor<T>> matchingConstructors = new ArrayList<Constructor<T>>(); 235 Constructor<T>[] allConstructors = getConstructors(); 236 // filter out all constructors that will definately not match 237 for (Constructor<T> constructor : allConstructors) { 238 if ((parameters == null || constructor.getParameterTypes().length == parameters.length) && (constructor.getModifiers() & Modifier.PUBLIC) != 0) { 239 matchingConstructors.add(constructor); 240 } 241 } 242 // optimize list of constructors moving the longest at the beginning 243 if (parameters == null) { 244 Collections.sort(matchingConstructors, new Comparator<Constructor>() { 245 public int compare(Constructor arg0, Constructor arg1) { 246 return arg1.getParameterTypes().length - arg0.getParameterTypes().length; 247 } 248 }); 249 } 250 return matchingConstructors; 251 } 252 253 private Constructor<T>[] getConstructors() { 254 return AccessController.doPrivileged(new PrivilegedAction<Constructor<T>[]>() { 255 public Constructor<T>[] run() { 256 return (Constructor<T>[]) getComponentImplementation().getDeclaredConstructors(); 257 } 258 }); 259 } 260 261 @Override 262 public void verify(final PicoContainer container) throws PicoCompositionException { 263 if (verifyingGuard == null) { 264 verifyingGuard = new ThreadLocalCyclicDependencyGuard() { 265 public Object run() { 266 final Constructor constructor = getGreediestSatisfiableConstructor(guardedContainer); 267 final Class[] parameterTypes = constructor.getParameterTypes(); 268 final Parameter[] currentParameters = parameters != null ? parameters : createDefaultParameters(parameterTypes); 269 for (int i = 0; i < currentParameters.length; i++) { 270 currentParameters[i].verify(container, ConstructorInjector.this, box(parameterTypes[i]), 271 new ParameterNameBinding(getParanamer(), getComponentImplementation(), constructor, i), 272 useNames(), getBindings(constructor.getParameterAnnotations())[i]); 273 } 274 return null; 275 } 276 }; 277 } 278 verifyingGuard.setGuardedContainer(container); 279 verifyingGuard.observe(getComponentImplementation()); 280 } 281 282 public String getDescriptor() { 283 return "ConstructorInjector-"; 284 } 285 286 287 }