All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.junit.gen5.commons.util.ReflectionUtils Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2015-2016 the original author or authors.
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License v1.0 which
 * accompanies this distribution and is available at
 *
 * http://www.eclipse.org/legal/epl-v10.html
 */

package org.junit.gen5.commons.util;

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static org.junit.gen5.commons.meta.API.Usage.Internal;

import java.io.File;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;

import org.junit.gen5.commons.meta.API;

/**
 * Collection of utilities for working with the Java reflection APIs.
 *
 * 

DISCLAIMER

* *

These utilities are intended solely for usage within the JUnit framework * itself. Any usage by external parties is not supported. * Use at your own risk! * * @since 5.0 */ @API(Internal) public final class ReflectionUtils { public enum MethodSortOrder { HierarchyDown, HierarchyUp } private ReflectionUtils() { /* no-op */ } public static ClassLoader getDefaultClassLoader() { try { return Thread.currentThread().getContextClassLoader(); } catch (Throwable ex) { /* ignore */ } return ClassLoader.getSystemClassLoader(); } public static boolean isPublic(Class clazz) { return Modifier.isPublic(clazz.getModifiers()); } public static boolean isPublic(Member member) { return Modifier.isPublic(member.getModifiers()); } public static boolean isPrivate(Class clazz) { return Modifier.isPrivate(clazz.getModifiers()); } public static boolean isPrivate(Member member) { return Modifier.isPrivate(member.getModifiers()); } public static boolean isAbstract(Class clazz) { return Modifier.isAbstract(clazz.getModifiers()); } public static boolean isAbstract(Member member) { return Modifier.isAbstract(member.getModifiers()); } public static boolean isStatic(Class clazz) { return Modifier.isStatic(clazz.getModifiers()); } public static boolean isStatic(Member member) { return Modifier.isStatic(member.getModifiers()); } /** * Create a new instance of the specified {@link Class} by invoking * the constructor whose argument list matches the types of the supplied * arguments. * *

The constructor will be made accessible if necessary, and any checked * exception will be {@linkplain ExceptionUtils#throwAsUncheckedException masked} * as an unchecked exception. * * @param clazz the class to instantiate; never {@code null} * @param args the arguments to pass to the constructor none of which may be {@code null} * @return the new instance * @see ExceptionUtils#throwAsUncheckedException(Throwable) */ public static T newInstance(Class clazz, Object... args) { Preconditions.notNull(clazz, "class must not be null"); Preconditions.notNull(args, "none of the arguments may be null"); try { Class[] parameterTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new); Constructor constructor = makeAccessible(clazz.getDeclaredConstructor(parameterTypes)); return constructor.newInstance(args); } catch (Throwable t) { throw ExceptionUtils.throwAsUncheckedException(getUnderlyingCause(t)); } } /** * Invoke the supplied method, making it accessible if necessary and * {@linkplain ExceptionUtils#throwAsUncheckedException masking} any * checked exception as an unchecked exception. * * @param method the method to invoke; never {@code null} * @param target the object on which to invoke the method; may be * {@code null} if the method is {@code static} * @param args the arguments to pass to the method * @return the value returned by the method invocation or {@code null} * if the return type is {@code void} * @see ExceptionUtils#throwAsUncheckedException(Throwable) */ public static Object invokeMethod(Method method, Object target, Object... args) { Preconditions.notNull(method, "method must not be null"); Preconditions.condition((target != null || isStatic(method)), () -> String.format("Cannot invoke non-static method [%s] on a null target.", method.toGenericString())); try { return makeAccessible(method).invoke(target, args); } catch (Throwable t) { throw ExceptionUtils.throwAsUncheckedException(getUnderlyingCause(t)); } } public static Optional> loadClass(String name) { return loadClass(name, getDefaultClassLoader()); } public static Optional> loadClass(String name, ClassLoader classLoader) { Preconditions.notBlank(name, "class name must not be null or empty"); Preconditions.notNull(classLoader, "ClassLoader must not be null"); try { // TODO Add support for primitive types and arrays. return Optional.of(classLoader.loadClass(name.trim())); } catch (ClassNotFoundException e) { return Optional.empty(); } } /** * Try to load a method by its fully qualified name (if such a thing exists for methods). * @param fullyQualifiedMethodName In the form 'package.subpackage.ClassName#methodName' * @return Optional of Method */ public static Optional loadMethod(String fullyQualifiedMethodName) { Preconditions.notBlank(fullyQualifiedMethodName, "full method name must not be null or empty"); // TODO Handle overloaded and inherited methods Optional testMethodOptional = Optional.empty(); int hashPosition = fullyQualifiedMethodName.lastIndexOf('#'); if (hashPosition >= 0 && hashPosition < fullyQualifiedMethodName.length()) { String className = fullyQualifiedMethodName.substring(0, hashPosition); String methodName = fullyQualifiedMethodName.substring(hashPosition + 1); Optional> methodClassOptional = loadClass(className); if (methodClassOptional.isPresent()) { try { testMethodOptional = Optional.of(methodClassOptional.get().getDeclaredMethod(methodName)); } catch (NoSuchMethodException ignore) { } } } return testMethodOptional; } public static Optional getOuterInstance(Object inner) { // This is risky since it depends on the name of the field which is nowhere guaranteed // but has been stable so far in all JDKs return Arrays.stream(inner.getClass().getDeclaredFields()).filter( f -> f.getName().startsWith("this$")).findFirst().map(f -> { try { return makeAccessible(f).get(inner); } catch (IllegalAccessException e) { return Optional.empty(); } }); } public static Optional getOuterInstance(Object inner, Class targetType) { if (targetType.isInstance(inner)) return Optional.of(inner); Optional candidate = getOuterInstance(inner); if (candidate.isPresent()) return getOuterInstance(candidate.get(), targetType); else return Optional.empty(); } public static boolean isPackage(String packageName) { return new ClasspathScanner(ReflectionUtils::getDefaultClassLoader, ReflectionUtils::loadClass).isPackage( packageName); } public static Set getAllClasspathRootDirectories() { // TODO This is quite a hack, since sometimes the classpath is quite different String fullClassPath = System.getProperty("java.class.path"); final String separator = System.getProperty("path.separator"); // @formatter:off return Arrays.stream(fullClassPath.split(separator)) .filter(part -> !part.endsWith(".jar")) .map(File::new) .filter(File::isDirectory) .collect(toSet()); // @formatter:on } public static List> findAllClassesInClasspathRoot(File root, Predicate> classTester) { return new ClasspathScanner(ReflectionUtils::getDefaultClassLoader, ReflectionUtils::loadClass).scanForClassesInClasspathRoot(root, classTester); } public static List> findAllClassesInPackage(String basePackageName, Predicate> classTester) { return new ClasspathScanner(ReflectionUtils::getDefaultClassLoader, ReflectionUtils::loadClass).scanForClassesInPackage(basePackageName, classTester); } public static List> findNestedClasses(Class clazz, Predicate> predicate) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(predicate, "predicate must not be null"); return Arrays.stream(clazz.getDeclaredClasses()).filter(predicate).collect(toList()); } public static Optional findMethod(Class clazz, String methodName, Class... parameterTypes) { Predicate nameAndParameterTypesMatch = (method -> method.getName().equals(methodName) && Arrays.equals(method.getParameterTypes(), parameterTypes)); List candidates = findMethods(clazz, nameAndParameterTypesMatch); return (!candidates.isEmpty() ? Optional.of(candidates.get(0)) : Optional.empty()); } public static List findMethods(Class clazz, Predicate predicate) { return findMethods(clazz, predicate, MethodSortOrder.HierarchyDown); } public static List findMethods(Class clazz, Predicate predicate, MethodSortOrder sortOrder) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(predicate, "predicate must not be null"); Preconditions.notNull(sortOrder, "MethodSortOrder must not be null"); // @formatter:off return findAllMethodsInHierarchy(clazz, sortOrder).stream() .filter(predicate) .collect(toList()); // @formatter:on } /** * Return all methods in superclass hierarchy except from Object. */ public static List findAllMethodsInHierarchy(Class clazz, MethodSortOrder sortOrder) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(sortOrder, "MethodSortOrder must not be null"); // TODO Support interface default methods. // TODO Determine if we need to support bridged methods. List localMethods = Arrays.asList(clazz.getDeclaredMethods()); // @formatter:off List superclassMethods = getSuperclassMethods(clazz, sortOrder).stream() .filter(method -> !isMethodShadowedByLocalMethods(method, localMethods)) .collect(toList()); // @formatter:on // @formatter:off List interfaceMethods = getInterfaceMethods(clazz, sortOrder).stream() .filter(method -> !isMethodShadowedByLocalMethods(method, localMethods)) .collect(toList()); // @formatter:on List methods = new ArrayList<>(); if (sortOrder == MethodSortOrder.HierarchyDown) { methods.addAll(superclassMethods); methods.addAll(interfaceMethods); } methods.addAll(localMethods); if (sortOrder == MethodSortOrder.HierarchyUp) { methods.addAll(interfaceMethods); methods.addAll(superclassMethods); } return methods; } /** * Reads the value of a potentially inaccessible field. * *

If the field does not exist, an exception occurs while reading it, or * the value of the field is {@code null}, an empty {@link Optional} is * returned. * * @param clazz the class where the field is declared. * @param fieldName the name of the field * @param instance the instance from where the value is to be read */ public static Optional readFieldValue(Class clazz, String fieldName, T instance) { try { Field field = makeAccessible(clazz.getDeclaredField(fieldName)); return Optional.ofNullable(field.get(instance)); } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { return Optional.empty(); } } private static List getInterfaceMethods(Class clazz, MethodSortOrder sortOrder) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(sortOrder, "MethodSortOrder must not be null"); List allInterfaceMethods = new ArrayList<>(); for (Class ifc : clazz.getInterfaces()) { List localMethods = Arrays.stream(ifc.getDeclaredMethods()).filter(Method::isDefault).collect( toList()); // @formatter:off List subInterfaceMethods = getInterfaceMethods(ifc, sortOrder).stream() .filter(method -> !isMethodShadowedByLocalMethods(method, localMethods)) .collect(toList()); // @formatter:on if (sortOrder == MethodSortOrder.HierarchyDown) { allInterfaceMethods.addAll(subInterfaceMethods); } allInterfaceMethods.addAll(localMethods); if (sortOrder == MethodSortOrder.HierarchyUp) { allInterfaceMethods.addAll(subInterfaceMethods); } } return allInterfaceMethods; } private static List getSuperclassMethods(Class clazz, MethodSortOrder sortOrder) { if (clazz.getSuperclass() != Object.class) { return findAllMethodsInHierarchy(clazz.getSuperclass(), sortOrder); } else { return Collections.emptyList(); } } private static boolean isMethodShadowedByLocalMethods(Method method, List localMethods) { return localMethods.stream().anyMatch(local -> isMethodShadowedBy(method, local)); } private static boolean isMethodShadowedBy(Method upper, Method lower) { if (!lower.getName().equals(upper.getName())) { return false; } Class[] lowerParameterTypes = lower.getParameterTypes(); Class[] upperParameterTypes = upper.getParameterTypes(); if (lowerParameterTypes.length != upperParameterTypes.length) { return false; } for (int i = 0; i < lowerParameterTypes.length; i++) { if (!lowerParameterTypes[i].equals(upperParameterTypes[i])) { return false; } } return true; } private static T makeAccessible(T object) { if (!object.isAccessible()) { object.setAccessible(true); } return object; } /** * Get the underlying cause of the supplied {@link Throwable}. * *

If the supplied {@code Throwable} is an instance of * {@link InvocationTargetException}, this method will be invoked * recursively with the underlying * {@linkplain InvocationTargetException#getTargetException() target * exception}; otherwise, this method simply returns the supplied * {@code Throwable}. */ private static Throwable getUnderlyingCause(Throwable t) { if (t instanceof InvocationTargetException) { return getUnderlyingCause(((InvocationTargetException) t).getTargetException()); } return t; } /** * Return all classes and interfaces that can be used as assignment types * for instances of the specified {@link Class}, including itself. * * @param clazz the {@code Class} to lookup * @see Class#isAssignableFrom */ public static Set> getAllAssignmentCompatibleClasses(Class clazz) { Set> result = new LinkedHashSet<>(); getAllAssignmentCompatibleClasses(clazz, result); return result; } private static void getAllAssignmentCompatibleClasses(Class clazz, Set> result) { for (Class current = clazz; current != null; current = current.getSuperclass()) { result.add(current); for (Class interfaceClass : current.getInterfaces()) { if (!result.contains(interfaceClass)) { getAllAssignmentCompatibleClasses(interfaceClass, result); } } } } }