View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.xbean.spring.context.impl;
18  
19  import org.springframework.beans.BeansException;
20  import org.springframework.beans.FatalBeanException;
21  import org.springframework.beans.MutablePropertyValues;
22  import org.springframework.beans.PropertyValue;
23  import org.springframework.beans.factory.FactoryBean;
24  import org.springframework.beans.factory.config.BeanDefinitionHolder;
25  import org.springframework.beans.factory.config.ConstructorArgumentValues;
26  import org.springframework.beans.factory.support.AbstractBeanDefinition;
27  
28  import java.lang.reflect.Constructor;
29  import java.lang.reflect.Method;
30  import java.util.ArrayList;
31  import java.util.Arrays;
32  import java.util.Collections;
33  import java.util.Comparator;
34  import java.util.HashMap;
35  import java.util.HashSet;
36  import java.util.Iterator;
37  import java.util.LinkedList;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.Set;
41  
42  /**
43   * NamedConstructorArgs is a BeanFactoryPostProcessor that converts property declarations into indexed constructor args
44   * based on the the constructor parameter names annotation.  This process first selects a constructor and then fills in
45   * the constructor arguments from the properties defined in the bean definition.  If a property is not defined in the
46   * bean definition, first the defaultValues map is checked for a value and if a value is not present a Java default
47   * value is provided for the constructor argument (e.g. numbers are assigned 0 and objects are assigned null).
48   *
49   * @author Dain Sundstrom
50   * @version $Id$
51   * @since 2.0
52   */
53  public class NamedConstructorArgs {
54      private Map defaultValues = new HashMap();
55  
56      /**
57       * Gets the default values that are assigned to constructor arguments without a defined value.
58       * @return the default values that are assigned to constructor arguments without a defined value
59       */
60      public List getDefaultValues() {
61          List values = new LinkedList();
62          for (Iterator iterator = defaultValues.entrySet().iterator(); iterator.hasNext();) {
63              Map.Entry entry = (Map.Entry) iterator.next();
64              PropertyKey key = (PropertyKey) entry.getKey();
65              Object value = entry.getValue();
66              values.add(new DefaultProperty(key.name, key.type, value));
67          }
68          return values;
69      }
70  
71      /**
72       * Sets the default values that are assigned to constructor arguments without a defined value.
73       * @param defaultValues the values that are assigned to constructor arguments without a defined value
74       */
75      public void setDefaultValues(List defaultValues) {
76          this.defaultValues.clear();
77          for (Iterator iterator = defaultValues.iterator(); iterator.hasNext();) {
78              addDefaultValue((DefaultProperty) iterator.next());
79          }
80      }
81  
82      /**
83       * Adds a default value for a property with the specified name and type.
84       * @param name the name of the property
85       * @param type the type of the property
86       * @param value the default value for a property with the specified name and type
87       */
88      public void addDefaultValue(String name, Class type, Object value) {
89          defaultValues.put(new PropertyKey(name, type), value);
90      }
91  
92      /**
93       * Adds a defautl value for a property.
94       * @param defaultProperty the default property information
95       */
96      private void addDefaultValue(DefaultProperty defaultProperty) {
97          defaultValues.put(new PropertyKey(defaultProperty.getName(), defaultProperty.getType()), defaultProperty.getValue());
98      }
99  
100     public void processParameters(BeanDefinitionHolder definitionHolder, MappingMetaData metadata) throws BeansException {
101         // this only works if we have an abstsract bean definition
102         if (!(definitionHolder.getBeanDefinition() instanceof AbstractBeanDefinition)) {
103             return;
104         }
105 
106         AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) definitionHolder.getBeanDefinition();
107         ConstructorArgumentValues constructorArgumentValues = beanDefinition.getConstructorArgumentValues();
108 
109         // if this bean already has constructor arguments defined, don't mess with them
110         if (constructorArgumentValues.getArgumentCount() > 0) {
111             return;
112         }
113 
114         // try to get a list of constructor arg names to use
115         ConstructionInfo constructionInfo = selectConstructionMethod(beanDefinition, metadata);
116         if (constructionInfo == null) {
117             return;
118         }
119 
120         // remove each named property and add an indexed constructor arg
121         MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
122         String[] parameterNames = constructionInfo.parameterNames;
123         Class[] parameterTypes = constructionInfo.parameterTypes;
124         for (int i = 0; i < parameterNames.length; i++) {
125             String parameterName = parameterNames[i];
126             Class parameterType = parameterTypes[i];
127 
128             PropertyValue propertyValue = propertyValues.getPropertyValue(parameterName);
129             if (propertyValue != null) {
130                 propertyValues.removePropertyValue(parameterName);
131                 constructorArgumentValues.addIndexedArgumentValue(i, propertyValue.getValue(), parameterType.getName());
132             } else {
133                 Object defaultValue = defaultValues.get(new PropertyKey(parameterName, parameterType));
134                 if (defaultValue == null) {
135                     defaultValue = DEFAULT_VALUE.get(parameterType);
136                 }
137                 if (defaultValue instanceof FactoryBean) {
138                     try {
139                         defaultValue = ((FactoryBean)defaultValue).getObject();
140                     } catch (Exception e) {
141                         throw new FatalBeanException("Unable to get object value from bean factory", e);
142                     }
143                 }
144                 constructorArgumentValues.addIndexedArgumentValue(i, defaultValue, parameterType.getName());
145             }
146         }
147 
148         // todo set any usable default values on the bean definition
149     }
150 
151     private ConstructionInfo selectConstructionMethod(AbstractBeanDefinition beanDefinition, MappingMetaData metadata) {
152         Class beanClass = beanDefinition.getBeanClass();
153 
154         // get a set containing the names of the defined properties
155         Set definedProperties = new HashSet();
156         PropertyValue[] values = beanDefinition.getPropertyValues().getPropertyValues();
157         for (int i = 0; i < values.length; i++) {
158             definedProperties.add(values[i].getName());
159         }
160 
161         // first check for a factory method
162         if (beanDefinition.getFactoryMethodName() != null) {
163             return selectFactory(beanClass, beanDefinition, metadata, definedProperties);
164         } else {
165             return selectConstructor(beanClass, metadata, definedProperties);
166         }
167     }
168 
169     private ConstructionInfo selectFactory(Class beanClass, AbstractBeanDefinition beanDefinition, MappingMetaData metadata, Set definedProperties) {
170         String factoryMethodName = beanDefinition.getFactoryMethodName();
171 
172         // get the factory methods sorted by longest arg length first
173         Method[] methods = beanClass.getMethods();
174         List factoryMethods = new ArrayList(methods.length);
175         for (int i = 0; i < methods.length; i++) {
176             Method method = methods[i];
177             if (method.getName().equals(factoryMethodName)) {
178                 factoryMethods.add(method);
179             }
180         }
181 
182         Collections.sort(factoryMethods, new ArgLengthComparator());
183 
184         // if a factory method has been annotated as the default constructor we always use that constructor
185         for (Iterator iterator = factoryMethods.iterator(); iterator.hasNext();) {
186             Method factoryMethod = (Method) iterator.next();
187 
188             if (metadata.isDefaultFactoryMethod(beanClass, factoryMethod)) {
189                 return new ConstructionInfo(beanClass, factoryMethod, metadata);
190             }
191         }
192 
193         // try to find a constructor for which we have all of the properties defined
194         for (Iterator iterator = factoryMethods.iterator(); iterator.hasNext();) {
195             Method factoryMethod = (Method) iterator.next();
196             ConstructionInfo constructionInfo = new ConstructionInfo(beanClass, factoryMethod, metadata);
197             if (isUsableConstructor(constructionInfo, definedProperties)) {
198                 return constructionInfo;
199             }
200         }
201         return null;
202     }
203 
204     private ConstructionInfo selectConstructor(Class beanClass, MappingMetaData metadata, Set definedProperties) {
205         // get the constructors sorted by longest arg length first
206         List constructors = new ArrayList(Arrays.asList(beanClass.getConstructors()));
207         Collections.sort(constructors, new ArgLengthComparator());
208 
209         // if a constructor has been annotated as the default constructor we always use that constructor
210         for (Iterator iterator = constructors.iterator(); iterator.hasNext();) {
211             Constructor constructor = (Constructor) iterator.next();
212 
213             if (metadata.isDefaultConstructor(constructor)) {
214                 return new ConstructionInfo(constructor, metadata);
215             }
216         }
217 
218         // try to find a constructor for which we have all of the properties defined
219         for (Iterator iterator = constructors.iterator(); iterator.hasNext();) {
220             Constructor constructor = (Constructor) iterator.next();
221             ConstructionInfo constructionInfo = new ConstructionInfo(constructor, metadata);
222             if (isUsableConstructor(constructionInfo, definedProperties)) {
223                 return constructionInfo;
224             }
225         }
226         return null;
227     }
228 
229     private boolean isUsableConstructor(ConstructionInfo constructionInfo, Set definedProperties) {
230         // if we don't have parameter names this is not the constructor we are looking for
231         String[] parameterNames = constructionInfo.parameterNames;
232         if (parameterNames == null) {
233             return false;
234         }
235 
236         Class[] parameterTypes = constructionInfo.parameterTypes;
237         for (int i = 0; i < parameterNames.length; i++) {
238             String parameterName = parameterNames[i];
239             Class parameterType = parameterTypes[i];
240 
241             // can we satify this property using a defined property or default property
242             if (!definedProperties.contains(parameterName) && !defaultValues.containsKey(new PropertyKey(parameterName, parameterType))) {
243                 return false;
244             }
245         }
246 
247         return true;
248     }
249 
250     private class ConstructionInfo {
251         private final Class[] parameterTypes;
252         private final String[] parameterNames;
253 
254         public ConstructionInfo(Constructor constructor, MappingMetaData metadata) {
255             this.parameterTypes = constructor.getParameterTypes();
256             String[] names = metadata.getParameterNames(constructor);
257 
258             // verify that we have enough parameter names
259             int expectedParameterCount = parameterTypes.length;
260             if (names != null && names.length != expectedParameterCount) {
261                 throw new FatalBeanException("Excpected " + expectedParameterCount + " parameter names for constructor but only got " +
262                         names.length + ": " + constructor.toString());
263             }
264             if (expectedParameterCount == 0) {
265                 names = new String[0];
266             }
267 
268             this.parameterNames = names;
269         }
270 
271         public ConstructionInfo(Class beanClass, Method factoryMethod, MappingMetaData metadata) {
272             this.parameterTypes = factoryMethod.getParameterTypes();
273 
274             String[] names = metadata.getParameterNames(beanClass, factoryMethod);
275 
276             // verify that we have enough parameter names
277             int expectedParameterCount = parameterTypes.length;
278             if (names != null && names.length != expectedParameterCount) {
279                 throw new FatalBeanException("Excpected " + expectedParameterCount + " parameter names for factory method but only got " +
280                         names.length + ": " + factoryMethod.toString());
281             }
282             if (expectedParameterCount == 0) {
283                 names = new String[0];
284             }
285 
286             this.parameterNames = names;
287         }
288     }
289 
290     private static class ArgLengthComparator implements Comparator {
291         public int compare(Object o1, Object o2) {
292             return getArgLength(o2) - getArgLength(o1);
293         }
294 
295         private int getArgLength(Object object) {
296             if (object instanceof Method) {
297                 return ((Method) object).getParameterTypes().length;
298             } else {
299                 return ((Constructor) object).getParameterTypes().length;
300             }
301         }
302     }
303 
304     private static class PropertyKey {
305         private final String name;
306         private final Class type;
307 
308         public PropertyKey(String name, Class type) {
309             this.name = name;
310             this.type = type;
311         }
312 
313         public boolean equals(Object object) {
314             if (!(object instanceof PropertyKey)) {
315                 return false;
316             }
317 
318             PropertyKey defaultProperty = (PropertyKey) object;
319             return name.equals(defaultProperty.name) && type.equals(type);
320         }
321 
322         public int hashCode() {
323             int result = 17;
324             result = 37 * result + name.hashCode();
325             result = 37 * result + type.hashCode();
326             return result;
327         }
328 
329         public String toString() {
330             return "[" + name + " " + type + "]";
331         }
332     }
333 
334     private static final Map DEFAULT_VALUE;
335     static {
336         Map temp = new HashMap();
337         temp.put(Boolean.TYPE, Boolean.FALSE);
338         temp.put(Byte.TYPE, new Byte((byte) 0));
339         temp.put(Character.TYPE, new Character((char) 0));
340         temp.put(Short.TYPE, new Short((short) 0));
341         temp.put(Integer.TYPE, new Integer(0));
342         temp.put(Long.TYPE, new Long(0));
343         temp.put(Float.TYPE, new Float(0));
344         temp.put(Double.TYPE, new Double(0));
345 
346         DEFAULT_VALUE = Collections.unmodifiableMap(temp);
347     }
348 }