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

org.junit.jupiter.engine.execution.ExecutableInvoker Maven / Gradle / Ivy

There is a newer version: 5.5.0-M1
Show newest version
/*
 * Copyright 2015-2018 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 v2.0 which
 * accompanies this distribution and is available at
 *
 * http://www.eclipse.org/legal/epl-v20.html
 */

package org.junit.jupiter.engine.execution;

import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static org.apiguardian.api.API.Status.INTERNAL;
import static org.junit.platform.commons.util.ReflectionUtils.isAssignableTo;

import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.List;
import java.util.Optional;

import org.apiguardian.api.API;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.engine.extension.ExtensionRegistry;
import org.junit.platform.commons.logging.Logger;
import org.junit.platform.commons.logging.LoggerFactory;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.commons.util.ReflectionUtils;

/**
 * {@code ExecutableInvoker} encapsulates the invocation of a
 * {@link java.lang.reflect.Executable} (i.e., method or constructor),
 * including support for dynamic resolution of method parameters via
 * {@link ParameterResolver ParameterResolvers}.
 *
 * @since 5.0
 */
@API(status = INTERNAL, since = "5.0")
public class ExecutableInvoker {

	private static final Logger logger = LoggerFactory.getLogger(ExecutableInvoker.class);

	/**
	 * Invoke the supplied constructor with dynamic parameter resolution.
	 *
	 * @param constructor the constructor to invoke and resolve parameters for
	 * @param extensionContext the current {@code ExtensionContext}
	 * @param extensionRegistry the {@code ExtensionRegistry} to retrieve
	 * {@code ParameterResolvers} from
	 */
	public  T invoke(Constructor constructor, ExtensionContext extensionContext,
			ExtensionRegistry extensionRegistry) {

		return ReflectionUtils.newInstance(constructor,
			resolveParameters(constructor, Optional.empty(), extensionContext, extensionRegistry));
	}

