001package co.codewizards.cloudstore.core.util; 002 003import static co.codewizards.cloudstore.core.util.Util.*; 004import static java.util.Objects.*; 005 006import java.lang.ref.Reference; 007import java.lang.ref.WeakReference; 008import java.lang.reflect.Constructor; 009import java.lang.reflect.Field; 010import java.lang.reflect.InvocationTargetException; 011import java.lang.reflect.Method; 012import java.lang.reflect.ParameterizedType; 013import java.lang.reflect.Type; 014import java.lang.reflect.TypeVariable; 015import java.util.ArrayList; 016import java.util.Collection; 017import java.util.Collections; 018import java.util.HashMap; 019import java.util.LinkedHashSet; 020import java.util.LinkedList; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024import java.util.WeakHashMap; 025 026import org.slf4j.Logger; 027import org.slf4j.LoggerFactory; 028 029public class ReflectionUtil { 030 031 private static final Logger logger = LoggerFactory.getLogger(ReflectionUtil.class); 032 033 private ReflectionUtil() { } 034 035 public static <T> T invokeConstructor(final Class<T> clazz, final Object ... args) { 036 requireNonNull(clazz, "clazz"); 037 038 final Class<?>[] argTypes = getArgumentTypes(args); 039 040 final List<Constructor<?>> compatibleConstructors = new ArrayList<>(); 041 for (Constructor<?> constructor : clazz.getDeclaredConstructors()) { 042 if (isConstructorCompatible(constructor, argTypes)) 043 compatibleConstructors.add(constructor); 044 } 045 046 if (compatibleConstructors.isEmpty()) { 047 final String methodNameWithParameterTypes = createMethodNameWithParameterTypes(clazz.getSimpleName(), argTypes); 048 throw new IllegalArgumentException(new NoSuchMethodException(String.format("None of the constructors of %s matches %s (or an equivalent using super-types of these parameter-types)!", clazz.getName(), methodNameWithParameterTypes))); 049 } 050 051 if (compatibleConstructors.size() > 1 && logger.isDebugEnabled()) { 052 // TODO find + invoke the *most* *suitable* one - instead of logging this warning (and simply invoking the first). 053 final String methodNameWithParameterTypes = createMethodNameWithParameterTypes(clazz.getSimpleName(), argTypes); 054 final String msg = String.format("%s declares multiple constructors matching %s (or an equivalent using super-types of these parameter-types)!", clazz.getName(), methodNameWithParameterTypes); 055// throw new IllegalArgumentException(new NoSuchMethodException(msg)); 056 logger.warn("invokeConstructor: {}", msg); 057 } 058 059 return cast(invoke(compatibleConstructors.get(0), args)); 060 } 061 062 public static <T> T invokeConstructor(final Class<T> clazz, Class<?>[] parameterTypes, final Object ... args) { 063 final Constructor<T> constructor = getDeclaredConstructorOrFail(clazz, parameterTypes); 064 return invoke(constructor, args); 065 } 066 067 public static <T> T invokeStatic(final Class<?> clazz, final String methodName, final Object ... args) { 068 requireNonNull(clazz, "clazz"); 069 requireNonNull(methodName, "methodName"); 070 return invoke(clazz, (Object)null, methodName, args); 071 } 072 073 public static <T> T invokeStatic(final Class<?> clazz, final String methodName, Class<?>[] parameterTypes, final Object ... args) { 074 final Method method = getDeclaredMethodOrFail(clazz, methodName, parameterTypes); 075 return invoke((Object)null, method, args); 076 } 077 078 public static <T> T invoke(final Object object, final String methodName, final Object ... args) { 079 requireNonNull(object, "object"); 080 requireNonNull(methodName, "methodName"); 081 return invoke(object.getClass(), object, methodName, args); 082 } 083 084 private static <T> T invoke(final Class<?> clazz, final Object object, final String methodName, final Object ... args) { 085 requireNonNull(clazz, "clazz"); 086 // object may be null 087 requireNonNull(methodName, "methodName"); 088 // args may be null 089 090 final Class<?>[] argTypes = getArgumentTypes(args); 091 092 // TODO cache methods - don't search for them again and again 093 final List<Method> methods = getDeclaredMethods(clazz, methodName); 094 final List<Method> compatibleMethods = new ArrayList<>(Math.min(5, methods.size())); 095 for (final Method method : methods) { 096 if (isMethodCompatible(method, argTypes)) 097 compatibleMethods.add(method); 098 } 099 100 if (compatibleMethods.isEmpty()) { 101 final String methodNameWithParameterTypes = createMethodNameWithParameterTypes(methodName, argTypes); 102 throw new IllegalArgumentException(new NoSuchMethodException(String.format("Neither %s nor one of its super-classes declares the method %s (or an equivalent using super-types of these parameter-types)!", clazz.getName(), methodNameWithParameterTypes))); 103 } 104 105 if (compatibleMethods.size() > 1 && logger.isDebugEnabled()) { 106 // TODO find + invoke the *most* *suitable* one - instead of logging this warning (and simply invoking the first). 107 final String methodNameWithParameterTypes = createMethodNameWithParameterTypes(methodName, argTypes); 108 final String msg = String.format("%s and its super-classes declare multiple methods matching %s (or an equivalent using super-types of these parameter-types)!", clazz.getName(), methodNameWithParameterTypes); 109// final Exception x = new NoSuchMethodException(msg); 110 logger.warn("invoke: {}", msg); 111 } 112 113 return invoke(object, compatibleMethods.get(0), args); 114 } 115 116 private static Class<?>[] getArgumentTypes(final Object... args) { 117 final Class<?>[] argTypes = args == null ? new Class<?>[0] : new Class<?>[args.length]; 118 for (int i = 0; i < argTypes.length; i++) 119 argTypes[i] = args[i] == null ? null : args[i].getClass(); 120 return argTypes; 121 } 122 123 public static <T> T invoke(final Object object, final String methodName, Class<?>[] parameterTypes, final Object ... args) { 124 requireNonNull(object, "object"); 125 requireNonNull(methodName, "methodName"); 126 127 if (parameterTypes == null) 128 return invoke(object, methodName, args); 129 130 final Method method = getDeclaredMethodOrFail(object.getClass(), methodName, parameterTypes); 131 return invoke(object, method, args); 132 } 133 134 private static <T> T invoke(final Constructor<T> constructor, final Object ... args) { 135 try { 136 constructor.setAccessible(true); 137 138 final Object result = constructor.newInstance(args); 139 return cast(result); 140 } catch (IllegalArgumentException e) { 141 throw e; 142 } catch (IllegalAccessException | InstantiationException e) { 143 throw new RuntimeException(e); 144 } catch (InvocationTargetException e) { 145 throw new RuntimeException(e.getCause()); 146 } 147 } 148 149 private static <T> T invoke(final Object object, Method method, final Object ... args) { 150 try { 151 method.setAccessible(true); 152 153 final Object result = method.invoke(object, args); 154 return cast(result); 155 } catch (final IllegalArgumentException e) { 156 throw e; 157 } catch (final IllegalAccessException e) { 158 throw new RuntimeException(e); 159 } catch (final InvocationTargetException e) { 160 final Throwable cause = e.getCause(); 161 if (cause instanceof RuntimeException) 162 throw (RuntimeException) cause; 163 else if (cause instanceof Error) 164 throw (Error) cause; 165 else 166 throw new RuntimeException(cause); 167 } 168 } 169 170 private static boolean isConstructorCompatible(final Constructor constructor, final Class<?>[] argTypes) { 171 final Class<?>[] parameterTypes = constructor.getParameterTypes(); 172 return areMethodParametersCompatible(parameterTypes, argTypes); 173 } 174 175 private static boolean isMethodCompatible(final Method method, final Class<?>[] argTypes) { 176 final Class<?>[] parameterTypes = method.getParameterTypes(); 177 return areMethodParametersCompatible(parameterTypes, argTypes); 178 } 179 180 private static boolean areMethodParametersCompatible(final Class<?>[] parameterTypes, final Class<?>[] argTypes) { 181 if (argTypes.length != parameterTypes.length) 182 return false; 183 184 for (int i = 0; i < parameterTypes.length; i++) { 185 if (argTypes[i] != null 186 && ! parameterTypes[i].isAssignableFrom(argTypes[i]) 187 && ! isSimpleTypeAssignmentPossible(parameterTypes[i], argTypes[i])) 188 return false; 189 } 190 191 return true; 192 } 193 194 private static boolean isSimpleTypeAssignmentPossible(Class<?> parameterType, Class<?> argType) { 195 if (parameterType == boolean.class && argType == Boolean.class) 196 return true; 197 198 if (parameterType == byte.class && argType == Byte.class) 199 return true; 200 201 if (parameterType == char.class && argType == Character.class) 202 return true; 203 204 if (parameterType == double.class && argType == Double.class) 205 return true; 206 207 if (parameterType == float.class && argType == Float.class) 208 return true; 209 210 if (parameterType == int.class && argType == Integer.class) 211 return true; 212 213 if (parameterType == long.class && argType == Long.class) 214 return true; 215 216 if (parameterType == short.class && argType == Short.class) 217 return true; 218 219 return false; 220 } 221 222 public static List<Method> getDeclaredMethods(final Class<?> clazz, final String name) { 223 final List<Method> result = new ArrayList<>(); 224 225 Class<?> c = clazz; 226 while (c != null) { 227 final Method[] methods = c.getDeclaredMethods(); 228 for (final Method method : methods) { 229 if (name.equals(method.getName())) 230 result.add(method); 231 } 232 c = c.getSuperclass(); 233 } 234 235 return result; 236 } 237 238 public static <T> Constructor<T> getDeclaredConstructorOrFail(final Class<T> clazz, final Class<?>[] parameterTypes) { 239 final Constructor<T> constructor; 240 try { 241 constructor = clazz.getDeclaredConstructor(parameterTypes); 242 } catch (NoSuchMethodException | SecurityException e) { 243 final String methodNameWithParameterTypes = createMethodNameWithParameterTypes(clazz.getName(), parameterTypes); 244 throw new IllegalArgumentException(new NoSuchMethodException(String.format("%s does not declare the method %s!", clazz.getName(), methodNameWithParameterTypes)).initCause(e)); 245 } 246 return constructor; 247 } 248 249 public static Method getDeclaredMethodOrFail(final Class<?> clazz, final String name, final Class<?>[] parameterTypes) { 250 final Method method = getDeclaredMethod(clazz, name, parameterTypes); 251 if (method == null) { 252 final String methodNameWithParameterTypes = createMethodNameWithParameterTypes(name, parameterTypes); 253 throw new IllegalArgumentException(new NoSuchMethodException(String.format("Neither %s nor one of its super-classes declares the method %s!", clazz.getName(), methodNameWithParameterTypes))); 254 } 255 return method; 256 } 257 258 private static String createMethodNameWithParameterTypes(final String name, final Class<?>[] parameterTypes) { 259 final StringBuilder sb = new StringBuilder(); 260 if (parameterTypes == null) 261 return name + "(...)"; 262 263 for (Class<?> parameterType : parameterTypes) { 264 if (sb.length() > 0) 265 sb.append(", "); 266 267 sb.append(parameterType.getName()); 268 } 269 return name + '(' + sb.toString() + ')'; 270 } 271 272 public static Method getDeclaredMethod(final Class<?> clazz, final String name, final Class<?>[] parameterTypes) { 273 Class<?> c = clazz; 274 while (c != null) { 275 try { 276 final Method declaredMethod = c.getDeclaredMethod(name, parameterTypes); 277 return declaredMethod; 278 } catch (NoSuchMethodException x) { 279 doNothing(); // expected in many cases ;-) 280 } 281 c = c.getSuperclass(); 282 } 283 return null; 284 } 285 286 private static final Map<Class<?>, Reference<List<Field>>> class2AllDeclaredFields = Collections.synchronizedMap( 287 new WeakHashMap<Class<?>, Reference<List<Field>>>()); 288 289 /** 290 * Gets all fields declared by the given {@code clazz} including its super-classes; starting from the most concrete sub-class 291 * (i.e. the given {@code clazz}). 292 * <p> 293 * Please note that the order of the fields declared by one single class is unspecified according to the Java specification. 294 * The only guaranteed order is that between fields declared by one class and fields declared by its super-classes: The result 295 * of this method begins with fields of the most concrete class, followed by its super-class's fields, followed by the 296 * fields of the super-class' super-class and so on. 297 * <p> 298 * For example, consider the following classes: {@code Roadster} <i>extends</i> {@code Car} <i>extends</i> {@code Vehicle}. This 299 * method would thus return all fields declared by all three classes, starting with the fields of {@code Roadster}, 300 * followed by the fields of {@code Car} and finally followed by the fields of {@code Vehicle}. 301 * 302 * @param clazz the class whose fields to obtain. 303 * @return the list of fields declared by the given class and all its super-classes. Never <code>null</code>, but maybe 304 * empty. 305 */ 306 public static List<Field> getAllDeclaredFields(final Class<?> clazz) { 307 requireNonNull(clazz, "clazz"); 308 synchronized(clazz) { 309 final Reference<List<Field>> resultRef = class2AllDeclaredFields.get(clazz); 310 List<Field> result = resultRef == null ? null : resultRef.get(); 311 if (result == null) { 312 result = new ArrayList<>(); 313 Class<?> c = clazz; 314 while (c != null) { 315 final Field[] declaredFields = c.getDeclaredFields(); 316 for (Field field : declaredFields) 317 result.add(field); 318 319 c = c.getSuperclass(); 320 } 321 ((ArrayList<?>)result).trimToSize(); 322 class2AllDeclaredFields.put(clazz, new WeakReference<List<Field>>(result)); 323 } 324 return result; 325 } 326 } 327 328 public static Map<Field, Object> getAllDeclaredFieldValues(final Object object) { 329 requireNonNull(object, "object"); 330 331 final List<Field> allDeclaredFields = getAllDeclaredFields(object.getClass()); 332 final Map<Field, Object> result = new HashMap<>(allDeclaredFields.size()); 333 for (Field field : allDeclaredFields) { 334 field.setAccessible(true); 335 try { 336 final Object fieldValue = field.get(object); 337 result.put(field, fieldValue); 338 } catch (IllegalAccessException e) { 339 throw new RuntimeException(e); 340 } 341 } 342 return result; 343 } 344 345 public static <V> V getFieldValue(final Object object, final String fieldName) { 346 requireNonNull(object, "object"); 347 requireNonNull(fieldName, "fieldName"); 348 349 // TODO pretty inefficient implementation - make better! 350 351 String className = null; 352 String simpleFieldName = fieldName; 353 354 final int lastDotIndex = fieldName.lastIndexOf('.'); 355 if (lastDotIndex >= 0) { 356 className = fieldName.substring(0, lastDotIndex); 357 simpleFieldName = fieldName.substring(lastDotIndex + 1); 358 } 359 360 final List<Field> declaredFields = getAllDeclaredFields(object.getClass()); 361 for (final Field field : declaredFields) { 362 if (className != null && !className.equals(field.getDeclaringClass().getName())) 363 continue; 364 365 if (!simpleFieldName.equals(field.getName())) 366 continue; 367 368 field.setAccessible(true); 369 try { 370 @SuppressWarnings("unchecked") 371 final V value = (V) field.get(object); 372 return value; 373 } catch (IllegalAccessException e) { 374 throw new RuntimeException(e); 375 } 376 } 377 378 throw new IllegalArgumentException(String.format("object's class %s does not have this field: %s", object.getClass(), fieldName)); 379 } 380 381 /** 382 * Sets the {@code object}'s field – identified by {@code fieldName} – to the given {@code value}. 383 * <p> 384 * The {@code fieldName} can be simple (e.g. "firstName") or fully qualified (e.g. "co.codewizards.bla.Person.firstName"). 385 * If it is simple, the most concrete sub-class' matching field is used. 386 * 387 * @param object the object whose field to manipulate. Must not be <code>null</code>. 388 * @param fieldName the simple or fully qualified field name (fully qualified means prefixed by the class name). Must not be <code>null</code>. 389 * @param value the value to be assigned. May be <code>null</code>. 390 */ 391 public static void setFieldValue(final Object object, final String fieldName, final Object value) { 392 requireNonNull(object, "object"); 393 requireNonNull(fieldName, "fieldName"); 394 395 // TODO pretty inefficient implementation - make better! 396 397 String className = null; 398 String simpleFieldName = fieldName; 399 400 final int lastDotIndex = fieldName.lastIndexOf('.'); 401 if (lastDotIndex >= 0) { 402 className = fieldName.substring(0, lastDotIndex); 403 simpleFieldName = fieldName.substring(lastDotIndex + 1); 404 } 405 406 final List<Field> declaredFields = getAllDeclaredFields(object.getClass()); 407 for (final Field field : declaredFields) { 408 if (className != null && !className.equals(field.getDeclaringClass().getName())) 409 continue; 410 411 if (!simpleFieldName.equals(field.getName())) 412 continue; 413 414 field.setAccessible(true); 415 try { 416 field.set(object, value); 417 } catch (IllegalAccessException e) { 418 throw new RuntimeException(e); 419 } 420 return; 421 } 422 423 throw new IllegalArgumentException(String.format("object's class %s does not have this field: %s", object.getClass(), fieldName)); 424 } 425 426 public static Set<Class<?>> getAllInterfaces(final Class<?> clazz) { 427 requireNonNull(clazz, "clazz"); 428 429 final Set<Class<?>> interfaces = new LinkedHashSet<>(); 430 431 Class<?> c = clazz; 432 while (c != null) { 433 populateInterfaces(interfaces, c); 434 c = c.getSuperclass(); 435 } 436 return interfaces; 437 } 438 439 private static void populateInterfaces(Collection<Class<?>> interfaces, Class<?> clazz) { 440 for (final Class<?> iface : clazz.getInterfaces()) 441 interfaces.add(iface); 442 443 for (final Class<?> iface : clazz.getInterfaces()) 444 populateInterfaces(interfaces, iface); 445 } 446 447 /** 448 * Resolves the actual type arguments of a base-class declared in a concrete sub-class. 449 * <p> 450 * This is a convenience method delegating to {@link #resolveActualTypeArguments(Class, Class)} 451 * passing {@code concreteObject.getClass()} as {@code concreteClass}. 452 * @param baseClass the base class. Must not be <code>null</code>. 453 * @param concreteObject an instance of a sub-class of the generic {@code baseClass}. 454 * @return the resolved type arguments. Never <code>null</code> (empty array for a non-generic base-class). 455 */ 456 public static final <T> Type[] resolveActualTypeArguments(final Class<T> baseClass, final T concreteObject) { 457 requireNonNull(baseClass, "baseClass"); 458 requireNonNull(concreteObject, "concreteObject"); 459 @SuppressWarnings("unchecked") 460 final Class<? extends T> concreteClass = (Class<? extends T>) concreteObject.getClass(); 461 return resolveActualTypeArguments(baseClass, concreteClass); 462 } 463 464 /** 465 * Resolves the actual type arguments of a base-class declared in a concrete sub-class. 466 * <p> 467 * The length as well as the order of the resolved type arguments matches the declaration order 468 * in the base-class. If a type argument could successfully be resolved, it is usually an instance of 469 * {@link Class}. If it could not be resolved (because the sub-class does not specify the generic type info 470 * - directly or indirectly), it is an instance of {@link TypeVariable}. 471 * <p> 472 * A typical use-case is this: 473 * <pre> 474 * public abstract class MyBase<A, B, C> { 475 * final Class<A> actualTypeArgumentA; 476 * final Class<B> actualTypeArgumentB; 477 * final Class<C> actualTypeArgumentC; 478 * 479 * public MyBase() { 480 * final Type[] actualTypeArguments = resolveActualTypeArguments(MyBase.class, this); 481 * 482 * // The following assignments fail - of course -, if the concrete class lacks 483 * // generic type info - like the example class "MyFail" below. 484 * actualTypeArgumentA = (Class<A>) actualTypeArguments[0]; 485 * actualTypeArgumentB = (Class<B>) actualTypeArguments[1]; 486 * actualTypeArgumentC = (Class<C>) actualTypeArguments[2]; 487 * } 488 * } 489 * 490 * public class MyConcrete extends MyBase<Long, Boolean, String> { 491 * } 492 * 493 * public class MyFail extends MyBase { 494 * } 495 * </pre> 496 * 497 * @param baseClass the base class. Must not be <code>null</code>. 498 * @param concreteClass a sub-class of the generic {@code baseClass}. 499 * @return the resolved type arguments. Never <code>null</code> (empty array for a non-generic base-class). 500 */ 501 public static final <T> Type[] resolveActualTypeArguments(final Class<T> baseClass, final Class<? extends T> concreteClass) { 502 return _resolveActualTypeArgs(baseClass, concreteClass); 503 } 504 505 private static final <T> Type[] _resolveActualTypeArgs(final Class<T> baseClass, final Class<? extends T> concreteClass, final Type... actualArgs) { 506 requireNonNull(baseClass, "baseClass"); 507 requireNonNull(concreteClass, "concreteClass"); 508 requireNonNull(actualArgs, "actualArgs"); 509 510 if (actualArgs.length != 0 && actualArgs.length != concreteClass.getTypeParameters().length) 511 throw new IllegalArgumentException("actualArgs.length != 0 && actualArgs.length != concreteClass.typeParameters.length"); 512 513 final Type[] _actualArgs = actualArgs.length == 0 ? concreteClass.getTypeParameters() : actualArgs; 514 515 // map type parameters into the actual types 516 Map<String, Type> typeVariables = new HashMap<String, Type>(); 517 for (int i = 0; i < _actualArgs.length; i++) { 518 TypeVariable<?> typeVariable = concreteClass.getTypeParameters()[i]; 519 typeVariables.put(typeVariable.getName(), _actualArgs[i]); 520 } 521 522 // Find direct ancestors (superclass, interfaces) 523 List<Type> ancestors = new LinkedList<Type>(); 524 if (concreteClass.getGenericSuperclass() != null) { 525 ancestors.add(concreteClass.getGenericSuperclass()); 526 } 527 for (Type t : concreteClass.getGenericInterfaces()) { 528 ancestors.add(t); 529 } 530 531 // Recurse into ancestors (superclass, interfaces) 532 for (Type type : ancestors) { 533 if (type instanceof Class<?>) { 534 // ancestor is non-parameterized. Recurse only if it matches the base class. 535 Class<?> ancestorClass = (Class<?>) type; 536 if (baseClass.isAssignableFrom(ancestorClass)) { 537 Type[] result = _resolveActualTypeArgs(baseClass, (Class<? extends T>) ancestorClass); 538 if (result != null) { 539 return result; 540 } 541 } 542 } 543 if (type instanceof ParameterizedType) { 544 // ancestor is parameterized. Recurse only if the raw type matches the base class. 545 ParameterizedType parameterizedType = (ParameterizedType) type; 546 Type rawType = parameterizedType.getRawType(); 547 if (rawType instanceof Class<?>) { 548 Class<?> rawTypeClass = (Class<?>) rawType; 549 if (baseClass.isAssignableFrom(rawTypeClass)) { 550 551 // loop through all type arguments and replace type variables with the actually known types 552 List<Type> resolvedTypes = new LinkedList<Type>(); 553 for (Type t : parameterizedType.getActualTypeArguments()) { 554 if (t instanceof TypeVariable<?>) { 555 Type resolvedType = typeVariables.get(((TypeVariable<?>) t).getName()); 556 resolvedTypes.add(resolvedType != null ? resolvedType : t); 557 } else { 558 resolvedTypes.add(t); 559 } 560 } 561 562 Type[] result = _resolveActualTypeArgs(baseClass, (Class<? extends T>) rawTypeClass, resolvedTypes.toArray(new Type[] {})); 563 if (result != null) { 564 return result; 565 } 566 } 567 } 568 } 569 } 570 571 // we have a result if we reached the base class. 572 return concreteClass.equals(baseClass) ? _actualArgs : null; 573 } 574}