Skip to content

Commit

Permalink
add possibility to check for overriding methods to domain and lang
Browse files Browse the repository at this point in the history
Resolves: #1198

Signed-off-by: Roland Weisleder <[email protected]>
  • Loading branch information
rweisleder committed Nov 20, 2023
1 parent 789706f commit c8563d9
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 2 deletions.
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 @@ -125,6 +136,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 @@ -140,4 +175,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,59 @@
package com.tngtech.archunit.core.domain;

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()).isTrue();
}

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

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

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

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

@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();
}
}
}
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) {
}
}
}

0 comments on commit c8563d9

Please sign in to comment.