	/**
	 * Invoke the supplied constructor with the supplied outer instance and
	 * dynamic parameter resolution.
	 *
	 * 

This method should only be used to invoke the constructor for * an inner class. * * @param constructor the constructor to invoke and resolve parameters for * @param outerInstance the outer instance to supply as the first argument * to the constructor * @param extensionContext the current {@code ExtensionContext} * @param extensionRegistry the {@code ExtensionRegistry} to retrieve * {@code ParameterResolvers} from */ public T invoke(Constructor constructor, Object outerInstance, ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) { return ReflectionUtils.newInstance(constructor, resolveParameters(constructor, Optional.empty(), outerInstance, extensionContext, extensionRegistry)); } /** * Invoke the supplied {@code static} method with dynamic parameter resolution. * * @param method the method to invoke and resolve parameters for * @param extensionContext the current {@code ExtensionContext} * @param extensionRegistry the {@code ExtensionRegistry} to retrieve * {@code ParameterResolvers} from */ public Object invoke(Method method, ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) { return ReflectionUtils.invokeMethod(method, null, resolveParameters(method, Optional.empty(), extensionContext, extensionRegistry)); } /** * Invoke the supplied method on the supplied target object with dynamic parameter * resolution. * * @param method the method to invoke and resolve parameters for * @param target the object on which the method will be invoked; should be * {@code null} for static methods * @param extensionContext the current {@code ExtensionContext} * @param extensionRegistry the {@code ExtensionRegistry} to retrieve * {@code ParameterResolvers} from */ public Object invoke(Method method, Object target, ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) { @SuppressWarnings("unchecked") Optional optionalTarget = (target instanceof Optional ? (Optional) target : Optional.ofNullable(target)); return ReflectionUtils.invokeMethod(method, target, resolveParameters(method, optionalTarget, extensionContext, extensionRegistry)); } /** * Resolve the array of parameters for the supplied executable and target. * * @param executable the executable for which to resolve parameters * @param target an {@code Optional} containing the target on which the * executable will be invoked; never {@code null} but should be empty for * static methods and constructors * @param extensionContext the current {@code ExtensionContext} * @param extensionRegistry the {@code ExtensionRegistry} to retrieve * {@code ParameterResolvers} from * @return the array of Objects to be used as parameters in the executable * invocation; never {@code null} though potentially empty */ private Object[] resolveParameters(Executable executable, Optional target, ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) { return resolveParameters(executable, target, null, extensionContext, extensionRegistry); } /** * Resolve the array of parameters for the supplied executable, target, and * outer instance. * * @param executable the executable for which to resolve parameters * @param target an {@code Optional} containing the target on which the * executable will be invoked; never {@code null} but should be empty for * static methods and constructors * @param outerInstance the outer instance that will be supplied as the * first argument to a constructor for an inner class; should be {@code null} * for methods and constructors for top-level or static classes * @param extensionContext the current {@code ExtensionContext} * @param extensionRegistry the {@code ExtensionRegistry} to retrieve * {@code ParameterResolvers} from * @return the array of Objects to be used as parameters in the executable * invocation; never {@code null} though potentially empty */ private Object[] resolveParameters(Executable executable, Optional target, Object outerInstance, ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) { Preconditions.notNull(target, "target must not be null"); Parameter[] parameters = executable.getParameters(); Object[] values = new Object[parameters.length]; int start = 0; // Ensure that the outer instance is resolved as the first parameter if // the executable is a constructor for an inner class. if (outerInstance != null) { values[0] = outerInstance; start = 1; } // Resolve remaining parameters dynamically for (int i = start; i < parameters.length; i++) { ParameterContext parameterContext = new DefaultParameterContext(parameters[i], i, target); values[i] = resolveParameter(parameterContext, executable, extensionContext, extensionRegistry); } return values; } private Object resolveParameter(ParameterContext parameterContext, Executable executable, ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) { try { // @formatter:off List matchingResolvers = extensionRegistry.stream(ParameterResolver.class) .filter(resolver -> resolver.supportsParameter(parameterContext, extensionContext)) .collect(toList()); // @formatter:on if (matchingResolvers.isEmpty()) { throw new ParameterResolutionException( String.format("No ParameterResolver registered for parameter [%s] in executable [%s].", parameterContext.getParameter(), executable.toGenericString())); } if (matchingResolvers.size() > 1) { // @formatter:off String resolverNames = matchingResolvers.stream() .map(resolver -> resolver.getClass().getName()) .collect(joining(", ")); // @formatter:on throw new ParameterResolutionException(String.format( "Discovered multiple competing ParameterResolvers for parameter [%s] in executable [%s]: %s", parameterContext.getParameter(), executable.toGenericString(), resolverNames)); } ParameterResolver resolver = matchingResolvers.get(0); Object value = resolver.resolveParameter(parameterContext, extensionContext); validateResolvedType(parameterContext.getParameter(), value, executable, resolver); logger.trace(() -> String.format( "ParameterResolver [%s] resolved a value of type [%s] for parameter [%s] in executable [%s].", resolver.getClass().getName(), (value != null ? value.getClass().getName() : null), parameterContext.getParameter(), executable.toGenericString())); return value; } catch (ParameterResolutionException ex) { throw ex; } catch (Throwable ex) { throw new ParameterResolutionException(String.format("Failed to resolve parameter [%s] in executable [%s]", parameterContext.getParameter(), executable.toGenericString()), ex); } } private void validateResolvedType(Parameter parameter, Object value, Executable executable, ParameterResolver resolver) { Class type = parameter.getType(); // Note: null is permissible as a resolved value but only for non-primitive types. if (!isAssignableTo(value, type)) { String message; if (value == null && type.isPrimitive()) { message = String.format( "ParameterResolver [%s] resolved a null value for parameter [%s] " + "in executable [%s], but a primitive of type [%s] is required.", resolver.getClass().getName(), parameter, executable.toGenericString(), type.getName()); } else { message = String.format( "ParameterResolver [%s] resolved a value of type [%s] for parameter [%s] " + "in executable [%s], but a value assignment compatible with [%s] is required.", resolver.getClass().getName(), (value != null ? value.getClass().getName() : null), parameter, executable.toGenericString(), type.getName()); } throw new ParameterResolutionException(message); } } }