From 0507507a563c432392d6c658acdd78b268eab24f Mon Sep 17 00:00:00 2001 From: mnhock Date: Sun, 23 Jun 2024 18:57:49 +0200 Subject: [PATCH] Add Support for Configuration of JavaClasses globally Closes gh-58 --- docs/USERGUIDE.md | 21 ++++++++-- src/main/java/com/enofex/taikai/Taikai.java | 21 +++++++--- .../java/com/enofex/taikai/TaikaiRule.java | 6 ++- .../java/com/enofex/taikai/TaikaiTest.java | 20 +++++++--- .../taikai/logging/LoggingConfigurerTest.java | 38 ++++++------------- 5 files changed, 61 insertions(+), 45 deletions(-) diff --git a/docs/USERGUIDE.md b/docs/USERGUIDE.md index a28cb44..394e84f 100644 --- a/docs/USERGUIDE.md +++ b/docs/USERGUIDE.md @@ -32,7 +32,21 @@ Taikai.builder() .check(); ``` -### 3.2 Enforcing Rules on Empty Sets +### 3.2 Setting the JavaClasses + +You can configure `classes` as well. This allows you to specify specific Java classes to analyze. Note that setting both `namespace` and `classes` simultaneously is not supported and will result in an `IllegalArgumentException`. + +```java +JavaClasses classes = new ClassFileImporter() + .importClasses(ClassToCheck.class) + +Taikai.builder() + .classes(classes) + .build() + .check(); +``` + +### 3.3 Enforcing Rules on Empty Sets The `failOnEmpty` setting determines whether the build should fail if no classes match a given rule. This is useful to ensure that your rules are applied consistently and to avoid false positives. The default is `false`. @@ -44,7 +58,7 @@ Taikai.builder() .check(); ``` -### 3.3 Excluding Classes Globally +### 3.4 Excluding Classes Globally You can globally exclude specific classes from all rule checks by using the `excludeClass` or `excludeClasses` methods in the builder. This ensures that the specified classes are not checked by any rule. @@ -57,7 +71,7 @@ Taikai.builder() .check(); ``` -### 3.4 Modifying an Existing Configuration +### 3.5 Modifying an Existing Configuration The `toBuilder` method allows you to create a new Builder instance from an existing Taikai configuration. This is useful if you need to modify an existing configuration. ```java @@ -83,7 +97,6 @@ Taikai modifiedTaikai = taikai.toBuilder() modifiedTaikai.check(); ``` - ## 4. Rules Overview Taikai's architecture rules cover a wide range of categories to enforce best practices and maintain consistency. diff --git a/src/main/java/com/enofex/taikai/Taikai.java b/src/main/java/com/enofex/taikai/Taikai.java index ca2c4da..e7aa8bb 100644 --- a/src/main/java/com/enofex/taikai/Taikai.java +++ b/src/main/java/com/enofex/taikai/Taikai.java @@ -25,18 +25,24 @@ public final class Taikai { private final boolean failOnEmpty; private final String namespace; + private final JavaClasses classes; private final Set excludedClasses; private final Collection rules; private Taikai(Builder builder) { this.failOnEmpty = builder.failOnEmpty; this.namespace = builder.namespace; + this.classes = builder.classes;; this.excludedClasses = requireNonNullElse(builder.excludedClasses, Collections.emptySet()); this.rules = Stream.concat( builder.configurers.all().stream().flatMap(configurer -> configurer.rules().stream()), builder.rules.stream()) .toList(); + if (this.namespace != null && this.classes != null) { + throw new IllegalArgumentException("Setting namespace and classes are not supported"); + } + ArchConfiguration.get() .setProperty("archRule.failOnEmptyShould", Boolean.toString(this.failOnEmpty)); } @@ -50,11 +56,7 @@ public String namespace() { } public JavaClasses classes() { - return Namespace.withoutTests(this.namespace); - } - - public JavaClasses classesWithTests() { - return Namespace.withTests(this.namespace); + return this.classes; } public Set excludedClasses() { @@ -66,7 +68,7 @@ public Collection rules() { } public void check() { - this.rules.forEach(rule -> rule.check(this.namespace, this.excludedClasses)); + this.rules.forEach(rule -> rule.check(this.namespace, this.classes, this.excludedClasses)); } public static Builder builder() { @@ -84,6 +86,7 @@ public static final class Builder { private final Set excludedClasses; private boolean failOnEmpty; private String namespace; + private JavaClasses classes; public Builder() { this.configurers = new Configurers(); @@ -97,6 +100,7 @@ public Builder(Taikai taikai) { this.excludedClasses = taikai.excludedClasses(); this.failOnEmpty = taikai.failOnEmpty(); this.namespace = taikai.namespace(); + this.classes = taikai.classes(); } public Builder addRule(TaikaiRule rule) { @@ -119,6 +123,11 @@ public Builder namespace(String namespace) { return this; } + public Builder classes(JavaClasses classes) { + this.classes = classes; + return this; + } + public Builder excludeClass(String className) { this.excludedClasses.add(className); return this; diff --git a/src/main/java/com/enofex/taikai/TaikaiRule.java b/src/main/java/com/enofex/taikai/TaikaiRule.java index 681da98..ca6391c 100644 --- a/src/main/java/com/enofex/taikai/TaikaiRule.java +++ b/src/main/java/com/enofex/taikai/TaikaiRule.java @@ -39,12 +39,14 @@ public static TaikaiRule of(ArchRule archRule, Configuration configuration) { } public void check(String globalNamespace) { - check(globalNamespace, Collections.emptySet()); + check(globalNamespace, null, Collections.emptySet()); } - public void check(String globalNamespace, Set excludedClasses) { + public void check(String globalNamespace, JavaClasses classes, Set excludedClasses) { if (this.configuration.javaClasses() != null) { this.archRule.check(this.configuration.javaClasses()); + } else if (classes != null) { + this.archRule.check(classes); } else { String namespace = this.configuration.namespace() != null ? this.configuration.namespace() diff --git a/src/test/java/com/enofex/taikai/TaikaiTest.java b/src/test/java/com/enofex/taikai/TaikaiTest.java index abdd877..054bb88 100644 --- a/src/test/java/com/enofex/taikai/TaikaiTest.java +++ b/src/test/java/com/enofex/taikai/TaikaiTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.any; @@ -16,6 +17,7 @@ import com.enofex.taikai.spring.SpringConfigurer; import com.enofex.taikai.test.TestConfigurer; import com.tngtech.archunit.ArchConfiguration; +import com.tngtech.archunit.core.importer.ClassFileImporter; import java.util.Collection; import java.util.Collections; import java.util.Set; @@ -39,8 +41,7 @@ void shouldBuildTaikaiWithDefaultValues() { assertFalse(taikai.failOnEmpty()); assertEquals(VALID_NAMESPACE, taikai.namespace()); - assertNotNull(taikai.classes()); - assertNotNull(taikai.classesWithTests()); + assertNull(taikai.classes()); assertTrue(taikai.rules().isEmpty()); assertTrue(taikai.excludedClasses().isEmpty()); } @@ -51,7 +52,7 @@ void shouldBuildTaikaiWithCustomValues() { Collection rules = Collections.singletonList(mockRule); Taikai taikai = Taikai.builder() - .namespace(VALID_NAMESPACE) + .classes(new ClassFileImporter().importClasses(TaikaiTest.class)) .excludeClass("com.enofex.taikai.SomeClassToExclude") .excludeClasses( Set.of("com.enofex.taikai.foo.ClassToExclude", "com.enofex.taikai.bar.ClassToExclude")) @@ -60,9 +61,8 @@ void shouldBuildTaikaiWithCustomValues() { .build(); assertTrue(taikai.failOnEmpty()); - assertEquals(VALID_NAMESPACE, taikai.namespace()); + assertNull(taikai.namespace()); assertNotNull(taikai.classes()); - assertNotNull(taikai.classesWithTests()); assertEquals(1, taikai.rules().size()); assertTrue(taikai.rules().contains(mockRule)); assertEquals(3, taikai.excludedClasses().size()); @@ -134,7 +134,7 @@ void shouldCheckRules() { .build() .check(); - verify(mockRule, times(1)).check(VALID_NAMESPACE, Collections.emptySet()); + verify(mockRule, times(1)).check(VALID_NAMESPACE, null, Collections.emptySet()); } @Test @@ -164,4 +164,12 @@ void shouldRebuildTaikaiWithNewValues() { assertTrue( modifiedTaikai.excludedClasses().contains("com.enofex.taikai.AnotherClassToExclude")); } + + @Test + void shouldThrowExceptionIfNamespaceAndClasses() { + assertThrows(IllegalArgumentException.class, () -> Taikai.builder() + .namespace(VALID_NAMESPACE) + .classes(new ClassFileImporter().importClasses(TaikaiTest.class)) + .build()); + } } diff --git a/src/test/java/com/enofex/taikai/logging/LoggingConfigurerTest.java b/src/test/java/com/enofex/taikai/logging/LoggingConfigurerTest.java index cdc8252..a5f3fb3 100644 --- a/src/test/java/com/enofex/taikai/logging/LoggingConfigurerTest.java +++ b/src/test/java/com/enofex/taikai/logging/LoggingConfigurerTest.java @@ -6,7 +6,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.enofex.taikai.Taikai; -import com.enofex.taikai.TaikaiRule; import com.tngtech.archunit.core.importer.ClassFileImporter; import java.util.EnumSet; import java.util.logging.Logger; @@ -16,14 +15,10 @@ class LoggingConfigurerTest { @Test void shouldApplyLoggerConventionsWithClass() { - TaikaiRule.Configuration configuration = TaikaiRule.Configuration.of( - new ClassFileImporter().importClasses(LoggerConventionsFollowed.class)); - Taikai taikai = Taikai.builder() - .namespace("com.enofex.taikai") - .logging(logging -> logging - .loggersShouldFollowConventions(Logger.class, "logger", EnumSet.of(PRIVATE, FINAL), - configuration)) + .classes(new ClassFileImporter().importClasses(LoggerConventionsFollowed.class)) + .logging(logging -> logging.loggersShouldFollowConventions(Logger.class, "logger", + EnumSet.of(PRIVATE, FINAL))) .build(); assertDoesNotThrow(taikai::check); @@ -31,14 +26,11 @@ void shouldApplyLoggerConventionsWithClass() { @Test void shouldApplyLoggerConventionsWithTypeName() { - TaikaiRule.Configuration configuration = TaikaiRule.Configuration.of( - new ClassFileImporter().importClasses(LoggerConventionsFollowed.class)); - Taikai taikai = Taikai.builder() - .namespace("com.enofex.taikai") + .classes(new ClassFileImporter().importClasses(LoggerConventionsFollowed.class)) .logging(logging -> logging .loggersShouldFollowConventions("java.util.logging.Logger", "logger", - EnumSet.of(PRIVATE, FINAL), configuration)) + EnumSet.of(PRIVATE, FINAL))) .build(); assertDoesNotThrow(taikai::check); @@ -46,14 +38,10 @@ void shouldApplyLoggerConventionsWithTypeName() { @Test void shouldThrowLoggerConventionsWithClassNaming() { - TaikaiRule.Configuration configuration = TaikaiRule.Configuration.of( - new ClassFileImporter().importClasses(LoggerConventionsNotFollowedNaming.class)); - Taikai taikai = Taikai.builder() - .namespace("com.enofex.taikai") - .logging(logging -> logging - .loggersShouldFollowConventions(Logger.class, "logger", EnumSet.of(PRIVATE, FINAL), - configuration)) + .classes(new ClassFileImporter().importClasses(LoggerConventionsNotFollowedNaming.class)) + .logging(logging -> logging.loggersShouldFollowConventions(Logger.class, "logger", + EnumSet.of(PRIVATE, FINAL))) .build(); assertThrows(AssertionError.class, () -> taikai.check()); @@ -61,14 +49,10 @@ void shouldThrowLoggerConventionsWithClassNaming() { @Test void shouldThrowLoggerConventionsWithClassModifier() { - TaikaiRule.Configuration configuration = TaikaiRule.Configuration.of( - new ClassFileImporter().importClasses(LoggerConventionsPartiallyModifier.class)); - Taikai taikai = Taikai.builder() - .namespace("com.enofex.taikai") - .logging(logging -> logging - .loggersShouldFollowConventions(Logger.class, "logger", EnumSet.of(PRIVATE, FINAL), - configuration)) + .classes(new ClassFileImporter().importClasses(LoggerConventionsPartiallyModifier.class)) + .logging(logging -> logging.loggersShouldFollowConventions(Logger.class, "logger", + EnumSet.of(PRIVATE, FINAL))) .build(); assertThrows(AssertionError.class, () -> taikai.check());