1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.xbean.recipe;
18
19 import java.lang.reflect.Field;
20 import java.lang.reflect.InvocationTargetException;
21 import java.lang.reflect.Method;
22 import java.lang.reflect.Modifier;
23 import java.lang.reflect.Type;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collections;
27 import java.util.EnumSet;
28 import java.util.LinkedHashMap;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Set;
32 import org.apache.xbean.recipe.ReflectionUtil.*;
33
34
35
36
37 public class ObjectRecipe extends AbstractRecipe {
38 private String typeName;
39 private Class typeClass;
40 private String factoryMethod;
41 private List<String> constructorArgNames;
42 private List<Class<?>> constructorArgTypes;
43 private final LinkedHashMap<Property,Object> properties = new LinkedHashMap<Property,Object>();
44 private final EnumSet<Option> options = EnumSet.of(Option.FIELD_INJECTION);
45 private final Map<String,Object> unsetProperties = new LinkedHashMap<String,Object>();
46
47 public ObjectRecipe(Class typeClass) {
48 this(typeClass, null, null, null, null);
49 }
50
51 public ObjectRecipe(Class typeClass, String factoryMethod) {
52 this(typeClass, factoryMethod, null, null, null);
53 }
54
55 public ObjectRecipe(Class typeClass, Map<String,Object> properties) {
56 this(typeClass, null, null, null, properties);
57 }
58
59 public ObjectRecipe(Class typeClass, String[] constructorArgNames) {
60 this(typeClass, null, constructorArgNames, null, null);
61 }
62
63 public ObjectRecipe(Class typeClass, String[] constructorArgNames, Class[] constructorArgTypes) {
64 this(typeClass, null, constructorArgNames, constructorArgTypes, null);
65 }
66
67 public ObjectRecipe(Class type, String factoryMethod, String[] constructorArgNames) {
68 this(type, factoryMethod, constructorArgNames, null, null);
69 }
70
71 public ObjectRecipe(Class type, String factoryMethod, String[] constructorArgNames, Class[] constructorArgTypes) {
72 this(type, factoryMethod, constructorArgNames, constructorArgTypes, null);
73 }
74
75 public ObjectRecipe(Class typeClass, String factoryMethod, String[] constructorArgNames, Class[] constructorArgTypes, Map<String,Object> properties) {
76 this.typeClass = typeClass;
77 this.factoryMethod = factoryMethod;
78 this.constructorArgNames = constructorArgNames != null ? Arrays.asList(constructorArgNames) : null;
79 this.constructorArgTypes = constructorArgTypes != null ? Arrays.<Class<?>>asList(constructorArgTypes) : null;
80 if (properties != null) {
81 setAllProperties(properties);
82 }
83 }
84
85 public ObjectRecipe(String typeName) {
86 this(typeName, null, null, null, null);
87 }
88
89 public ObjectRecipe(String typeName, String factoryMethod) {
90 this(typeName, factoryMethod, null, null, null);
91 }
92
93 public ObjectRecipe(String typeName, Map<String,Object> properties) {
94 this(typeName, null, null, null, properties);
95 }
96
97 public ObjectRecipe(String typeName, String[] constructorArgNames) {
98 this(typeName, null, constructorArgNames, null, null);
99 }
100
101 public ObjectRecipe(String typeName, String[] constructorArgNames, Class[] constructorArgTypes) {
102 this(typeName, null, constructorArgNames, constructorArgTypes, null);
103 }
104
105 public ObjectRecipe(String typeName, String factoryMethod, String[] constructorArgNames) {
106 this(typeName, factoryMethod, constructorArgNames, null, null);
107 }
108
109 public ObjectRecipe(String typeName, String factoryMethod, String[] constructorArgNames, Class[] constructorArgTypes) {
110 this(typeName, factoryMethod, constructorArgNames, constructorArgTypes, null);
111 }
112
113 public ObjectRecipe(String typeName, String factoryMethod, String[] constructorArgNames, Class[] constructorArgTypes, Map<String,Object> properties) {
114 this.typeName = typeName;
115 this.factoryMethod = factoryMethod;
116 this.constructorArgNames = constructorArgNames != null ? Arrays.asList(constructorArgNames) : null;
117 this.constructorArgTypes = constructorArgTypes != null ? Arrays.<Class<?>>asList(constructorArgTypes) : null;
118 if (properties != null) {
119 setAllProperties(properties);
120 }
121 }
122
123 public void allow(Option option){
124 options.add(option);
125 }
126
127 public void disallow(Option option){
128 options.remove(option);
129 }
130
131 public Set<Option> getOptions() {
132 return Collections.unmodifiableSet(options);
133 }
134
135 public List<String> getConstructorArgNames() {
136 return constructorArgNames;
137 }
138
139 public void setConstructorArgNames(String[] constructorArgNames) {
140 this.constructorArgNames = constructorArgNames != null ? Arrays.asList(constructorArgNames) : null;
141 }
142
143 public void setConstructorArgNames(List<String> constructorArgNames) {
144 this.constructorArgNames = constructorArgNames;
145 }
146
147 public List<Class<?>> getConstructorArgTypes() {
148 return constructorArgTypes;
149 }
150
151 public void setConstructorArgTypes(Class[] constructorArgTypes) {
152 this.constructorArgTypes = constructorArgTypes != null ? Arrays.<Class<?>>asList(constructorArgTypes) : null;
153 }
154
155 public void setConstructorArgTypes(List<? extends Class<?>> constructorArgTypes) {
156 this.constructorArgTypes = new ArrayList<Class<?>>(constructorArgTypes);
157 }
158
159 public String getFactoryMethod() {
160 return factoryMethod;
161 }
162
163 public void setFactoryMethod(String factoryMethod) {
164 this.factoryMethod = factoryMethod;
165 }
166
167 public Object getProperty(String name) {
168 Object value = properties.get(new Property(name));
169 return value;
170 }
171
172 public Map<String, Object> getProperties() {
173 LinkedHashMap<String, Object> properties = new LinkedHashMap<String, Object>();
174 for (Map.Entry<Property, Object> entry : this.properties.entrySet()) {
175 properties.put(entry.getKey().name, entry.getValue());
176 }
177 return properties;
178 }
179
180 public void setProperty(String name, Object value) {
181 setProperty(new Property(name), value);
182 }
183
184 public void setFieldProperty(String name, Object value){
185 setProperty(new FieldProperty(name), value);
186 options.add(Option.FIELD_INJECTION);
187 }
188
189 public void setMethodProperty(String name, Object value){
190 setProperty(new SetterProperty(name), value);
191 }
192
193 public void setAutoMatchProperty(String type, Object value){
194 setProperty(new AutoMatchProperty(type), value);
195 }
196
197 public void setCompoundProperty(String name, Object value) {
198 setProperty(new CompoundProperty(name), value);
199 }
200
201 private void setProperty(Property key, Object value) {
202 if (value instanceof UnsetPropertiesRecipe) {
203 allow(Option.IGNORE_MISSING_PROPERTIES);
204 }
205 properties.put(key, value);
206 }
207
208
209 public void setAllProperties(Map<?,?> map) {
210 if (map == null) throw new NullPointerException("map is null");
211 for (Map.Entry<?, ?> entry : map.entrySet()) {
212 String name = (String) entry.getKey();
213 Object value = entry.getValue();
214 setProperty(name, value);
215 }
216 }
217
218 public Map<String,Object> getUnsetProperties() {
219 return unsetProperties;
220 }
221
222 public List<Recipe> getNestedRecipes() {
223 List<Recipe> nestedRecipes = new ArrayList<Recipe>(properties.size());
224 for (Object o : properties.values()) {
225 if (o instanceof Recipe) {
226 Recipe recipe = (Recipe) o;
227 nestedRecipes.add(recipe);
228 }
229 }
230 return nestedRecipes;
231 }
232
233 public List<Recipe> getConstructorRecipes() {
234
235 Factory factory = findFactory(Object.class);
236
237
238
239 if (factoryMethod != null && !(factory instanceof StaticFactory)) {
240
241 List<String> parameterNames = factory.getParameterNames();
242 List<Recipe> nestedRecipes = new ArrayList<Recipe>(parameterNames.size());
243 for (Map.Entry<Property, Object> entry : properties.entrySet()) {
244 if (parameterNames.contains(entry.getKey().name) && entry.getValue() instanceof Recipe) {
245 Recipe recipe = (Recipe) entry.getValue();
246 nestedRecipes.add(recipe);
247 }
248 }
249 return nestedRecipes;
250 } else {
251
252 return getNestedRecipes();
253 }
254 }
255
256 public boolean canCreate(Type type) {
257 Class myType = getType();
258 return RecipeHelper.isAssignable(type, myType) || RecipeHelper.isAssignable(type, myType);
259 }
260
261 protected Object internalCreate(Type expectedType, boolean lazyRefAllowed) throws ConstructionException {
262 unsetProperties.clear();
263
264
265
266 Class typeClass = getType();
267
268
269
270 Map<Property,Object> propertyValues = new LinkedHashMap<Property,Object>(properties);
271
272
273
274 Factory factory = findFactory(expectedType);
275 Object[] parameters = extractConstructorArgs(propertyValues, factory);
276 Object instance = factory.create(parameters);
277
278
279
280 if (getName() != null) {
281 ExecutionContext.getContext().addObject(getName(), instance);
282 }
283
284
285
286 setProperties(propertyValues, instance, instance.getClass());
287
288
289
290
291
292
293 if (factoryMethod != null && !(factory instanceof StaticFactory)) {
294
295 Method instanceFactory = ReflectionUtil.findInstanceFactory(instance.getClass(), factoryMethod, null);
296
297 try {
298 instance = instanceFactory.invoke(instance);
299 } catch (Exception e) {
300 Throwable t = e;
301 if (e instanceof InvocationTargetException) {
302 InvocationTargetException invocationTargetException = (InvocationTargetException) e;
303 if (invocationTargetException.getCause() != null) {
304 t = invocationTargetException.getCause();
305 }
306 }
307 throw new ConstructionException("Error calling instance factory method: " + instanceFactory, t);
308 }
309 }
310
311 return instance;
312 }
313
314 public void setProperties(Object instance) throws ConstructionException {
315 unsetProperties.clear();
316
317
318 Map<Property,Object> propertyValues = new LinkedHashMap<Property,Object>(properties);
319
320 setProperties(propertyValues, instance, instance.getClass());
321 }
322
323 public Class setStaticProperties() throws ConstructionException {
324 unsetProperties.clear();
325
326
327 Class typeClass = getType();
328
329
330 if (!Modifier.isPublic(typeClass.getModifiers())) {
331 throw new ConstructionException("Class is not public: " + typeClass.getName());
332 }
333 if (Modifier.isInterface(typeClass.getModifiers())) {
334 throw new ConstructionException("Class is an interface: " + typeClass.getName());
335 }
336 if (Modifier.isAbstract(typeClass.getModifiers())) {
337 throw new ConstructionException("Class is abstract: " + typeClass.getName());
338 }
339
340
341 Map<Property,Object> propertyValues = new LinkedHashMap<Property,Object>(properties);
342
343 setProperties(propertyValues, null, typeClass);
344
345 return typeClass;
346 }
347
348 public Class getType() {
349 if (typeClass != null || typeName != null) {
350 Class type = typeClass;
351 if (type == null) {
352 try {
353 type = RecipeHelper.loadClass(typeName);
354 } catch (ClassNotFoundException e) {
355 throw new ConstructionException("Type class could not be found: " + typeName);
356 }
357 }
358
359 return type;
360 }
361
362 return null;
363 }
364
365 private void setProperties(Map<Property, Object> propertyValues, Object instance, Class clazz) {
366
367 for (Map.Entry<Property, Object> entry : RecipeHelper.prioritizeProperties(propertyValues)) {
368 Property propertyName = entry.getKey();
369 Object propertyValue = entry.getValue();
370
371 setProperty(instance, clazz, propertyName, propertyValue);
372 }
373
374 }
375
376 private void setProperty(Object instance, Class clazz, Property propertyName, Object propertyValue) {
377
378 List<Member> members = new ArrayList<Member>();
379 try {
380 if (propertyName instanceof SetterProperty){
381 List<Method> setters = ReflectionUtil.findAllSetters(clazz, propertyName.name, propertyValue, options);
382 for (Method setter : setters) {
383 MethodMember member = new MethodMember(setter);
384 members.add(member);
385 }
386 } else if (propertyName instanceof FieldProperty){
387 FieldMember member = new FieldMember(ReflectionUtil.findField(clazz, propertyName.name, propertyValue, options));
388 members.add(member);
389 } else if (propertyName instanceof AutoMatchProperty){
390 MissingAccessorException noField = null;
391 if (options.contains(Option.FIELD_INJECTION)) {
392 List<Field> fieldsByType = null;
393 try {
394 fieldsByType = ReflectionUtil.findAllFieldsByType(clazz, propertyValue, options);
395 FieldMember member = new FieldMember(fieldsByType.iterator().next());
396 members.add(member);
397 } catch (MissingAccessorException e) {
398 noField = e;
399 }
400
401
402 if (fieldsByType != null && fieldsByType.size() > 1) {
403 List<String> matches = new ArrayList<String>();
404 for (Field field : fieldsByType) {
405 matches.add(field.getName());
406 }
407 throw new MissingAccessorException("Property of type " + propertyValue.getClass().getName() + " can be mapped to more then one field: " + matches, 0);
408 }
409 }
410
411
412 if (members.isEmpty()) {
413 List<Method> settersByType;
414 try {
415 settersByType = ReflectionUtil.findAllSettersByType(clazz, propertyValue, options);
416 MethodMember member = new MethodMember(settersByType.iterator().next());
417 members.add(member);
418 } catch (MissingAccessorException noSetter) {
419 throw (noField == null || noSetter.getMatchLevel() > noField.getMatchLevel())? noSetter: noField;
420 }
421
422
423 if (settersByType != null && settersByType.size() > 1) {
424 List<String> matches = new ArrayList<String>();
425 for (Method setter : settersByType) {
426 matches.add(setter.getName());
427 }
428 throw new MissingAccessorException("Property of type " + propertyValue.getClass().getName() + " can be mapped to more then one setter: " + matches, 0);
429 }
430 }
431 } else if (propertyName instanceof CompoundProperty) {
432 String[] names = propertyName.name.split("\\.");
433 for (int i = 0; i < names.length - 1; i++) {
434 Method getter = ReflectionUtil.findGetter(clazz, names[i], options);
435 if (getter != null) {
436 try {
437 instance = getter.invoke(instance);
438 clazz = instance.getClass();
439 } catch (Exception e) {
440 Throwable t = e;
441 if (e instanceof InvocationTargetException) {
442 InvocationTargetException invocationTargetException = (InvocationTargetException) e;
443 if (invocationTargetException.getCause() != null) {
444 t = invocationTargetException.getCause();
445 }
446 }
447 throw new ConstructionException("Error setting property: " + names[i], t);
448 }
449 } else {
450 throw new ConstructionException("No getter for " + names[i] + " property");
451 }
452 }
453 List<Method> setters = ReflectionUtil.findAllSetters(clazz, names[names.length - 1], propertyValue, options);
454 for (Method setter : setters) {
455 MethodMember member = new MethodMember(setter);
456 members.add(member);
457 }
458 } else {
459
460 MissingAccessorException noSetter = null;
461 try {
462 List<Method> setters = ReflectionUtil.findAllSetters(clazz, propertyName.name, propertyValue, options);
463 for (Method setter : setters) {
464 MethodMember member = new MethodMember(setter);
465 members.add(member);
466 }
467 } catch (MissingAccessorException e) {
468 noSetter = e;
469 if (!options.contains(Option.FIELD_INJECTION)) {
470 throw noSetter;
471 }
472 }
473
474 if (options.contains(Option.FIELD_INJECTION)) {
475 try {
476 FieldMember member = new FieldMember(ReflectionUtil.findField(clazz, propertyName.name, propertyValue, options));
477 members.add(member);
478 } catch (MissingAccessorException noField) {
479 if (members.isEmpty()) {
480 throw (noSetter == null || noField.getMatchLevel() > noSetter.getMatchLevel())? noField: noSetter;
481 }
482 }
483 }
484 }
485 } catch (MissingAccessorException e) {
486 if (options.contains(Option.IGNORE_MISSING_PROPERTIES)) {
487 unsetProperties.put(propertyName.name, propertyValue);
488 return;
489 }
490 throw e;
491 }
492
493 ConstructionException conversionException = null;
494 for (Member member : members) {
495
496 try {
497 propertyValue = RecipeHelper.convert(member.getType(), propertyValue, false);
498 } catch (Exception e) {
499
500 if (conversionException == null) {
501 String valueType = propertyValue == null ? "null" : propertyValue.getClass().getName();
502 String memberType = member.getType() instanceof Class ? ((Class) member.getType()).getName() : member.getType().toString();
503 conversionException = new ConstructionException("Unable to convert property value" +
504 " from " + valueType +
505 " to " + memberType +
506 " for injection " + member, e);
507 }
508 continue;
509 }
510 try {
511
512 member.setValue(instance, propertyValue);
513 } catch (Exception e) {
514 Throwable t = e;
515 if (e instanceof InvocationTargetException) {
516 InvocationTargetException invocationTargetException = (InvocationTargetException) e;
517 if (invocationTargetException.getCause() != null) {
518 t = invocationTargetException.getCause();
519 }
520 }
521 throw new ConstructionException("Error setting property: " + member, t);
522 }
523
524
525 return;
526 }
527
528 throw conversionException;
529 }
530
531 private Factory findFactory(Type expectedType) {
532 Class type = getType();
533
534
535
536 if (factoryMethod != null) {
537 try {
538 StaticFactory staticFactory = ReflectionUtil.findStaticFactory(
539 type,
540 factoryMethod,
541 constructorArgNames,
542 constructorArgTypes,
543 getProperties().keySet(),
544 options);
545 return staticFactory;
546 } catch (MissingFactoryMethodException ignored) {
547 }
548
549 }
550
551
552
553
554
555
556 Class consturctorClass;
557 if (RecipeHelper.isAssignable(type, expectedType)) {
558 consturctorClass = RecipeHelper.toClass(expectedType);
559 } else {
560 consturctorClass = type;
561 }
562
563 ConstructorFactory constructor = ReflectionUtil.findConstructor(
564 consturctorClass,
565 constructorArgNames,
566 constructorArgTypes,
567 getProperties().keySet(),
568 options);
569
570 return constructor;
571 }
572
573 private Object[] extractConstructorArgs(Map propertyValues, Factory factory) {
574 List<String> parameterNames = factory.getParameterNames();
575 List<Type> parameterTypes = factory.getParameterTypes();
576
577 Object[] parameters = new Object[parameterNames.size()];
578 for (int i = 0; i < parameterNames.size(); i++) {
579 Property name = new Property(parameterNames.get(i));
580 Type type = parameterTypes.get(i);
581
582 Object value;
583 if (propertyValues.containsKey(name)) {
584 value = propertyValues.remove(name);
585 if (!RecipeHelper.isInstance(type, value) && !RecipeHelper.isConvertable(type, value)) {
586 throw new ConstructionException("Invalid and non-convertable constructor parameter type: " +
587 "name=" + name + ", " +
588 "index=" + i + ", " +
589 "expected=" + RecipeHelper.toClass(type).getName() + ", " +
590 "actual=" + (value == null ? "null" : value.getClass().getName()));
591 }
592 value = RecipeHelper.convert(type, value, false);
593 } else {
594 value = getDefaultValue(RecipeHelper.toClass(type));
595 }
596
597
598 parameters[i] = value;
599 }
600 return parameters;
601 }
602
603 private static Object getDefaultValue(Class type) {
604 if (type.equals(Boolean.TYPE)) {
605 return Boolean.FALSE;
606 } else if (type.equals(Character.TYPE)) {
607 return (char) 0;
608 } else if (type.equals(Byte.TYPE)) {
609 return (byte) 0;
610 } else if (type.equals(Short.TYPE)) {
611 return (short) 0;
612 } else if (type.equals(Integer.TYPE)) {
613 return 0;
614 } else if (type.equals(Long.TYPE)) {
615 return (long) 0;
616 } else if (type.equals(Float.TYPE)) {
617 return (float) 0;
618 } else if (type.equals(Double.TYPE)) {
619 return (double) 0;
620 }
621 return null;
622 }
623
624 public static interface Member {
625 Type getType();
626 void setValue(Object instance, Object value) throws Exception;
627 }
628
629 public static class MethodMember implements Member {
630 private final Method setter;
631
632 public MethodMember(Method method) {
633 this.setter = method;
634 }
635
636 public Type getType() {
637 return setter.getGenericParameterTypes()[0];
638 }
639
640 public void setValue(Object instance, Object value) throws Exception {
641 setter.invoke(instance, value);
642 }
643
644 public String toString() {
645 return setter.toString();
646 }
647 }
648
649 public static class FieldMember implements Member {
650 private final Field field;
651
652 public FieldMember(Field field) {
653 this.field = field;
654 }
655
656 public Type getType() {
657 return field.getGenericType();
658 }
659
660 public void setValue(Object instance, Object value) throws Exception {
661 field.set(instance, value);
662 }
663
664 public String toString() {
665 return field.toString();
666 }
667 }
668
669 public static class Property {
670 private final String name;
671
672 public Property(String name) {
673 if (name == null) throw new NullPointerException("name is null");
674 this.name = name;
675 }
676
677 public boolean equals(Object o) {
678 if (this == o) return true;
679 if (o == null) return false;
680 if (o instanceof String){
681 return this.name.equals(o);
682 }
683 if (o instanceof Property) {
684 Property property = (Property) o;
685 return this.name.equals(property.name);
686 }
687 return false;
688 }
689
690 public int hashCode() {
691 return name.hashCode();
692 }
693
694 public String toString() {
695 return name;
696 }
697 }
698
699 public static class SetterProperty extends Property {
700 public SetterProperty(String name) {
701 super(name);
702 }
703 public int hashCode() {
704 return super.hashCode()+2;
705 }
706 public String toString() {
707 return "[setter] "+super.toString();
708 }
709
710 }
711
712 public static class FieldProperty extends Property {
713 public FieldProperty(String name) {
714 super(name);
715 }
716
717 public int hashCode() {
718 return super.hashCode()+1;
719 }
720 public String toString() {
721 return "[field] "+ super.toString();
722 }
723 }
724
725 public static class AutoMatchProperty extends Property {
726 public AutoMatchProperty(String type) {
727 super(type);
728 }
729
730 public int hashCode() {
731 return super.hashCode()+1;
732 }
733 public String toString() {
734 return "[auto-match] "+ super.toString();
735 }
736 }
737
738 public static class CompoundProperty extends Property {
739 public CompoundProperty(String type) {
740 super(type);
741 }
742
743 public int hashCode() {
744 return super.hashCode()+1;
745 }
746 public String toString() {
747 return "[compound] "+ super.toString();
748 }
749 }
750 }