Skip to content

Commit

Permalink
Treat default methods like locally declared methods
Browse files Browse the repository at this point in the history
Prior to this commit, a non-overridden interface default method would
incorrectly be considered overridden by a locally declared method that
overloads the signature for the default method.

This commit addresses this issue by treating default methods as if they
were locally declared methods.

In addition, this commit improves the documentation for
ReflectionSupport and overhauls the internals of ReflectionUtils in the
following ways.

- removal of dead code
- removal of superfluous precondition checks
- consistent error messages for violated preconditions
- enforcement of immutable return types where applicable
- improved diagnostics for failed parameter type resolution
- stable sorting of methods declared in interfaces

Issue: #976
  • Loading branch information
sbrannen committed Jul 24, 2017
1 parent 90b0043 commit fae5b6a
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 168 deletions.
8 changes: 7 additions & 1 deletion documentation/src/docs/asciidoc/release-notes-5.0.0-RC1.adoc
Expand Up @@ -22,7 +22,10 @@ on GitHub.
* Generic, interface `default` methods that are not overridden by an implementing class
can now be _selected_ by class, method name, and parameter types or by _fully qualified
method name_. This applies to method selectors in `DiscoverySelectors` as well as to
both `findMethod(*)` variants in `ReflectionSupport`.
both `findMethod()` variants in `ReflectionSupport`.
* A non-overridden interface default method whose method signature is overloaded by a
locally declared method is now properly discovered when searching for methods within a
class hierarchy using `findMethods()` in `ReflectionSupport`.

===== Deprecations and Breaking Changes

