001/** 002 * Copyright 2010-2013 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.common.util; 017 018import static com.google.common.base.Preconditions.checkArgument; 019 020import java.lang.annotation.Annotation; 021import java.lang.reflect.Field; 022import java.lang.reflect.InvocationTargetException; 023import java.lang.reflect.Modifier; 024import java.lang.reflect.ParameterizedType; 025import java.lang.reflect.Type; 026import java.util.ArrayList; 027import java.util.Collection; 028import java.util.List; 029import java.util.Map; 030import java.util.Set; 031 032import org.apache.commons.beanutils.BeanUtils; 033import org.springframework.util.MethodInvoker; 034 035import com.google.common.base.Optional; 036import com.google.common.base.Preconditions; 037import com.google.common.collect.ImmutableCollection; 038import com.google.common.collect.ImmutableList; 039import com.google.common.collect.ImmutableMap; 040import com.google.common.collect.ImmutableSet; 041import com.google.common.collect.Lists; 042import com.google.common.collect.Maps; 043import com.google.common.collect.Sets; 044 045public class ReflectionUtils extends org.springframework.util.ReflectionUtils { 046 047 /** 048 * Returns the path from {@code java.lang.Object}, to {@code type}. The last element in the list is {@code type}. 049 */ 050 public static List<Class<?>> getTypeHierarchy(Class<?> type) { 051 List<Class<?>> list = Lists.newArrayList(); 052 if (type.getSuperclass() != null) { 053 list.addAll(getTypeHierarchy(type.getSuperclass())); 054 } 055 list.add(type); 056 return list; 057 } 058 059 /** 060 * Return a map containing every parameterized interface implemented by {@code type}, includes interfaces implemented by super classes 061 */ 062 public static Map<Class<?>, ParameterizedType> getAllParameterizedInterfaces(Class<?> type) { 063 List<Type> list = getAllGenericInterfaces(type); 064 Map<Class<?>, ParameterizedType> map = Maps.newHashMap(); 065 for (Type element : list) { 066 if (element instanceof ParameterizedType) { 067 ParameterizedType pType = (ParameterizedType) element; 068 Class<?> interfaceClass = (Class<?>) pType.getRawType(); 069 map.put(interfaceClass, pType); 070 } 071 } 072 return map; 073 } 074 075 /** 076 * Return a list containing every interface implemented by {@code type}, includes interfaces implemented by super classes. Type objects returned accurately reflect the actual 077 * type parameters used in the source code. 078 */ 079 public static List<Type> getAllGenericInterfaces(Class<?> type) { 080 List<Class<?>> path = getTypeHierarchy(type); 081 List<Type> list = Lists.newArrayList(); 082 for (Class<?> element : path) { 083 Type[] interfaces = element.getGenericInterfaces(); 084 list.addAll(ImmutableList.copyOf(interfaces)); 085 } 086 return list; 087 } 088 089 /** 090 * Return true if this class is declared as final 091 */ 092 public static boolean isFinal(Class<?> type) { 093 return Modifier.isFinal(type.getModifiers()); 094 } 095 096 /** 097 * Return true if this field is declared as final 098 */ 099 public static boolean isFinal(Field field) { 100 return Modifier.isFinal(field.getModifiers()); 101 } 102 103 /** 104 * Return true if this class is an immutable Guava collection 105 */ 106 public static boolean isImmutableGuavaCollection(Class<?> type) { 107 return ImmutableCollection.class.isAssignableFrom(type); 108 } 109 110 /** 111 * Return true if this class is an immutable Guava map 112 */ 113 public static boolean isImmutableGuavaMap(Class<?> type) { 114 return ImmutableMap.class.isAssignableFrom(type); 115 } 116 117 /** 118 * Return true if this field is a {@code java.util.Collection} 119 */ 120 public static boolean isCollection(Field field) { 121 return Collection.class.isAssignableFrom(field.getType()); 122 } 123 124 /** 125 * Return true if this field is a {@code java.util.Collection<String>} 126 */ 127 public static boolean isStringCollection(Field field) { 128 return isCollection(field) && hasMatchingParameterizedArgTypes(field, String.class); 129 } 130 131 /** 132 * Return true if this field is a {@code java.util.Map} 133 */ 134 public static boolean isMap(Field field) { 135 return Map.class.isAssignableFrom(field.getType()); 136 } 137 138 /** 139 * Return true if this field extends from {@code java.util.Map} and uses {@code String} for its keys 140 * 141 * <pre> 142 * Map<String,String> returns true 143 * Map<String,Object> returns true 144 * Map<String,Integer> returns true 145 * Map<Integer,String> returns false 146 * </pre> 147 */ 148 public static boolean isStringKeyedMap(Field field) { 149 return isMap(field) && hasMatchingParameterizedArgTypes(field, String.class); 150 } 151 152 /** 153 * Return true if this field is a {@code java.lang.String} 154 */ 155 public static boolean isString(Field field) { 156 // Safe to do this since java.lang.String is final 157 return field.getType() == String.class; 158 } 159 160 /** 161 * Return true if this field is a CharSequence 162 */ 163 public static boolean isCharSequence(Field field) { 164 return isCharSequence(field.getType()); 165 } 166 167 /** 168 * Return true if this field is a CharSequence 169 */ 170 public static boolean isCharSequence(Class<?> type) { 171 return CharSequence.class.isAssignableFrom(type); 172 } 173 174 /** 175 * Return true iff this field is a Guava {@code com.google.common.base.Optional} 176 */ 177 public static boolean isOptional(Field field) { 178 return Optional.class.isAssignableFrom(field.getType()); 179 } 180 181 /** 182 * Return true iff this field is a Guava {@code com.google.common.base.Optional<String>} 183 */ 184 public static boolean isOptionalString(Field field) { 185 return isOptional(field) && hasMatchingParameterizedArgTypes(field, String.class); 186 } 187 188 /** 189 * <p> 190 * Return true if this field is a generic whose argument types match {@code expectedTypeArguments} 191 * </p> 192 * 193 * For example to match a field declared as {@code Collection<String>} 194 * 195 * <pre> 196 * hasMatchingParameterizedArgTypes(myField, String.class) 197 * </pre> 198 */ 199 public static boolean hasMatchingParameterizedArgTypes(Field field, Class<?>... expectedTypeArguments) { 200 Type genericType = field.getGenericType(); 201 if (genericType instanceof ParameterizedType) { 202 ParameterizedType parameterizedType = (ParameterizedType) genericType; 203 return hasMatchingActualTypeArguments(parameterizedType, expectedTypeArguments); 204 } else { 205 return false; 206 } 207 } 208 209 protected static boolean hasMatchingActualTypeArguments(ParameterizedType type, Class<?>... expectedTypeArguments) { 210 Type[] actualTypeArguments = type.getActualTypeArguments(); 211 for (int i = 0; i < expectedTypeArguments.length; i++) { 212 Class<?> expectedTypeArgument = expectedTypeArguments[i]; 213 if (i >= actualTypeArguments.length) { 214 return false; 215 } 216 Class<?> actualTypeArgument = (Class<?>) actualTypeArguments[i]; 217 if (actualTypeArgument != expectedTypeArgument) { 218 return false; 219 } 220 } 221 return true; 222 } 223 224 /** 225 * <p> 226 * Throw an exception unless {@code child} is the same as {@code parent} <b>OR</b> descends from {@code parent}. If {@code child} is a primitive type, throw an exception unless 227 * both {@code child} and {@code parent} are the exact same primitive type. 228 * </p> 229 * 230 * @see equalsOrDescendsFrom 231 * 232 * @throws IllegalArgumentException 233 * if {@code equalsOrDescendsFrom(child,parent)} returns {@code false} 234 */ 235 public static void validateIsSuperType(Class<?> superType, Class<?> type) { 236 boolean expression = isSuperType(superType, type); 237 Preconditions.checkArgument(expression, "[%s] must descend from (or be) [%s]", type.getCanonicalName(), superType.getCanonicalName()); 238 } 239 240 /** 241 * <p> 242 * Return true if {@code type} descends from {@code superType} <b>OR</b> is the same as {@code superType}. If {@code type} is a primitive type, return {@code true} only if both 243 * {@code type} and {@code superType} are the exact same primitive type. 244 * </p> 245 */ 246 public static boolean isSuperType(Class<?> superType, Class<?> type) { 247 return superType.isAssignableFrom(type); 248 } 249 250 /** 251 * 252 * @deprecated Use Annotations.get() instead 253 */ 254 @Deprecated 255 public static <T extends Annotation> Optional<T> getAnnotation(Class<?> type, Class<T> annotationClass) { 256 return Optional.fromNullable(type.getAnnotation(annotationClass)); 257 } 258 259 /** 260 * 261 * @deprecated Use Annotations.get() instead 262 */ 263 @Deprecated 264 public static <T extends Annotation> Optional<T> getAnnotation(Field field, Class<T> annotationClass) { 265 return Optional.fromNullable(field.getAnnotation(annotationClass)); 266 } 267 268 public static List<Class<?>> getDeclarationHierarchy(Class<?> type) { 269 List<Class<?>> hierarchy = new ArrayList<Class<?>>(); 270 Class<?> declaringClass = type.getDeclaringClass(); 271 if (declaringClass != null) { 272 hierarchy.addAll(getDeclarationHierarchy(declaringClass)); 273 } 274 hierarchy.add(type); 275 return hierarchy; 276 } 277 278 public static String getDeclarationPath(Class<?> type) { 279 List<Class<?>> hierarchy = getDeclarationHierarchy(type); 280 List<String> names = new ArrayList<String>(); 281 for (Class<?> element : hierarchy) { 282 names.add(element.getSimpleName()); 283 } 284 return CollectionUtils.getStringWithSeparator(names, "."); 285 } 286 287 /** 288 * Unconditionally attempt to get the value of this field on this bean. If the field is not accessible make it accessible, get the value, then revert the field back to being 289 * inaccessible. 290 */ 291 public static Optional<Object> get(Field field, Object instance) { 292 293 // Be thread safe 294 synchronized (field) { 295 296 // Preserve the original accessibility indicator 297 boolean accessible = field.isAccessible(); 298 299 // If it's not accessible, change it so it is 300 if (!accessible) { 301 field.setAccessible(true); 302 } 303 304 try { 305 // Attempt to get the value of this field on the instance 306 return Optional.fromNullable(field.get(instance)); 307 } catch (IllegalAccessException e) { 308 throw new IllegalStateException(e); 309 } finally { 310 // Always flip the accessible flag back to what it was before (if we need to) 311 if (!accessible) { 312 field.setAccessible(false); 313 } 314 } 315 } 316 } 317 318 /** 319 * Unconditionally attempt to set a value on this field of this instance. If the field is not accessible, make it accessible, set the value, then revert the field to being 320 * inaccessible. 321 */ 322 public static void set(Object instance, Field field, Object value) { 323 324 // Be thread safe 325 synchronized (field) { 326 327 // Preserve the original accessibility indicator 328 boolean accessible = field.isAccessible(); 329 330 // If it's not accessible, change it so it is 331 if (!accessible) { 332 field.setAccessible(true); 333 } 334 335 try { 336 // Attempt to set the value on this field of the instance 337 field.set(instance, value); 338 } catch (IllegalAccessException e) { 339 throw new IllegalStateException(e); 340 } finally { 341 // Always flip the accessible flag back to what it was before (if we need to) 342 if (!accessible) { 343 field.setAccessible(false); 344 } 345 } 346 } 347 } 348 349 /** 350 * Get fields declared directly on this type as an immutable set. 351 */ 352 public static Set<Field> getFields(Class<?> type) { 353 return ImmutableSet.copyOf(type.getDeclaredFields()); 354 } 355 356 /** 357 * Get fields for a given type with the option to include all inherited fields 358 * 359 * <p> 360 * NOTE: field.getName() is not necessarily unique for the elements in the set if includeInheritedFields is true 361 * </p> 362 */ 363 public static Set<Field> getFields(Class<?> type, boolean includeInheritedFields) { 364 if (includeInheritedFields) { 365 return getAllFields(type); 366 } else { 367 return getFields(type); 368 } 369 } 370 371 /** 372 * Convert the set of fields into a Map keyed by field name. If there are fields that contain duplicate names in the set, which one makes it into the map is undefined 373 * 374 * @deprecated use getNameMap(List) instead 375 */ 376 @Deprecated 377 public static Map<String, Field> getNameMap(Set<Field> fields) { 378 Map<String, Field> map = Maps.newHashMap(); 379 for (Field field : fields) { 380 map.put(field.getName(), field); 381 } 382 return map; 383 } 384 385 /** 386 * Convert the list of fields into a Map keyed by field name. If there are duplicate field names in the list, "last one in wins" 387 */ 388 public static Map<String, Field> getNameMap(List<Field> fields) { 389 Map<String, Field> map = Maps.newHashMap(); 390 for (Field field : fields) { 391 map.put(field.getName(), field); 392 } 393 return map; 394 } 395 396 /** 397 * Get a list of all fields contained anywhere in the type hierarchy keyed by field name. 398 * 399 * @throws IllegalArgumentException 400 * if {@code type} contains duplicate field names 401 */ 402 public static Map<String, Field> getUniqueFieldNames(Class<?> type) { 403 Set<Field> fields = getAllFields(type); 404 checkArgument(hasUniqueFieldNames(fields), "[%s] contains duplicate field names"); 405 return getNameMap(Lists.newArrayList(fields)); 406 } 407 408 /** 409 * Return the field corresponding 410 * 411 * @param type 412 * @param fieldNames 413 * @return 414 */ 415 public static Map<String, Field> getFields(Class<?> type, Set<String> fieldNames) { 416 Map<String, Field> fields = Maps.newHashMap(); 417 for (String fieldName : fieldNames) { 418 try { 419 fields.put(fieldName, type.getDeclaredField(fieldName)); 420 } catch (NoSuchFieldException e) { 421 throw new IllegalStateException(e); 422 } catch (SecurityException e) { 423 throw new IllegalStateException(e); 424 } 425 } 426 return fields; 427 } 428 429 /** 430 * <p> 431 * Recursively examine the type hierarchy and extract every field encountered anywhere in the hierarchy into an immutable set 432 * </p> 433 * 434 * <p> 435 * NOTE: field.getName() is not necessarily unique for the elements in the set 436 * </p> 437 */ 438 public static Set<Field> getAllFields(Class<?> type) { 439 Set<Field> fields = Sets.newHashSet(); 440 for (Class<?> c = type; c != null; c = c.getSuperclass()) { 441 Set<Field> set = getFields(c); 442 fields.addAll(set); 443 } 444 return ImmutableSet.copyOf(fields); 445 } 446 447 /** 448 * <p> 449 * Recursively examine the type hierarchy and extract every field encountered anywhere in the hierarchy into an immutable list 450 * </p> 451 * 452 * <p> 453 * NOTE: field.getName() is not necessarily unique for the elements in the list 454 * </p> 455 */ 456 public static List<Field> getAllFieldsList(Class<?> type) { 457 List<Field> fields = Lists.newArrayList(); 458 for (Class<?> c = type; c != null; c = c.getSuperclass()) { 459 Set<Field> set = getFields(c); 460 fields.addAll(set); 461 } 462 return ImmutableList.copyOf(Lists.reverse(fields)); 463 } 464 465 /** 466 * Return true if every single field in the recursive type hiearchy has a unique name, false otherwise 467 */ 468 public static boolean hasUniqueFieldNames(Class<?> type) { 469 return hasUniqueFieldNames(getAllFields(type)); 470 } 471 472 /** 473 * Return true if the fields in this set can be uniquely represented by field name alone 474 */ 475 public static boolean hasUniqueFieldNames(Set<Field> fields) { 476 return getNameMap(Lists.newArrayList(fields)).size() == fields.size(); 477 } 478 479 public static boolean hasUniqueFieldNames(List<Field> fields) { 480 return getNameMap(fields).size() == fields.size(); 481 } 482 483 @SuppressWarnings("unchecked") 484 public static Map<String, Object> describe(Object bean) { 485 try { 486 return BeanUtils.describe(bean); 487 } catch (IllegalAccessException e) { 488 throw new IllegalStateException(e); 489 } catch (InvocationTargetException e) { 490 throw new IllegalStateException(e); 491 } catch (NoSuchMethodException e) { 492 throw new IllegalStateException(e); 493 } 494 } 495 496 public static void copyProperty(Object bean, String name, Object value) { 497 try { 498 BeanUtils.copyProperty(bean, name, value); 499 } catch (IllegalAccessException e) { 500 throw new IllegalStateException(e); 501 } catch (InvocationTargetException e) { 502 throw new IllegalStateException(e); 503 } 504 } 505 506 public static Object invokeMethod(Class<?> targetClass, String targetMethod, Object... arguments) { 507 MethodInvoker invoker = new MethodInvoker(); 508 invoker.setTargetClass(targetClass); 509 invoker.setTargetMethod(targetMethod); 510 invoker.setArguments(arguments); 511 return invoke(invoker); 512 } 513 514 public static Object invokeMethod(Object targetObject, String targetMethod, Object... arguments) { 515 MethodInvoker invoker = new MethodInvoker(); 516 invoker.setTargetObject(targetObject); 517 invoker.setTargetMethod(targetMethod); 518 invoker.setArguments(arguments); 519 return invoke(invoker); 520 } 521 522 public static Object invoke(MethodInvoker invoker) { 523 try { 524 invoker.prepare(); 525 return invoker.invoke(); 526 } catch (ClassNotFoundException e) { 527 throw new IllegalStateException(e); 528 } catch (NoSuchMethodException e) { 529 throw new IllegalStateException(e); 530 } catch (InvocationTargetException e) { 531 throw new IllegalStateException(e); 532 } catch (IllegalAccessException e) { 533 throw new IllegalStateException(e); 534 } 535 } 536 537 public static Class<?> getClass(String className) { 538 try { 539 return Class.forName(className); 540 } catch (ClassNotFoundException e) { 541 throw new IllegalArgumentException(e); 542 } 543 } 544 545 @SuppressWarnings("unchecked") 546 public static <T> Class<? extends T> getTypedClass(String className) { 547 try { 548 return (Class<? extends T>) Class.forName(className); 549 } catch (ClassNotFoundException e) { 550 throw new IllegalArgumentException(e); 551 } 552 } 553 554 public static <T> T newInstance(String className) { 555 @SuppressWarnings("unchecked") 556 Class<T> clazz = (Class<T>) getClass(className); 557 return (T) newInstance(clazz); 558 } 559 560 public static <T> T newInstance(Class<T> instanceClass) { 561 try { 562 return (T) instanceClass.newInstance(); 563 } catch (IllegalAccessException e) { 564 throw new IllegalArgumentException("Unexpected error", e); 565 } catch (InstantiationException e) { 566 throw new IllegalArgumentException("Unexpected error", e); 567 } 568 } 569 570}