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