/*
 * Copyright 2015-2019 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.BlacklistedExceptions;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.commons.util.ReflectionUtils;
import org.junit.platform.commons.util.StringUtils;

ExecutableInvoker encapsulates the invocation of a Executable (i.e., method or constructor), including support for dynamic resolution of method parameters via ParameterResolvers.
Since:5.0
/** * {@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.
Params:
  • constructor – the constructor to invoke and resolve parameters for
  • extensionContext – the current ExtensionContext
  • extensionRegistry – the ExtensionRegistry to retrieve ParameterResolvers from
/** * 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> T invoke(Constructor<T> 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.

Params:
  • constructor – the constructor to invoke and resolve parameters for
  • outerInstance – the outer instance to supply as the first argument to the constructor
  • extensionContext – the current ExtensionContext
  • extensionRegistry – the ExtensionRegistry to retrieve ParameterResolvers from
/** * Invoke the supplied constructor with the supplied outer instance and * dynamic parameter resolution. * * <p>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> T invoke(Constructor<T> constructor, Object outerInstance, ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) { return ReflectionUtils.newInstance(constructor, resolveParameters(constructor, Optional.empty(), outerInstance, extensionContext, extensionRegistry)); }
Invoke the supplied static method with dynamic parameter resolution.
Params:
  • method – the method to invoke and resolve parameters for
  • extensionContext – the current ExtensionContext
  • extensionRegistry – the ExtensionRegistry to retrieve ParameterResolvers from
/** * 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.
Params:
  • method – the method to invoke and resolve parameters for
  • target – the object on which the method will be invoked; should be null for static methods
  • extensionContext – the current ExtensionContext
  • extensionRegistry – the ExtensionRegistry to retrieve ParameterResolvers from
/** * 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<Object> optionalTarget = (target instanceof Optional ? (Optional<Object>) 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.
Params:
  • executable – the executable for which to resolve parameters
  • target – an Optional containing the target on which the executable will be invoked; never null but should be empty for static methods and constructors
  • extensionContext – the current ExtensionContext
  • extensionRegistry – the ExtensionRegistry to retrieve ParameterResolvers from
Returns:the array of Objects to be used as parameters in the executable invocation; never null though potentially empty
/** * 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<Object> 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.
Params:
  • executable – the executable for which to resolve parameters
  • target – an Optional containing the target on which the executable will be invoked; never null but should be empty for static methods and constructors
  • outerInstance – the outer instance that will be supplied as the first argument to a constructor for an inner class; should be null for methods and constructors for top-level or static classes
  • extensionContext – the current ExtensionContext
  • extensionRegistry – the ExtensionRegistry to retrieve ParameterResolvers from
Returns:the array of Objects to be used as parameters in the executable invocation; never null though potentially empty
/** * 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<Object> 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<ParameterResolver> 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 %s [%s].", parameterContext.getParameter(), asLabel(executable), executable.toGenericString())); } if (matchingResolvers.size() > 1) { // @formatter:off String resolvers = matchingResolvers.stream() .map(StringUtils::defaultToString) .collect(joining(", ")); // @formatter:on throw new ParameterResolutionException( String.format("Discovered multiple competing ParameterResolvers for parameter [%s] in %s [%s]: %s", parameterContext.getParameter(), asLabel(executable), executable.toGenericString(), resolvers)); } 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 %s [%s].", resolver.getClass().getName(), (value != null ? value.getClass().getName() : null), parameterContext.getParameter(), asLabel(executable), executable.toGenericString())); return value; } catch (ParameterResolutionException ex) { throw ex; } catch (Throwable throwable) { BlacklistedExceptions.rethrowIfBlacklisted(throwable); String message = String.format("Failed to resolve parameter [%s] in %s [%s]", parameterContext.getParameter(), asLabel(executable), executable.toGenericString()); if (StringUtils.isNotBlank(throwable.getMessage())) { message += ": " + throwable.getMessage(); } throw new ParameterResolutionException(message, throwable); } } 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 %s [%s], but a primitive of type [%s] is required.", resolver.getClass().getName(), parameter, asLabel(executable), executable.toGenericString(), type.getName()); } else { message = String.format( "ParameterResolver [%s] resolved a value of type [%s] for parameter [%s] " + "in %s [%s], but a value assignment compatible with [%s] is required.", resolver.getClass().getName(), (value != null ? value.getClass().getName() : null), parameter, asLabel(executable), executable.toGenericString(), type.getName()); } throw new ParameterResolutionException(message); } } private static String asLabel(Executable executable) { return executable instanceof Constructor ? "constructor" : "method"; } }