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 org.apache.xbean.propertyeditor.PropertyEditors; 020 021 import java.lang.reflect.Constructor; 022 import java.lang.reflect.InvocationTargetException; 023 import java.lang.reflect.Method; 024 import java.lang.reflect.Modifier; 025 import java.lang.reflect.Field; 026 import java.util.ArrayList; 027 import java.util.Arrays; 028 import java.util.LinkedHashMap; 029 import java.util.List; 030 import java.util.Map; 031 import java.util.Iterator; 032 import java.security.AccessController; 033 import java.security.PrivilegedAction; 034 035 /** 036 * @version $Rev: 6688 $ $Date: 2005-12-29T02:08:29.200064Z $ 037 */ 038 public class ObjectRecipe extends AbstractRecipe { 039 private final String type; 040 private final String factoryMethod; 041 private final String[] constructorArgNames; 042 private final Class[] constructorArgTypes; 043 private final LinkedHashMap<Property,Object> properties; 044 private final List<Option> options = new ArrayList<Option>(); 045 private final Map<String,Object> unsetProperties = new LinkedHashMap<String,Object>(); 046 047 public ObjectRecipe(Class type) { 048 this(type.getName()); 049 } 050 051 public ObjectRecipe(Class type, String factoryMethod) { 052 this(type.getName(), factoryMethod); 053 } 054 055 public ObjectRecipe(Class type, Map<String,Object> properties) { 056 this(type.getName(), properties); 057 } 058 059 public ObjectRecipe(Class type, String[] constructorArgNames, Class[] constructorArgTypes) { 060 this(type.getName(), constructorArgNames, constructorArgTypes); 061 } 062 063 public ObjectRecipe(Class type, String factoryMethod, String[] constructorArgNames, Class[] constructorArgTypes) { 064 this(type.getName(), factoryMethod, constructorArgNames, constructorArgTypes); 065 } 066 067 public ObjectRecipe(String typeName) { 068 this(typeName, null, null, null, null); 069 } 070 071 public ObjectRecipe(String typeName, String factoryMethod) { 072 this(typeName, factoryMethod, null, null, null); 073 } 074 075 public ObjectRecipe(String typeName, Map<String,Object> properties) { 076 this(typeName, null, null, null, properties); 077 } 078 079 public ObjectRecipe(String typeName, String[] constructorArgNames, Class[] constructorArgTypes) { 080 this(typeName, null, constructorArgNames, constructorArgTypes, null); 081 } 082 083 public ObjectRecipe(String typeName, String factoryMethod, String[] constructorArgNames, Class[] constructorArgTypes) { 084 this(typeName, factoryMethod, constructorArgNames, constructorArgTypes, null); 085 } 086 087 public ObjectRecipe(String type, String factoryMethod, String[] constructorArgNames, Class[] constructorArgTypes, Map<String,Object> properties) { 088 options.add(Option.FIELD_INJECTION); 089 090 this.type = type; 091 this.factoryMethod = factoryMethod; 092 if (constructorArgNames != null) { 093 this.constructorArgNames = constructorArgNames; 094 } else { 095 this.constructorArgNames = new String[0]; 096 } 097 if (constructorArgTypes != null) { 098 this.constructorArgTypes = constructorArgTypes; 099 } else { 100 this.constructorArgTypes = new Class[0]; 101 } 102 if (properties != null) { 103 this.properties = new LinkedHashMap<Property,Object>(); 104 setAllProperties(properties); 105 } else { 106 this.properties = new LinkedHashMap<Property,Object>(); 107 } 108 } 109 110 public void allow(Option option){ 111 options.add(option); 112 } 113 114 public void disallow(Option option){ 115 options.remove(option); 116 } 117 118 public Object getProperty(String name) { 119 Object value = properties.get(new Property(name)); 120 return value; 121 } 122 123 public void setProperty(String name, Object value) { 124 setProperty(new Property(name), value); 125 } 126 127 public void setFieldProperty(String name, Object value){ 128 setProperty(new FieldProperty(name), value); 129 options.add(Option.FIELD_INJECTION); 130 } 131 132 public void setMethodProperty(String name, Object value){ 133 setProperty(new SetterProperty(name), value); 134 } 135 136 private void setProperty(Property key, Object value) { 137 if (value instanceof UnsetPropertiesRecipe) { 138 allow(Option.IGNORE_MISSING_PROPERTIES); 139 } 140 properties.put(key, value); 141 } 142 143 144 public void setAllProperties(Map map) { 145 if (map == null) throw new NullPointerException("map is null"); 146 for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) { 147 Map.Entry entry = (Map.Entry) iterator.next(); 148 String name = (String) entry.getKey(); 149 Object value = entry.getValue(); 150 setProperty(name, value); 151 } 152 } 153 154 public Map<String,Object> getUnsetProperties() { 155 return unsetProperties; 156 } 157 158 public boolean canCreate(Class type, ClassLoader classLoader) { 159 Class myType = getType(classLoader); 160 return type.isAssignableFrom(myType); 161 } 162 163 public Object create(ClassLoader classLoader) throws ConstructionException { 164 unsetProperties.clear(); 165 // load the type class 166 Class typeClass = getType(classLoader); 167 168 // verify that it is a class we can construct 169 if (!Modifier.isPublic(typeClass.getModifiers())) { 170 throw new ConstructionException("Class is not public: " + typeClass.getName()); 171 } 172 if (Modifier.isInterface(typeClass.getModifiers())) { 173 throw new ConstructionException("Class is an interface: " + typeClass.getName()); 174 } 175 if (Modifier.isAbstract(typeClass.getModifiers())) { 176 throw new ConstructionException("Class is abstract: " + typeClass.getName()); 177 } 178 179 // clone the properties so they can be used again 180 Map<Property,Object> propertyValues = new LinkedHashMap<Property,Object>(properties); 181 182 // find the factory method is one is declared 183 Method factoryMethod = null; 184 if (this.factoryMethod != null) { 185 factoryMethod = findFactoryMethod(typeClass, this.factoryMethod); 186 } 187 188 // create the instance 189 Object result; 190 if (factoryMethod != null && Modifier.isStatic(factoryMethod.getModifiers())) { 191 result = createInstance(factoryMethod, propertyValues, classLoader); 192 } else { 193 Constructor constructor = selectConstructor(typeClass); 194 result = createInstance(constructor, propertyValues, classLoader); 195 } 196 Object instance = result; 197 198 setProperties(propertyValues, instance, instance.getClass(), classLoader); 199 // call instance factory method 200 if (factoryMethod != null && !Modifier.isStatic(factoryMethod.getModifiers())) { 201 try { 202 instance = factoryMethod.invoke(instance); 203 } catch (Exception e) { 204 Throwable t = e; 205 if (e instanceof InvocationTargetException) { 206 InvocationTargetException invocationTargetException = (InvocationTargetException) e; 207 if (invocationTargetException.getCause() != null) { 208 t = invocationTargetException.getCause(); 209 } 210 } 211 throw new ConstructionException("Error calling factory method: " + factoryMethod, t); 212 } 213 } 214 215 return instance; 216 } 217 218 public Class setStaticProperties(ClassLoader classLoader) throws ConstructionException { 219 unsetProperties.clear(); 220 // load the type class 221 Class typeClass = getType(classLoader); 222 223 // verify that it is a class we can construct 224 if (!Modifier.isPublic(typeClass.getModifiers())) { 225 throw new ConstructionException("Class is not public: " + typeClass.getName()); 226 } 227 if (Modifier.isInterface(typeClass.getModifiers())) { 228 throw new ConstructionException("Class is an interface: " + typeClass.getName()); 229 } 230 if (Modifier.isAbstract(typeClass.getModifiers())) { 231 throw new ConstructionException("Class is abstract: " + typeClass.getName()); 232 } 233 234 // clone the properties so they can be used again 235 Map<Property,Object> propertyValues = new LinkedHashMap<Property,Object>(properties); 236 237 setProperties(propertyValues, null, typeClass, classLoader); 238 239 return typeClass; 240 } 241 242 private void setProperties(Map<Property, Object> propertyValues, Object instance, Class clazz, ClassLoader classLoader) { 243 boolean allowPrivate = options.contains(Option.PRIVATE_PROPERTIES); 244 boolean allowStatic = options.contains(Option.STATIC_PROPERTIES); 245 boolean ignoreMissingProperties = options.contains(Option.IGNORE_MISSING_PROPERTIES); 246 boolean caseInsesnitive = options.contains(Option.CASE_INSENSITIVE_PROPERTIES); 247 // set remaining properties 248 for (Map.Entry<Property, Object> entry : RecipeHelper.prioritizeProperties(propertyValues)) { 249 Property propertyName = entry.getKey(); 250 Object propertyValue = entry.getValue(); 251 252 setProperty(instance, clazz, propertyName, propertyValue, allowPrivate, allowStatic, ignoreMissingProperties, caseInsesnitive, classLoader); 253 } 254 255 } 256 257 private Class getType(ClassLoader classLoader) { 258 Class typeClass = null; 259 try { 260 typeClass = Class.forName(type, true, classLoader); 261 } catch (ClassNotFoundException e) { 262 throw new ConstructionException("Type class could not be found: " + type); 263 } 264 return typeClass; 265 } 266 267 private void setProperty(Object instance, Class clazz, Property propertyName, Object propertyValue, boolean allowPrivate, boolean allowStatic, boolean ignoreMissingProperties, boolean caseInsesnitive, ClassLoader classLoader) { 268 Member member; 269 try { 270 if (propertyName instanceof SetterProperty){ 271 member = new MethodMember(findSetter(clazz, propertyName.name, propertyValue, allowPrivate, allowStatic, caseInsesnitive, classLoader)); 272 } else if (propertyName instanceof FieldProperty){ 273 member = new FieldMember(findField(clazz, propertyName.name, propertyValue, allowPrivate, allowStatic, caseInsesnitive, classLoader)); 274 } else { 275 try { 276 member = new MethodMember(findSetter(clazz, propertyName.name, propertyValue, allowPrivate, allowStatic, caseInsesnitive, classLoader)); 277 } catch (MissingAccessorException noSetter) { 278 if (!options.contains(Option.FIELD_INJECTION)) { 279 throw noSetter; 280 } 281 282 try { 283 member = new FieldMember(findField(clazz, propertyName.name, propertyValue, allowPrivate, allowStatic, caseInsesnitive, classLoader)); 284 } catch (MissingAccessorException noField) { 285 throw (noField.getMatchLevel() > noSetter.getMatchLevel())? noField: noSetter; 286 } 287 } 288 } 289 } catch (MissingAccessorException e) { 290 if (ignoreMissingProperties){ 291 unsetProperties.put(propertyName.name, propertyValue); 292 return; 293 } else { 294 throw e; 295 } 296 } 297 298 try { 299 propertyValue = convert(member.getType(), propertyValue, classLoader); 300 member.setValue(instance, propertyValue); 301 } catch (Exception e) { 302 Throwable t = e; 303 if (e instanceof InvocationTargetException) { 304 InvocationTargetException invocationTargetException = (InvocationTargetException) e; 305 if (invocationTargetException.getCause() != null) { 306 t = invocationTargetException.getCause(); 307 } 308 } 309 throw new ConstructionException("Error setting property: " + member, t); 310 } 311 } 312 313 private Object[] extractConstructorArgs(Map propertyValues, Class[] constructorArgTypes, ClassLoader classLoader) { 314 Object[] parameters = new Object[constructorArgNames.length]; 315 for (int i = 0; i < constructorArgNames.length; i++) { 316 Property name = new Property(constructorArgNames[i]); 317 Class type = constructorArgTypes[i]; 318 319 Object value; 320 if (propertyValues.containsKey(name)) { 321 value = propertyValues.remove(name); 322 if (!RecipeHelper.isInstance(type, value) && !RecipeHelper.isConvertable(type, value, classLoader)) { 323 throw new ConstructionException("Invalid and non-convertable constructor parameter type: " + 324 "name=" + name + ", " + 325 "index=" + i + ", " + 326 "expected=" + type.getName() + ", " + 327 "actual=" + getClassName(value)); 328 } 329 value = convert(type, value, classLoader); 330 } else { 331 value = getDefaultValue(type); 332 } 333 334 335 parameters[i] = value; 336 } 337 return parameters; 338 } 339 340 private static String getClassName(Object value) { 341 if (value == null) return "null"; 342 343 return value.getClass().getName(); 344 } 345 346 private Object convert(Class type, Object value, ClassLoader classLoader) { 347 if (value instanceof Recipe) { 348 if (value instanceof SecretRecipe) { 349 SecretRecipe recipe = (SecretRecipe) value; 350 value = recipe.create(this, classLoader); 351 } else { 352 Recipe recipe = (Recipe) value; 353 value = recipe.create(classLoader); 354 } 355 } 356 357 if (value instanceof String && (type != Object.class)) { 358 String stringValue = (String) value; 359 value = PropertyEditors.getValue(type, stringValue); 360 } 361 return value; 362 } 363 364 private static Object getDefaultValue(Class type) { 365 if (type.equals(Boolean.TYPE)) { 366 return Boolean.FALSE; 367 } else if (type.equals(Character.TYPE)) { 368 return new Character((char) 0); 369 } else if (type.equals(Byte.TYPE)) { 370 return new Byte((byte) 0); 371 } else if (type.equals(Short.TYPE)) { 372 return new Short((short) 0); 373 } else if (type.equals(Integer.TYPE)) { 374 return new Integer(0); 375 } else if (type.equals(Long.TYPE)) { 376 return new Long(0); 377 } else if (type.equals(Float.TYPE)) { 378 return new Float(0); 379 } else if (type.equals(Double.TYPE)) { 380 return new Double(0); 381 } 382 return null; 383 } 384 385 private Object createInstance(Constructor constructor, Map propertyValues, ClassLoader classLoader) { 386 // get the constructor parameters 387 Object[] parameters = extractConstructorArgs(propertyValues, constructor.getParameterTypes(), classLoader); 388 389 try { 390 Object object = constructor.newInstance(parameters); 391 return object; 392 } catch (Exception e) { 393 Throwable t = e; 394 if (e instanceof InvocationTargetException) { 395 InvocationTargetException invocationTargetException = (InvocationTargetException) e; 396 if (invocationTargetException.getCause() != null) { 397 t = invocationTargetException.getCause(); 398 } 399 } 400 throw new ConstructionException("Error invoking constructor: " + constructor, t); 401 } 402 } 403 404 private Object createInstance(Method method, Map propertyValues, ClassLoader classLoader) { 405 // get the constructor parameters 406 Object[] parameters = extractConstructorArgs(propertyValues, method.getParameterTypes(), classLoader); 407 408 try { 409 Object object = method.invoke(null, parameters); 410 return object; 411 } catch (Exception e) { 412 Throwable t = e; 413 if (e instanceof InvocationTargetException) { 414 InvocationTargetException invocationTargetException = (InvocationTargetException) e; 415 if (invocationTargetException.getCause() != null) { 416 t = invocationTargetException.getCause(); 417 } 418 } 419 throw new ConstructionException("Error invoking factory method: " + method, t); 420 } 421 } 422 423 private Constructor selectConstructor(Class typeClass) { 424 if (constructorArgNames.length > 0 && constructorArgTypes.length == 0) { 425 ArrayList<Constructor> matches = new ArrayList<Constructor>(); 426 427 Constructor[] constructors = typeClass.getConstructors(); 428 for (Constructor constructor : constructors) { 429 if (constructor.getParameterTypes().length == constructorArgNames.length) { 430 matches.add(constructor); 431 } 432 } 433 434 if (matches.size() < 1) { 435 StringBuffer buffer = new StringBuffer("No parameter types supplied; unable to find a potentially valid constructor: "); 436 buffer.append("constructor= public ").append(typeClass.getName()); 437 buffer.append(toArgumentList(constructorArgNames)); 438 throw new ConstructionException(buffer.toString()); 439 } else if (matches.size() > 1) { 440 StringBuffer buffer = new StringBuffer("No parameter types supplied; found too many potentially valid constructors: "); 441 buffer.append("constructor= public ").append(typeClass.getName()); 442 buffer.append(toArgumentList(constructorArgNames)); 443 throw new ConstructionException(buffer.toString()); 444 } 445 446 return matches.get(0); 447 } 448 449 try { 450 Constructor constructor = typeClass.getConstructor(constructorArgTypes); 451 452 if (!Modifier.isPublic(constructor.getModifiers())) { 453 // this will never occur since private constructors are not returned from 454 // getConstructor, but leave this here anyway, just to be safe 455 throw new ConstructionException("Constructor is not public: " + constructor); 456 } 457 458 return constructor; 459 } catch (NoSuchMethodException e) { 460 // try to find a matching private method 461 Constructor[] constructors = typeClass.getDeclaredConstructors(); 462 for (Constructor constructor : constructors) { 463 if (isAssignableFrom(constructorArgTypes, constructor.getParameterTypes())) { 464 if (!Modifier.isPublic(constructor.getModifiers())) { 465 throw new ConstructionException("Constructor is not public: " + constructor); 466 } 467 } 468 } 469 470 StringBuffer buffer = new StringBuffer("Unable to find a valid constructor: "); 471 buffer.append("constructor= public ").append(typeClass.getName()); 472 buffer.append(toParameterList(constructorArgTypes)); 473 throw new ConstructionException(buffer.toString()); 474 } 475 } 476 477 private String toParameterList(Class[] parameterTypes) { 478 StringBuffer buffer = new StringBuffer(); 479 buffer.append("("); 480 for (int i = 0; i < parameterTypes.length; i++) { 481 Class type = parameterTypes[i]; 482 if (i > 0) buffer.append(", "); 483 buffer.append(type.getName()); 484 } 485 buffer.append(")"); 486 return buffer.toString(); 487 } 488 489 private String toArgumentList(String[] parameterNames) { 490 StringBuffer buffer = new StringBuffer(); 491 buffer.append("("); 492 for (int i = 0; i < parameterNames.length; i++) { 493 String parameterName = parameterNames[i]; 494 if (i > 0) buffer.append(", "); 495 buffer.append('<').append(parameterName).append('>'); 496 } 497 buffer.append(")"); 498 return buffer.toString(); 499 } 500 501 public Method findFactoryMethod(Class typeClass, String factoryMethod) { 502 if (factoryMethod == null) throw new NullPointerException("name is null"); 503 if (factoryMethod.length() == 0) throw new IllegalArgumentException("name is an empty string"); 504 505 int matchLevel = 0; 506 MissingFactoryMethodException missException = null; 507 508 List<Method> methods = new ArrayList<Method>(Arrays.asList(typeClass.getMethods())); 509 methods.addAll(Arrays.asList(typeClass.getDeclaredMethods())); 510 for (Method method : methods) { 511 if (method.getName().equals(factoryMethod)) { 512 if (Modifier.isStatic(method.getModifiers())) { 513 if (method.getParameterTypes().length != constructorArgNames.length) { 514 if (matchLevel < 1) { 515 matchLevel = 1; 516 missException = new MissingFactoryMethodException("Static factory method has " + method.getParameterTypes().length + " arugments " + 517 "but expected " + constructorArgNames.length + " arguments: " + method); 518 } 519 continue; 520 } 521 522 if (constructorArgTypes.length > 0 && !isAssignableFrom(constructorArgTypes, method.getParameterTypes())) { 523 if (matchLevel < 2) { 524 matchLevel = 2; 525 missException = new MissingFactoryMethodException("Static factory method has signature " + 526 "public static " + typeClass.getName() + "." + factoryMethod + toParameterList(method.getParameterTypes()) + 527 " but expected signature " + 528 "public static " + typeClass.getName() + "." + factoryMethod + toParameterList(constructorArgTypes)); 529 } 530 continue; 531 } 532 } else { 533 if (method.getParameterTypes().length != 0) { 534 if (matchLevel < 1) { 535 matchLevel = 1; 536 missException = new MissingFactoryMethodException("Instance factory method has parameters: " + method); 537 } 538 continue; 539 } 540 } 541 542 if (method.getReturnType() == Void.TYPE) { 543 if (matchLevel < 3) { 544 matchLevel = 3; 545 missException = new MissingFactoryMethodException("Factory method does not return a value: " + method); 546 } 547 continue; 548 } 549 550 if (Modifier.isAbstract(method.getModifiers())) { 551 if (matchLevel < 4) { 552 matchLevel = 4; 553 missException = new MissingFactoryMethodException("Factory method is abstract: " + method); 554 } 555 continue; 556 } 557 558 if (!Modifier.isPublic(method.getModifiers())) { 559 if (matchLevel < 5) { 560 matchLevel = 5; 561 missException = new MissingFactoryMethodException("Factory method is not public: " + method); 562 } 563 continue; 564 } 565 566 return method; 567 } 568 } 569 570 if (missException != null) { 571 throw missException; 572 } else { 573 StringBuffer buffer = new StringBuffer("Unable to find a valid factory method: "); 574 buffer.append("public void ").append(typeClass.getName()).append("."); 575 buffer.append(factoryMethod).append("()"); 576 throw new MissingFactoryMethodException(buffer.toString()); 577 } 578 } 579 580 /** 581 * @deprecated use the method with allowStatic 582 * @param typeClass 583 * @param propertyName 584 * @param propertyValue 585 * @param allowPrivate 586 * @param classLoader 587 * @return field 588 */ 589 public static Method findSetter(Class typeClass, String propertyName, Object propertyValue, boolean allowPrivate, ClassLoader classLoader) { 590 return findSetter(typeClass, propertyName, propertyValue, allowPrivate, false, false, classLoader); 591 } 592 593 public static Method findSetter(Class typeClass, String propertyName, Object propertyValue, boolean allowPrivate, boolean allowStatic, boolean caseInsesnitive, ClassLoader classLoader) { 594 if (propertyName == null) throw new NullPointerException("name is null"); 595 if (propertyName.length() == 0) throw new IllegalArgumentException("name is an empty string"); 596 597 if (propertyName.contains("/")){ 598 String[] strings = propertyName.split("/"); 599 if (strings == null || strings.length != 2) throw new IllegalArgumentException("badly formed <class>/<attribute> property name: " + propertyName); 600 601 String className = strings[0]; 602 propertyName = strings[1]; 603 604 boolean found = false; 605 while(!typeClass.equals(Object.class) && !found){ 606 if (typeClass.getName().equals(className)){ 607 found = true; 608 break; 609 } else { 610 typeClass = typeClass.getSuperclass(); 611 } 612 } 613 614 if (!found) throw new MissingAccessorException("Type not assignable to class: " + className, -1); 615 } 616 617 String setterName = "set" + Character.toUpperCase(propertyName.charAt(0)); 618 if (propertyName.length() > 0) { 619 setterName += propertyName.substring(1); 620 } 621 622 623 int matchLevel = 0; 624 MissingAccessorException missException = null; 625 626 List<Method> methods = new ArrayList<Method>(Arrays.asList(typeClass.getMethods())); 627 methods.addAll(Arrays.asList(typeClass.getDeclaredMethods())); 628 for (Method method : methods) { 629 if (method.getName().equals(setterName) || (caseInsesnitive && method.getName().equalsIgnoreCase(setterName))) { 630 if (method.getParameterTypes().length == 0) { 631 if (matchLevel < 1) { 632 matchLevel = 1; 633 missException = new MissingAccessorException("Setter takes no parameters: " + method, matchLevel); 634 } 635 continue; 636 } 637 638 if (method.getParameterTypes().length > 1) { 639 if (matchLevel < 1) { 640 matchLevel = 1; 641 missException = new MissingAccessorException("Setter takes more then one parameter: " + method, matchLevel); 642 } 643 continue; 644 } 645 646 if (method.getReturnType() != Void.TYPE) { 647 if (matchLevel < 2) { 648 matchLevel = 2; 649 missException = new MissingAccessorException("Setter returns a value: " + method, matchLevel); 650 } 651 continue; 652 } 653 654 if (Modifier.isAbstract(method.getModifiers())) { 655 if (matchLevel < 3) { 656 matchLevel = 3; 657 missException = new MissingAccessorException("Setter is abstract: " + method, matchLevel); 658 } 659 continue; 660 } 661 662 if (!allowPrivate && !Modifier.isPublic(method.getModifiers())) { 663 if (matchLevel < 4) { 664 matchLevel = 4; 665 missException = new MissingAccessorException("Setter is not public: " + method, matchLevel); 666 } 667 continue; 668 } 669 670 if (!allowStatic && Modifier.isStatic(method.getModifiers())) { 671 if (matchLevel < 4) { 672 matchLevel = 4; 673 missException = new MissingAccessorException("Setter is static: " + method, matchLevel); 674 } 675 continue; 676 } 677 678 Class methodParameterType = method.getParameterTypes()[0]; 679 if (methodParameterType.isPrimitive() && propertyValue == null) { 680 if (matchLevel < 6) { 681 matchLevel = 6; 682 missException = new MissingAccessorException("Null can not be assigned to " + 683 methodParameterType.getName() + ": " + method, matchLevel); 684 } 685 continue; 686 } 687 688 689 if (!RecipeHelper.isInstance(methodParameterType, propertyValue) && !RecipeHelper.isConvertable(methodParameterType, propertyValue, classLoader)) { 690 if (matchLevel < 5) { 691 matchLevel = 5; 692 missException = new MissingAccessorException(getClassName(propertyValue) + " can not be assigned or converted to " + 693 methodParameterType.getName() + ": " + method, matchLevel); 694 } 695 continue; 696 } 697 698 if (allowPrivate && !Modifier.isPublic(method.getModifiers())) { 699 setAccessible(method); 700 } 701 702 return method; 703 } 704 705 } 706 707 if (missException != null) { 708 throw missException; 709 } else { 710 StringBuffer buffer = new StringBuffer("Unable to find a valid setter method: "); 711 buffer.append("public void ").append(typeClass.getName()).append("."); 712 buffer.append(setterName).append("(").append(getClassName(propertyValue)).append(")"); 713 throw new MissingAccessorException(buffer.toString(), -1); 714 } 715 } 716 717 /** 718 * @deprecated use the method with allowStatic 719 * @param typeClass 720 * @param propertyName 721 * @param propertyValue 722 * @param allowPrivate 723 * @param classLoader 724 * @return field 725 */ 726 public static Field findField(Class typeClass, String propertyName, Object propertyValue, boolean allowPrivate, ClassLoader classLoader) { 727 return findField(typeClass, propertyName, propertyValue, allowPrivate, false, false, classLoader); 728 } 729 730 public static Field findField(Class typeClass, String propertyName, Object propertyValue, boolean allowPrivate, boolean allowStatic, boolean caseInsesnitive, ClassLoader classLoader) { 731 if (propertyName == null) throw new NullPointerException("name is null"); 732 if (propertyName.length() == 0) throw new IllegalArgumentException("name is an empty string"); 733 734 int matchLevel = 0; 735 MissingAccessorException missException = null; 736 737 if (propertyName.contains("/")){ 738 String[] strings = propertyName.split("/"); 739 if (strings == null || strings.length != 2) throw new IllegalArgumentException("badly formed <class>/<attribute> property name: " + propertyName); 740 741 String className = strings[0]; 742 propertyName = strings[1]; 743 744 boolean found = false; 745 while(!typeClass.equals(Object.class) && !found){ 746 if (typeClass.getName().equals(className)){ 747 found = true; 748 break; 749 } else { 750 typeClass = typeClass.getSuperclass(); 751 } 752 } 753 754 if (!found) throw new MissingAccessorException("Type not assignable to class: " + className, -1); 755 } 756 757 List<Field> fields = new ArrayList<Field>(Arrays.asList(typeClass.getDeclaredFields())); 758 Class parent = typeClass.getSuperclass(); 759 while (parent != null){ 760 fields.addAll(Arrays.asList(parent.getDeclaredFields())); 761 parent = parent.getSuperclass(); 762 } 763 764 for (Field field : fields) { 765 if (field.getName().equals(propertyName) || (caseInsesnitive && field.getName().equalsIgnoreCase(propertyName))) { 766 767 if (!allowPrivate && !Modifier.isPublic(field.getModifiers())) { 768 if (matchLevel < 4) { 769 matchLevel = 4; 770 missException = new MissingAccessorException("Field is not public: " + field, matchLevel); 771 } 772 continue; 773 } 774 775 if (!allowStatic && Modifier.isStatic(field.getModifiers())) { 776 if (matchLevel < 4) { 777 matchLevel = 4; 778 missException = new MissingAccessorException("Field is static: " + field, matchLevel); 779 } 780 continue; 781 } 782 783 Class fieldType = field.getType(); 784 if (fieldType.isPrimitive() && propertyValue == null) { 785 if (matchLevel < 6) { 786 matchLevel = 6; 787 missException = new MissingAccessorException("Null can not be assigned to " + 788 fieldType.getName() + ": " + field, matchLevel); 789 } 790 continue; 791 } 792 793 794 if (!RecipeHelper.isInstance(fieldType, propertyValue) && !RecipeHelper.isConvertable(fieldType, propertyValue, classLoader)) { 795 if (matchLevel < 5) { 796 matchLevel = 5; 797 missException = new MissingAccessorException(getClassName(propertyValue) + " can not be assigned or converted to " + 798 fieldType.getName() + ": " + field, matchLevel); 799 } 800 continue; 801 } 802 803 if (allowPrivate && !Modifier.isPublic(field.getModifiers())) { 804 setAccessible(field); 805 } 806 807 return field; 808 } 809 810 } 811 812 if (missException != null) { 813 throw missException; 814 } else { 815 StringBuffer buffer = new StringBuffer("Unable to find a valid field: "); 816 buffer.append("public ").append(" ").append(getClassName(propertyValue)); 817 buffer.append(" ").append(propertyName).append(";"); 818 throw new MissingAccessorException(buffer.toString(), -1); 819 } 820 } 821 822 public static boolean isAssignableFrom(Class[] expectedTypes, Class[] actualTypes) { 823 if (expectedTypes.length != actualTypes.length) { 824 return false; 825 } 826 for (int i = 0; i < expectedTypes.length; i++) { 827 Class expectedType = expectedTypes[i]; 828 Class actualType = actualTypes[i]; 829 if (expectedType != actualType && !RecipeHelper.isAssignableFrom(expectedType, actualType)) { 830 return false; 831 } 832 } 833 return true; 834 } 835 836 private static void setAccessible(final Method method) { 837 AccessController.doPrivileged(new PrivilegedAction<Object>() { 838 public Object run() { 839 method.setAccessible(true); 840 return null; 841 } 842 }); 843 } 844 845 private static void setAccessible(final Field field) { 846 AccessController.doPrivileged(new PrivilegedAction<Object>() { 847 public Object run() { 848 field.setAccessible(true); 849 return null; 850 } 851 }); 852 } 853 854 public static interface Member { 855 Class getType(); 856 void setValue(Object instance, Object value) throws Exception; 857 } 858 859 public static class MethodMember implements Member { 860 private final Method setter; 861 862 public MethodMember(Method method) { 863 this.setter = method; 864 } 865 866 public Class getType() { 867 return setter.getParameterTypes()[0]; 868 } 869 870 public void setValue(Object instance, Object value) throws Exception { 871 setter.invoke(instance, value); 872 } 873 874 public String toString() { 875 return setter.toString(); 876 } 877 } 878 879 public static class FieldMember implements Member { 880 private final Field field; 881 882 public FieldMember(Field field) { 883 this.field = field; 884 } 885 886 public Class getType() { 887 return field.getType(); 888 } 889 890 public void setValue(Object instance, Object value) throws Exception { 891 field.set(instance, value); 892 } 893 894 public String toString() { 895 return field.toString(); 896 } 897 } 898 899 private static class Property { 900 private final String name; 901 902 public Property(String name) { 903 if (name == null) throw new NullPointerException("name is null"); 904 this.name = name; 905 } 906 907 public boolean equals(Object o) { 908 if (this == o) return true; 909 if (o == null) return false; 910 if (o instanceof String){ 911 return this.name.equals(o); 912 } 913 if (o instanceof Property) { 914 Property property = (Property) o; 915 return this.name.equals(property.name); 916 } 917 return false; 918 } 919 920 public int hashCode() { 921 return name.hashCode(); 922 } 923 924 public String toString() { 925 return name; 926 } 927 } 928 929 private static class SetterProperty extends Property { 930 public SetterProperty(String name) { 931 super(name); 932 } 933 public int hashCode() { 934 return super.hashCode()+2; 935 } 936 public String toString() { 937 return "[setter] "+toString(); 938 } 939 940 } 941 private static class FieldProperty extends Property { 942 public FieldProperty(String name) { 943 super(name); 944 } 945 946 public int hashCode() { 947 return super.hashCode()+1; 948 } 949 public String toString() { 950 return "[field] "+toString(); 951 } 952 } 953 }