1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
230
231
232
233
234
235
236
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
358
359 validSetters.addFirst(method);
360 } else {
361 validSetters.add(method);
362 }
363 }
364
365 }
366
367 if (!validSetters.isEmpty()) {
368
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
443
444 validFields.addFirst(field);
445 } else {
446 validFields.add(field);
447 }
448 }
449 }
450
451 if (!validFields.isEmpty()) {
452
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
532
533 validSetters.addFirst(method);
534 } else {
535 validSetters.add(method);
536 }
537 }
538
539 }
540
541 if (!validSetters.isEmpty()) {
542
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
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
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
590
591 parameterNames = Collections.emptyList();
592 parameterTypes = Collections.emptyList();
593 }
594
595
596
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
606 int matchLevel = 0;
607 MissingFactoryMethodException missException = null;
608
609 boolean allowPrivate = options.contains(Option.PRIVATE_CONSTRUCTOR);
610 for (Constructor constructor : constructors) {
611
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
634
635
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
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
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
703
704 parameterNames = Collections.emptyList();
705 parameterTypes = Collections.emptyList();
706 }
707
708
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
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
726 if (!method.getName().equals(factoryMethod) && (!caseInsesnitive || !method.getName().equalsIgnoreCase(method.getName()))) {
727 continue;
728 }
729
730
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
753
754
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
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
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 }