Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add possibility to check for overriding methods to domain and lang #1200

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,29 @@
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;

import com.tngtech.archunit.PublicAPI;
import com.tngtech.archunit.base.ArchUnitException.InconsistentClassPathException;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.base.MayResolveTypesViaReflection;
import com.tngtech.archunit.base.ResolvesTypesViaReflection;
import com.tngtech.archunit.base.Suppliers;
import com.tngtech.archunit.core.domain.properties.CanBeAnnotated;
import com.tngtech.archunit.core.domain.properties.HasModifiers;
import com.tngtech.archunit.core.domain.properties.HasName;
import com.tngtech.archunit.core.domain.properties.HasOwner;
import com.tngtech.archunit.core.domain.properties.HasParameterTypes;
import com.tngtech.archunit.core.domain.properties.HasReturnType;
import com.tngtech.archunit.core.domain.properties.HasThrowsClause;
import com.tngtech.archunit.core.importer.DomainBuilders;

import static com.google.common.collect.Sets.union;
import static com.tngtech.archunit.PublicAPI.Usage.ACCESS;
import static com.tngtech.archunit.base.DescribedPredicate.describe;
import static com.tngtech.archunit.core.domain.Formatters.formatMethod;
import static com.tngtech.archunit.core.domain.properties.HasName.Utils.namesOf;
import static java.util.stream.Collectors.toList;