Expand All @@ -40,6 +43,9 @@ on GitHub.
`@AfterAll`, `BeforeEach`, or `@AfterEach` no longer halt the execution of the entire
test plan during the discovery phase. Rather, such errors are now reported during the
execution of the affected test class.
* A non-overridden interface default method whose method signature is overloaded by a
locally declared method is now properly included in the test plan. This applies to
default methods annotated with Jupiter annotations such as `@Test, `@BeforeEach`, etc.

===== Deprecations and Breaking Changes

Expand Down
Expand Up @@ -22,7 +22,6 @@
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
Expand Down Expand Up @@ -111,8 +110,6 @@ void executeTestCaseWithOverloadedMethodNextToGenericDefaultMethodSelectedByFull
// @formatter:on
}

// TODO [#976] Enable failing @Disabled test.
@Disabled("Disabled until #976 is resolved")
@Test
void executeTestCaseWithOverloadedMethodNextToGenericDefaultMethodSelectedByClass() throws Exception {
Class<?> clazz = GenericTestCaseWithDefaultMethod.class;
Expand Down
Expand Up @@ -36,6 +36,24 @@ private ReflectionSupport() {
}
///CLOVER:ON

/**
* Load a class by its <em>primitive name</em> or <em>fully qualified name</em>,
* using the default {@link ClassLoader}.
*
* <p>Class names for arrays may be specified using either the JVM's internal
* String representation (e.g., {@code [[I} for {@code int[][]},
* {@code [Lava.lang.String;} for {@code java.lang.String[]}, etc.) or
* <em>source code syntax</em> (e.g., {@code int[][]}, {@code java.lang.String[]},
* etc.).
*
* @param name the name of the class to load; never {@code null} or blank
* @return an {@code Optional} containing the loaded class; never {@code null}
* but potentially empty if no such class could be loaded
*/
public static Optional<Class<?>> loadClass(String name) {
return ReflectionUtils.loadClass(name);
}

/**
* Find all {@linkplain Class classes} of the supplied {@code root}
* {@linkplain URI} that match the specified {@code classTester} and
Expand All @@ -44,7 +62,8 @@ private ReflectionSupport() {
* @param root the root URI to start scanning
* @param classTester the class type filter; never {@code null}
* @param classNameFilter the class name filter; never {@code null}
* @return the list of all such classes found; neither {@code null} nor mutable
* @return an immutable list of all such classes found; never {@code null}
* but potentially empty
*/
public static List<Class<?>> findAllClassesInClasspathRoot(URI root, Predicate<Class<?>> classTester,
Predicate<String> classNameFilter) {
Expand All @@ -59,44 +78,47 @@ public static List<Class<?>> findAllClassesInClasspathRoot(URI root, Predicate<C
* @param basePackageName the base package name to start scanning
* @param classTester the class type filter; never {@code null}
* @param classNameFilter the class name filter; never {@code null}
* @return the list of all such classes found; neither {@code null} nor mutable
* @return an immutable list of all such classes found; never {@code null}
* but potentially empty
*/
public static List<Class<?>> findAllClassesInPackage(String basePackageName, Predicate<Class<?>> classTester,
Predicate<String> classNameFilter) {
return ReflectionUtils.findAllClassesInPackage(basePackageName, classTester, classNameFilter);
}

/**
* Find all {@linkplain Method methods} of the supplied class or interface
* that match the specified {@code predicate}.
* Create a new instance of the specified {@link Class} by invoking
* the constructor whose argument list matches the types of the supplied
* arguments.
*
* <p>If you're are looking for methods annotated with a certain annotation
* type, consider using {@linkplain AnnotationSupport#findAnnotatedMethods(Class, Class, HierarchyTraversalMode)}.
* <p>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 or interface in which to find the methods; never {@code null}
* @param predicate the method filter; never {@code null}
* @param traversalMode the hierarchy traversal mode; never {@code null}
* @return the list of all such methods found; neither {@code null} nor mutable
* @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 List<Method> findMethods(Class<?> clazz, Predicate<Method> predicate,
HierarchyTraversalMode traversalMode) {

return ReflectionUtils.findMethods(clazz, predicate,
ReflectionUtils.HierarchyTraversalMode.valueOf(traversalMode.name()));
public static <T> T newInstance(Class<T> clazz, Object... args) {
return ReflectionUtils.newInstance(clazz, args);
}

/**
* Load a class by its <em>primitive name</em> or <em>fully qualified name</em>,
* using the default {@link ClassLoader}.
*
* <p>See {@link ReflectionUtils#loadClass(String, ClassLoader)} for details on
* support for class names for arrays.
* Invoke the supplied method, making it accessible if necessary and
* {@linkplain ExceptionUtils#throwAsUncheckedException masking} any
* checked exception as an unchecked exception.
*
* @param name the name of the class to load; never {@code null} or blank
* @see ReflectionUtils#loadClass(String, ClassLoader)
* @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 Optional<Class<?>> loadClass(String name) {
return ReflectionUtils.loadClass(name);
public static Object invokeMethod(Method method, Object target, Object... args) {
return ReflectionUtils.invokeMethod(method, target, args);
}

/**
Expand Down Expand Up @@ -136,39 +158,23 @@ public static Optional<Method> findMethod(Class<?> clazz, String methodName, Cla
}

/**
* Create a new instance of the specified {@link Class} by invoking
* the constructor whose argument list matches the types of the supplied
* arguments.
* Find all {@linkplain Method methods} of the supplied class or interface
* that match the specified {@code predicate}.
*
* <p>The constructor will be made accessible if necessary, and any checked
* exception will be {@linkplain ExceptionUtils#throwAsUncheckedException masked}
* as an unchecked exception.
* <p>If you're are looking for methods annotated with a certain annotation
* type, consider using {@linkplain AnnotationSupport#findAnnotatedMethods(Class, Class, HierarchyTraversalMode)}.
*
* @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 ReflectionUtils#newInstance(java.lang.reflect.Constructor, Object...)
* @see ExceptionUtils#throwAsUncheckedException(Throwable)
* @param clazz the class or interface in which to find the methods; never {@code null}
* @param predicate the method filter; never {@code null}
* @param traversalMode the hierarchy traversal mode; never {@code null}
* @return an immutable list of all such methods found; never {@code null}
* but potentially empty
*/
public static <T> T newInstance(Class<T> clazz, Object... args) {
return ReflectionUtils.newInstance(clazz, args);
}
public static List<Method> findMethods(Class<?> clazz, Predicate<Method> predicate,
HierarchyTraversalMode traversalMode) {

/**
* 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) {
return ReflectionUtils.invokeMethod(method, target, args);
return ReflectionUtils.findMethods(clazz, predicate,
ReflectionUtils.HierarchyTraversalMode.valueOf(traversalMode.name()));
}

/**
Expand All @@ -177,7 +183,8 @@ public static Object invokeMethod(Method method, Object target, Object... args)
* @param clazz the class to be searched; never {@code null}
* @param predicate the predicate against which the list of nested classes is
* checked; never {@code null}
* @return the list of all such classes found; never {@code null}
* @return an immutable list of all such classes found; never {@code null}
* but potentially empty
*/
public static List<Class<?>> findNestedClasses(Class<?> clazz, Predicate<Class<?>> predicate) {
return ReflectionUtils.findNestedClasses(clazz, predicate);
Expand Down

0 comments on commit fae5b6a

Please sign in to comment.