//Execution environment
* AdoptOpenJDK 11.0.9.1+1
* JUnit 5.7.0
* ArchUnit 0.14.1
Day 16 ArchUnit Practice: Enforce visibility of methods called only from the same package to package private or private and Day 17 [ArchUnit Practice: Visibility of methods called only from own class] The two methods that are subject to the architecture test of Force Private (https://qiita.com/drafts/a3dde913894232fa2aa5) are in the inclusion relationship (methods called only from their own class ⊆ methods called only from the same package
).
If you run two tests at the same time, the same method may be detected as a violation in both tests, which can be a hassle to scrutinize the test results, so try to combine them into one test.
package com.example;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaAccess;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.domain.JavaMethod;
import com.tngtech.archunit.core.domain.JavaModifier;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.SimpleConditionEvent;
import org.junit.jupiter.api.Test;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
class ArchitectureTest {
//Class to be inspected
private static final JavaClasses CLASSES =
new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
.importPackages("com.example");
@Test
void Enforce visibility of limited-use methods into package private or private() {
methods()
.should(new ArchCondition<>("be package private or be private, if the scope of use is limited") {
@Override
public void check(final JavaMethod input, final ConditionEvents events) {
Method method = new Method(input);
if (! method.isPrivate()
&& method.isOnlyCalledInDeclaredClass()
) {
//Methods called only from your class should be private
events.add(SimpleConditionEvent.violated(input,
String.format("`%s` should be private.", input.getFullName())));
} else if (! method.isPackagePrivate()
&& method.isOnlyCalledFromClassesInSamePackage()
) {
//Methods called only from the same package should be package private
events.add(SimpleConditionEvent.violated(input,
String.format("`%s` should be package private.", input.getFullName())));
}
}
})
.check(CLASSES);
}
class Method {
private final Set<JavaModifier> modifiers;
private final JavaClass ownerClass;
private final Set<JavaClass> callerClasses;
Method(final JavaMethod method) {
modifiers = method.getModifiers();
ownerClass = method.getOwner();
callerClasses = method.getAccessesToSelf()
.stream()
.map(JavaAccess::getOriginOwner)
.collect(Collectors.toSet());
}
boolean isPrivate() {
return modifiers.contains(JavaModifier.PRIVATE);
}
boolean isPackagePrivate() {
return ! modifiers.contains(JavaModifier.PUBLIC)
&& ! modifiers.contains(JavaModifier.PROTECTED)
&& ! modifiers.contains(JavaModifier.PRIVATE);
}
boolean isOnlyCalledInDeclaredClass() {
return callerClasses
.stream()
.allMatch(callerClass -> callerClass.isEquivalentTo(ownerClass.reflect()));
}
boolean isOnlyCalledFromClassesInSamePackage() {
return callerClasses
.stream()
.allMatch(callerClass -> callerClass.getPackageName().equals(ownerClass.getPackageName()));
}
}
}
Recommended Posts