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