001/** 002 * 003 * Licensed to the Apache Software Foundation (ASF) under one or more 004 * contributor license agreements. See the NOTICE file distributed with 005 * this work for additional information regarding copyright ownership. 006 * The ASF licenses this file to You under the Apache License, Version 2.0 007 * (the "License"); you may not use this file except in compliance with 008 * the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.xbean.recipe; 019 020import java.lang.annotation.Annotation; 021import java.lang.reflect.AccessibleObject; 022import java.lang.reflect.Constructor; 023import java.lang.reflect.Field; 024import java.lang.reflect.InvocationTargetException; 025import java.lang.reflect.Method; 026import java.lang.reflect.Modifier; 027import java.lang.reflect.Type; 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.Collections; 031import java.util.Comparator; 032import java.util.EnumSet; 033import java.util.LinkedHashSet; 034import java.util.LinkedList; 035import java.util.List; 036import java.util.Set; 037 038import static org.apache.xbean.recipe.RecipeHelper.isAssignableFrom; 039 040import org.apache.xbean.propertyeditor.PropertyEditorRegistry; 041 042public final class ReflectionUtil { 043 private static final class ParameterLoader { 044 private static ParameterNameLoader PARAMETER_NAME_LOADER; 045 static { 046 if (isClassAvailable("org.apache.xbean.asm9.ClassReader")) { 047 PARAMETER_NAME_LOADER = new XbeanAsmParameterNameLoader(); 048 } else if (isClassAvailable("org.objectweb.asm.ClassReader")) { 049 PARAMETER_NAME_LOADER = new AsmParameterNameLoader(); 050 } else if (isClassAvailable("org.apache.xbean.asm.ClassReader") 051 || isClassAvailable("org.apache.xbean.asm4.ClassReader") 052 || isClassAvailable("org.apache.xbean.asm5.ClassReader") 053 || isClassAvailable("org.apache.xbean.asm6.ClassReader") 054 || isClassAvailable("org.apache.xbean.asm8.ClassReader") 055 || isClassAvailable("org.apache.xbean.asm7.ClassReader")) { 056 throw new RuntimeException("Your xbean-asm-shade is too old, please upgrade to xbean-asm9-shaded"); 057 } 058 } 059 } 060 061 private ReflectionUtil() { 062 } 063 064 private static boolean isClassAvailable(String className) { 065 try { 066 ReflectionUtil.class.getClassLoader().loadClass(className); 067 return true; 068 } catch (Throwable ignored) { 069 return false; 070 } 071 } 072 073 public static Field findField(Class typeClass, String propertyName, Object propertyValue, Set<Option> options, 074 PropertyEditorRegistry registry) { 075 if (typeClass == null) throw new NullPointerException("typeClass is null"); 076 if (propertyName == null) throw new NullPointerException("name is null"); 077 if (propertyName.length() == 0) throw new IllegalArgumentException("name is an empty string"); 078 if (options == null) options = EnumSet.noneOf(Option.class); 079 080 int matchLevel = 0; 081 MissingAccessorException missException = null; 082 083 if (propertyName.contains("/")){ 084 String[] strings = propertyName.split("/"); 085 if (strings == null || strings.length != 2) throw new IllegalArgumentException("badly formed <class>/<attribute> property name: " + propertyName); 086 087 String className = strings[0]; 088 propertyName = strings[1]; 089 090 boolean found = false; 091 while(!typeClass.equals(Object.class) && !found){ 092 if (typeClass.getName().equals(className)){ 093 found = true; 094 break; 095 } else { 096 typeClass = typeClass.getSuperclass(); 097 } 098 } 099 100 if (!found) throw new MissingAccessorException("Type not assignable to class: " + className, -1); 101 } 102 103 List<Field> fields = new ArrayList<Field>(Arrays.asList(typeClass.getDeclaredFields())); 104 Class parent = typeClass.getSuperclass(); 105 while (parent != null){ 106 fields.addAll(Arrays.asList(parent.getDeclaredFields())); 107 parent = parent.getSuperclass(); 108 } 109 110 boolean allowPrivate = options.contains(Option.PRIVATE_PROPERTIES); 111 boolean allowStatic = options.contains(Option.STATIC_PROPERTIES); 112 boolean caseInsesnitive = options.contains(Option.CASE_INSENSITIVE_PROPERTIES); 113 114 for (Field field : fields) { 115 if (field.getName().equals(propertyName) || (caseInsesnitive && field.getName().equalsIgnoreCase(propertyName))) { 116 117 if (!allowPrivate && !Modifier.isPublic(field.getModifiers())) { 118 if (matchLevel < 4) { 119 matchLevel = 4; 120 missException = new MissingAccessorException("Field is not public: " + field, matchLevel); 121 } 122 continue; 123 } 124 125 if (!allowStatic && Modifier.isStatic(field.getModifiers())) { 126 if (matchLevel < 4) { 127 matchLevel = 4; 128 missException = new MissingAccessorException("Field is static: " + field, matchLevel); 129 } 130 continue; 131 } 132 133 Class fieldType = field.getType(); 134 if (fieldType.isPrimitive() && propertyValue == null) { 135 if (matchLevel < 6) { 136 matchLevel = 6; 137 missException = new MissingAccessorException("Null can not be assigned to " + 138 fieldType.getName() + ": " + field, matchLevel); 139 } 140 continue; 141 } 142 143 144 if (!RecipeHelper.isInstance(fieldType, propertyValue) && !RecipeHelper.isConvertable(fieldType, propertyValue, registry)) { 145 if (matchLevel < 5) { 146 matchLevel = 5; 147 missException = new MissingAccessorException((propertyValue == null ? "null" : propertyValue.getClass().getName()) + " can not be assigned or converted to " + 148 fieldType.getName() + ": " + field, matchLevel); 149 } 150 continue; 151 } 152 153 if (allowPrivate && !Modifier.isPublic(field.getModifiers())) { 154 setAccessible(field); 155 } 156 157 return field; 158 } 159 160 } 161 162 if (missException != null) { 163 throw missException; 164 } else { 165 StringBuffer buffer = new StringBuffer("Unable to find a valid field: "); 166 buffer.append("public ").append(" ").append(propertyValue == null ? "null" : propertyValue.getClass().getName()); 167 buffer.append(" ").append(propertyName).append(";"); 168 throw new MissingAccessorException(buffer.toString(), -1); 169 } 170 } 171 172 public static Method findGetter(Class typeClass, String propertyName, Set<Option> options) { 173 if (typeClass == null) throw new NullPointerException("typeClass is null"); 174 if (propertyName == null) throw new NullPointerException("name is null"); 175 if (propertyName.length() == 0) throw new IllegalArgumentException("name is an empty string"); 176 if (options == null) options = EnumSet.noneOf(Option.class); 177 178 if (propertyName.contains("/")){ 179 String[] strings = propertyName.split("/"); 180 if (strings == null || strings.length != 2) throw new IllegalArgumentException("badly formed <class>/<attribute> property name: " + propertyName); 181 182 String className = strings[0]; 183 propertyName = strings[1]; 184 185 boolean found = false; 186 while(!typeClass.equals(Object.class) && !found){ 187 if (typeClass.getName().equals(className)){ 188 found = true; 189 break; 190 } else { 191 typeClass = typeClass.getSuperclass(); 192 } 193 } 194 195 if (!found) throw new MissingAccessorException("Type not assignable to class: " + className, -1); 196 } 197 198 String getterName = "get" + Character.toUpperCase(propertyName.charAt(0)); 199 if (propertyName.length() > 0) { 200 getterName += propertyName.substring(1); 201 } 202 203 boolean allowPrivate = options.contains(Option.PRIVATE_PROPERTIES); 204 boolean allowStatic = options.contains(Option.STATIC_PROPERTIES); 205 boolean caseInsesnitive = options.contains(Option.CASE_INSENSITIVE_PROPERTIES); 206 207 List<Method> methods = new ArrayList<Method>(Arrays.asList(typeClass.getMethods())); 208 methods.addAll(Arrays.asList(typeClass.getDeclaredMethods())); 209 for (Method method : methods) { 210 if (method.getName().equals(getterName) || (caseInsesnitive && method.getName().equalsIgnoreCase(getterName))) { 211 if (method.getParameterTypes().length > 0) { 212 continue; 213 } 214 if (method.getReturnType() == Void.TYPE) { 215 continue; 216 } 217 if (Modifier.isAbstract(method.getModifiers())) { 218 continue; 219 } 220 if (!allowPrivate && !Modifier.isPublic(method.getModifiers())) { 221 continue; 222 } 223 if (!allowStatic && Modifier.isStatic(method.getModifiers())) { 224 continue; 225 } 226 227 if (allowPrivate && !Modifier.isPublic(method.getModifiers())) { 228 setAccessible(method); 229 } 230 231 return method; 232 } 233 } 234 235 return null; 236 } 237 238 public static Method findSetter(Class typeClass, String propertyName, Object propertyValue, Set<Option> options, 239 PropertyEditorRegistry registry) { 240 List<Method> setters = findAllSetters(typeClass, propertyName, propertyValue, options, registry); 241 return setters.get(0); 242 } 243 244 /** 245 * Finds all valid setters for the property. Due to automatic type conversion there may be more than one possible 246 * setter that could be used to set the property. The setters that do not require type converstion will be a the 247 * head of the returned list of setters. 248 * @param typeClass the class to search for setters 249 * @param propertyName the name of the property 250 * @param propertyValue the value that must be settable either directly or after conversion 251 * @param options controls which setters are considered valid 252 * @return the valid setters; never null or empty 253 */ 254 public static List<Method> findAllSetters(Class typeClass, String propertyName, Object propertyValue, Set<Option> options, 255 PropertyEditorRegistry registry) { 256 if (typeClass == null) throw new NullPointerException("typeClass is null"); 257 if (propertyName == null) throw new NullPointerException("name is null"); 258 if (propertyName.length() == 0) throw new IllegalArgumentException("name is an empty string"); 259 if (options == null) options = EnumSet.noneOf(Option.class); 260 261 if (propertyName.contains("/")){ 262 String[] strings = propertyName.split("/"); 263 if (strings == null || strings.length != 2) throw new IllegalArgumentException("badly formed <class>/<attribute> property name: " + propertyName); 264 265 String className = strings[0]; 266 propertyName = strings[1]; 267 268 boolean found = false; 269 while(!typeClass.equals(Object.class) && !found){ 270 if (typeClass.getName().equals(className)){ 271 found = true; 272 break; 273 } else { 274 typeClass = typeClass.getSuperclass(); 275 } 276 } 277 278 if (!found) throw new MissingAccessorException("Type not assignable to class: " + className, -1); 279 } 280 281 String setterName = "set" + Character.toUpperCase(propertyName.charAt(0)); 282 if (propertyName.length() > 0) { 283 setterName += propertyName.substring(1); 284 } 285 286 287 int matchLevel = 0; 288 MissingAccessorException missException = null; 289 290 boolean allowPrivate = options.contains(Option.PRIVATE_PROPERTIES); 291 boolean allowStatic = options.contains(Option.STATIC_PROPERTIES); 292 boolean caseInsesnitive = options.contains(Option.CASE_INSENSITIVE_PROPERTIES); 293 294 295 LinkedList<Method> validSetters = new LinkedList<Method>(); 296 297 List<Method> methods = new ArrayList<Method>(Arrays.asList(typeClass.getMethods())); 298 methods.addAll(Arrays.asList(typeClass.getDeclaredMethods())); 299 for (Method method : methods) { 300 if (method.getName().equals(setterName) || (caseInsesnitive && method.getName().equalsIgnoreCase(setterName))) { 301 if (method.getParameterTypes().length == 0) { 302 if (matchLevel < 1) { 303 matchLevel = 1; 304 missException = new MissingAccessorException("Setter takes no parameters: " + method, matchLevel); 305 } 306 continue; 307 } 308 309 if (method.getParameterTypes().length > 1) { 310 if (matchLevel < 1) { 311 matchLevel = 1; 312 missException = new MissingAccessorException("Setter takes more then one parameter: " + method, matchLevel); 313 } 314 continue; 315 } 316 317 if (method.getReturnType() != Void.TYPE) { 318 if (matchLevel < 2) { 319 matchLevel = 2; 320 missException = new MissingAccessorException("Setter returns a value: " + method, matchLevel); 321 } 322 continue; 323 } 324 325 if (Modifier.isAbstract(method.getModifiers())) { 326 if (matchLevel < 3) { 327 matchLevel = 3; 328 missException = new MissingAccessorException("Setter is abstract: " + method, matchLevel); 329 } 330 continue; 331 } 332 333 if (!allowPrivate && !Modifier.isPublic(method.getModifiers())) { 334 if (matchLevel < 4) { 335 matchLevel = 4; 336 missException = new MissingAccessorException("Setter is not public: " + method, matchLevel); 337 } 338 continue; 339 } 340 341 if (!allowStatic && Modifier.isStatic(method.getModifiers())) { 342 if (matchLevel < 4) { 343 matchLevel = 4; 344 missException = new MissingAccessorException("Setter is static: " + method, matchLevel); 345 } 346 continue; 347 } 348 349 Class methodParameterType = method.getParameterTypes()[0]; 350 if (methodParameterType.isPrimitive() && propertyValue == null) { 351 if (matchLevel < 6) { 352 matchLevel = 6; 353 missException = new MissingAccessorException("Null can not be assigned to " + 354 methodParameterType.getName() + ": " + method, matchLevel); 355 } 356 continue; 357 } 358 359 360 if (!RecipeHelper.isInstance(methodParameterType, propertyValue) && !RecipeHelper.isConvertable(methodParameterType, propertyValue, registry)) { 361 if (matchLevel < 5) { 362 matchLevel = 5; 363 missException = new MissingAccessorException((propertyValue == null ? "null" : propertyValue.getClass().getName()) + " can not be assigned or converted to " + 364 methodParameterType.getName() + ": " + method, matchLevel); 365 } 366 continue; 367 } 368 369 if (allowPrivate && !Modifier.isPublic(method.getModifiers())) { 370 setAccessible(method); 371 } 372 373 if (RecipeHelper.isInstance(methodParameterType, propertyValue)) { 374 // This setter requires no conversion, which means there can not be a conversion error. 375 // Therefore this setter is perferred and put a the head of the list 376 validSetters.addFirst(method); 377 } else { 378 validSetters.add(method); 379 } 380 } 381 382 } 383 384 if (!validSetters.isEmpty()) { 385 // remove duplicate methods (can happen with inheritance) 386 return new ArrayList<Method>(new LinkedHashSet<Method>(validSetters)); 387 } 388 389 if (missException != null) { 390 throw missException; 391 } else { 392 StringBuffer buffer = new StringBuffer("Unable to find a valid setter method: "); 393 buffer.append("public void ").append(typeClass.getName()).append("."); 394 buffer.append(setterName).append("("); 395 if (propertyValue == null) { 396 buffer.append("null"); 397 } else if (propertyValue instanceof String || propertyValue instanceof Recipe) { 398 buffer.append("..."); 399 } else { 400 buffer.append(propertyValue.getClass().getName()); 401 } 402 buffer.append(")"); 403 throw new MissingAccessorException(buffer.toString(), -1); 404 } 405 } 406 407 public static List<Field> findAllFieldsByType(Class typeClass, Object propertyValue, Set<Option> options, 408 PropertyEditorRegistry registry) { 409 if (typeClass == null) throw new NullPointerException("typeClass is null"); 410 if (options == null) options = EnumSet.noneOf(Option.class); 411 412 int matchLevel = 0; 413 MissingAccessorException missException = null; 414 415 List<Field> fields = new ArrayList<Field>(Arrays.asList(typeClass.getDeclaredFields())); 416 Class parent = typeClass.getSuperclass(); 417 while (parent != null){ 418 fields.addAll(Arrays.asList(parent.getDeclaredFields())); 419 parent = parent.getSuperclass(); 420 } 421 422 boolean allowPrivate = options.contains(Option.PRIVATE_PROPERTIES); 423 boolean allowStatic = options.contains(Option.STATIC_PROPERTIES); 424 425 LinkedList<Field> validFields = new LinkedList<Field>(); 426 for (Field field : fields) { 427 Class fieldType = field.getType(); 428 if (RecipeHelper.isInstance(fieldType, propertyValue) || RecipeHelper.isConvertable(fieldType, propertyValue, registry)) { 429 if (!allowPrivate && !Modifier.isPublic(field.getModifiers())) { 430 if (matchLevel < 4) { 431 matchLevel = 4; 432 missException = new MissingAccessorException("Field is not public: " + field, matchLevel); 433 } 434 continue; 435 } 436 437 if (!allowStatic && Modifier.isStatic(field.getModifiers())) { 438 if (matchLevel < 4) { 439 matchLevel = 4; 440 missException = new MissingAccessorException("Field is static: " + field, matchLevel); 441 } 442 continue; 443 } 444 445 446 if (fieldType.isPrimitive() && propertyValue == null) { 447 if (matchLevel < 6) { 448 matchLevel = 6; 449 missException = new MissingAccessorException("Null can not be assigned to " + 450 fieldType.getName() + ": " + field, matchLevel); 451 } 452 continue; 453 } 454 455 if (allowPrivate && !Modifier.isPublic(field.getModifiers())) { 456 setAccessible(field); 457 } 458 459 if (RecipeHelper.isInstance(fieldType, propertyValue)) { 460 // This field requires no conversion, which means there can not be a conversion error. 461 // Therefore this setter is perferred and put a the head of the list 462 validFields.addFirst(field); 463 } else { 464 validFields.add(field); 465 } 466 } 467 } 468 469 if (!validFields.isEmpty()) { 470 // remove duplicate methods (can happen with inheritance) 471 return new ArrayList<Field>(new LinkedHashSet<Field>(validFields)); 472 } 473 474 if (missException != null) { 475 throw missException; 476 } else { 477 StringBuffer buffer = new StringBuffer("Unable to find a valid field "); 478 if (propertyValue instanceof Recipe) { 479 buffer.append("for ").append(propertyValue == null ? "null" : propertyValue); 480 } else { 481 buffer.append("of type ").append(propertyValue == null ? "null" : propertyValue.getClass().getName()); 482 } 483 buffer.append(" in class ").append(typeClass.getName()); 484 throw new MissingAccessorException(buffer.toString(), -1); 485 } 486 } 487 public static List<Method> findAllSettersByType(Class typeClass, Object propertyValue, Set<Option> options, 488 PropertyEditorRegistry registry) { 489 if (typeClass == null) throw new NullPointerException("typeClass is null"); 490 if (options == null) options = EnumSet.noneOf(Option.class); 491 492 int matchLevel = 0; 493 MissingAccessorException missException = null; 494 495 boolean allowPrivate = options.contains(Option.PRIVATE_PROPERTIES); 496 boolean allowStatic = options.contains(Option.STATIC_PROPERTIES); 497 498 LinkedList<Method> validSetters = new LinkedList<Method>(); 499 List<Method> methods = new ArrayList<Method>(Arrays.asList(typeClass.getMethods())); 500 methods.addAll(Arrays.asList(typeClass.getDeclaredMethods())); 501 for (Method method : methods) { 502 if (method.getName().startsWith("set") && method.getParameterTypes().length == 1 && (RecipeHelper.isInstance(method.getParameterTypes()[0], propertyValue) || RecipeHelper.isConvertable(method.getParameterTypes()[0], propertyValue, registry))) { 503 if (method.getReturnType() != Void.TYPE) { 504 if (matchLevel < 2) { 505 matchLevel = 2; 506 missException = new MissingAccessorException("Setter returns a value: " + method, matchLevel); 507 } 508 continue; 509 } 510 511 if (Modifier.isAbstract(method.getModifiers())) { 512 if (matchLevel < 3) { 513 matchLevel = 3; 514 missException = new MissingAccessorException("Setter is abstract: " + method, matchLevel); 515 } 516 continue; 517 } 518 519 if (!allowPrivate && !Modifier.isPublic(method.getModifiers())) { 520 if (matchLevel < 4) { 521 matchLevel = 4; 522 missException = new MissingAccessorException("Setter is not public: " + method, matchLevel); 523 } 524 continue; 525 } 526 527 Class methodParameterType = method.getParameterTypes()[0]; 528 if (methodParameterType.isPrimitive() && propertyValue == null) { 529 if (matchLevel < 6) { 530 matchLevel = 6; 531 missException = new MissingAccessorException("Null can not be assigned to " + 532 methodParameterType.getName() + ": " + method, matchLevel); 533 } 534 continue; 535 } 536 537 if (!allowStatic && Modifier.isStatic(method.getModifiers())) { 538 if (matchLevel < 4) { 539 matchLevel = 4; 540 missException = new MissingAccessorException("Setter is static: " + method, matchLevel); 541 } 542 continue; 543 } 544 545 if (allowPrivate && !Modifier.isPublic(method.getModifiers())) { 546 setAccessible(method); 547 } 548 549 if (RecipeHelper.isInstance(methodParameterType, propertyValue)) { 550 // This setter requires no conversion, which means there can not be a conversion error. 551 // Therefore this setter is perferred and put a the head of the list 552 validSetters.addFirst(method); 553 } else { 554 validSetters.add(method); 555 } 556 } 557 558 } 559 560 if (!validSetters.isEmpty()) { 561 // remove duplicate methods (can happen with inheritance) 562 return new ArrayList<Method>(new LinkedHashSet<Method>(validSetters)); 563 } 564 565 if (missException != null) { 566 throw missException; 567 } else { 568 StringBuffer buffer = new StringBuffer("Unable to find a valid setter "); 569 if (propertyValue instanceof Recipe) { 570 buffer.append("for ").append(propertyValue == null ? "null" : propertyValue); 571 } else { 572 buffer.append("of type ").append(propertyValue == null ? "null" : propertyValue.getClass().getName()); 573 } 574 buffer.append(" in class ").append(typeClass.getName()); 575 throw new MissingAccessorException(buffer.toString(), -1); 576 } 577 } 578 579 public static ConstructorFactory findConstructor(Class typeClass, List<? extends Class<?>> parameterTypes, Set<Option> options) { 580 return findConstructor(typeClass, null, parameterTypes, null, options); 581 582 } 583 public static ConstructorFactory findConstructor(Class typeClass, List<String> parameterNames, List<? extends Class<?>> parameterTypes, Set<String> availableProperties, Set<Option> options) { 584 if (typeClass == null) throw new NullPointerException("typeClass is null"); 585 if (availableProperties == null) availableProperties = Collections.emptySet(); 586 if (options == null) options = EnumSet.noneOf(Option.class); 587 588 // 589 // verify that it is a class we can construct 590 if (!Modifier.isPublic(typeClass.getModifiers())) { 591 throw new ConstructionException("Class is not public: " + typeClass.getName()); 592 } 593 if (Modifier.isInterface(typeClass.getModifiers())) { 594 throw new ConstructionException("Class is an interface: " + typeClass.getName()); 595 } 596 if (Modifier.isAbstract(typeClass.getModifiers())) { 597 throw new ConstructionException("Class is abstract: " + typeClass.getName()); 598 } 599 600 // verify parameter names and types are the same length 601 if (parameterNames != null) { 602 if (parameterTypes == null) parameterTypes = Collections.nCopies(parameterNames.size(), null); 603 if (parameterNames.size() != parameterTypes.size()) { 604 throw new ConstructionException("Invalid ObjectRecipe: recipe has " + parameterNames.size() + 605 " parameter names and " + parameterTypes.size() + " parameter types"); 606 } 607 } else if (!options.contains(Option.NAMED_PARAMETERS)) { 608 // Named parameters are not supported and no explicit parameters were given, 609 // so we will only use the no-arg constructor 610 parameterNames = Collections.emptyList(); 611 parameterTypes = Collections.emptyList(); 612 } 613 614 615 // get all methods sorted so that the methods with the most constructor args are first 616 List<Constructor> constructors = new ArrayList<Constructor>(Arrays.asList(typeClass.getConstructors())); 617 constructors.addAll(Arrays.asList(typeClass.getDeclaredConstructors())); 618 Collections.sort(constructors, new Comparator<Constructor>() { 619 public int compare(Constructor constructor1, Constructor constructor2) { 620 return constructor2.getParameterTypes().length - constructor1.getParameterTypes().length; 621 } 622 }); 623 624 // as we check each constructor, we remember the closest invalid match so we can throw a nice exception to the user 625 int matchLevel = 0; 626 MissingFactoryMethodException missException = null; 627 628 boolean allowPrivate = options.contains(Option.PRIVATE_CONSTRUCTOR); 629 for (Constructor constructor : constructors) { 630 // if an explicit constructor is specified (via parameter types), look a constructor that matches 631 if (parameterTypes != null) { 632 if (constructor.getParameterTypes().length != parameterTypes.size()) { 633 if (matchLevel < 1) { 634 matchLevel = 1; 635 missException = new MissingFactoryMethodException("Constructor has " + constructor.getParameterTypes().length + " arugments " + 636 "but expected " + parameterTypes.size() + " arguments: " + constructor); 637 } 638 continue; 639 } 640 641 if (!isAssignableFrom(parameterTypes, Arrays.<Class<?>>asList(constructor.getParameterTypes()))) { 642 if (matchLevel < 2) { 643 matchLevel = 2; 644 missException = new MissingFactoryMethodException("Constructor has signature " + 645 "public static " + typeClass.getName() + toParameterList(constructor.getParameterTypes()) + 646 " but expected signature " + 647 "public static " + typeClass.getName() + toParameterList(parameterTypes)); 648 } 649 continue; 650 } 651 } else { 652 // Implicit constructor selection based on named constructor args 653 // 654 // Only consider methods where we can supply a value for all of the parameters 655 parameterNames = getParameterNames(constructor); 656 if (parameterNames == null || !availableProperties.containsAll(parameterNames)) { 657 continue; 658 } 659 } 660 661 if (Modifier.isAbstract(constructor.getModifiers())) { 662 if (matchLevel < 4) { 663 matchLevel = 4; 664 missException = new MissingFactoryMethodException("Constructor is abstract: " + constructor); 665 } 666 continue; 667 } 668 669 if (!allowPrivate && !Modifier.isPublic(constructor.getModifiers())) { 670 if (matchLevel < 5) { 671 matchLevel = 5; 672 missException = new MissingFactoryMethodException("Constructor is not public: " + constructor); 673 } 674 continue; 675 } 676 677 if (allowPrivate && !Modifier.isPublic(constructor.getModifiers())) { 678 setAccessible(constructor); 679 } 680 681 return new ConstructorFactory(constructor, parameterNames); 682 } 683 684 if (missException != null) { 685 throw missException; 686 } else { 687 StringBuffer buffer = new StringBuffer("Unable to find a valid constructor: "); 688 buffer.append("public void ").append(typeClass.getName()).append(toParameterList(parameterTypes)); 689 throw new ConstructionException(buffer.toString()); 690 } 691 } 692 693 public static StaticFactory findStaticFactory(Class typeClass, String factoryMethod, List<? extends Class<?>> parameterTypes, Set<Option> options) { 694 return findStaticFactory(typeClass, factoryMethod, null, parameterTypes, null, options); 695 } 696 697 public static StaticFactory findStaticFactory(Class typeClass, String factoryMethod, List<String> parameterNames, List<? extends Class<?>> parameterTypes, Set<String> allProperties, Set<Option> options) { 698 if (typeClass == null) throw new NullPointerException("typeClass is null"); 699 if (factoryMethod == null) throw new NullPointerException("name is null"); 700 if (factoryMethod.length() == 0) throw new IllegalArgumentException("name is an empty string"); 701 if (allProperties == null) allProperties = Collections.emptySet(); 702 if (options == null) options = EnumSet.noneOf(Option.class); 703 704 // 705 // verify that it is a class we can construct 706 if (!Modifier.isPublic(typeClass.getModifiers())) { 707 throw new ConstructionException("Class is not public: " + typeClass.getName()); 708 } 709 if (Modifier.isInterface(typeClass.getModifiers())) { 710 throw new ConstructionException("Class is an interface: " + typeClass.getName()); 711 } 712 713 // verify parameter names and types are the same length 714 if (parameterNames != null) { 715 if (parameterTypes == null) parameterTypes = Collections.nCopies(parameterNames.size(), null); 716 if (parameterNames.size() != parameterTypes.size()) { 717 throw new ConstructionException("Invalid ObjectRecipe: recipe has " + parameterNames.size() + 718 " parameter names and " + parameterTypes.size() + " parameter types"); 719 } 720 } else if (!options.contains(Option.NAMED_PARAMETERS)) { 721 // Named parameters are not supported and no explicit parameters were given, 722 // so we will only use the no-arg constructor 723 parameterNames = Collections.emptyList(); 724 parameterTypes = Collections.emptyList(); 725 } 726 727 // get all methods sorted so that the methods with the most constructor args are first 728 List<Method> methods = new ArrayList<Method>(Arrays.asList(typeClass.getMethods())); 729 methods.addAll(Arrays.asList(typeClass.getDeclaredMethods())); 730 Collections.sort(methods, new Comparator<Method>() { 731 public int compare(Method method2, Method method1) { 732 return method1.getParameterTypes().length - method2.getParameterTypes().length; 733 } 734 }); 735 736 737 // as we check each constructor, we remember the closest invalid match so we can throw a nice exception to the user 738 int matchLevel = 0; 739 MissingFactoryMethodException missException = null; 740 741 boolean allowPrivate = options.contains(Option.PRIVATE_FACTORY); 742 boolean caseInsesnitive = options.contains(Option.CASE_INSENSITIVE_FACTORY); 743 for (Method method : methods) { 744 // Only consider methods where the name matches 745 if (!method.getName().equals(factoryMethod) && (!caseInsesnitive || !method.getName().equalsIgnoreCase(method.getName()))) { 746 continue; 747 } 748 749 // if an explicit constructor is specified (via parameter types), look a constructor that matches 750 if (parameterTypes != null) { 751 if (method.getParameterTypes().length != parameterTypes.size()) { 752 if (matchLevel < 1) { 753 matchLevel = 1; 754 missException = new MissingFactoryMethodException("Static factory method has " + method.getParameterTypes().length + " arugments " + 755 "but expected " + parameterTypes.size() + " arguments: " + method); 756 } 757 continue; 758 } 759 760 if (!isAssignableFrom(parameterTypes, Arrays.asList(method.getParameterTypes()))) { 761 if (matchLevel < 2) { 762 matchLevel = 2; 763 missException = new MissingFactoryMethodException("Static factory method has signature " + 764 "public static " + typeClass.getName() + "." + factoryMethod + toParameterList(method.getParameterTypes()) + 765 " but expected signature " + 766 "public static " + typeClass.getName() + "." + factoryMethod + toParameterList(parameterTypes)); 767 } 768 continue; 769 } 770 } else { 771 // Implicit constructor selection based on named constructor args 772 // 773 // Only consider methods where we can supply a value for all of the parameters 774 parameterNames = getParameterNames(method); 775 if (parameterNames == null || !allProperties.containsAll(parameterNames)) { 776 continue; 777 } 778 } 779 780 if (method.getReturnType() == Void.TYPE) { 781 if (matchLevel < 3) { 782 matchLevel = 3; 783 missException = new MissingFactoryMethodException("Static factory method does not return a value: " + method); 784 } 785 continue; 786 } 787 788 if (Modifier.isAbstract(method.getModifiers())) { 789 if (matchLevel < 4) { 790 matchLevel = 4; 791 missException = new MissingFactoryMethodException("Static factory method is abstract: " + method); 792 } 793 continue; 794 } 795 796 if (!allowPrivate && !Modifier.isPublic(method.getModifiers())) { 797 if (matchLevel < 5) { 798 matchLevel = 5; 799 missException = new MissingFactoryMethodException("Static factory method is not public: " + method); 800 } 801 continue; 802 } 803 804 if (!Modifier.isStatic(method.getModifiers())) { 805 if (matchLevel < 6) { 806 matchLevel = 6; 807 missException = new MissingFactoryMethodException("Static factory method is not static: " + method); 808 } 809 continue; 810 } 811 812 if (allowPrivate && !Modifier.isPublic(method.getModifiers())) { 813 setAccessible(method); 814 } 815 816 return new StaticFactory(method, parameterNames); 817 } 818 819 if (missException != null) { 820 throw missException; 821 } else { 822 StringBuffer buffer = new StringBuffer("Unable to find a valid factory method: "); 823 buffer.append("public void ").append(typeClass.getName()).append("."); 824 buffer.append(factoryMethod).append(toParameterList(parameterTypes)); 825 throw new MissingFactoryMethodException(buffer.toString()); 826 } 827 } 828 829 public static Method findInstanceFactory(Class typeClass, String factoryMethod, Set<Option> options) { 830 if (typeClass == null) throw new NullPointerException("typeClass is null"); 831 if (factoryMethod == null) throw new NullPointerException("name is null"); 832 if (factoryMethod.length() == 0) throw new IllegalArgumentException("name is an empty string"); 833 if (options == null) options = EnumSet.noneOf(Option.class); 834 835 int matchLevel = 0; 836 MissingFactoryMethodException missException = null; 837 838 boolean allowPrivate = options.contains(Option.PRIVATE_FACTORY); 839 boolean caseInsesnitive = options.contains(Option.CASE_INSENSITIVE_FACTORY); 840 841 List<Method> methods = new ArrayList<Method>(Arrays.asList(typeClass.getMethods())); 842 methods.addAll(Arrays.asList(typeClass.getDeclaredMethods())); 843 for (Method method : methods) { 844 if (method.getName().equals(factoryMethod) || (caseInsesnitive && method.getName().equalsIgnoreCase(method.getName()))) { 845 if (Modifier.isStatic(method.getModifiers())) { 846 if (matchLevel < 1) { 847 matchLevel = 1; 848 missException = new MissingFactoryMethodException("Instance factory method is static: " + method); 849 } 850 continue; 851 } 852 853 if (method.getParameterTypes().length != 0) { 854 if (matchLevel < 2) { 855 matchLevel = 2; 856 missException = new MissingFactoryMethodException("Instance factory method has signature " + 857 "public " + typeClass.getName() + "." + factoryMethod + toParameterList(method.getParameterTypes()) + 858 " but expected signature " + 859 "public " + typeClass.getName() + "." + factoryMethod + "()"); 860 } 861 continue; 862 } 863 864 if (method.getReturnType() == Void.TYPE) { 865 if (matchLevel < 3) { 866 matchLevel = 3; 867 missException = new MissingFactoryMethodException("Instance factory method does not return a value: " + method); 868 } 869 continue; 870 } 871 872 if (Modifier.isAbstract(method.getModifiers())) { 873 if (matchLevel < 4) { 874 matchLevel = 4; 875 missException = new MissingFactoryMethodException("Instance factory method is abstract: " + method); 876 } 877 continue; 878 } 879 880 if (!allowPrivate && !Modifier.isPublic(method.getModifiers())) { 881 if (matchLevel < 5) { 882 matchLevel = 5; 883 missException = new MissingFactoryMethodException("Instance factory method is not public: " + method); 884 } 885 continue; 886 } 887 888 if (allowPrivate && !Modifier.isPublic(method.getModifiers())) { 889 setAccessible(method); 890 } 891 892 return method; 893 } 894 } 895 896 if (missException != null) { 897 throw missException; 898 } else { 899 StringBuffer buffer = new StringBuffer("Unable to find a valid factory method: "); 900 buffer.append("public void ").append(typeClass.getName()).append("."); 901 buffer.append(factoryMethod).append("()"); 902 throw new MissingFactoryMethodException(buffer.toString()); 903 } 904 } 905 906 public static List<String> getParameterNames(Constructor<?> constructor) { 907 // use reflection to get Java6 ConstructorParameter annotation value 908 try { 909 Class<? extends Annotation> constructorPropertiesClass = ClassLoader.getSystemClassLoader().loadClass("java.beans.ConstructorProperties").asSubclass(Annotation.class); 910 Annotation constructorProperties = constructor.getAnnotation(constructorPropertiesClass); 911 if (constructorProperties != null) { 912 String[] parameterNames = (String[]) constructorPropertiesClass.getMethod("value").invoke(constructorProperties); 913 if (parameterNames != null) { 914 return Arrays.asList(parameterNames); 915 } 916 } 917 } catch (Throwable e) { 918 } 919 920 ParameterNames parameterNames = constructor.getAnnotation(ParameterNames.class); 921 if (parameterNames != null && parameterNames.value() != null) { 922 return Arrays.asList(parameterNames.value()); 923 } 924 if (ParameterLoader.PARAMETER_NAME_LOADER != null) { 925 return ParameterLoader.PARAMETER_NAME_LOADER.get(constructor); 926 } 927 return null; 928 } 929 930 public static List<String> getParameterNames(Method method) { 931 ParameterNames parameterNames = method.getAnnotation(ParameterNames.class); 932 if (parameterNames != null && parameterNames.value() != null) { 933 return Arrays.asList(parameterNames.value()); 934 } 935 if (ParameterLoader.PARAMETER_NAME_LOADER != null) { 936 return ParameterLoader.PARAMETER_NAME_LOADER.get(method); 937 } 938 return null; 939 } 940 941 public static interface Factory { 942 List<String> getParameterNames(); 943 944 List<Type> getParameterTypes(); 945 946 Object create(Object... parameters) throws ConstructionException; 947 } 948 949 public static class ConstructorFactory implements Factory { 950 private Constructor constructor; 951 private List<String> parameterNames; 952 953 public ConstructorFactory(Constructor constructor, List<String> parameterNames) { 954 if (constructor == null) throw new NullPointerException("constructor is null"); 955 if (parameterNames == null) throw new NullPointerException("parameterNames is null"); 956 this.constructor = constructor; 957 this.parameterNames = parameterNames; 958 } 959 960 public List<String> getParameterNames() { 961 return parameterNames; 962 } 963 964 public List<Type> getParameterTypes() { 965 return new ArrayList<Type>(Arrays.asList(constructor.getGenericParameterTypes())); 966 } 967 968 public Object create(Object... parameters) throws ConstructionException { 969 // create the instance 970 try { 971 Object instance = constructor.newInstance(parameters); 972 return instance; 973 } catch (Exception e) { 974 Throwable t = e; 975 if (e instanceof InvocationTargetException) { 976 InvocationTargetException invocationTargetException = (InvocationTargetException) e; 977 if (invocationTargetException.getCause() != null) { 978 t = invocationTargetException.getCause(); 979 } 980 } 981 throw new ConstructionException("Error invoking constructor: " + constructor, t); 982 } 983 } 984 } 985 986 public static class StaticFactory implements Factory { 987 private Method staticFactory; 988 private List<String> parameterNames; 989 990 public StaticFactory(Method staticFactory, List<String> parameterNames) { 991 this.staticFactory = staticFactory; 992 this.parameterNames = parameterNames; 993 } 994 995 public List<String> getParameterNames() { 996 if (parameterNames == null) { 997 throw new ConstructionException("InstanceFactory has not been initialized"); 998 } 999 1000 return parameterNames; 1001 } 1002 1003 public List<Type> getParameterTypes() { 1004 return new ArrayList<Type>(Arrays.asList(staticFactory.getGenericParameterTypes())); 1005 } 1006 1007 public Object create(Object... parameters) throws ConstructionException { 1008 try { 1009 Object instance = staticFactory.invoke(null, parameters); 1010 return instance; 1011 } catch (Exception e) { 1012 Throwable t = e; 1013 if (e instanceof InvocationTargetException) { 1014 InvocationTargetException invocationTargetException = (InvocationTargetException) e; 1015 if (invocationTargetException.getCause() != null) { 1016 t = invocationTargetException.getCause(); 1017 } 1018 } 1019 throw new ConstructionException("Error invoking factory method: " + staticFactory, t); 1020 } 1021 } 1022 } 1023 1024 private static void setAccessible(final AccessibleObject accessibleObject) { 1025 accessibleObject.setAccessible(true); 1026 } 1027 1028 private static String toParameterList(Class<?>[] parameterTypes) { 1029 return toParameterList(parameterTypes != null ? Arrays.asList(parameterTypes) : null); 1030 } 1031 1032 private static String toParameterList(List<? extends Class<?>> parameterTypes) { 1033 StringBuffer buffer = new StringBuffer(); 1034 buffer.append("("); 1035 if (parameterTypes != null) { 1036 for (int i = 0; i < parameterTypes.size(); i++) { 1037 Class type = parameterTypes.get(i); 1038 if (i > 0) buffer.append(", "); 1039 buffer.append(type.getName()); 1040 } 1041 } else { 1042 buffer.append("..."); 1043 } 1044 buffer.append(")"); 1045 return buffer.toString(); 1046 } 1047}