View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one or more
4    * contributor license agreements.  See the NOTICE file distributed with
5    * this work for additional information regarding copyright ownership.
6    * The ASF licenses this file to You under the Apache License, Version 2.0
7    * (the "License"); you may not use this file except in compliance with
8    * the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing, software
13   *  distributed under the License is distributed on an "AS IS" BASIS,
14   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   *  See the License for the specific language governing permissions and
16   *  limitations under the License.
17   */
18  package org.apache.xbean.recipe;
19  
20  import java.lang.annotation.Annotation;
21  import java.lang.reflect.AccessibleObject;
22  import java.lang.reflect.Constructor;
23  import java.lang.reflect.Field;
24  import java.lang.reflect.InvocationTargetException;
25  import java.lang.reflect.Method;
26  import java.lang.reflect.Modifier;
27  import java.lang.reflect.Type;
28  import java.security.AccessController;
29  import java.security.PrivilegedAction;
30  import java.util.ArrayList;
31  import java.util.Arrays;
32  import java.util.Collections;
33  import java.util.Comparator;
34  import java.util.EnumSet;
35  import java.util.LinkedHashSet;
36  import java.util.LinkedList;
37  import java.util.List;
38  import java.util.Set;
39  
40  import static org.apache.xbean.recipe.RecipeHelper.isAssignableFrom;
41  
42  public final class ReflectionUtil {
43      private static ParameterNameLoader parameterNamesLoader;
44      static {
45          String[] impls = {"org.apache.xbean.recipe.XbeanAsmParameterNameLoader", "org.apache.xbean.recipe.AsmParameterNameLoader"};
46          for (String impl : impls) {
47              try {
48                  Class<? extends ParameterNameLoader> loaderClass = ReflectionUtil.class.getClassLoader().loadClass(impl).asSubclass(ParameterNameLoader.class);
49                  parameterNamesLoader = loaderClass.newInstance();
50                  break;
51              } catch (Throwable ignored) {
52              }
53          }
54      }
55  
56      private ReflectionUtil() {
57      }
58  
59      public static Field findField(Class typeClass, String propertyName, Object propertyValue, Set<Option> options) {
60          if (typeClass == null) throw new NullPointerException("typeClass is null");
61          if (propertyName == null) throw new NullPointerException("name is null");
62          if (propertyName.length() == 0) throw new IllegalArgumentException("name is an empty string");
63          if (options == null) options = EnumSet.noneOf(Option.class);
64  
65          int matchLevel = 0;
66          MissingAccessorException missException = null;
67  
68          if (propertyName.contains("/")){
69              String[] strings = propertyName.split("/");
70              if (strings == null || strings.length != 2) throw new IllegalArgumentException("badly formed <class>/<attribute> property name: " + propertyName);
71  
72              String className = strings[0];
73              propertyName = strings[1];
74  
75              boolean found = false;
76              while(!typeClass.equals(Object.class) && !found){
77                  if (typeClass.getName().equals(className)){
78                      found = true;
79                      break;
80                  } else {
81                      typeClass = typeClass.getSuperclass();
82                  }
83              }
84  
85              if (!found) throw new MissingAccessorException("Type not assignable to class: " + className, -1);
86          }
87  
88          List<Field> fields = new ArrayList<Field>(Arrays.asList(typeClass.getDeclaredFields()));
89          Class parent = typeClass.getSuperclass();
90          while (parent != null){
91              fields.addAll(Arrays.asList(parent.getDeclaredFields()));
92              parent = parent.getSuperclass();
93          }
94  
95          boolean allowPrivate = options.contains(Option.PRIVATE_PROPERTIES);
96          boolean allowStatic = options.contains(Option.STATIC_PROPERTIES);
97          boolean caseInsesnitive = options.contains(Option.CASE_INSENSITIVE_PROPERTIES);
98  
99          for (Field field : fields) {
100             if (field.getName().equals(propertyName) || (caseInsesnitive && field.getName().equalsIgnoreCase(propertyName))) {
101 
102                 if (!allowPrivate && !Modifier.isPublic(field.getModifiers())) {
103                     if (matchLevel < 4) {
104                         matchLevel = 4;
105                         missException = new MissingAccessorException("Field is not public: " + field, matchLevel);
106                     }
107                     continue;
108                 }
109 
110                 if (!allowStatic && Modifier.isStatic(field.getModifiers())) {
111                     if (matchLevel < 4) {
112                         matchLevel = 4;
113                         missException = new MissingAccessorException("Field is static: " + field, matchLevel);
114                     }
115                     continue;
116                 }
117 
118                 Class fieldType = field.getType();
119                 if (fieldType.isPrimitive() && propertyValue == null) {
120                     if (matchLevel < 6) {
121                         matchLevel = 6;
122                         missException = new MissingAccessorException("Null can not be assigned to " +
123                                 fieldType.getName() + ": " + field, matchLevel);
124                     }
125                     continue;
126                 }
127 
128 
129                 if (!RecipeHelper.isInstance(fieldType, propertyValue) && !RecipeHelper.isConvertable(fieldType, propertyValue)) {
130                     if (matchLevel < 5) {
131                         matchLevel = 5;
132                         missException = new MissingAccessorException((propertyValue == null ? "null" : propertyValue.getClass().getName()) + " can not be assigned or converted to " +
133                                 fieldType.getName() + ": " + field, matchLevel);
134                     }
135                     continue;
136                 }
137 
138                 if (allowPrivate && !Modifier.isPublic(field.getModifiers())) {
139                     setAccessible(field);
140                 }
141 
142                 return field;
143             }
144 
145         }
146 
147         if (missException != null) {
148             throw missException;
149         } else {
150             StringBuffer buffer = new StringBuffer("Unable to find a valid field: ");
151             buffer.append("public ").append(" ").append(propertyValue == null ? "null" : propertyValue.getClass().getName());
152             buffer.append(" ").append(propertyName).append(";");
153             throw new MissingAccessorException(buffer.toString(), -1);
154         }
155     }
156 
157     public static Method findGetter(Class typeClass, String propertyName, Set<Option> options) {
158         if (typeClass == null) throw new NullPointerException("typeClass is null");
159         if (propertyName == null) throw new NullPointerException("name is null");
160         if (propertyName.length() == 0) throw new IllegalArgumentException("name is an empty string");
161         if (options == null) options = EnumSet.noneOf(Option.class);
162 
163         if (propertyName.contains("/")){
164             String[] strings = propertyName.split("/");
165             if (strings == null || strings.length != 2) throw new IllegalArgumentException("badly formed <class>/<attribute> property name: " + propertyName);
166 
167             String className = strings[0];
168             propertyName = strings[1];
169 
170             boolean found = false;
171             while(!typeClass.equals(Object.class) && !found){
172                 if (typeClass.getName().equals(className)){
173                     found = true;
174                     break;
175                 } else {
176                     typeClass = typeClass.getSuperclass();
177                 }
178             }
179 
180             if (!found) throw new MissingAccessorException("Type not assignable to class: " + className, -1);
181         }
182 
183         String getterName = "get" + Character.toUpperCase(propertyName.charAt(0));
184         if (propertyName.length() > 0) {
185             getterName += propertyName.substring(1);
186         }
187         
188         boolean allowPrivate = options.contains(Option.PRIVATE_PROPERTIES);
189         boolean allowStatic = options.contains(Option.STATIC_PROPERTIES);
190         boolean caseInsesnitive = options.contains(Option.CASE_INSENSITIVE_PROPERTIES);
191 
192         List<Method> methods = new ArrayList<Method>(Arrays.asList(typeClass.getMethods()));
193         methods.addAll(Arrays.asList(typeClass.getDeclaredMethods()));
194         for (Method method : methods) {
195             if (method.getName().equals(getterName) || (caseInsesnitive && method.getName().equalsIgnoreCase(getterName))) {
196                 if (method.getParameterTypes().length > 0) {
197                     continue;
198                 }
199                 if (method.getReturnType() == Void.TYPE) {
200                     continue;
201                 }
202                 if (Modifier.isAbstract(method.getModifiers())) {
203                     continue;
204                 }
205                 if (!allowPrivate && !Modifier.isPublic(method.getModifiers())) {
206                     continue;
207                 }
208                 if (!allowStatic && Modifier.isStatic(method.getModifiers())) {
209                     continue;
210                 }
211 
212                 if (allowPrivate && !Modifier.isPublic(method.getModifiers())) {
213                     setAccessible(method);
214                 }
215                 
216                 return method;
217             }
218         }
219         
220         return null;
221     }
222     
223     public static Method findSetter(Class typeClass, String propertyName, Object propertyValue, Set<Option> options) {
224         List<Method> setters = findAllSetters(typeClass, propertyName, propertyValue, options);
225         return setters.get(0);
226     }
227 
228     /**
229      * Finds all valid setters for the property.  Due to automatic type conversion there may be more than one possible
230      * setter that could be used to set the property.  The setters that do not require type converstion will be a the
231      * head of the returned list of setters.
232      * @param typeClass the class to search for setters
233      * @param propertyName the name of the property
234      * @param propertyValue the value that must be settable either directly or after conversion
235      * @param options controls which setters are considered valid
236      * @return the valid setters; never null or empty
237      */
238     public static List<Method> findAllSetters(Class typeClass, String propertyName, Object propertyValue, Set<Option> options) {
239         if (typeClass == null) throw new NullPointerException("typeClass is null");
240         if (propertyName == null) throw new NullPointerException("name is null");
241         if (propertyName.length() == 0) throw new IllegalArgumentException("name is an empty string");
242         if (options == null) options = EnumSet.noneOf(Option.class);
243 
244         if (propertyName.contains("/")){
245             String[] strings = propertyName.split("/");
246             if (strings == null || strings.length != 2) throw new IllegalArgumentException("badly formed <class>/<attribute> property name: " + propertyName);
247 
248             String className = strings[0];
249             propertyName = strings[1];
250 
251             boolean found = false;
252             while(!typeClass.equals(Object.class) && !found){
253                 if (typeClass.getName().equals(className)){
254                     found = true;
255                     break;
256                 } else {
257                     typeClass = typeClass.getSuperclass();
258                 }
259             }
260 
261             if (!found) throw new MissingAccessorException("Type not assignable to class: " + className, -1);
262         }
263 
264         String setterName = "set" + Character.toUpperCase(propertyName.charAt(0));
265         if (propertyName.length() > 0) {
266             setterName += propertyName.substring(1);
267         }
268 
269 
270         int matchLevel = 0;
271         MissingAccessorException missException = null;
272 
273         boolean allowPrivate = options.contains(Option.PRIVATE_PROPERTIES);
274         boolean allowStatic = options.contains(Option.STATIC_PROPERTIES);
275         boolean caseInsesnitive = options.contains(Option.CASE_INSENSITIVE_PROPERTIES);
276 
277 
278         LinkedList<Method> validSetters = new LinkedList<Method>();
279 
280         List<Method> methods = new ArrayList<Method>(Arrays.asList(typeClass.getMethods()));
281         methods.addAll(Arrays.asList(typeClass.getDeclaredMethods()));
282         for (Method method : methods) {
283             if (method.getName().equals(setterName) || (caseInsesnitive && method.getName().equalsIgnoreCase(setterName))) {
284                 if (method.getParameterTypes().length == 0) {
285                     if (matchLevel < 1) {
286                         matchLevel = 1;
287                         missException = new MissingAccessorException("Setter takes no parameters: " + method, matchLevel);
288                     }
289                     continue;
290                 }
291 
292                 if (method.getParameterTypes().length > 1) {
293                     if (matchLevel < 1) {
294                         matchLevel = 1;
295                         missException = new MissingAccessorException("Setter takes more then one parameter: " + method, matchLevel);
296                     }
297                     continue;
298                 }
299 
300                 if (method.getReturnType() != Void.TYPE) {
301                     if (matchLevel < 2) {
302                         matchLevel = 2;
303                         missException = new MissingAccessorException("Setter returns a value: " + method, matchLevel);
304                     }
305                     continue;
306                 }
307 
308                 if (Modifier.isAbstract(method.getModifiers())) {
309                     if (matchLevel < 3) {
310                         matchLevel = 3;
311                         missException = new MissingAccessorException("Setter is abstract: " + method, matchLevel);
312                     }
313                     continue;
314                 }
315 
316                 if (!allowPrivate && !Modifier.isPublic(method.getModifiers())) {
317                     if (matchLevel < 4) {
318                         matchLevel = 4;
319                         missException = new MissingAccessorException("Setter is not public: " + method, matchLevel);
320                     }
321                     continue;
322                 }
323 
324                 if (!allowStatic && Modifier.isStatic(method.getModifiers())) {
325                     if (matchLevel < 4) {
326                         matchLevel = 4;
327                         missException = new MissingAccessorException("Setter is static: " + method, matchLevel);
328                     }
329                     continue;
330                 }
331 
332                 Class methodParameterType = method.getParameterTypes()[0];
333                 if (methodParameterType.isPrimitive() && propertyValue == null) {
334                     if (matchLevel < 6) {
335                         matchLevel = 6;
336                         missException = new MissingAccessorException("Null can not be assigned to " +
337                                 methodParameterType.getName() + ": " + method, matchLevel);
338                     }
339                     continue;
340                 }
341 
342 
343                 if (!RecipeHelper.isInstance(methodParameterType, propertyValue) && !RecipeHelper.isConvertable(methodParameterType, propertyValue)) {
344                     if (matchLevel < 5) {
345                         matchLevel = 5;
346                         missException = new MissingAccessorException((propertyValue == null ? "null" : propertyValue.getClass().getName()) + " can not be assigned or converted to " +
347                                 methodParameterType.getName() + ": " + method, matchLevel);
348                     }
349                     continue;
350                 }
351 
352                 if (allowPrivate && !Modifier.isPublic(method.getModifiers())) {
353                     setAccessible(method);
354                 }
355 
356                 if (RecipeHelper.isInstance(methodParameterType, propertyValue)) {
357                     // This setter requires no conversion, which means there can not be a conversion error.
358                     // Therefore this setter is perferred and put a the head of the list
359                     validSetters.addFirst(method);
360                 } else {
361                     validSetters.add(method);
362                 }
363             }
364 
365         }
366 
367         if (!validSetters.isEmpty()) {
368             // remove duplicate methods (can happen with inheritance)
369             return new ArrayList<Method>(new LinkedHashSet<Method>(validSetters));
370         }
371         
372         if (missException != null) {
373             throw missException;
374         } else {
375             StringBuffer buffer = new StringBuffer("Unable to find a valid setter method: ");
376             buffer.append("public void ").append(typeClass.getName()).append(".");
377             buffer.append(setterName).append("(");
378             if (propertyValue == null) {
379                 buffer.append("null");
380             } else if (propertyValue instanceof String || propertyValue instanceof Recipe) {
381                 buffer.append("...");
382             } else {
383                 buffer.append(propertyValue.getClass().getName());
384             }
385             buffer.append(")");
386             throw new MissingAccessorException(buffer.toString(), -1);
387         }
388     }
389 
390     public static List<Field> findAllFieldsByType(Class typeClass, Object propertyValue, Set<Option> options) {
391         if (typeClass == null) throw new NullPointerException("typeClass is null");
392         if (options == null) options = EnumSet.noneOf(Option.class);
393 
394         int matchLevel = 0;
395         MissingAccessorException missException = null;
396 
397         List<Field> fields = new ArrayList<Field>(Arrays.asList(typeClass.getDeclaredFields()));
398         Class parent = typeClass.getSuperclass();
399         while (parent != null){
400             fields.addAll(Arrays.asList(parent.getDeclaredFields()));
401             parent = parent.getSuperclass();
402         }
403 
404         boolean allowPrivate = options.contains(Option.PRIVATE_PROPERTIES);
405         boolean allowStatic = options.contains(Option.STATIC_PROPERTIES);
406 
407         LinkedList<Field> validFields = new LinkedList<Field>();
408         for (Field field : fields) {
409             Class fieldType = field.getType();
410             if (RecipeHelper.isInstance(fieldType, propertyValue) || RecipeHelper.isConvertable(fieldType, propertyValue)) {
411                 if (!allowPrivate && !Modifier.isPublic(field.getModifiers())) {
412                     if (matchLevel < 4) {
413                         matchLevel = 4;
414                         missException = new MissingAccessorException("Field is not public: " + field, matchLevel);
415                     }
416                     continue;
417                 }
418 
419                 if (!allowStatic && Modifier.isStatic(field.getModifiers())) {
420                     if (matchLevel < 4) {
421                         matchLevel = 4;
422                         missException = new MissingAccessorException("Field is static: " + field, matchLevel);
423                     }
424                     continue;
425                 }
426 
427 
428                 if (fieldType.isPrimitive() && propertyValue == null) {
429                     if (matchLevel < 6) {
430                         matchLevel = 6;
431                         missException = new MissingAccessorException("Null can not be assigned to " +
432                                 fieldType.getName() + ": " + field, matchLevel);
433                     }
434                     continue;
435                 }
436 
437                 if (allowPrivate && !Modifier.isPublic(field.getModifiers())) {
438                     setAccessible(field);
439                 }
440 
441                 if (RecipeHelper.isInstance(fieldType, propertyValue)) {
442                     // This field requires no conversion, which means there can not be a conversion error.
443                     // Therefore this setter is perferred and put a the head of the list
444                     validFields.addFirst(field);
445                 } else {
446                     validFields.add(field);
447                 }
448             }
449         }
450 
451         if (!validFields.isEmpty()) {
452             // remove duplicate methods (can happen with inheritance)
453             return new ArrayList<Field>(new LinkedHashSet<Field>(validFields));
454         }
455 
456         if (missException != null) {
457             throw missException;
458         } else {
459             StringBuffer buffer = new StringBuffer("Unable to find a valid field ");
460             if (propertyValue instanceof Recipe) {
461                 buffer.append("for ").append(propertyValue == null ? "null" : propertyValue);
462             } else {
463                 buffer.append("of type ").append(propertyValue == null ? "null" : propertyValue.getClass().getName());
464             }
465             buffer.append(" in class ").append(typeClass.getName());
466             throw new MissingAccessorException(buffer.toString(), -1);
467         }
468     }
469     public static List<Method> findAllSettersByType(Class typeClass, Object propertyValue, Set<Option> options) {
470         if (typeClass == null) throw new NullPointerException("typeClass is null");
471         if (options == null) options = EnumSet.noneOf(Option.class);
472 
473         int matchLevel = 0;
474         MissingAccessorException missException = null;
475 
476         boolean allowPrivate = options.contains(Option.PRIVATE_PROPERTIES);
477         boolean allowStatic = options.contains(Option.STATIC_PROPERTIES);
478 
479         LinkedList<Method> validSetters = new LinkedList<Method>();
480         List<Method> methods = new ArrayList<Method>(Arrays.asList(typeClass.getMethods()));
481         methods.addAll(Arrays.asList(typeClass.getDeclaredMethods()));
482         for (Method method : methods) {
483             if (method.getName().startsWith("set") && method.getParameterTypes().length == 1 && (RecipeHelper.isInstance(method.getParameterTypes()[0], propertyValue) || RecipeHelper.isConvertable(method.getParameterTypes()[0], propertyValue))) {
484                 if (method.getReturnType() != Void.TYPE) {
485                     if (matchLevel < 2) {
486                         matchLevel = 2;
487                         missException = new MissingAccessorException("Setter returns a value: " + method, matchLevel);
488                     }
489                     continue;
490                 }
491 
492                 if (Modifier.isAbstract(method.getModifiers())) {
493                     if (matchLevel < 3) {
494                         matchLevel = 3;
495                         missException = new MissingAccessorException("Setter is abstract: " + method, matchLevel);
496                     }
497                     continue;
498                 }
499 
500                 if (!allowPrivate && !Modifier.isPublic(method.getModifiers())) {
501                     if (matchLevel < 4) {
502                         matchLevel = 4;
503                         missException = new MissingAccessorException("Setter is not public: " + method, matchLevel);
504                     }
505                     continue;
506                 }
507 
508                 Class methodParameterType = method.getParameterTypes()[0];
509                 if (methodParameterType.isPrimitive() && propertyValue == null) {
510                     if (matchLevel < 6) {
511                         matchLevel = 6;
512                         missException = new MissingAccessorException("Null can not be assigned to " +
513                                 methodParameterType.getName() + ": " + method, matchLevel);
514                     }
515                     continue;
516                 }
517 
518                 if (!allowStatic && Modifier.isStatic(method.getModifiers())) {
519                     if (matchLevel < 4) {
520                         matchLevel = 4;
521                         missException = new MissingAccessorException("Setter is static: " + method, matchLevel);
522                     }
523                     continue;
524                 }
525 
526                 if (allowPrivate && !Modifier.isPublic(method.getModifiers())) {
527                     setAccessible(method);
528                 }
529 
530                 if (RecipeHelper.isInstance(methodParameterType, propertyValue)) {
531                     // This setter requires no conversion, which means there can not be a conversion error.
532                     // Therefore this setter is perferred and put a the head of the list
533                     validSetters.addFirst(method);
534                 } else {
535                     validSetters.add(method);
536                 }
537             }
538 
539         }
540 
541         if (!validSetters.isEmpty()) {
542             // remove duplicate methods (can happen with inheritance)
543             return new ArrayList<Method>(new LinkedHashSet<Method>(validSetters));
544         }
545 
546         if (missException != null) {
547             throw missException;
548         } else {
549             StringBuffer buffer = new StringBuffer("Unable to find a valid setter ");
550             if (propertyValue instanceof Recipe) {
551                 buffer.append("for ").append(propertyValue == null ? "null" : propertyValue);
552             } else {
553                 buffer.append("of type ").append(propertyValue == null ? "null" : propertyValue.getClass().getName());
554             }
555             buffer.append(" in class ").append(typeClass.getName());
556             throw new MissingAccessorException(buffer.toString(), -1);
557         }
558     }
559 
560     public static ConstructorFactory findConstructor(Class typeClass, List<? extends Class<?>> parameterTypes, Set<Option> options) {
561         return findConstructor(typeClass, null, parameterTypes, null, options);
562 
563     }
564     public static ConstructorFactory findConstructor(Class typeClass, List<String> parameterNames, List<? extends Class<?>> parameterTypes, Set<String> availableProperties, Set<Option> options) {
565         if (typeClass == null) throw new NullPointerException("typeClass is null");
566         if (availableProperties == null) availableProperties = Collections.emptySet();
567         if (options == null) options = EnumSet.noneOf(Option.class);
568 
569         //
570         // verify that it is a class we can construct
571         if (!Modifier.isPublic(typeClass.getModifiers())) {
572             throw new ConstructionException("Class is not public: " + typeClass.getName());
573         }
574         if (Modifier.isInterface(typeClass.getModifiers())) {
575             throw new ConstructionException("Class is an interface: " + typeClass.getName());
576         }
577         if (Modifier.isAbstract(typeClass.getModifiers())) {
578             throw new ConstructionException("Class is abstract: " + typeClass.getName());
579         }
580 
581         // verify parameter names and types are the same length
582         if (parameterNames != null) {
583             if (parameterTypes == null) parameterTypes = Collections.nCopies(parameterNames.size(), null);
584             if (parameterNames.size() != parameterTypes.size()) {
585                 throw new ConstructionException("Invalid ObjectRecipe: recipe has " + parameterNames.size() +
586                         " parameter names and " + parameterTypes.size() + " parameter types");
587             }
588         } else if (!options.contains(Option.NAMED_PARAMETERS)) {
589             // Named parameters are not supported and no explicit parameters were given,
590             // so we will only use the no-arg constructor
591             parameterNames = Collections.emptyList();
592             parameterTypes = Collections.emptyList();
593         }
594 
595 
596         // get all methods sorted so that the methods with the most constructor args are first
597         List<Constructor> constructors = new ArrayList<Constructor>(Arrays.asList(typeClass.getConstructors()));
598         constructors.addAll(Arrays.asList(typeClass.getDeclaredConstructors()));
599         Collections.sort(constructors, new Comparator<Constructor>() {
600             public int compare(Constructor constructor1, Constructor constructor2) {
601                 return constructor2.getParameterTypes().length - constructor1.getParameterTypes().length;
602             }
603         });
604 
605         // as we check each constructor, we remember the closest invalid match so we can throw a nice exception to the user
606         int matchLevel = 0;
607         MissingFactoryMethodException missException = null;
608 
609         boolean allowPrivate = options.contains(Option.PRIVATE_CONSTRUCTOR);
610         for (Constructor constructor : constructors) {
611             // if an explicit constructor is specified (via parameter types), look a constructor that matches
612             if (parameterTypes != null) {
613                 if (constructor.getParameterTypes().length != parameterTypes.size()) {
614                     if (matchLevel < 1) {
615                         matchLevel = 1;
616                         missException = new MissingFactoryMethodException("Constructor has " + constructor.getParameterTypes().length + " arugments " +
617                                 "but expected " + parameterTypes.size() + " arguments: " + constructor);
618                     }
619                     continue;
620                 }
621 
622                 if (!isAssignableFrom(parameterTypes, Arrays.<Class<?>>asList(constructor.getParameterTypes()))) {
623                     if (matchLevel < 2) {
624                         matchLevel = 2;
625                         missException = new MissingFactoryMethodException("Constructor has signature " +
626                                 "public static " + typeClass.getName() + toParameterList(constructor.getParameterTypes()) +
627                                 " but expected signature " +
628                                 "public static " + typeClass.getName() + toParameterList(parameterTypes));
629                     }
630                     continue;
631                 }
632             } else {
633                 // Implicit constructor selection based on named constructor args
634                 //
635                 // Only consider methods where we can supply a value for all of the parameters
636                 parameterNames = getParameterNames(constructor);
637                 if (parameterNames == null || !availableProperties.containsAll(parameterNames)) {
638                     continue;
639                 }
640             }
641 
642             if (Modifier.isAbstract(constructor.getModifiers())) {
643                 if (matchLevel < 4) {
644                     matchLevel = 4;
645                     missException = new MissingFactoryMethodException("Constructor is abstract: " + constructor);
646                 }
647                 continue;
648             }
649 
650             if (!allowPrivate && !Modifier.isPublic(constructor.getModifiers())) {
651                 if (matchLevel < 5) {
652                     matchLevel = 5;
653                     missException = new MissingFactoryMethodException("Constructor is not public: " + constructor);
654                 }
655                 continue;
656             }
657 
658             if (allowPrivate && !Modifier.isPublic(constructor.getModifiers())) {
659                 setAccessible(constructor);
660             }
661 
662             return new ConstructorFactory(constructor, parameterNames);
663         }
664 
665         if (missException != null) {
666             throw missException;
667         } else {
668             StringBuffer buffer = new StringBuffer("Unable to find a valid constructor: ");
669             buffer.append("public void ").append(typeClass.getName()).append(toParameterList(parameterTypes));
670             throw new ConstructionException(buffer.toString());
671         }
672     }
673 
674     public static StaticFactory findStaticFactory(Class typeClass, String factoryMethod, List<? extends Class<?>>  parameterTypes, Set<Option> options) {
675         return findStaticFactory(typeClass, factoryMethod, null, parameterTypes, null, options);
676     }
677 
678     public static StaticFactory findStaticFactory(Class typeClass, String factoryMethod, List<String> parameterNames, List<? extends Class<?>> parameterTypes, Set<String> allProperties, Set<Option> options) {
679         if (typeClass == null) throw new NullPointerException("typeClass is null");
680         if (factoryMethod == null) throw new NullPointerException("name is null");
681         if (factoryMethod.length() == 0) throw new IllegalArgumentException("name is an empty string");
682         if (allProperties == null) allProperties = Collections.emptySet();
683         if (options == null) options = EnumSet.noneOf(Option.class);
684 
685         //
686         // verify that it is a class we can construct
687         if (!Modifier.isPublic(typeClass.getModifiers())) {
688             throw new ConstructionException("Class is not public: " + typeClass.getName());
689         }
690         if (Modifier.isInterface(typeClass.getModifiers())) {
691             throw new ConstructionException("Class is an interface: " + typeClass.getName());
692         }
693 
694         // verify parameter names and types are the same length
695         if (parameterNames != null) {
696             if (parameterTypes == null) parameterTypes = Collections.nCopies(parameterNames.size(), null);
697             if (parameterNames.size() != parameterTypes.size()) {
698                 throw new ConstructionException("Invalid ObjectRecipe: recipe has " + parameterNames.size() +
699                         " parameter names and " + parameterTypes.size() + " parameter types");
700             }
701         } else if (!options.contains(Option.NAMED_PARAMETERS)) {
702             // Named parameters are not supported and no explicit parameters were given,
703             // so we will only use the no-arg constructor
704             parameterNames = Collections.emptyList();
705             parameterTypes = Collections.emptyList();
706         }
707 
708         // get all methods sorted so that the methods with the most constructor args are first
709         List<Method> methods = new ArrayList<Method>(Arrays.asList(typeClass.getMethods()));
710         methods.addAll(Arrays.asList(typeClass.getDeclaredMethods()));
711         Collections.sort(methods, new Comparator<Method>() {
712             public int compare(Method method2, Method method1) {
713                 return method1.getParameterTypes().length - method2.getParameterTypes().length;
714             }
715         });
716 
717 
718         // as we check each constructor, we remember the closest invalid match so we can throw a nice exception to the user
719         int matchLevel = 0;
720         MissingFactoryMethodException missException = null;
721 
722         boolean allowPrivate = options.contains(Option.PRIVATE_FACTORY);
723         boolean caseInsesnitive = options.contains(Option.CASE_INSENSITIVE_FACTORY);
724         for (Method method : methods) {
725             // Only consider methods where the name matches
726             if (!method.getName().equals(factoryMethod) && (!caseInsesnitive || !method.getName().equalsIgnoreCase(method.getName()))) {
727                 continue;
728             }
729 
730             // if an explicit constructor is specified (via parameter types), look a constructor that matches
731             if (parameterTypes != null) {
732                 if (method.getParameterTypes().length != parameterTypes.size()) {
733                     if (matchLevel < 1) {
734                         matchLevel = 1;
735                         missException = new MissingFactoryMethodException("Static factory method has " + method.getParameterTypes().length + " arugments " +
736                                 "but expected " + parameterTypes.size() + " arguments: " + method);
737                     }
738                     continue;
739                 }
740 
741                 if (!isAssignableFrom(parameterTypes, Arrays.asList(method.getParameterTypes()))) {
742                     if (matchLevel < 2) {
743                         matchLevel = 2;
744                         missException = new MissingFactoryMethodException("Static factory method has signature " +
745                                 "public static " + typeClass.getName() + "." + factoryMethod + toParameterList(method.getParameterTypes()) +
746                                 " but expected signature " +
747                                 "public static " + typeClass.getName() + "." + factoryMethod + toParameterList(parameterTypes));
748                     }
749                     continue;
750                 }
751             } else {
752                 // Implicit constructor selection based on named constructor args
753                 //
754                 // Only consider methods where we can supply a value for all of the parameters
755                 parameterNames = getParameterNames(method);
756                 if (parameterNames == null || !allProperties.containsAll(parameterNames)) {
757                     continue;
758                 }
759             }
760 
761             if (method.getReturnType() == Void.TYPE) {
762                 if (matchLevel < 3) {
763                     matchLevel = 3;
764                     missException = new MissingFactoryMethodException("Static factory method does not return a value: " + method);
765                 }
766                 continue;
767             }
768 
769             if (Modifier.isAbstract(method.getModifiers())) {
770                 if (matchLevel < 4) {
771                     matchLevel = 4;
772                     missException = new MissingFactoryMethodException("Static factory method is abstract: " + method);
773                 }
774                 continue;
775             }
776 
777             if (!allowPrivate && !Modifier.isPublic(method.getModifiers())) {
778                 if (matchLevel < 5) {
779                     matchLevel = 5;
780                     missException = new MissingFactoryMethodException("Static factory method is not public: " + method);
781                 }
782                 continue;
783             }
784 
785             if (!Modifier.isStatic(method.getModifiers())) {
786                 if (matchLevel < 6) {
787                     matchLevel = 6;
788                     missException = new MissingFactoryMethodException("Static factory method is not static: " + method);
789                 }
790                 continue;
791             }
792 
793             if (allowPrivate && !Modifier.isPublic(method.getModifiers())) {
794                 setAccessible(method);
795             }
796 
797             return new StaticFactory(method, parameterNames);
798         }
799 
800         if (missException != null) {
801             throw missException;
802         } else {
803             StringBuffer buffer = new StringBuffer("Unable to find a valid factory method: ");
804             buffer.append("public void ").append(typeClass.getName()).append(".");
805             buffer.append(factoryMethod).append(toParameterList(parameterTypes));
806             throw new MissingFactoryMethodException(buffer.toString());
807         }
808     }
809 
810     public static Method findInstanceFactory(Class typeClass, String factoryMethod, Set<Option> options) {
811         if (typeClass == null) throw new NullPointerException("typeClass is null");
812         if (factoryMethod == null) throw new NullPointerException("name is null");
813         if (factoryMethod.length() == 0) throw new IllegalArgumentException("name is an empty string");
814         if (options == null) options = EnumSet.noneOf(Option.class);
815         
816         int matchLevel = 0;
817         MissingFactoryMethodException missException = null;
818 
819         boolean allowPrivate = options.contains(Option.PRIVATE_FACTORY);
820         boolean caseInsesnitive = options.contains(Option.CASE_INSENSITIVE_FACTORY);
821 
822         List<Method> methods = new ArrayList<Method>(Arrays.asList(typeClass.getMethods()));
823         methods.addAll(Arrays.asList(typeClass.getDeclaredMethods()));
824         for (Method method : methods) {
825             if (method.getName().equals(factoryMethod) || (caseInsesnitive && method.getName().equalsIgnoreCase(method.getName()))) {
826                 if (Modifier.isStatic(method.getModifiers())) {
827                     if (matchLevel < 1) {
828                         matchLevel = 1;
829                         missException = new MissingFactoryMethodException("Instance factory method is static: " + method);
830                     }
831                     continue;
832                 }
833 
834                 if (method.getParameterTypes().length != 0) {
835                     if (matchLevel < 2) {
836                         matchLevel = 2;
837                         missException = new MissingFactoryMethodException("Instance factory method has signature " +
838                                 "public " + typeClass.getName() + "." + factoryMethod + toParameterList(method.getParameterTypes()) +
839                                 " but expected signature " +
840                                 "public " + typeClass.getName() + "." + factoryMethod + "()");
841                     }
842                     continue;
843                 }
844 
845                 if (method.getReturnType() == Void.TYPE) {
846                     if (matchLevel < 3) {
847                         matchLevel = 3;
848                         missException = new MissingFactoryMethodException("Instance factory method does not return a value: " + method);
849                     }
850                     continue;
851                 }
852 
853                 if (Modifier.isAbstract(method.getModifiers())) {
854                     if (matchLevel < 4) {
855                         matchLevel = 4;
856                         missException = new MissingFactoryMethodException("Instance factory method is abstract: " + method);
857                     }
858                     continue;
859                 }
860 
861                 if (!allowPrivate && !Modifier.isPublic(method.getModifiers())) {
862                     if (matchLevel < 5) {
863                         matchLevel = 5;
864                         missException = new MissingFactoryMethodException("Instance factory method is not public: " + method);
865                     }
866                     continue;
867                 }
868 
869                 if (allowPrivate && !Modifier.isPublic(method.getModifiers())) {
870                     setAccessible(method);
871                 }
872 
873                 return method;
874             }
875         }
876 
877         if (missException != null) {
878             throw missException;
879         } else {
880             StringBuffer buffer = new StringBuffer("Unable to find a valid factory method: ");
881             buffer.append("public void ").append(typeClass.getName()).append(".");
882             buffer.append(factoryMethod).append("()");
883             throw new MissingFactoryMethodException(buffer.toString());
884         }
885     }
886 
887     public static List<String> getParameterNames(Constructor<?> constructor) {
888         // use reflection to get Java6 ConstructorParameter annotation value
889         try {
890             Class<? extends Annotation> constructorPropertiesClass = ClassLoader.getSystemClassLoader().loadClass("java.beans.ConstructorProperties").asSubclass(Annotation.class);
891             Annotation constructorProperties = constructor.getAnnotation(constructorPropertiesClass);
892             if (constructorProperties != null) {
893                 String[] parameterNames = (String[]) constructorPropertiesClass.getMethod("value").invoke(constructorProperties);
894                 if (parameterNames != null) {
895                     return Arrays.asList(parameterNames);
896                 }
897             }
898         } catch (Throwable e) {
899         }
900 
901         ParameterNames parameterNames = constructor.getAnnotation(ParameterNames.class);
902         if (parameterNames != null && parameterNames.value() != null) {
903             return Arrays.asList(parameterNames.value());
904         }
905         if (parameterNamesLoader != null) {
906             return parameterNamesLoader.get(constructor);
907         }
908         return null;
909     }
910 
911     public static List<String> getParameterNames(Method method) {
912         ParameterNames parameterNames = method.getAnnotation(ParameterNames.class);
913         if (parameterNames != null && parameterNames.value() != null) {
914             return Arrays.asList(parameterNames.value());
915         }
916         if (parameterNamesLoader != null) {
917             return parameterNamesLoader.get(method);
918         }
919         return null;
920     }
921 
922     public static interface Factory {
923         List<String> getParameterNames();
924 
925         List<Type> getParameterTypes();
926 
927         Object create(Object... parameters) throws ConstructionException;
928     }
929 
930     public static class ConstructorFactory implements Factory {
931         private Constructor constructor;
932         private List<String> parameterNames;
933 
934         public ConstructorFactory(Constructor constructor, List<String> parameterNames) {
935             if (constructor == null) throw new NullPointerException("constructor is null");
936             if (parameterNames == null) throw new NullPointerException("parameterNames is null");
937             this.constructor = constructor;
938             this.parameterNames = parameterNames;
939         }
940 
941         public List<String> getParameterNames() {
942             return parameterNames;
943         }
944 
945         public List<Type> getParameterTypes() {
946             return new ArrayList<Type>(Arrays.asList(constructor.getGenericParameterTypes()));
947         }
948 
949         public Object create(Object... parameters) throws ConstructionException {
950             // create the instance
951             try {
952                 Object instance = constructor.newInstance(parameters);
953                 return instance;
954             } catch (Exception e) {
955                 Throwable t = e;
956                 if (e instanceof InvocationTargetException) {
957                     InvocationTargetException invocationTargetException = (InvocationTargetException) e;
958                     if (invocationTargetException.getCause() != null) {
959                         t = invocationTargetException.getCause();
960                     }
961                 }
962                 throw new ConstructionException("Error invoking constructor: " + constructor, t);
963             }
964         }
965     }
966 
967     public static class StaticFactory implements Factory {
968         private Method staticFactory;
969         private List<String> parameterNames;
970 
971         public StaticFactory(Method staticFactory, List<String> parameterNames) {
972             this.staticFactory = staticFactory;
973             this.parameterNames = parameterNames;
974         }
975 
976         public List<String> getParameterNames() {
977             if (parameterNames == null) {
978                 throw new ConstructionException("InstanceFactory has not been initialized");
979             }
980 
981             return parameterNames;
982         }
983 
984         public List<Type> getParameterTypes() {
985             return new ArrayList<Type>(Arrays.asList(staticFactory.getGenericParameterTypes()));
986         }
987 
988         public Object create(Object... parameters) throws ConstructionException {
989             try {
990                 Object instance = staticFactory.invoke(null, parameters);
991                 return instance;
992             } catch (Exception e) {
993                 Throwable t = e;
994                 if (e instanceof InvocationTargetException) {
995                     InvocationTargetException invocationTargetException = (InvocationTargetException) e;
996                     if (invocationTargetException.getCause() != null) {
997                         t = invocationTargetException.getCause();
998                     }
999                 }
1000                 throw new ConstructionException("Error invoking factory method: " + staticFactory, t);
1001             }
1002         }
1003     }
1004 
1005     private static void setAccessible(final AccessibleObject accessibleObject) {
1006         AccessController.doPrivileged(new PrivilegedAction<Object>() {
1007             public Object run() {
1008                 accessibleObject.setAccessible(true);
1009                 return null;
1010             }
1011         });
1012     }
1013 
1014     private static String toParameterList(Class<?>[] parameterTypes) {
1015         return toParameterList(parameterTypes != null ? Arrays.asList(parameterTypes) : null);
1016     }
1017 
1018     private static String toParameterList(List<? extends Class<?>> parameterTypes) {
1019         StringBuffer buffer = new StringBuffer();
1020         buffer.append("(");
1021         if (parameterTypes != null) {
1022             for (int i = 0; i < parameterTypes.size(); i++) {
1023                 Class type = parameterTypes.get(i);
1024                 if (i > 0) buffer.append(", ");
1025                 buffer.append(type.getName());
1026             }
1027         } else {
1028             buffer.append("...");
1029         }
1030         buffer.append(")");
1031         return buffer.toString();
1032     }
1033 }