-
Notifications
You must be signed in to change notification settings - Fork 298
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
resolve method call targets like Reflection API
Instead of having a complicated `JavaMethodCallTarget.resolve()` API that returns a `Set<JavaMethod>` we can follow the same logic as the Reflection API, which always offers one `Method` matching a certain signature. This should make the vast majority of cases a lot simpler, and for diamond scenarios we are following an established precedence logic. Thus, the argument why method x is chosen in a specific case is a lot simpler ("we just follow the established precedence of the Java Reflection API"). Note that this also fixes a bug in the field resolution logic. Earlier accesses of constants in interfaces of parent classes were not resolved correctly (i.e. if we find a field in an interface it should have precedence). Signed-off-by: Peter Gafert <[email protected]>
- Loading branch information
1 parent
8719a69
commit 4e5a726
Showing
28 changed files
with
1,599 additions
and
362 deletions.
There are no files selected for viewing
87 changes: 87 additions & 0 deletions
87
...ava/com/tngtech/archunit/core/importer/ClassFileImporterAccessesNewerJavaVersionTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package com.tngtech.archunit.core.importer; | ||
|
||
import java.lang.annotation.Annotation; | ||
import java.lang.reflect.Method; | ||
|
||
import com.tngtech.archunit.core.domain.AccessTarget.MethodCallTarget; | ||
import com.tngtech.archunit.core.domain.JavaMethod; | ||
import com.tngtech.archunit.core.importer.testexamples.methodresolution.ChildOverridesAllMethods; | ||
import com.tngtech.archunit.core.importer.testexamples.methodresolution.DeterminesMethodAnalogouslyToReflectionApi; | ||
import com.tngtech.archunit.core.importer.testexamples.methodresolution.ExpectedMethod; | ||
import com.tngtech.archunit.core.importer.testexamples.methodresolution.LeftAncestorPrecedesRightAncestor; | ||
import com.tngtech.archunit.core.importer.testexamples.methodresolution.OnlyDefinedInCommonAncestor; | ||
import com.tngtech.archunit.core.importer.testexamples.methodresolution.OnlyLeftAncestorOverridesRootMethod; | ||
import com.tngtech.archunit.core.importer.testexamples.methodresolution.OnlyRightAncestorOverridesRootMethod; | ||
import com.tngtech.java.junit.dataprovider.DataProvider; | ||
import com.tngtech.java.junit.dataprovider.DataProviderRunner; | ||
import com.tngtech.java.junit.dataprovider.UseDataProvider; | ||
import org.junit.Test; | ||
import org.junit.runner.RunWith; | ||
|
||
import static com.google.common.collect.Iterables.getOnlyElement; | ||
import static com.tngtech.archunit.testutil.Assertions.assertThat; | ||
import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach; | ||
|
||
@RunWith(DataProviderRunner.class) | ||
public class ClassFileImporterAccessesNewerJavaVersionTest { | ||
|
||
@DataProvider | ||
public static Object[][] method_resolution_scenarios() { | ||
return testForEach( | ||
OnlyDefinedInCommonAncestor.class, | ||
OnlyLeftAncestorOverridesRootMethod.class, | ||
OnlyRightAncestorOverridesRootMethod.class, | ||
LeftAncestorPrecedesRightAncestor.class, | ||
ChildOverridesAllMethods.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.EquivalentMethodsAreChosenDepthFirst.LeftLeftHasPrecedence.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.EquivalentMethodsAreChosenDepthFirst.LeftRightHasPrecedenceOverRight.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.EquivalentMethodsAreChosenDepthFirst.RightLeftHasPrecedenceOverRightRight.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.EquivalentMethodsAreChosenDepthFirst.RightRightIsPickedIfThereIsNoAlternative.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.EquivalentMethodsAreChosenDepthFirst.LeftOverriddenHasPrecedenceOverParents.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.EquivalentMethodsAreChosenDepthFirst.RightOverriddenHasPrecedenceOverParents.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.EquivalentMethodsAreChosenDepthFirst.LeftLeftHasPrecedenceOverOverriddenRight.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.EquivalentMethodsAreChosenDepthFirst.LeftRightHasPrecedenceOverOverriddenRight.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.EquivalentMethodsAreChosenDepthFirst.LeftOverriddenHasPrecedenceOverRightOverridden.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.ClassHasPrecedenceOverInterface.ParentClassHasPrecedenceOverChildInterfaces.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.ClassHasPrecedenceOverInterface.GrandParentClassHasPrecedenceOverChildInterfaces.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.InterfaceOnParentHasPrecedenceOverInterfaceOnChild.LeftLeftOnGrandparentHasPrecedenceOverAllOthers.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.InterfaceOnParentHasPrecedenceOverInterfaceOnChild.LeftRightOnGrandparentHasPrecedenceOverLeftOnParent.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.InterfaceOnParentHasPrecedenceOverInterfaceOnChild.RightLeftOnGrandparentHasPrecedenceOverLeftAndRightOnParent.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.InterfaceOnParentHasPrecedenceOverInterfaceOnChild.RightRightOnGrandparentHasPrecedenceOverLeftAndRightOnParent.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.MoreSpecificReturnTypeHasPrecedence.RightWithMoreSpecificReturnOnParentTypeHasPrecedenceOverAllOthers.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.MoreSpecificReturnTypeHasPrecedence.LeftWithMoreSpecificReturnTypeHasPrecedenceRightWithMoreSpecificReturnType.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.MoreSpecificReturnTypeHasPrecedence.ParentInterfaceWithMoreSpecificReturnTypeHasPrecedenceOverAllOthers.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.MoreSpecificReturnTypeHasPrecedence.InterfaceWithMoreSpecificReturnTypeHasPrecedenceOverGrandParentClass.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.MoreSpecificReturnTypeHasPrecedence.ParentInterfaceOnParentClassWithMoreSpecificReturnTypeHasPrecedenceOverGrandParentClass.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.MoreSpecificReturnTypeHasPrecedence.GrandParentInterfaceWithMoreSpecificReturnTypeHasPrecedenceOverAllOthers.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.MoreSpecificReturnTypeHasPrecedence.GrandParentInterfaceWithMoreSpecificReturnTypeHasPrecedenceOverFirstParentInterfaceWithMoreSpecificReturnType.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.MoreSpecificReturnTypeHasPrecedence.GrandParentInterfaceWithMoreSpecificReturnTypeHasPrecedenceOverAllParentInterfacesWithMoreSpecificReturnType.class, | ||
DeterminesMethodAnalogouslyToReflectionApi.StaticMethodsInInterfacesAreIgnored.class | ||
); | ||
} | ||
|
||
@Test | ||
@UseDataProvider("method_resolution_scenarios") | ||
public void resolves_method_call_targets(Class<?> scenario) throws NoSuchMethodException { | ||
JavaMethod origin = new ClassFileImporter().importPackagesOf(scenario).get(scenario).getMethod("scenario"); | ||
|
||
MethodCallTarget target = getOnlyElement(origin.getMethodCallsFromSelf()).getTarget(); | ||
|
||
Method methodWithAnnotation = findMethodWithAnnotation(scenario, ExpectedMethod.class); | ||
// Sanity check that the expected method really is the one that would be found via Reflection, too | ||
assertThat(target.getOwner().reflect().getMethod(target.getName())).as("Method resolved via Reflection").isEqualTo(methodWithAnnotation); | ||
|
||
assertThat(target.resolveMember().get()).isEquivalentTo(methodWithAnnotation); | ||
} | ||
|
||
private static Method findMethodWithAnnotation(Class<?> scenario, Class<? extends Annotation> annotationType) { | ||
for (Class<?> nestedClass : scenario.getDeclaredClasses()) { | ||
for (Method method : nestedClass.getMethods()) { | ||
if (method.isAnnotationPresent(annotationType)) { | ||
return method; | ||
} | ||
} | ||
} | ||
throw new IllegalStateException(String.format("No method of nested type in scenario %s is marked as @%s", scenario.getSimpleName(), annotationType.getSimpleName())); | ||
} | ||
} |
Oops, something went wrong.