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