001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.xbean.recipe;
018
019 import java.lang.reflect.Field;
020 import java.lang.reflect.InvocationTargetException;
021 import java.lang.reflect.Method;
022 import java.lang.reflect.Modifier;
023 import java.lang.reflect.Type;
024 import java.util.ArrayList;
025 import java.util.Arrays;
026 import java.util.Collections;
027 import java.util.EnumSet;
028 import java.util.LinkedHashMap;
029 import java.util.List;
030 import java.util.Map;
031 import java.util.Set;
032 import org.apache.xbean.recipe.ReflectionUtil.*;
033
034 /**
035 * @version $Rev: 6688 $ $Date: 2005-12-29T02:08:29.200064Z $
036 */
037 public class ObjectRecipe extends AbstractRecipe {
038 private String typeName;
039 private Class typeClass;
040 private String factoryMethod;
041 private List<String> constructorArgNames;
042 private List<Class<?>> constructorArgTypes;
043 private final LinkedHashMap<Property,Object> properties = new LinkedHashMap<Property,Object>();
044 private final EnumSet<Option> options = EnumSet.of(Option.FIELD_INJECTION);
045 private final Map<String,Object> unsetProperties = new LinkedHashMap<String,Object>();
046
047 public ObjectRecipe(Class typeClass) {
048 this(typeClass, null, null, null, null);
049 }
050
051 public ObjectRecipe(Class typeClass, String factoryMethod) {
052 this(typeClass, factoryMethod, null, null, null);
053 }
054
055 public ObjectRecipe(Class typeClass, Map<String,Object> properties) {
056 this(typeClass, null, null, null, properties);
057 }
058
059 public ObjectRecipe(Class typeClass, String[] constructorArgNames) {
060 this(typeClass, null, constructorArgNames, null, null);
061 }
062
063 public ObjectRecipe(Class typeClass, String[] constructorArgNames, Class[] constructorArgTypes) {
064 this(typeClass, null, constructorArgNames, constructorArgTypes, null);
065 }
066
067 public ObjectRecipe(Class type, String factoryMethod, String[] constructorArgNames) {
068 this(type, factoryMethod, constructorArgNames, null, null);
069 }
070
071 public ObjectRecipe(Class type, String factoryMethod, String[] constructorArgNames, Class[] constructorArgTypes) {
072 this(type, factoryMethod, constructorArgNames, constructorArgTypes, null);
073 }
074
075 public ObjectRecipe(Class typeClass, String factoryMethod, String[] constructorArgNames, Class[] constructorArgTypes, Map<String,Object> properties) {
076 this.typeClass = typeClass;
077 this.factoryMethod = factoryMethod;
078 this.constructorArgNames = constructorArgNames != null ? Arrays.asList(constructorArgNames) : null;
079 this.constructorArgTypes = constructorArgTypes != null ? Arrays.<Class<?>>asList(constructorArgTypes) : null;
080 if (properties != null) {
081 setAllProperties(properties);
082 }
083 }
084
085 public ObjectRecipe(String typeName) {
086 this(typeName, null, null, null, null);
087 }
088
089 public ObjectRecipe(String typeName, String factoryMethod) {
090 this(typeName, factoryMethod, null, null, null);
091 }
092
093 public ObjectRecipe(String typeName, Map<String,Object> properties) {
094 this(typeName, null, null, null, properties);
095 }
096
097 public ObjectRecipe(String typeName, String[] constructorArgNames) {
098 this(typeName, null, constructorArgNames, null, null);
099 }
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 private void setProperty(Property key, Object value) {
198 if (value instanceof UnsetPropertiesRecipe) {
199 allow(Option.IGNORE_MISSING_PROPERTIES);
200 }
201 properties.put(key, value);
202 }
203
204
205 public void setAllProperties(Map<?,?> map) {
206 if (map == null) throw new NullPointerException("map is null");
207 for (Map.Entry<?, ?> entry : map.entrySet()) {
208 String name = (String) entry.getKey();
209 Object value = entry.getValue();
210 setProperty(name, value);
211 }
212 }
213
214 public Map<String,Object> getUnsetProperties() {
215 return unsetProperties;
216 }
217
218 public List<Recipe> getNestedRecipes() {
219 List<Recipe> nestedRecipes = new ArrayList<Recipe>(properties.size());
220 for (Object o : properties.values()) {
221 if (o instanceof Recipe) {
222 Recipe recipe = (Recipe) o;
223 nestedRecipes.add(recipe);
224 }
225 }
226 return nestedRecipes;
227 }
228
229 public List<Recipe> getConstructorRecipes() {
230 // find the factory that will be used to create the class instance
231 Factory factory = findFactory(Object.class);
232
233 // if we are NOT using an instance factory to create the object
234 // (we have a factory method and it is not a static factory method)
235 if (factoryMethod != null && !(factory instanceof StaticFactory)) {
236 // only include recipes used in the construcor args
237 List<String> parameterNames = factory.getParameterNames();
238 List<Recipe> nestedRecipes = new ArrayList<Recipe>(parameterNames.size());
239 for (Map.Entry<Property, Object> entry : properties.entrySet()) {
240 if (parameterNames.contains(entry.getKey().name) && entry.getValue() instanceof Recipe) {
241 Recipe recipe = (Recipe) entry.getValue();
242 nestedRecipes.add(recipe);
243 }
244 }
245 return nestedRecipes;
246 } else {
247 // when there is an instance factory all nested recipes are used in the constructor
248 return getNestedRecipes();
249 }
250 }
251
252 public boolean canCreate(Type type) {
253 Class myType = getType();
254 return RecipeHelper.isAssignable(type, myType) || RecipeHelper.isAssignable(type, myType);
255 }
256
257 protected Object internalCreate(Type expectedType, boolean lazyRefAllowed) throws ConstructionException {
258 unsetProperties.clear();
259
260 //
261 // load the type class
262 Class typeClass = getType();
263
264 //
265 // clone the properties so they can be used again
266 Map<Property,Object> propertyValues = new LinkedHashMap<Property,Object>(properties);
267
268 //
269 // create the instance
270 Factory factory = findFactory(expectedType);
271 Object[] parameters = extractConstructorArgs(propertyValues, factory);
272 Object instance = factory.create(parameters);
273
274 //
275 // add to execution context if name is specified
276 if (getName() != null) {
277 ExecutionContext.getContext().addObject(getName(), instance);
278 }
279
280 //
281 // set the properties
282 setProperties(propertyValues, instance, instance.getClass());
283
284 //
285 // call instance factory method
286
287 // if we have a factory method name and did not find a static factory,
288 // then we have an instance factory
289 if (factoryMethod != null && !(factory instanceof StaticFactory)) {
290 // find the instance factory method
291 Method instanceFactory = ReflectionUtil.findInstanceFactory(instance.getClass(), factoryMethod, null);
292
293 try {
294 instance = instanceFactory.invoke(instance);
295 } catch (Exception e) {
296 Throwable t = e;
297 if (e instanceof InvocationTargetException) {
298 InvocationTargetException invocationTargetException = (InvocationTargetException) e;
299 if (invocationTargetException.getCause() != null) {
300 t = invocationTargetException.getCause();
301 }
302 }
303 throw new ConstructionException("Error calling instance factory method: " + instanceFactory, t);
304 }
305 }
306
307 return instance;
308 }
309
310 public void setProperties(Object instance) throws ConstructionException {
311 unsetProperties.clear();
312
313 // clone the properties so they can be used again
314 Map<Property,Object> propertyValues = new LinkedHashMap<Property,Object>(properties);
315
316 setProperties(propertyValues, instance, instance.getClass());
317 }
318
319 public Class setStaticProperties() throws ConstructionException {
320 unsetProperties.clear();
321
322 // load the type class
323 Class typeClass = getType();
324
325 // verify that it is a class we can construct
326 if (!Modifier.isPublic(typeClass.getModifiers())) {
327 throw new ConstructionException("Class is not public: " + typeClass.getName());
328 }
329 if (Modifier.isInterface(typeClass.getModifiers())) {
330 throw new ConstructionException("Class is an interface: " + typeClass.getName());
331 }
332 if (Modifier.isAbstract(typeClass.getModifiers())) {
333 throw new ConstructionException("Class is abstract: " + typeClass.getName());
334 }
335
336 // clone the properties so they can be used again
337 Map<Property,Object> propertyValues = new LinkedHashMap<Property,Object>(properties);
338
339 setProperties(propertyValues, null, typeClass);
340
341 return typeClass;
342 }
343
344 public Class getType() {
345 if (typeClass != null || typeName != null) {
346 Class type = typeClass;
347 if (type == null) {
348 try {
349 type = RecipeHelper.loadClass(typeName);
350 } catch (ClassNotFoundException e) {
351 throw new ConstructionException("Type class could not be found: " + typeName);
352 }
353 }
354
355 return type;
356 }
357
358 return null;
359 }
360
361 private void setProperties(Map<Property, Object> propertyValues, Object instance, Class clazz) {
362 // set remaining properties
363 for (Map.Entry<Property, Object> entry : RecipeHelper.prioritizeProperties(propertyValues)) {
364 Property propertyName = entry.getKey();
365 Object propertyValue = entry.getValue();
366
367 setProperty(instance, clazz, propertyName, propertyValue);
368 }
369
370 }
371
372 private void setProperty(Object instance, Class clazz, Property propertyName, Object propertyValue) {
373
374 List<Member> members = new ArrayList<Member>();
375 try {
376 if (propertyName instanceof SetterProperty){
377 List<Method> setters = ReflectionUtil.findAllSetters(clazz, propertyName.name, propertyValue, options);
378 for (Method setter : setters) {
379 MethodMember member = new MethodMember(setter);
380 members.add(member);
381 }
382 } else if (propertyName instanceof FieldProperty){
383 FieldMember member = new FieldMember(ReflectionUtil.findField(clazz, propertyName.name, propertyValue, options));
384 members.add(member);
385 } else if (propertyName instanceof AutoMatchProperty){
386 MissingAccessorException noField = null;
387 if (options.contains(Option.FIELD_INJECTION)) {
388 List<Field> fieldsByType = null;
389 try {
390 fieldsByType = ReflectionUtil.findAllFieldsByType(clazz, propertyValue, options);
391 FieldMember member = new FieldMember(fieldsByType.iterator().next());
392 members.add(member);
393 } catch (MissingAccessorException e) {
394 noField = e;
395 }
396
397 // if we got more then one matching field, that is an immidate error
398 if (fieldsByType != null && fieldsByType.size() > 1) {
399 List<String> matches = new ArrayList<String>();
400 for (Field field : fieldsByType) {
401 matches.add(field.getName());
402 }
403 throw new MissingAccessorException("Property of type " + propertyValue.getClass().getName() + " can be mapped to more then one field: " + matches, 0);
404 }
405 }
406
407 // if we didn't find any fields, try the setters
408 if (members.isEmpty()) {
409 List<Method> settersByType;
410 try {
411 settersByType = ReflectionUtil.findAllSettersByType(clazz, propertyValue, options);
412 MethodMember member = new MethodMember(settersByType.iterator().next());
413 members.add(member);
414 } catch (MissingAccessorException noSetter) {
415 throw (noField == null || noSetter.getMatchLevel() > noField.getMatchLevel())? noSetter: noField;
416 }
417
418 // if we got more then one matching field, that is an immidate error
419 if (settersByType != null && settersByType.size() > 1) {
420 List<String> matches = new ArrayList<String>();
421 for (Method setter : settersByType) {
422 matches.add(setter.getName());
423 }
424 throw new MissingAccessorException("Property of type " + propertyValue.getClass().getName() + " can be mapped to more then one setter: " + matches, 0);
425 }
426 }
427 } else {
428 // add setter members
429 MissingAccessorException noSetter = null;
430 try {
431 List<Method> setters = ReflectionUtil.findAllSetters(clazz, propertyName.name, propertyValue, options);
432 for (Method setter : setters) {
433 MethodMember member = new MethodMember(setter);
434 members.add(member);
435 }
436 } catch (MissingAccessorException e) {
437 noSetter = e;
438 if (!options.contains(Option.FIELD_INJECTION)) {
439 throw noSetter;
440 }
441 }
442
443 if (options.contains(Option.FIELD_INJECTION)) {
444 try {
445 FieldMember member = new FieldMember(ReflectionUtil.findField(clazz, propertyName.name, propertyValue, options));
446 members.add(member);
447 } catch (MissingAccessorException noField) {
448 if (members.isEmpty()) {
449 throw (noSetter == null || noField.getMatchLevel() > noSetter.getMatchLevel())? noField: noSetter;
450 }
451 }
452 }
453 }
454 } catch (MissingAccessorException e) {
455 if (options.contains(Option.IGNORE_MISSING_PROPERTIES)) {
456 unsetProperties.put(propertyName.name, propertyValue);
457 return;
458 }
459 throw e;
460 }
461
462 ConstructionException conversionException = null;
463 for (Member member : members) {
464 // convert the value to type of setter/field
465 try {
466 propertyValue = RecipeHelper.convert(member.getType(), propertyValue, false);
467 } catch (Exception e) {
468 // save off first conversion exception, in case setting failed
469 if (conversionException == null) {
470 String valueType = propertyValue == null ? "null" : propertyValue.getClass().getName();
471 String memberType = member.getType() instanceof Class ? ((Class) member.getType()).getName() : member.getType().toString();
472 conversionException = new ConstructionException("Unable to convert property value" +
473 " from " + valueType +
474 " to " + memberType +
475 " for injection " + member, e);
476 }
477 continue;
478 }
479 try {
480 // set value
481 member.setValue(instance, propertyValue);
482 } catch (Exception e) {
483 Throwable t = e;
484 if (e instanceof InvocationTargetException) {
485 InvocationTargetException invocationTargetException = (InvocationTargetException) e;
486 if (invocationTargetException.getCause() != null) {
487 t = invocationTargetException.getCause();
488 }
489 }
490 throw new ConstructionException("Error setting property: " + member, t);
491 }
492
493 // value set successfully
494 return;
495 }
496
497 throw conversionException;
498 }
499
500 private Factory findFactory(Type expectedType) {
501 Class type = getType();
502
503 //
504 // attempt to find a static factory
505 if (factoryMethod != null) {
506 try {
507 StaticFactory staticFactory = ReflectionUtil.findStaticFactory(
508 type,
509 factoryMethod,
510 constructorArgNames,
511 constructorArgTypes,
512 getProperties().keySet(),
513 options);
514 return staticFactory;
515 } catch (MissingFactoryMethodException ignored) {
516 }
517
518 }
519
520 //
521 // factory was not found, look for a constuctor
522
523 // if expectedType is a subclass of the assigned type, we create
524 // the sub class instead
525 Class consturctorClass;
526 if (RecipeHelper.isAssignable(type, expectedType)) {
527 consturctorClass = RecipeHelper.toClass(expectedType);
528 } else {
529 consturctorClass = type;
530 }
531
532 ConstructorFactory constructor = ReflectionUtil.findConstructor(
533 consturctorClass,
534 constructorArgNames,
535 constructorArgTypes,
536 getProperties().keySet(),
537 options);
538
539 return constructor;
540 }
541
542 private Object[] extractConstructorArgs(Map propertyValues, Factory factory) {
543 List<String> parameterNames = factory.getParameterNames();
544 List<Type> parameterTypes = factory.getParameterTypes();
545
546 Object[] parameters = new Object[parameterNames.size()];
547 for (int i = 0; i < parameterNames.size(); i++) {
548 Property name = new Property(parameterNames.get(i));
549 Type type = parameterTypes.get(i);
550
551 Object value;
552 if (propertyValues.containsKey(name)) {
553 value = propertyValues.remove(name);
554 if (!RecipeHelper.isInstance(type, value) && !RecipeHelper.isConvertable(type, value)) {
555 throw new ConstructionException("Invalid and non-convertable constructor parameter type: " +
556 "name=" + name + ", " +
557 "index=" + i + ", " +
558 "expected=" + RecipeHelper.toClass(type).getName() + ", " +
559 "actual=" + (value == null ? "null" : value.getClass().getName()));
560 }
561 value = RecipeHelper.convert(type, value, false);
562 } else {
563 value = getDefaultValue(RecipeHelper.toClass(type));
564 }
565
566
567 parameters[i] = value;
568 }
569 return parameters;
570 }
571
572 private static Object getDefaultValue(Class type) {
573 if (type.equals(Boolean.TYPE)) {
574 return Boolean.FALSE;
575 } else if (type.equals(Character.TYPE)) {
576 return (char) 0;
577 } else if (type.equals(Byte.TYPE)) {
578 return (byte) 0;
579 } else if (type.equals(Short.TYPE)) {
580 return (short) 0;
581 } else if (type.equals(Integer.TYPE)) {
582 return 0;
583 } else if (type.equals(Long.TYPE)) {
584 return (long) 0;
585 } else if (type.equals(Float.TYPE)) {
586 return (float) 0;
587 } else if (type.equals(Double.TYPE)) {
588 return (double) 0;
589 }
590 return null;
591 }
592
593 public static interface Member {
594 Type getType();
595 void setValue(Object instance, Object value) throws Exception;
596 }
597
598 public static class MethodMember implements Member {
599 private final Method setter;
600
601 public MethodMember(Method method) {
602 this.setter = method;
603 }
604
605 public Type getType() {
606 return setter.getGenericParameterTypes()[0];
607 }
608
609 public void setValue(Object instance, Object value) throws Exception {
610 setter.invoke(instance, value);
611 }
612
613 public String toString() {
614 return setter.toString();
615 }
616 }
617
618 public static class FieldMember implements Member {
619 private final Field field;
620
621 public FieldMember(Field field) {
622 this.field = field;
623 }
624
625 public Type getType() {
626 return field.getGenericType();
627 }
628
629 public void setValue(Object instance, Object value) throws Exception {
630 field.set(instance, value);
631 }
632
633 public String toString() {
634 return field.toString();
635 }
636 }
637
638 public static class Property {
639 private final String name;
640
641 public Property(String name) {
642 if (name == null) throw new NullPointerException("name is null");
643 this.name = name;
644 }
645
646 public boolean equals(Object o) {
647 if (this == o) return true;
648 if (o == null) return false;
649 if (o instanceof String){
650 return this.name.equals(o);
651 }
652 if (o instanceof Property) {
653 Property property = (Property) o;
654 return this.name.equals(property.name);
655 }
656 return false;
657 }
658
659 public int hashCode() {
660 return name.hashCode();
661 }
662
663 public String toString() {
664 return name;
665 }
666 }
667
668 public static class SetterProperty extends Property {
669 public SetterProperty(String name) {
670 super(name);
671 }
672 public int hashCode() {
673 return super.hashCode()+2;
674 }
675 public String toString() {
676 return "[setter] "+super.toString();
677 }
678
679 }
680
681 public static class FieldProperty extends Property {
682 public FieldProperty(String name) {
683 super(name);
684 }
685
686 public int hashCode() {
687 return super.hashCode()+1;
688 }
689 public String toString() {
690 return "[field] "+ super.toString();
691 }
692 }
693
694 public static class AutoMatchProperty extends Property {
695 public AutoMatchProperty(String type) {
696 super(type);
697 }
698
699 public int hashCode() {
700 return super.hashCode()+1;
701 }
702 public String toString() {
703 return "[auto-match] "+ super.toString();
704 }
705 }
706 }