@PublicAPI(usage = ACCESS)
public final class JavaMethod extends JavaCodeUnit {
Expand Down Expand Up @@ -126,6 +137,30 @@ public String getDescription() {
return "Method <" + getFullName() + ">";
}

/**
* Returns true, if this method overrides or implements a method from a supertype.
* The annotation {@link Override} may or may not be present on this method.
*
* @see Override
*/
@PublicAPI(usage = ACCESS)
public boolean isOverriding() {
List<JavaClass> supertypes = Stream.concat(
getOwner().getAllRawSuperclasses().stream(),
getOwner().getAllRawInterfaces().stream()
).collect(toList());

String name = getName();
String[] parameterTypes = getRawParameterTypes().stream().map(JavaClass::getFullName).toArray(String[]::new);
for (JavaClass supertype : supertypes) {
if (supertype.tryGetMethod(name, parameterTypes).isPresent()) {
return true;
}
}

return false;
}

@ResolvesTypesViaReflection
@MayResolveTypesViaReflection(reason = "Just part of a bigger resolution process")
private class ReflectMethodSupplier implements Supplier<Method> {
Expand All @@ -141,4 +176,33 @@ public Method get() {
}
}

/**
* Predefined {@link DescribedPredicate predicates} targeting {@link JavaMethod}.
* Note that due to inheritance further predicates for {@link JavaMethod} can be found in the following locations:
* <ul>
* <li>{@link JavaCodeUnit.Predicates}</li>
* <li>{@link JavaMember.Predicates}</li>
* <li>{@link HasName.Predicates}</li>
* <li>{@link HasName.AndFullName.Predicates}</li>
* <li>{@link HasModifiers.Predicates}</li>
* <li>{@link CanBeAnnotated.Predicates}</li>
* <li>{@link HasOwner.Predicates}</li>
* <li>{@link HasParameterTypes.Predicates}</li>
* <li>{@link HasReturnType.Predicates}</li>
* <li>{@link HasThrowsClause.Predicates}</li>
* </ul>
*/
@PublicAPI(usage = ACCESS)
public static final class Predicates {
private Predicates() {
}

/**
* @see JavaMethod#isOverriding()
*/
@PublicAPI(usage = ACCESS)
public static DescribedPredicate<JavaMethod> overriding() {
return describe("overriding", JavaMethod::isOverriding);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
import com.tngtech.archunit.lang.syntax.elements.MethodsShould;
import com.tngtech.archunit.lang.syntax.elements.MethodsShouldConjunction;

import static com.tngtech.archunit.core.domain.JavaMethod.Predicates.overriding;
import static com.tngtech.archunit.lang.conditions.ArchConditions.be;
import static com.tngtech.archunit.lang.conditions.ArchConditions.not;

class MethodsShouldInternal
extends AbstractCodeUnitsShouldInternal<JavaMethod, MethodsShouldInternal>
implements MethodsShould<MethodsShouldInternal>, MethodsShouldConjunction {
Expand Down Expand Up @@ -58,4 +62,14 @@ private MethodsShouldInternal(
MethodsShouldInternal copyWithNewCondition(ConditionAggregator<JavaMethod> newCondition) {
return new MethodsShouldInternal(classesTransformer, priority, newCondition, prepareCondition);
}

@Override
public MethodsShouldInternal beOverriding() {
return addCondition(be(overriding()));
}

@Override
public MethodsShouldInternal notBeOverriding() {
return addCondition(not(be(overriding())));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,33 @@
*/
package com.tngtech.archunit.lang.syntax;

import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaMethod;
import com.tngtech.archunit.lang.syntax.elements.MethodsThat;

import static com.tngtech.archunit.base.DescribedPredicate.not;
import static com.tngtech.archunit.core.domain.JavaMethod.Predicates.overriding;
import static com.tngtech.archunit.lang.conditions.ArchPredicates.are;

class MethodsThatInternal
extends CodeUnitsThatInternal<JavaMethod, GivenMethodsInternal>
implements MethodsThat<GivenMethodsInternal> {

MethodsThatInternal(GivenMethodsInternal givenMethods, PredicateAggregator<JavaMethod> currentPredicate) {
super(givenMethods, currentPredicate);
}

@Override
public GivenMethodsInternal areOverriding() {
return withPredicate(are(overriding()));
}

@Override
public GivenMethodsInternal areNotOverriding() {
return withPredicate(are(not(overriding())));
}

private GivenMethodsInternal withPredicate(DescribedPredicate<JavaMethod> predicate) {
return givenMembers.with(currentPredicate.add(predicate));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.tngtech.archunit.lang.syntax.elements;

import com.tngtech.archunit.PublicAPI;
import com.tngtech.archunit.core.domain.JavaMethod;

import static com.tngtech.archunit.PublicAPI.Usage.ACCESS;

Expand Down Expand Up @@ -53,4 +54,22 @@ public interface MethodsShould<CONJUNCTION extends MethodsShouldConjunction> ext
*/
@PublicAPI(usage = ACCESS)
CONJUNCTION notBeFinal();

/**
* Asserts that methods override other methods.
*
* @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule
* @see JavaMethod#isOverriding()
*/
@PublicAPI(usage = ACCESS)
CONJUNCTION beOverriding();

/**
* Asserts that methods do not override other methods.
*
* @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule
* @see JavaMethod#isOverriding()
*/
@PublicAPI(usage = ACCESS)
CONJUNCTION notBeOverriding();
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.tngtech.archunit.lang.syntax.elements;

import com.tngtech.archunit.PublicAPI;
import com.tngtech.archunit.core.domain.JavaMethod;

import static com.tngtech.archunit.PublicAPI.Usage.ACCESS;

Expand Down Expand Up @@ -53,4 +54,22 @@ public interface MethodsThat<CONJUNCTION> extends CodeUnitsThat<CONJUNCTION> {
*/
@PublicAPI(usage = ACCESS)
CONJUNCTION areNotFinal();

/**
* Matches methods that override other methods.
*
* @return A syntax conjunction element, which can be completed to form a full rule
* @see JavaMethod#isOverriding()
*/
@PublicAPI(usage = ACCESS)
CONJUNCTION areOverriding();

/**
* Matches methods that do not override other methods.
*
* @return A syntax conjunction element, which can be completed to form a full rule
* @see JavaMethod#isOverriding()
*/
@PublicAPI(usage = ACCESS)
CONJUNCTION areNotOverriding();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package com.tngtech.archunit.core.domain;

import java.io.File;

import org.junit.Test;

import static com.tngtech.archunit.core.domain.TestUtils.importMethod;
import static org.assertj.core.api.Assertions.assertThat;

public class JavaMethodTest {

@Test
public void isOverriding_returns_true_for_implemented_interface_method() {
assertThat(importMethod(SomeClass.class, "doSomething").isOverriding())
.as("method is detected as overriding").isTrue();
}

@Test
public void isOverriding_returns_false_for_interface_method() {
assertThat(importMethod(SomeInterface.class, "doSomething").isOverriding())
.as("method is detected as overriding").isFalse();
}

@Test
public void isOverriding_returns_true_for_overriding_method_of_java_lang_Object() {
assertThat(importMethod(SomeClass.class, "toString").isOverriding())
.as("method is detected as overriding").isTrue();
}

@Test
public void isOverriding_returns_false_for_non_overriding_method() {
assertThat(importMethod(SomeClass.class, "doSomethingElse").isOverriding())
.as("method is detected as overriding").isFalse();
}

@Test
public void isOverriding_returns_false_for_non_overriding_overloaded_method() {
assertThat(importMethod(SomeClass.class, "doSomething", Object.class).isOverriding())
.as("method is detected as overriding").isFalse();
}

@Test
public void isOverriding_returns_true_for_concrete_implementation_of_generic_interface_method() {
assertThat(importMethod(SomeClassImplementingGenericsConcrete.class, "genericParam", Object.class).isOverriding())
.as("method is detected as overriding").isTrue();
assertThat(importMethod(SomeClassImplementingGenericsConcrete.class, "genericParam", String.class).isOverriding())
.as("method is detected as overriding").isTrue();
assertThat(importMethod(SomeClassImplementingGenericsConcrete.class, "genericReturnType").isOverriding())
.as("method is detected as overriding").isTrue();
}

@Test
public void isOverriding_returns_true_for_generic_implementation_of_generic_interface_method() {
assertThat(importMethod(SomeClassImplementingGenericsGeneric.class, "genericParam", Object.class).isOverriding())
.as("method is detected as overriding").isTrue();
assertThat(importMethod(SomeClassImplementingGenericsConcrete.class, "genericParam", String.class).isOverriding())
.as("method is detected as overriding").isTrue();
assertThat(importMethod(SomeClassImplementingGenericsGeneric.class, "genericReturnType").isOverriding())
.as("method is detected as overriding").isTrue();
}

@SuppressWarnings("unused")
private interface SomeInterface {

void doSomething();
}

@SuppressWarnings("unused")
private static class SomeClass implements SomeInterface {

@Override
public void doSomething() {
}

public void doSomething(Object o) {
}

public void doSomethingElse() {
}

@Override
public String toString() {
return super.toString();
}
}

@SuppressWarnings("unused")
private interface SomeGenericInterface<T, U> {

void genericParam(T param);

U genericReturnType();
}

private static class SomeClassImplementingGenericsConcrete implements SomeGenericInterface<String, File> {

@Override
public void genericParam(String param) {
}

@Override
public File genericReturnType() {
return null;
}
}

private static class SomeClassImplementingGenericsGeneric<X extends String, Y extends File> implements SomeGenericInterface<X, Y> {

@Override
public void genericParam(X param) {
}

@Override
public Y genericReturnType() {
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ public static Object[][] restricted_property_rule_starts() {
$(described(methods().that().areNotFinal()), ImmutableSet.of(METHOD_C, METHOD_D)),
$(described(methods().that().areStatic()), ImmutableSet.of(METHOD_B, METHOD_D)),
$(described(methods().that().areNotStatic()), ImmutableSet.of(METHOD_A, METHOD_C)),
$(described(methods().that().areOverriding()), ImmutableSet.of(METHOD_A)),
$(described(methods().that().areNotOverriding()), ImmutableSet.of(METHOD_B, METHOD_C, METHOD_D)),
$(described(methods().that().areFinal().and().areStatic()), ImmutableSet.of(METHOD_B)),
$(described(methods().that().areFinal().or().areStatic()), ImmutableSet.of(METHOD_A, METHOD_B, METHOD_D))
);
Expand All @@ -47,7 +49,7 @@ public void property_predicates(DescribedRuleStart ruleStart, Collection<String>
private static final String METHOD_D = "methodD()";

@SuppressWarnings({"unused"})
private static class ClassWithVariousMembers {
private static class ClassWithVariousMembers extends SuperclassWithVariousMembers {
public final void methodA(int[] array) {
}
protected static final void methodB(boolean flag) {
Expand All @@ -58,4 +60,10 @@ static int methodD() {
return 0;
}
}

@SuppressWarnings("unused")
private static class SuperclassWithVariousMembers {
public void methodA(int[] array) {
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public static Object[][] restricted_property_rule_ends() {
$(methods().should().notBeFinal(), ImmutableSet.of(METHOD_A, METHOD_B)),
$(methods().should().beStatic(), ImmutableSet.of(METHOD_A, METHOD_C)),
$(methods().should().notBeStatic(), ImmutableSet.of(METHOD_B, METHOD_D)),
$(methods().should().beOverriding(), ImmutableSet.of(METHOD_B, METHOD_C, METHOD_D)),
$(methods().should().notBeOverriding(), ImmutableSet.of(METHOD_A)),
$(methods().should().notBeFinal().andShould().notBeStatic(), ImmutableSet.of(METHOD_A, METHOD_B, METHOD_D)),
$(methods().should().notBeFinal().orShould().notBeStatic(), ImmutableSet.of(METHOD_B))
);
Expand All @@ -49,7 +51,7 @@ public void property_predicates(ArchRule ruleStart, Collection<String> expectedM
private static final String METHOD_D = "methodD()";

@SuppressWarnings({"unused"})
private static class ClassWithVariousMembers {
private static class ClassWithVariousMembers extends SuperclassWithVariousMembers {
public final void methodA(int[] array) {
}
protected static final void methodB(boolean flag) {
Expand All @@ -60,4 +62,10 @@ static int methodD() {
return 0;
}
}

@SuppressWarnings("unused")
private static class SuperclassWithVariousMembers {
public void methodA(int[] array) {
}
}
}
Loading