diff --git a/pom.xml b/pom.xml
index e477f28..9861f7f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
it.aboutbits
archunit-toolbox
- 1.1.0
+ 1.2.0-RC1
Common ArchUnit tooling for Java / Spring Boot projects.
diff --git a/src/main/java/it/aboutbits/archunit/toolbox/ArchitectureTestBase.java b/src/main/java/it/aboutbits/archunit/toolbox/ArchitectureTestBase.java
index b8eb625..8e9050f 100644
--- a/src/main/java/it/aboutbits/archunit/toolbox/ArchitectureTestBase.java
+++ b/src/main/java/it/aboutbits/archunit/toolbox/ArchitectureTestBase.java
@@ -9,6 +9,9 @@
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.SimpleConditionEvent;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.jspecify.annotations.NullMarked;
@@ -24,6 +27,25 @@
@Slf4j
@NullMarked
public abstract class ArchitectureTestBase {
+ protected static final Features FEATURES = new Features();
+
+ @Setter
+ @Getter
+ @Accessors(fluent = true)
+ protected static class Features {
+ private Features.State blacklistMethods = Features.State.ENABLED;
+ private Features.State blacklistClasses = Features.State.ENABLED;
+ private Features.State blacklistAnnotations = Features.State.ENABLED;
+ private Features.State enforceJspecify = Features.State.ENABLED;
+
+ protected Features() {
+ }
+
+ public enum State {
+ ENABLED, DISABLED
+ }
+ }
+
protected static final Set BLACKLISTED_METHODS = new HashSet<>(
Set.of(
// We should use `assertThatExceptionOfType(...).isThrownBy(...)` instead of `assertThatThrownBy(...)`
@@ -194,152 +216,160 @@ void nested_test_classes_have_matching_production_method_name(JavaClasses classe
@SuppressWarnings("unused")
@ArchTest
- static final ArchRule no_blacklisted_methods_are_used = classes()
- .should(new ArchCondition<>("not use blacklisted methods or statically import them") {
- @Override
- public void check(JavaClass javaClass, ConditionEvents events) {
- // Check all method calls from this class
- for (var method : javaClass.getMethods()) {
- for (var methodCall : method.getMethodCallsFromSelf()) {
- var fullMethodName = "%s.%s".formatted(
- methodCall.getTargetOwner().getFullName(),
- methodCall.getTarget().getName()
- );
-
- if (BLACKLISTED_METHODS.contains(fullMethodName)) {
- var message = String.format(
- "Method %s calls blacklisted method %s (%s.java:%d)",
- method.getFullName(),
- fullMethodName,
- javaClass.getSimpleName(),
- methodCall.getSourceCodeLocation().getLineNumber()
- );
- events.add(SimpleConditionEvent.violated(method, message));
+ static final ArchRule no_blacklisted_methods_are_used = FEATURES.blacklistMethods() == Features.State.DISABLED
+ ? new InnertRule()
+ : classes()
+ .should(new ArchCondition<>("not use blacklisted methods or statically import them") {
+ @Override
+ public void check(JavaClass javaClass, ConditionEvents events) {
+ // Check all method calls from this class
+ for (var method : javaClass.getMethods()) {
+ for (var methodCall : method.getMethodCallsFromSelf()) {
+ var fullMethodName = "%s.%s".formatted(
+ methodCall.getTargetOwner().getFullName(),
+ methodCall.getTarget().getName()
+ );
+
+ if (BLACKLISTED_METHODS.contains(fullMethodName)) {
+ var message = String.format(
+ "Method %s calls blacklisted method %s (%s.java:%d)",
+ method.getFullName(),
+ fullMethodName,
+ javaClass.getSimpleName(),
+ methodCall.getSourceCodeLocation().getLineNumber()
+ );
+ events.add(SimpleConditionEvent.violated(method, message));
+ }
+ }
}
- }
- }
- // Check static initializers for method calls
- javaClass.getStaticInitializer().ifPresent(staticInitializer -> {
- for (var methodCall : staticInitializer.getMethodCallsFromSelf()) {
- var fullMethodName = "%s.%s".formatted(
- methodCall.getTargetOwner().getFullName(),
- methodCall.getTarget().getName()
- );
-
- if (BLACKLISTED_METHODS.contains(fullMethodName)) {
- var message = String.format(
- "Static initializer in %s calls blacklisted method %s (%s.java:%d)",
- javaClass.getFullName(),
- fullMethodName,
- javaClass.getSimpleName(),
- methodCall.getSourceCodeLocation().getLineNumber()
- );
- events.add(SimpleConditionEvent.violated(staticInitializer, message));
- }
+ // Check static initializers for method calls
+ javaClass.getStaticInitializer().ifPresent(staticInitializer -> {
+ for (var methodCall : staticInitializer.getMethodCallsFromSelf()) {
+ var fullMethodName = "%s.%s".formatted(
+ methodCall.getTargetOwner().getFullName(),
+ methodCall.getTarget().getName()
+ );
+
+ if (BLACKLISTED_METHODS.contains(fullMethodName)) {
+ var message = String.format(
+ "Static initializer in %s calls blacklisted method %s (%s.java:%d)",
+ javaClass.getFullName(),
+ fullMethodName,
+ javaClass.getSimpleName(),
+ methodCall.getSourceCodeLocation().getLineNumber()
+ );
+ events.add(SimpleConditionEvent.violated(staticInitializer, message));
+ }
+ }
+ });
}
});
- }
- });
@SuppressWarnings("unused")
@ArchTest
- static final ArchRule no_blacklisted_annotations_are_used = classes()
- .should(new ArchCondition<>(
- "not use blacklisted annotations on classes, methods, method parameters, or fields"
- ) {
- @Override
- public void check(JavaClass javaClass, ConditionEvents events) {
- // Check annotations on the class itself
- for (var annotation : javaClass.getAnnotations()) {
- if (BLACKLISTED_ANNOTATIONS.contains(annotation.getRawType().getFullName())) {
- var message = String.format(
- "Class %s is annotated with blacklisted annotation @%s (%s.java:%d)",
- javaClass.getFullName(),
- annotation.getRawType().getFullName(),
- javaClass.getSimpleName(),
- javaClass.getSourceCodeLocation().getLineNumber()
- );
- events.add(SimpleConditionEvent.violated(javaClass, message));
- }
- }
-
- // Check annotations on methods and their parameters
- for (var method : javaClass.getMethods()) {
- // Check method annotations
- for (var annotation : method.getAnnotations()) {
- if (BLACKLISTED_ANNOTATIONS.contains(annotation.getRawType().getFullName())) {
- var message = String.format(
- "Method %s is annotated with blacklisted annotation @%s (%s.java:%d)",
- method.getFullName(),
- annotation.getRawType().getFullName(),
- javaClass.getSimpleName(),
- method.getSourceCodeLocation().getLineNumber()
- );
- events.add(SimpleConditionEvent.violated(method, message));
- }
- }
- // Check method parameter annotations
- for (var parameter : method.getParameters()) {
- for (var annotation : parameter.getAnnotations()) {
+ static final ArchRule no_blacklisted_annotations_are_used = FEATURES.blacklistAnnotations() == Features.State.DISABLED
+ ? new InnertRule()
+ : classes()
+ .should(new ArchCondition<>(
+ "not use blacklisted annotations on classes, methods, method parameters, or fields"
+ ) {
+ @Override
+ public void check(JavaClass javaClass, ConditionEvents events) {
+ // Check annotations on the class itself
+ for (var annotation : javaClass.getAnnotations()) {
if (BLACKLISTED_ANNOTATIONS.contains(annotation.getRawType().getFullName())) {
var message = String.format(
- "Parameter %s of method %s is annotated with blacklisted annotation @%s (%s.java:%d)",
- parameter.getIndex(),
- method.getFullName(),
+ "Class %s is annotated with blacklisted annotation @%s (%s.java:%d)",
+ javaClass.getFullName(),
annotation.getRawType().getFullName(),
javaClass.getSimpleName(),
- method.getSourceCodeLocation().getLineNumber()
- ); // Parameter doesn't have its own SLOC, use method's
- events.add(SimpleConditionEvent.violated(parameter, message));
+ javaClass.getSourceCodeLocation().getLineNumber()
+ );
+ events.add(SimpleConditionEvent.violated(javaClass, message));
}
}
- }
- }
- // Check annotations on fields (ArchUnit includes record components as fields)
- for (var field : javaClass.getFields()) {
- for (var annotation : field.getAnnotations()) {
- if (BLACKLISTED_ANNOTATIONS.contains(annotation.getRawType().getFullName())) {
- var message = String.format(
- "Field %s in class %s is annotated with blacklisted annotation @%s (%s.java:%d)",
- field.getName(),
- javaClass.getFullName(),
- annotation.getRawType().getFullName(),
- javaClass.getSimpleName(),
- field.getSourceCodeLocation().getLineNumber()
- );
- events.add(SimpleConditionEvent.violated(field, message));
+ // Check annotations on methods and their parameters
+ for (var method : javaClass.getMethods()) {
+ // Check method annotations
+ for (var annotation : method.getAnnotations()) {
+ if (BLACKLISTED_ANNOTATIONS.contains(annotation.getRawType().getFullName())) {
+ var message = String.format(
+ "Method %s is annotated with blacklisted annotation @%s (%s.java:%d)",
+ method.getFullName(),
+ annotation.getRawType().getFullName(),
+ javaClass.getSimpleName(),
+ method.getSourceCodeLocation().getLineNumber()
+ );
+ events.add(SimpleConditionEvent.violated(method, message));
+ }
+ }
+ // Check method parameter annotations
+ for (var parameter : method.getParameters()) {
+ for (var annotation : parameter.getAnnotations()) {
+ if (BLACKLISTED_ANNOTATIONS.contains(annotation.getRawType().getFullName())) {
+ var message = String.format(
+ "Parameter %s of method %s is annotated with blacklisted annotation @%s (%s.java:%d)",
+ parameter.getIndex(),
+ method.getFullName(),
+ annotation.getRawType().getFullName(),
+ javaClass.getSimpleName(),
+ method.getSourceCodeLocation().getLineNumber()
+ ); // Parameter doesn't have its own SLOC, use method's
+ events.add(SimpleConditionEvent.violated(parameter, message));
+ }
+ }
+ }
+ }
+
+ // Check annotations on fields (ArchUnit includes record components as fields)
+ for (var field : javaClass.getFields()) {
+ for (var annotation : field.getAnnotations()) {
+ if (BLACKLISTED_ANNOTATIONS.contains(annotation.getRawType().getFullName())) {
+ var message = String.format(
+ "Field %s in class %s is annotated with blacklisted annotation @%s (%s.java:%d)",
+ field.getName(),
+ javaClass.getFullName(),
+ annotation.getRawType().getFullName(),
+ javaClass.getSimpleName(),
+ field.getSourceCodeLocation().getLineNumber()
+ );
+ events.add(SimpleConditionEvent.violated(field, message));
+ }
+ }
}
}
- }
- }
- });
+ });
@SuppressWarnings("unused")
@ArchTest
- static final ArchRule no_blacklisted_classes_are_used = noClasses()
- .should()
- .dependOnClassesThat(
- new DescribedPredicate<>("not use blacklisted classes") {
- @Override
- public boolean test(JavaClass javaClass) {
- return BLACKLISTED_CLASSES.contains(javaClass.getFullName());
- }
- }
- );
+ static final ArchRule no_blacklisted_classes_are_used = FEATURES.blacklistClasses() == Features.State.DISABLED
+ ? new InnertRule()
+ : noClasses()
+ .should()
+ .dependOnClassesThat(
+ new DescribedPredicate<>("not use blacklisted classes") {
+ @Override
+ public boolean test(JavaClass javaClass) {
+ return BLACKLISTED_CLASSES.contains(javaClass.getFullName());
+ }
+ }
+ );
@SuppressWarnings("unused")
@ArchTest
- static final ArchRule top_level_classes_must_be_annotated_with_jspecify = classes()
- .that()
- .areTopLevelClasses()
- .and()
- .areNotAnnotations()
- .should()
- .beAnnotatedWith(org.jspecify.annotations.NullMarked.class)
- .orShould()
- .beAnnotatedWith(org.jspecify.annotations.NullUnmarked.class);
+ static final ArchRule top_level_classes_must_be_annotated_with_jspecify = FEATURES.enforceJspecify() == Features.State.DISABLED
+ ? new InnertRule()
+ : classes()
+ .that()
+ .areTopLevelClasses()
+ .and()
+ .areNotAnnotations()
+ .should()
+ .beAnnotatedWith(org.jspecify.annotations.NullMarked.class)
+ .orShould()
+ .beAnnotatedWith(org.jspecify.annotations.NullUnmarked.class);
/* ****************************************************************** */
diff --git a/src/main/java/it/aboutbits/archunit/toolbox/InnertRule.java b/src/main/java/it/aboutbits/archunit/toolbox/InnertRule.java
new file mode 100644
index 0000000..65d9cbf
--- /dev/null
+++ b/src/main/java/it/aboutbits/archunit/toolbox/InnertRule.java
@@ -0,0 +1,41 @@
+package it.aboutbits.archunit.toolbox;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.lang.ArchRule;
+import com.tngtech.archunit.lang.EvaluationResult;
+import lombok.extern.slf4j.Slf4j;
+import org.jspecify.annotations.NullUnmarked;
+
+@Slf4j
+@NullUnmarked
+final class InnertRule implements ArchRule {
+ @Override
+ public void check(JavaClasses javaClasses) {
+ log.info("Rule disabled by config.");
+ }
+
+ @Override
+ public ArchRule because(String s) {
+ return null;
+ }
+
+ @Override
+ public ArchRule allowEmptyShould(boolean b) {
+ return null;
+ }
+
+ @Override
+ public ArchRule as(String s) {
+ return null;
+ }
+
+ @Override
+ public EvaluationResult evaluate(JavaClasses javaClasses) {
+ return null;
+ }
+
+ @Override
+ public String getDescription() {
+ return "";
+ }
+}