001 /*--------------------------------------------------------------------------+ 002 $Id: ReflectionUtils.java 26268 2010-02-18 10:44:30Z juergens $ 003 | | 004 | Copyright 2005-2010 Technische Universitaet Muenchen | 005 | | 006 | Licensed under the Apache License, Version 2.0 (the "License"); | 007 | you may not use this file except in compliance with the License. | 008 | 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 edu.tum.cs.commons.reflect; 019 020 import java.awt.Color; 021 import java.io.DataInputStream; 022 import java.io.File; 023 import java.io.IOException; 024 import java.io.InputStream; 025 import java.lang.reflect.Constructor; 026 import java.lang.reflect.InvocationTargetException; 027 import java.lang.reflect.Method; 028 import java.util.ArrayList; 029 import java.util.Collection; 030 import java.util.Collections; 031 import java.util.Enumeration; 032 import java.util.HashMap; 033 import java.util.LinkedList; 034 import java.util.List; 035 import java.util.Map; 036 import java.util.Queue; 037 import java.util.jar.JarEntry; 038 import java.util.jar.JarFile; 039 040 import edu.tum.cs.commons.color.ColorUtils; 041 import edu.tum.cs.commons.enums.EnumUtils; 042 import edu.tum.cs.commons.string.StringUtils; 043 import edu.tum.cs.commons.version.Version; 044 045 /** 046 * This class provides utility methods for reflection purposes. In particular it 047 * provides access to {@link FormalParameter}. 048 * 049 * @author Florian Deissenboeck 050 * @author Benjamin Hummel 051 * @author $Author: juergens $ 052 * @version $Rev: 26268 $ 053 * @levd.rating GREEN Hash: AD094410F95DFE6BB4CB12A5DC8A5F27 054 */ 055 public class ReflectionUtils { 056 057 /** To compare formal parameters. */ 058 private static FormalParameterComparator comparator = new FormalParameterComparator(); 059 060 /** 061 * Convert a String to an Object of the provided type. It supports 062 * conversion to primitive types and simple tests (char: use first character 063 * of string, boolean: test for values "true", "on", "1", "yes"). Enums are 064 * handled by the {@link EnumUtils#valueOfIgnoreCase(Class, String)} method. 065 * Otherwise it is checked if the target type has a constructor that takes a 066 * single string and it is invoked. For all other cases an exception is 067 * thrown, as no conversion is possible. 068 * 069 * <i>Maintainer note</i>: Make sure this method is changed in accordance 070 * with method {@link #isConvertibleFromString(Class)} 071 * 072 * @see #convertString(String, Class) 073 * 074 * @param value 075 * the string to be converted. 076 * @param targetType 077 * the type of the resulting object. 078 * @return the converted object. 079 * @throws TypeConversionException 080 * in the case that no conversion could be performed. 081 * @see #isConvertibleFromString(Class) 082 * 083 */ 084 @SuppressWarnings("unchecked") 085 public static <T> T convertString(String value, Class<T> targetType) 086 throws TypeConversionException { 087 088 // value must be provided 089 if (value == null) { 090 if (String.class.equals(targetType)) { 091 return (T) StringUtils.EMPTY_STRING; 092 } 093 throw new TypeConversionException( 094 "Null value can't be converted to type '" 095 + targetType.getName() + "'."); 096 } 097 098 if (targetType.equals(Object.class) || targetType.equals(String.class)) { 099 return (T) value; 100 } 101 if (targetType.isPrimitive() 102 || EJavaPrimitive.isWrapperType(targetType)) { 103 return convertPrimitive(value, targetType); 104 } 105 if (targetType.isEnum()) { 106 // we checked manually before 107 Object result = EnumUtils.valueOfIgnoreCase((Class) targetType, 108 value); 109 if (result == null) { 110 throw new TypeConversionException("'" + value 111 + "' is no valid value for enum " 112 + targetType.getName()); 113 } 114 return (T) result; 115 116 } 117 if (targetType.equals(Color.class)) { 118 Color result = ColorUtils.fromString(value); 119 if (result == null) { 120 throw new TypeConversionException("'" + value 121 + "' is not a valid color!"); 122 } 123 return (T) result; 124 } 125 126 // Check if the target type has a constructor taking a single string. 127 try { 128 Constructor<T> c = targetType.getConstructor(String.class); 129 return c.newInstance(value); 130 } catch (Exception e) { 131 throw new TypeConversionException( 132 "No constructor taking one String argument found for type '" 133 + targetType + "' (" + e.getMessage() + ")", e); 134 } 135 136 } 137 138 /** 139 * This method checks if the provided type can be converted from a string. 140 * With respect to {@link #convertString(String, Class)} the semantics are 141 * the following: If this method returns <code>true</code> a particular 142 * string <i>may</i> be convertible to the target type. If this method 143 * returns <code>false</code>, a call to 144 * {@link #convertString(String, Class)} is guaranteed to result in a 145 * {@link TypeConversionException}. If a call to 146 * {@link #convertString(String, Class)} does not result in an exception, a 147 * call to this method is guaranteed to return <code>true</code>. 148 * <p> 149 * <i>Maintainer note</i>: Make sure this method is change in accordance 150 * with method {@link #convertString(String, Class)} 151 * 152 * @see #convertString(String, Class) 153 */ 154 public static boolean isConvertibleFromString(Class<?> targetType) { 155 156 if (targetType.equals(Object.class) || targetType.equals(String.class)) { 157 return true; 158 } 159 if (targetType.isPrimitive() 160 || EJavaPrimitive.isWrapperType(targetType)) { 161 return true; 162 } 163 if (targetType.isEnum()) { 164 return true; 165 166 } 167 if (targetType.equals(Color.class)) { 168 return true; 169 } 170 171 try { 172 targetType.getConstructor(String.class); 173 return true; 174 } catch (SecurityException e) { 175 // case is handled at method end 176 } catch (NoSuchMethodException e) { 177 // case is handled at method end 178 } 179 180 return false; 181 } 182 183 /** 184 * Obtain array of formal parameters for a method. 185 * 186 * @see FormalParameter 187 */ 188 public static FormalParameter[] getFormalParameters(Method method) { 189 190 int parameterCount = method.getParameterTypes().length; 191 192 FormalParameter[] parameters = new FormalParameter[parameterCount]; 193 194 for (int i = 0; i < parameterCount; i++) { 195 parameters[i] = new FormalParameter(method, i); 196 } 197 198 return parameters; 199 } 200 201 /** 202 * Get super class list of a class. 203 * 204 * @param clazz 205 * the class to start traversal from 206 * @return a list of super class where the direct super class of the 207 * provided class is the first member of the list. <br> 208 * For {@link Object}, primitives and interfaces this returns an 209 * empty list. <br> 210 * For arrays this returns a list containing only {@link Object}. <br> 211 * For enums this returns a list containing {@link Enum} and 212 * {@link Object} 213 */ 214 public static List<Class<?>> getSuperClasses(Class<?> clazz) { 215 ArrayList<Class<?>> superClasses = new ArrayList<Class<?>>(); 216 findSuperClasses(clazz, superClasses); 217 return superClasses; 218 } 219 220 /** 221 * Invoke a method with parameters. 222 * 223 * @param method 224 * the method to invoke 225 * @param object 226 * the object the underlying method is invoked from 227 * @param parameterMap 228 * this maps from the formal parameter of the method to the 229 * parameter value 230 * @return the result of dispatching the method 231 * @throws IllegalArgumentException 232 * if the method is an instance method and the specified object 233 * argument is not an instance of the class or interface 234 * declaring the underlying method (or of a subclass or 235 * implementor thereof); if the number of actual and formal 236 * parameters differ; if an unwrapping conversion for primitive 237 * arguments fails; or if, after possible unwrapping, a 238 * parameter value cannot be converted to the corresponding 239 * formal parameter type by a method invocation conversion; if 240 * formal parameters belong to different methods. 241 * 242 * @throws IllegalAccessException 243 * if this Method object enforces Java language access control 244 * and the underlying method is inaccessible. 245 * @throws InvocationTargetException 246 * if the underlying method throws an exception. 247 * @throws NullPointerException 248 * if the specified object is null and the method is an instance 249 * method. 250 * @throws ExceptionInInitializerError 251 * if the initialization provoked by this method fails. 252 */ 253 public static Object invoke(Method method, Object object, 254 Map<FormalParameter, Object> parameterMap) 255 throws IllegalArgumentException, IllegalAccessException, 256 InvocationTargetException { 257 258 for (FormalParameter formalParameter : parameterMap.keySet()) { 259 if (!formalParameter.getMethod().equals(method)) { 260 throw new IllegalArgumentException( 261 "Parameters must belong to method."); 262 } 263 } 264 265 Object[] parameters = obtainParameters(parameterMap); 266 267 return method.invoke(object, parameters); 268 269 } 270 271 /** 272 * Check whether an Object of the source type can be used instead of an 273 * Object of the target type. This method is required, as the 274 * {@link Class#isAssignableFrom(java.lang.Class)} does not handle primitive 275 * types. 276 * 277 * @param source 278 * type of the source object 279 * @param target 280 * type of the target object 281 * @return whether an assignment would be possible. 282 */ 283 public static boolean isAssignable(Class<?> source, Class<?> target) { 284 return resolvePrimitiveClass(target).isAssignableFrom( 285 resolvePrimitiveClass(source)); 286 } 287 288 /** 289 * Returns the wrapper class type for a primitive type (e.g. 290 * <code>Integer</code> for an <code>int</code>). If the given class is not 291 * a primitive, the class itself is returned. 292 * 293 * @param clazz 294 * the class. 295 * @return the corresponding class type. 296 */ 297 public static Class<?> resolvePrimitiveClass(Class<?> clazz) { 298 if (!clazz.isPrimitive()) { 299 return clazz; 300 } 301 302 EJavaPrimitive primitive = EJavaPrimitive.getForPrimitiveClass(clazz); 303 if (primitive == null) { 304 throw new IllegalStateException("Did Java get a new primitive? " 305 + clazz.getName()); 306 } 307 return primitive.getWrapperClass(); 308 } 309 310 /** 311 * Convert a String to an Object of the provided type. This only works for 312 * primitive types and wrapper types. 313 * 314 * @param value 315 * the string to be converted. 316 * @param targetType 317 * the type of the resulting object. 318 * @return the converted object. 319 * @throws TypeConversionException 320 * in the case that no conversion could be performed. 321 */ 322 @SuppressWarnings("unchecked") 323 /* package */static <T> T convertPrimitive(String value, Class<T> targetType) 324 throws TypeConversionException { 325 326 EJavaPrimitive primitive = EJavaPrimitive 327 .getForPrimitiveOrWrapperClass(targetType); 328 if (primitive == null) { 329 throw new IllegalArgumentException("Type '" + targetType.getName() 330 + "' is not a primitive type!"); 331 } 332 333 try { 334 335 switch (primitive) { 336 case BOOLEAN: 337 boolean b = "1".equalsIgnoreCase(value) 338 || "true".equalsIgnoreCase(value) 339 || "on".equalsIgnoreCase(value) 340 || "yes".equalsIgnoreCase(value); 341 return (T) Boolean.valueOf(b); 342 343 case CHAR: 344 return (T) Character.valueOf(value.charAt(0)); 345 346 case BYTE: 347 return (T) Byte.valueOf(value); 348 349 case SHORT: 350 return (T) Short.valueOf(value); 351 352 case INT: 353 return (T) Integer.valueOf(value); 354 355 case LONG: 356 return (T) Long.valueOf(value); 357 358 case FLOAT: 359 return (T) Float.valueOf(value); 360 361 case DOUBLE: 362 return (T) Double.valueOf(value); 363 364 default: 365 throw new TypeConversionException("No conversion possible for " 366 + primitive); 367 } 368 369 } catch (NumberFormatException e) { 370 throw new TypeConversionException("Value'" + value 371 + "' can't be converted to type '" + targetType.getName() 372 + "' (" + e.getMessage() + ").", e); 373 } 374 } 375 376 /** 377 * Resolves the class object for a type name. Type name can be a primitive. 378 * For resolution, {@link Class#forName(String)} is used, that uses the 379 * caller's class loader. 380 * <p> 381 * While method <code>Class.forName(...)</code> resolves fully qualified 382 * names, it does not resolve primitives, e.g. "java.lang.Boolean" can be 383 * resolved but "boolean" cannot. 384 * 385 * @param typeName 386 * name of the type. For primitives case is ignored. 387 * 388 * @throws ClassNotFoundException 389 * if the typeName neither resolves to a primitive, nor to a 390 * known class. 391 */ 392 public static Class<?> resolveType(String typeName) 393 throws ClassNotFoundException { 394 return resolveType(typeName, null); 395 } 396 397 /** 398 * Resolves the class object for a type name. Type name can be a primitive. 399 * For resolution, the given class loader is used. 400 * <p> 401 * While method <code>Class.forName(...)</code> resolves fully qualified 402 * names, it does not resolve primitives, e.g. "java.lang.Boolean" can be 403 * resolved but "boolean" cannot. 404 * 405 * @param typeName 406 * name of the type. For primitives case is ignored. 407 * 408 * @param classLoader 409 * the class loader used for loading the class. If this is null, 410 * the caller class loader is used. 411 * 412 * @throws ClassNotFoundException 413 * if the typeName neither resolves to a primitive, nor to a 414 * known class. 415 */ 416 public static Class<?> resolveType(String typeName, ClassLoader classLoader) 417 throws ClassNotFoundException { 418 419 EJavaPrimitive primitive = EJavaPrimitive 420 .getPrimitiveIgnoreCase(typeName); 421 422 if (primitive != null) { 423 return primitive.getClassObject(); 424 } 425 426 if (classLoader == null) { 427 return Class.forName(typeName); 428 } 429 return Class.forName(typeName, true, classLoader); 430 } 431 432 /** 433 * Recursively add super classes to a list. 434 * 435 * @param clazz 436 * class to start from 437 * @param superClasses 438 * list to store super classes. 439 */ 440 private static void findSuperClasses(Class<?> clazz, 441 List<Class<?>> superClasses) { 442 Class<?> superClass = clazz.getSuperclass(); 443 if (superClass == null) { 444 return; 445 } 446 superClasses.add(superClass); 447 findSuperClasses(superClass, superClasses); 448 } 449 450 /** 451 * Obtain parameter array from parameter map. 452 */ 453 private static Object[] obtainParameters( 454 Map<FormalParameter, Object> parameterMap) { 455 456 ArrayList<FormalParameter> formalParameters = new ArrayList<FormalParameter>( 457 parameterMap.keySet()); 458 459 Collections.sort(formalParameters, comparator); 460 461 Object[] result = new Object[formalParameters.size()]; 462 463 for (int i = 0; i < formalParameters.size(); i++) { 464 result[i] = parameterMap.get(formalParameters.get(i)); 465 } 466 467 return result; 468 } 469 470 /** 471 * Obtain the return type of method. This method deals with bridge methods 472 * introduced by generics. This works for methods without parameters only. 473 * 474 * @param clazz 475 * the class 476 * @param methodName 477 * the name of the method. 478 * @return the return type 479 * @throws NoSuchMethodException 480 * if the class doesn't contain the desired method 481 */ 482 public static Class<?> obtainMethodReturnType(Class<?> clazz, 483 String methodName) throws NoSuchMethodException { 484 485 // due to the potential presense of bridge methods we can't use 486 // Clazz.getMethod() and have to iterate over all methods. 487 for (Method method : clazz.getMethods()) { 488 if (isValid(method, methodName)) { 489 return method.getReturnType(); 490 } 491 } 492 // method not found 493 throw new NoSuchMethodException("Class " + clazz.getName() 494 + " doesn't have parameterless method named " + methodName); 495 } 496 497 /** 498 * Obtain the generic return type of method. This method deals with the gory 499 * details of bridge methods and generics. This works for methods without 500 * parameters only. This doesn't work for interfaces, arrays and enums. 501 * 502 * @param clazz 503 * the class 504 * @param methodName 505 * the name of the method. 506 * @return the return type 507 * @throws NoSuchMethodException 508 * if the class doesn't contain the desired method 509 */ 510 public static Class<?> obtainGenericMethodReturnType(Class<?> clazz, 511 String methodName) throws NoSuchMethodException { 512 513 if (clazz.isArray() || clazz.isEnum()) { 514 throw new IllegalArgumentException( 515 "Doesn't work for arrays and enums."); 516 } 517 if (clazz.getTypeParameters().length != 0) { 518 throw new IllegalArgumentException( 519 "Doesn't work for generic classes."); 520 } 521 522 for (Method method : clazz.getMethods()) { 523 if (isValid(method, methodName)) { 524 return new GenericTypeResolver(clazz).resolveGenericType(method 525 .getGenericReturnType()); 526 } 527 } 528 529 // method not found 530 throw new NoSuchMethodException("Class " + clazz.getName() 531 + " doesn't have parameterless method named " + methodName); 532 } 533 534 /** 535 * Tests if a method has the correct name, no parameters and is no bridge 536 * method. 537 */ 538 private static boolean isValid(Method method, String methodName) { 539 return method.getName().equals(methodName) 540 && method.getParameterTypes().length == 0 && !method.isBridge(); 541 } 542 543 /** 544 * Returns the value from the map, whose key is the best match for the given 545 * class. The best match is defined by the first match occurring in a breath 546 * first search of the inheritance tree, where the base class is always 547 * visited before the implemented interfaces. Interfaces are traversed in 548 * the order they are defined in the source file. The only exception is 549 * {@link Object}, which is considered only as the very last option. 550 * <p> 551 * As this lookup can be expensive (reflective iteration over the entire 552 * inheritance tree) the results should be cached if multiple lookups for 553 * the same class are expected. 554 * 555 * 556 * @param clazz 557 * the class being looked up. 558 * @param classMap 559 * the map to perform the lookup in. 560 * @return the best match found or <code>null</code> if no matching entry 561 * was found. Note that <code>null</code> will also be returned if 562 * the entry for the best matching class was <code>null</code>. 563 */ 564 public static <T> T performNearestClassLookup(Class<?> clazz, 565 Map<Class<?>, T> classMap) { 566 Queue<Class<?>> q = new LinkedList<Class<?>>(); 567 q.add(clazz); 568 569 while (!q.isEmpty()) { 570 Class<?> current = q.poll(); 571 if (classMap.containsKey(current)) { 572 return classMap.get(current); 573 } 574 575 Class<?> superClass = current.getSuperclass(); 576 if (superClass != null && superClass != Object.class) { 577 q.add(superClass); 578 } 579 580 for (Class<?> iface : current.getInterfaces()) { 581 q.add(iface); 582 } 583 } 584 return classMap.get(Object.class); 585 } 586 587 /** 588 * Returns whether the given object is an instance of at least one of the 589 * given classes. 590 */ 591 public static boolean isInstanceOfAny(Object o, Class<?>... classes) { 592 for (Class<?> c : classes) { 593 if (c.isInstance(o)) { 594 return true; 595 } 596 } 597 return false; 598 } 599 600 /** 601 * Returns whether the given object is an instance of all of the given 602 * classes. 603 */ 604 public static boolean isInstanceOfAll(Object o, Class<?>... classes) { 605 for (Class<?> c : classes) { 606 if (!c.isInstance(o)) { 607 return false; 608 } 609 } 610 return true; 611 } 612 613 /** 614 * Returns the first object in the given collection which is an instance of 615 * the given class (or null otherwise). 616 */ 617 @SuppressWarnings("unchecked") 618 public static <T> T pickInstanceOf(Class<T> clazz, Collection<?> objects) { 619 for (Object o : objects) { 620 if (clazz.isInstance(o)) { 621 return (T) o; 622 } 623 } 624 return null; 625 } 626 627 /** 628 * Obtains the version of a Java class file. 629 * 630 * Class file versions (from 631 * http://thiamteck.blogspot.com/2007/11/determine- 632 * java-class-file-version.html): 633 * 634 * <pre> 635 * major minor Java Version 636 * 45 3 1.0 637 * 45 3 1.1 638 * 46 0 1.2 639 * 47 0 1.3 640 * 48 0 1.4 641 * 49 0 1.5 642 * 50 0 1.6 643 * </pre> 644 * 645 * @param inputStream 646 * stream to read class file from. 647 * @return the class file version or <code>null</code> if stream does not 648 * contain a class file. 649 * @throws IOException 650 * if an IO problem occurs. 651 */ 652 public static Version obtainClassFileVersion(InputStream inputStream) 653 throws IOException { 654 DataInputStream classfile = new DataInputStream(inputStream); 655 int magic = classfile.readInt(); 656 if (magic != 0xcafebabe) { 657 return null; 658 } 659 int minorVersion = classfile.readUnsignedShort(); 660 int majorVersion = classfile.readUnsignedShort(); 661 662 return new Version(majorVersion, minorVersion); 663 } 664 665 /** 666 * This method extracts the class file version from each class file in the 667 * provided jar. 668 * 669 * @return the result maps from the class file to its version. 670 */ 671 public static HashMap<String, Version> getClassFileVersions(File jarFile) 672 throws IOException { 673 674 HashMap<String, Version> result = new HashMap<String, Version>(); 675 676 JarFile jar = new JarFile(jarFile); 677 Enumeration<JarEntry> entries = jar.entries(); 678 679 while (entries.hasMoreElements()) { 680 JarEntry entry = entries.nextElement(); 681 if (!entry.isDirectory() && entry.getName().endsWith(".class")) { 682 InputStream entryStream = jar.getInputStream(entry); 683 Version version = obtainClassFileVersion(entryStream); 684 result.put(entry.getName(), version); 685 entryStream.close(); 686 } 687 } 688 689 jar.close(); 690 691 return result; 692 } 693 694 /** 695 * Creates a list that contains only the types that are instances of a 696 * specified type from the objects of an input list. The input list is not 697 * modified. 698 * 699 * @param objects 700 * List of objects that gets filtered 701 * 702 * @param type 703 * target type whose instances are returned 704 */ 705 @SuppressWarnings("unchecked") 706 public static <T> List<T> listInstances(List<?> objects, Class<T> type) { 707 List<T> filtered = new ArrayList<T>(); 708 709 for (Object object : objects) { 710 if (type.isInstance(object)) { 711 filtered.add((T) object); 712 } 713 } 714 715 return filtered; 716 } 717 }