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

net.jqwik.engine.support.JqwikReflectionSupport Maven / Gradle / Ivy

There is a newer version: 1.9.1
Show newest version
package net.jqwik.engine.support;

import java.io.*;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.nio.file.*;
import java.util.*;
import java.util.function.*;
import java.util.stream.*;

import org.junit.platform.commons.support.*;

import net.jqwik.api.providers.*;

import static java.util.stream.Collectors.*;

import static net.jqwik.engine.support.OverriddenMethodAnnotationSupport.*;

public class JqwikReflectionSupport {

	public static Stream streamInstancesFromInside(Object inner) {
		return addInstances(inner, new ArrayList<>()).stream();
	}

	public static List getInstancesFromInside(Object inner) {
		List instances = streamInstancesFromInside(inner).collect(toList());
		Collections.reverse(instances);
		return instances;
	}

	private static List addInstances(Object inner, List instances) {
		instances.add(inner);
		Optional outer = getOuterInstance(inner);
		outer.ifPresent(o -> addInstances(o, instances));
		return instances;
	}

	private 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(field -> field.getName().startsWith("this$"))
				   .findFirst()
				   .map(field -> {
					   try {
						   return makeAccessible(field).get(inner);
					   } catch (SecurityException | IllegalArgumentException | IllegalAccessException ex) {
						   return Optional.empty();
					   }
				   });
	}

	private static  T makeAccessible(T object) {
		if (!object.isAccessible()) {
			object.setAccessible(true);
		}
		return object;
	}

	/**
	 * Create instance of a class that can potentially be a non static inner class
	 *
	 * @param    The type of the instance to create
	 * @param clazz The class to instantiate
	 * @return the newly created instance
	 */
	public static  T newInstanceWithDefaultConstructor(Class clazz) {
		if (isInnerClass(clazz)) {
			Object parentInstance = newInstanceWithDefaultConstructor(clazz.getDeclaringClass());
			return ReflectionSupport.newInstance(clazz, parentInstance);
		} else {
			return ReflectionSupport.newInstance(clazz);
		}
	}

	/**
	 * Create instance of a class that can potentially be a non static inner class
	 * and its outer instance might be {@code context}
	 *
	 * @param      The type of the instance to create
	 * @param clazz   The class to instantiate
	 * @param context The potential context instance
	 * @return the newly created instance
	 */
	public static  T newInstanceInTestContext(Class clazz, Object context) {
		if (!isInnerClass(clazz)) {
			return ReflectionSupport.newInstance(clazz);
		}
		Class outerClass = clazz.getDeclaringClass();
		Object parentInstance = outerClass.isAssignableFrom(context.getClass()) ?
									context : newInstanceWithDefaultConstructor(outerClass);
		try {
			Constructor constructor = clazz.getDeclaredConstructor(outerClass);
			return newInstance(constructor, parentInstance);
		} catch (NoSuchMethodException e) {
			return JqwikExceptionSupport.throwAsUncheckedException(e);
		}
	}

	public static  T newInstance(Constructor constructor, Object... args) {
		try {
			return makeAccessible(constructor).newInstance(args);
		} catch (Throwable t) {
			return JqwikExceptionSupport.throwAsUncheckedException(t);
		}
	}

	/**
	 * Find all {@linkplain Method methods} as in ReflectionSupport.findMethods(..) but also use outer classes to look for
	 * methods.
	 *
	 * @param clazz         The class in which you start the search
	 * @param predicate     The condition to check for all candidate methods
	 * @param traversalMode Traverse hierarchy up or down. Determines the order in resulting list.
	 * @return List of found methods
	 */
	public static List findMethodsPotentiallyOuter(
		Class clazz,
		Predicate predicate,
		HierarchyTraversalMode traversalMode
	) {
		List> searchClasses = getDeclaringClasses(clazz, traversalMode);
		List foundMethods = new ArrayList<>();
		for (Class searchClass : searchClasses) {
			foundMethods.addAll(ReflectionSupport.findMethods(searchClass, predicate, traversalMode));
		}
		return foundMethods;
	}

	private static List> getDeclaringClasses(Class clazz, HierarchyTraversalMode traversalMode) {
		List> declaringClasses = new ArrayList<>();
		Class nextClass = clazz;
		while (nextClass != null) {
			if (traversalMode == HierarchyTraversalMode.BOTTOM_UP) {
				declaringClasses.add(nextClass);
			} else {
				declaringClasses.add(0, nextClass);
			}
			nextClass = nextClass.getDeclaringClass();
		}
		return declaringClasses;
	}

	/**
	 * Invoke the supplied {@linkplain Method method} as in ReflectionSupport.invokeMethod(..) but potentially use the outer
	 * instance if the method belongs to the outer instance of an object.
	 *
	 * @param method The method to invoke
	 * @param target The object to invoke the method on
	 * @param args   The arguments of the method invocation
	 * @return Result of method invocation if there is one, otherwise null
	 */
	public static Object invokeMethodPotentiallyOuter(Method method, Object target, Object... args) {
		if (method.getDeclaringClass().isAssignableFrom(target.getClass())) {
			return ReflectionSupport.invokeMethod(method, target, args);
		} else {
			if (target.getClass().getDeclaringClass() != null) {
				Optional newTarget = getOuterInstance(target);
				if (newTarget.isPresent()) {
					return invokeMethodPotentiallyOuter(method, newTarget.get(), args);
				}
			}
			throw new IllegalArgumentException(String.format("Method [%s] cannot be invoked on target [%s].", method, target));
		}
	}

	public static Set getAllClasspathRootDirectories() {
		// TODO: This is quite a hack, since sometimes the classpath is quite different.
		// Especially under Java >=9's module system this will probably no longer work.
		String classpath = System.getProperty("java.class.path");
		return Arrays.stream(classpath.split(File.pathSeparator))
					 .map(Paths::get).filter(Files::isDirectory)
					 .collect(toSet());
	}

	public static List getMethodParameters(Executable method, Class containerClass) {
		List list = new ArrayList<>();
		Parameter[] parameters = method.getParameters();
		GenericsClassContext containerClassContext = GenericsSupport.contextFor(containerClass);

		for (int i = 0; i < parameters.length; i++) {
			Parameter parameter = parameters[i];
			TypeResolution resolution = containerClassContext.resolveParameter(parameter);
			MethodParameter methodParameter = new MethodParameter(parameter, resolution, i);
			list.add(methodParameter);
		}
		return list;
	}

	public static MethodParameter getMethodParameter(Parameter parameter, int index, Class containerClass) {
		GenericsClassContext containerClassContext = GenericsSupport.contextFor(containerClass);
		TypeResolution resolution = containerClassContext.resolveParameter(parameter);
		return new MethodParameter(parameter, resolution, index);
	}

	public static Optional findGeneratorMethod(
		String generatorToFind,
		Class containerClass,
		Class requiredGeneratorAnnotation,
		Function generatorNameSupplier,
		TypeUsage targetType
	) {
		List creators = findMethodsPotentiallyOuter(
			containerClass,
			isGeneratorMethod(targetType, requiredGeneratorAnnotation),
			HierarchyTraversalMode.BOTTOM_UP
		);
		return creators.stream().filter(generatorMethod -> {
			String generatorName = generatorNameSupplier.apply(generatorMethod);
			if (generatorName.isEmpty()) {
				generatorName = JqwikKotlinSupport.javaOrKotlinName(generatorMethod);
			}
			return generatorName.equals(generatorToFind);
		}).findFirst();
	}

	public static  Constructor findConstructor(Class type, Class... parameterTypes) {
		try {
			Constructor ctor = type.getDeclaredConstructor(parameterTypes);
			// Constructor ctor = type.getConstructor(parameterTypes);
			ctor.setAccessible(true);
			return ctor;
		} catch (Throwable t) {
			return JqwikExceptionSupport.throwAsUncheckedException(t);
		}
	}

	public static Predicate isGeneratorMethod(TypeUsage targetType, Class requiredAnnotation) {
		return method -> {
			if (!findDeclaredOrInheritedAnnotation(method, requiredAnnotation).isPresent()) {
				return false;
			}
			TypeUsage generatorReturnType = TypeUsage.forType(method.getAnnotatedReturnType().getType());
			return generatorReturnType.canBeAssignedTo(targetType)
					   // for generic types in test hierarchies:
					   || targetType.canBeAssignedTo(generatorReturnType);
		};
	}

	public static boolean isInnerClass(Class clazz) {
		return clazz.isMemberClass() && !ModifierSupport.isStatic(clazz);
	}

	public static boolean isFunctionalType(Class candidateType) {
		if (!candidateType.isInterface()) {
			return false;
		}
		return countInterfaceMethods(candidateType) == 1;
	}

	private static long countInterfaceMethods(Class candidateType) {
		Method[] methods = candidateType.getMethods();
		return findInterfaceMethods(methods).size();
	}

	private static List findInterfaceMethods(Method[] methods) {
		return Arrays
				   .stream(methods)
				   .filter(m -> !m.isDefault() && !ModifierSupport.isStatic(m))
				   .collect(Collectors.toList());
	}

	public static Optional getFunctionMethod(Class candidateType) {
		Method[] methods = candidateType.getMethods();
		List candidates = findInterfaceMethods(methods);
		if (candidates.size() != 1) {
			return Optional.empty();
		}
		return Optional.of(candidates.get(0));
	}

	public static boolean isEqualsMethod(Method method) {
		try {
			return method.equals(Object.class.getDeclaredMethod("equals", Object.class));
		} catch (NoSuchMethodException shouldNeverHappen) {
			return false;
		}
	}

	public static boolean isToStringMethod(Method method) {
		try {
			return method.equals(Object.class.getDeclaredMethod("toString"));
		} catch (NoSuchMethodException shouldNeverHappen) {
			return false;
		}
	}

	public static boolean isHashCodeMethod(Method method) {
		try {
			return method.equals(Object.class.getDeclaredMethod("hashCode"));
		} catch (NoSuchMethodException shouldNeverHappen) {
			return false;
		}
	}

	public static boolean hasDefaultConstructor(Class aClass) {
		return hasConstructor(aClass);
	}

	public static boolean hasConstructor(Class aClass, Class ... parameterTypes) {
		try {
			aClass.getDeclaredConstructor(parameterTypes);
			return true;
		} catch (NoSuchMethodException e) {
			return false;
		}
	}

	public static boolean isJava9orAbove() {
		try {
			//noinspection JavaReflectionMemberAccess
			Runtime.class.getMethod("version");
			return true;
		} catch (NoSuchMethodException e) {
			return false;
		}
	}

	public static boolean isStatic(Class clazz) {
		return Modifier.isStatic(clazz.getModifiers());
	}

	public static boolean isPrivate(Class clazz) {
		return Modifier.isPrivate(clazz.getModifiers());
	}

	public static boolean isNotStatic(Class clazz) {
		return !isStatic(clazz);
	}

	public static boolean isStatic(Member member) {
		return Modifier.isStatic(member.getModifiers());
	}

	public static boolean isNotStatic(Member member) {
		return !isStatic(member);
	}

	public static boolean returnsVoid(Method method) {
		return method.getReturnType().equals(Void.TYPE);
	}

	public static boolean implementsMethod(Class aClass, String methodName, Class[] parameterTypes, Class ignoreImplementationClass) {
		Optional optionalMethod = ReflectionSupport.findMethod(aClass, methodName, parameterTypes);
		return optionalMethod.map(method -> !method.getDeclaringClass().equals(ignoreImplementationClass)).orElse(false);
	}

	public static Class extractRawType(Type parameterizedType) {
		if (parameterizedType instanceof Class) {
			return (Class) parameterizedType;
		}
		if (parameterizedType instanceof ParameterizedType) {
			return (Class) ((ParameterizedType) parameterizedType).getRawType();
		}
		if (parameterizedType instanceof GenericArrayType) {
			return Object[].class;
		}
		// Now we have a type variable (java.lang.reflect.TypeVariable)
		return Object.class;
	}

}