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 }