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.defaults; 012 013 import org.picocontainer.ComponentMonitor; 014 import org.picocontainer.Parameter; 015 import org.picocontainer.PicoContainer; 016 import org.picocontainer.PicoInitializationException; 017 import org.picocontainer.PicoIntrospectionException; 018 019 import java.lang.reflect.Constructor; 020 import java.lang.reflect.InvocationTargetException; 021 import java.lang.reflect.Modifier; 022 import java.security.AccessController; 023 import java.security.PrivilegedAction; 024 import java.util.ArrayList; 025 import java.util.Arrays; 026 import java.util.Collections; 027 import java.util.Comparator; 028 import java.util.HashSet; 029 import java.util.List; 030 import java.util.Set; 031 032 /** 033 * Instantiates components using Constructor Injection. 034 * <em> 035 * Note that this class doesn't cache instances. If you want caching, 036 * use a {@link CachingComponentAdapter} around this one. 037 * </em> 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 * @version $Revision: 2971 $ 046 */ 047 public class ConstructorInjectionComponentAdapter extends InstantiatingComponentAdapter { 048 private transient List sortedMatchingConstructors; 049 private transient Guard instantiationGuard; 050 051 private static abstract class Guard extends ThreadLocalCyclicDependencyGuard { 052 protected PicoContainer guardedContainer; 053 054 private void setArguments(PicoContainer container) { 055 this.guardedContainer = container; 056 } 057 } 058 059 /** 060 * Creates a ConstructorInjectionComponentAdapter 061 * 062 * @param componentKey the search key for this implementation 063 * @param componentImplementation the concrete implementation 064 * @param parameters the parameters to use for the initialization 065 * @param allowNonPublicClasses flag to allow instantiation of non-public classes. 066 * @param monitor the component monitor used by this adapter 067 * @param lifecycleStrategy the component lifecycle strategy used by this adapter 068 * @throws AssignabilityRegistrationException 069 * if the key is a type and the implementation cannot be assigned to. 070 * @throws NotConcreteRegistrationException 071 * if the implementation is not a concrete class. 072 * @throws NullPointerException if one of the parameters is <code>null</code> 073 */ 074 public ConstructorInjectionComponentAdapter(final Object componentKey, final Class componentImplementation, Parameter[] parameters, boolean allowNonPublicClasses, ComponentMonitor monitor, LifecycleStrategy lifecycleStrategy) throws AssignabilityRegistrationException, NotConcreteRegistrationException { 075 super(componentKey, componentImplementation, parameters, allowNonPublicClasses, monitor, lifecycleStrategy); 076 } 077 078 /** 079 * Creates a ConstructorInjectionComponentAdapter 080 * 081 * @param componentKey the search key for this implementation 082 * @param componentImplementation the concrete implementation 083 * @param parameters the parameters to use for the initialization 084 * @param allowNonPublicClasses flag to allow instantiation of non-public classes. 085 * @param monitor the component monitor used by this adapter 086 * @throws AssignabilityRegistrationException 087 * if the key is a type and the implementation cannot be assigned to. 088 * @throws NotConcreteRegistrationException 089 * if the implementation is not a concrete class. 090 * @throws NullPointerException if one of the parameters is <code>null</code> 091 */ 092 public ConstructorInjectionComponentAdapter(final Object componentKey, final Class componentImplementation, Parameter[] parameters, boolean allowNonPublicClasses, ComponentMonitor monitor) throws AssignabilityRegistrationException, NotConcreteRegistrationException { 093 super(componentKey, componentImplementation, parameters, allowNonPublicClasses, monitor); 094 } 095 096 /** 097 * Creates a ConstructorInjectionComponentAdapter 098 * 099 * @param componentKey the search key for this implementation 100 * @param componentImplementation the concrete implementation 101 * @param parameters the parameters to use for the initialization 102 * @param allowNonPublicClasses flag to allow instantiation of non-public classes. 103 * @throws AssignabilityRegistrationException 104 * if the key is a type and the implementation cannot be assigned to. 105 * @throws NotConcreteRegistrationException 106 * if the implementation is not a concrete class. 107 * @throws NullPointerException if one of the parameters is <code>null</code> 108 */ 109 public ConstructorInjectionComponentAdapter(final Object componentKey, final Class componentImplementation, Parameter[] parameters, boolean allowNonPublicClasses) throws AssignabilityRegistrationException, NotConcreteRegistrationException { 110 super(componentKey, componentImplementation, parameters, allowNonPublicClasses); 111 } 112 113 /** 114 * Creates a ConstructorInjectionComponentAdapter with key, implementation and parameters 115 * 116 * @param componentKey the search key for this implementation 117 * @param componentImplementation the concrete implementation 118 * @param parameters the parameters to use for the initialization 119 * @throws AssignabilityRegistrationException 120 * if the key is a type and the implementation cannot be assigned to. 121 * @throws NotConcreteRegistrationException 122 * if the implementation is not a concrete class. 123 * @throws NullPointerException if one of the parameters is <code>null</code> 124 */ 125 public ConstructorInjectionComponentAdapter(Object componentKey, Class componentImplementation, Parameter[] parameters) { 126 this(componentKey, componentImplementation, parameters, false); 127 } 128 129 /** 130 * Creates a ConstructorInjectionComponentAdapter with key and implementation 131 * 132 * @param componentKey the search key for this implementation 133 * @param componentImplementation the concrete implementation 134 * @throws AssignabilityRegistrationException 135 * if the key is a type and the implementation cannot be assigned to. 136 * @throws NotConcreteRegistrationException 137 * if the implementation is not a concrete class. 138 * @throws NullPointerException if one of the parameters is <code>null</code> 139 */ 140 public ConstructorInjectionComponentAdapter(Object componentKey, Class componentImplementation) throws AssignabilityRegistrationException, NotConcreteRegistrationException { 141 this(componentKey, componentImplementation, null); 142 } 143 144 protected Constructor getGreediestSatisfiableConstructor(PicoContainer container) throws PicoIntrospectionException, UnsatisfiableDependenciesException, AmbiguousComponentResolutionException, AssignabilityRegistrationException, NotConcreteRegistrationException { 145 final Set conflicts = new HashSet(); 146 final Set unsatisfiableDependencyTypes = new HashSet(); 147 if (sortedMatchingConstructors == null) { 148 sortedMatchingConstructors = getSortedMatchingConstructors(); 149 } 150 Constructor greediestConstructor = null; 151 int lastSatisfiableConstructorSize = -1; 152 Class unsatisfiedDependencyType = null; 153 for (int i = 0; i < sortedMatchingConstructors.size(); i++) { 154 boolean failedDependency = false; 155 Constructor constructor = (Constructor) sortedMatchingConstructors.get(i); 156 Class[] parameterTypes = constructor.getParameterTypes(); 157 Parameter[] currentParameters = parameters != null ? parameters : createDefaultParameters(parameterTypes); 158 159 // remember: all constructors with less arguments than the given parameters are filtered out already 160 for (int j = 0; j < currentParameters.length; j++) { 161 // check wether this constructor is statisfiable 162 if (currentParameters[j].isResolvable(container, this, parameterTypes[j])) { 163 continue; 164 } 165 unsatisfiableDependencyTypes.add(Arrays.asList(parameterTypes)); 166 unsatisfiedDependencyType = parameterTypes[j]; 167 failedDependency = true; 168 break; 169 } 170 171 if (greediestConstructor != null && parameterTypes.length != lastSatisfiableConstructorSize) { 172 if (conflicts.isEmpty()) { 173 // we found our match [aka. greedy and satisfied] 174 return greediestConstructor; 175 } else { 176 // fits although not greedy 177 conflicts.add(constructor); 178 } 179 } else if (!failedDependency && lastSatisfiableConstructorSize == parameterTypes.length) { 180 // satisfied and same size as previous one? 181 conflicts.add(constructor); 182 conflicts.add(greediestConstructor); 183 } else if (!failedDependency) { 184 greediestConstructor = constructor; 185 lastSatisfiableConstructorSize = parameterTypes.length; 186 } 187 } 188 if (!conflicts.isEmpty()) { 189 throw new TooManySatisfiableConstructorsException(getComponentImplementation(), conflicts); 190 } else if (greediestConstructor == null && !unsatisfiableDependencyTypes.isEmpty()) { 191 throw new UnsatisfiableDependenciesException(this, unsatisfiedDependencyType, unsatisfiableDependencyTypes, container); 192 } else if (greediestConstructor == null) { 193 // be nice to the user, show all constructors that were filtered out 194 final Set nonMatching = new HashSet(); 195 final Constructor[] constructors = getConstructors(); 196 for (int i = 0; i < constructors.length; i++) { 197 nonMatching.add(constructors[i]); 198 } 199 throw new PicoInitializationException("Either do the specified parameters not match any of the following constructors: " + nonMatching.toString() + " or the constructors were not accessible for '" + getComponentImplementation() + "'"); 200 } 201 return greediestConstructor; 202 } 203 204 public Object getComponentInstance(PicoContainer container) throws PicoInitializationException, PicoIntrospectionException, AssignabilityRegistrationException, NotConcreteRegistrationException { 205 if (instantiationGuard == null) { 206 instantiationGuard = new Guard() { 207 public Object run() { 208 final Constructor constructor; 209 try { 210 constructor = getGreediestSatisfiableConstructor(guardedContainer); 211 } catch (AmbiguousComponentResolutionException e) { 212 e.setComponent(getComponentImplementation()); 213 throw e; 214 } 215 ComponentMonitor componentMonitor = currentMonitor(); 216 try { 217 Object[] parameters = getConstructorArguments(guardedContainer, constructor); 218 componentMonitor.instantiating(constructor); 219 long startTime = System.currentTimeMillis(); 220 Object inst = newInstance(constructor, parameters); 221 componentMonitor.instantiated(constructor, inst, parameters, System.currentTimeMillis() - startTime); 222 return inst; 223 } catch (InvocationTargetException e) { 224 componentMonitor.instantiationFailed(constructor, e); 225 if (e.getTargetException() instanceof RuntimeException) { 226 throw (RuntimeException) e.getTargetException(); 227 } else if (e.getTargetException() instanceof Error) { 228 throw (Error) e.getTargetException(); 229 } 230 throw new PicoInvocationTargetInitializationException(e.getTargetException()); 231 } catch (InstantiationException e) { 232 // can't get here because checkConcrete() will catch it earlier, but see PICO-191 233 ///CLOVER:OFF 234 componentMonitor.instantiationFailed(constructor, e); 235 throw new PicoInitializationException("Should never get here"); 236 ///CLOVER:ON 237 } catch (IllegalAccessException e) { 238 // can't get here because either filtered or access mode set 239 ///CLOVER:OFF 240 componentMonitor.instantiationFailed(constructor, e); 241 throw new PicoInitializationException(e); 242 ///CLOVER:ON 243 } 244 } 245 }; 246 } 247 instantiationGuard.setArguments(container); 248 return instantiationGuard.observe(getComponentImplementation()); 249 } 250 251 protected Object[] getConstructorArguments(PicoContainer container, Constructor ctor) { 252 Class[] parameterTypes = ctor.getParameterTypes(); 253 Object[] result = new Object[parameterTypes.length]; 254 Parameter[] currentParameters = parameters != null ? parameters : createDefaultParameters(parameterTypes); 255 256 for (int i = 0; i < currentParameters.length; i++) { 257 result[i] = currentParameters[i].resolveInstance(container, this, parameterTypes[i]); 258 } 259 return result; 260 } 261 262 private List getSortedMatchingConstructors() { 263 List matchingConstructors = new ArrayList(); 264 Constructor[] allConstructors = getConstructors(); 265 // filter out all constructors that will definately not match 266 for (int i = 0; i < allConstructors.length; i++) { 267 Constructor constructor = allConstructors[i]; 268 if ((parameters == null || constructor.getParameterTypes().length == parameters.length) && (allowNonPublicClasses || (constructor.getModifiers() & Modifier.PUBLIC) != 0)) { 269 matchingConstructors.add(constructor); 270 } 271 } 272 // optimize list of constructors moving the longest at the beginning 273 if (parameters == null) { 274 Collections.sort(matchingConstructors, new Comparator() { 275 public int compare(Object arg0, Object arg1) { 276 return ((Constructor) arg1).getParameterTypes().length - ((Constructor) arg0).getParameterTypes().length; 277 } 278 }); 279 } 280 return matchingConstructors; 281 } 282 283 private Constructor[] getConstructors() { 284 return (Constructor[]) AccessController.doPrivileged(new PrivilegedAction() { 285 public Object run() { 286 return getComponentImplementation().getDeclaredConstructors(); 287 } 288 }); 289 } 290 }