From dbbd5ff95c5884feaa9b55f703ce9e90d01c5166 Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Sun, 17 Mar 2019 19:24:30 +0100 Subject: [PATCH 01/10] Set master to next SNAPSHOT version Signed-off-by: Peter Gafert --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 1583f036e7..a307c352ae 100644 --- a/build.gradle +++ b/build.gradle @@ -112,7 +112,7 @@ wrapper { allprojects { group = 'com.tngtech.archunit' - version = '0.10.1' + version = '0.11.0-SNAPSHOT' repositories { mavenCentral() From d90822c4b8602e84d14dfc38c55d114fceadcf66 Mon Sep 17 00:00:00 2001 From: Benjamin Asbach Date: Sat, 23 Mar 2019 20:46:34 +0100 Subject: [PATCH 02/10] #162: Added negation for `haveRawType` Signed-off-by: Benjamin Asbach --- .../lang/syntax/FieldsShouldInternal.java | 15 +++++ .../lang/syntax/elements/FieldsShould.java | 60 +++++++++++++++++++ .../syntax/elements/FieldsShouldTest.java | 18 ++++++ 3 files changed, 93 insertions(+) diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/FieldsShouldInternal.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/FieldsShouldInternal.java index 0009188159..e17e12ebf1 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/FieldsShouldInternal.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/FieldsShouldInternal.java @@ -66,13 +66,28 @@ public FieldsShouldInternal haveRawType(Class type) { return addCondition(ArchConditions.haveRawType(type)); } + @Override + public FieldsShouldInternal notHaveRawType(Class type) { + return addCondition(ArchConditions.not(ArchConditions.haveRawType(type))); + } + @Override public FieldsShouldInternal haveRawType(String typeName) { return addCondition(ArchConditions.haveRawType(typeName)); } + @Override + public FieldsShouldInternal notHaveRawType(String typeName) { + return addCondition(ArchConditions.not(ArchConditions.haveRawType(typeName))); + } + @Override public FieldsShouldInternal haveRawType(DescribedPredicate predicate) { return addCondition(ArchConditions.haveRawType(predicate)); } + + @Override + public FieldsShouldInternal notHaveRawType(DescribedPredicate predicate) { + return addCondition(ArchConditions.not(ArchConditions.haveRawType(predicate))); + } } diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/FieldsShould.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/FieldsShould.java index 453708a2d3..86d53c7c03 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/FieldsShould.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/FieldsShould.java @@ -44,6 +44,26 @@ public interface FieldsShould exten @PublicAPI(usage = ACCESS) CONJUNCTION haveRawType(Class type); + /** + * Asserts that fields do not have a certain raw type. + *

+ * E.g. + *

+     * {@link ArchRuleDefinition#fields() fields()}.{@link GivenFields#should() should()}.{@link FieldsShould#notHaveRawType(Class) notHaveRawType(String.class)}
+     * 
+ * would be violated by someField in + * + *

+     * class Example {
+     *     Object someField;
+     * }
+ * + * @param type Type fields should not have + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION notHaveRawType(Class type); + /** * Asserts that fields have a certain fully qualified name of their raw type. *

@@ -64,6 +84,26 @@ public interface FieldsShould exten @PublicAPI(usage = ACCESS) CONJUNCTION haveRawType(String typeName); + /** + * Asserts that fields do not have a certain fully qualified name of their raw type. + *

+ * E.g. + *

+     * {@link ArchRuleDefinition#fields() fields()}.{@link GivenFields#should() should()}.{@link FieldsShould#notHaveRawType(String) notHaveRawType(String.class.getName())}
+     * 
+ * would be violated by someField in + * + *

+     * class Example {
+     *     Object someField;
+     * }
+ * + * @param typeName Name of type fields should not have + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION notHaveRawType(String typeName); + /** * Asserts that fields have a raw type matching the given predicate. *

@@ -83,4 +123,24 @@ public interface FieldsShould exten */ @PublicAPI(usage = ACCESS) CONJUNCTION haveRawType(DescribedPredicate predicate); + + /** + * Asserts that fields do not have a raw type matching the given predicate. + *

+ * E.g. + *

+     * {@link ArchRuleDefinition#fields() fields()}.{@link GivenFields#should() should()}.{@link FieldsShould#notHaveRawType(DescribedPredicate) notHaveRawType(assignableTo(Serializable.class))}
+     * 
+ * would be violated by someField in + * + *

+     * class Example {
+     *     Object someField;
+     * }
+ * + * @param predicate A predicate determining which sort of types fields should not have + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION notHaveRawType(DescribedPredicate predicate); } diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/FieldsShouldTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/FieldsShouldTest.java index c07010c211..2614e2e6e3 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/FieldsShouldTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/FieldsShouldTest.java @@ -60,6 +60,24 @@ public void property_predicates(ArchRule rule) { assertThat(actualFields).containsOnly(FIELD_B, FIELD_C, FIELD_D); } + @DataProvider + public static Object[][] restricted_property_rule_ends2() { + return testForEach( + fields().should().notHaveRawType(String.class), + fields().should().notHaveRawType(String.class.getName()), + fields().should().notHaveRawType(equivalentTo(String.class).as(String.class.getName()))); + } + + @Test + @UseDataProvider("restricted_property_rule_ends2") + public void property_predicates2(ArchRule rule) { + EvaluationResult result = rule + .evaluate(importClasses(ClassWithVariousMembers.class)); + + Set actualFields = parseMembers(ClassWithVariousMembers.class, result.getFailureReport().getDetails()); + assertThat(actualFields).containsOnly(FIELD_A); + } + private static final String FIELD_A = "fieldA"; private static final String FIELD_B = "fieldB"; private static final String FIELD_C = "fieldC"; From 6f479632d5e0d493aa06d0c845df03965eb78c81 Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Sun, 24 Mar 2019 14:52:27 +0100 Subject: [PATCH 03/10] Review: - in the Javadoc the field type must be String to be violated - combined the two tests by parameterizing the expected violations Signed-off-by: Peter Gafert --- .../lang/syntax/FieldsShouldInternal.java | 8 ++-- .../lang/syntax/elements/FieldsShould.java | 6 +-- .../syntax/elements/FieldsShouldTest.java | 38 +++++++------------ 3 files changed, 21 insertions(+), 31 deletions(-) diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/FieldsShouldInternal.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/FieldsShouldInternal.java index e17e12ebf1..f65db9b1cd 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/FieldsShouldInternal.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/FieldsShouldInternal.java @@ -26,6 +26,8 @@ import com.tngtech.archunit.lang.syntax.elements.FieldsShould; import com.tngtech.archunit.lang.syntax.elements.FieldsShouldConjunction; +import static com.tngtech.archunit.lang.conditions.ArchConditions.not; + class FieldsShouldInternal extends AbstractMembersShouldInternal implements FieldsShould, FieldsShouldConjunction { @@ -68,7 +70,7 @@ public FieldsShouldInternal haveRawType(Class type) { @Override public FieldsShouldInternal notHaveRawType(Class type) { - return addCondition(ArchConditions.not(ArchConditions.haveRawType(type))); + return addCondition(not(ArchConditions.haveRawType(type))); } @Override @@ -78,7 +80,7 @@ public FieldsShouldInternal haveRawType(String typeName) { @Override public FieldsShouldInternal notHaveRawType(String typeName) { - return addCondition(ArchConditions.not(ArchConditions.haveRawType(typeName))); + return addCondition(not(ArchConditions.haveRawType(typeName))); } @Override @@ -88,6 +90,6 @@ public FieldsShouldInternal haveRawType(DescribedPredicate pr @Override public FieldsShouldInternal notHaveRawType(DescribedPredicate predicate) { - return addCondition(ArchConditions.not(ArchConditions.haveRawType(predicate))); + return addCondition(not(ArchConditions.haveRawType(predicate))); } } diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/FieldsShould.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/FieldsShould.java index 86d53c7c03..772039de22 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/FieldsShould.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/FieldsShould.java @@ -55,7 +55,7 @@ public interface FieldsShould exten * *

      * class Example {
-     *     Object someField;
+     *     String someField;
      * }
* * @param type Type fields should not have @@ -95,7 +95,7 @@ public interface FieldsShould exten * *

      * class Example {
-     *     Object someField;
+     *     String someField;
      * }
* * @param typeName Name of type fields should not have @@ -135,7 +135,7 @@ public interface FieldsShould exten * *

      * class Example {
-     *     Object someField;
+     *     String someField;
      * }
* * @param predicate A predicate determining which sort of types fields should not have diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/FieldsShouldTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/FieldsShouldTest.java index 2614e2e6e3..8ccb082487 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/FieldsShouldTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/FieldsShouldTest.java @@ -1,9 +1,11 @@ package com.tngtech.archunit.lang.syntax.elements; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; +import com.google.common.collect.ImmutableList; import com.tngtech.archunit.lang.ArchRule; import com.tngtech.archunit.lang.EvaluationResult; import com.tngtech.java.junit.dataprovider.DataProvider; @@ -19,7 +21,8 @@ import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.areNoFieldsWithType; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.assertViolation; import static com.tngtech.archunit.lang.syntax.elements.MembersShouldTest.parseMembers; -import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach; +import static com.tngtech.java.junit.dataprovider.DataProviders.$; +import static com.tngtech.java.junit.dataprovider.DataProviders.$$; import static org.assertj.core.api.Assertions.assertThat; @RunWith(DataProviderRunner.class) @@ -44,38 +47,23 @@ public void complex_field_syntax() { @DataProvider public static Object[][] restricted_property_rule_ends() { - return testForEach( - fields().should().haveRawType(String.class), - fields().should().haveRawType(String.class.getName()), - fields().should().haveRawType(equivalentTo(String.class).as(String.class.getName()))); + return $$( + $(fields().should().haveRawType(String.class), ImmutableList.of(FIELD_B, FIELD_C, FIELD_D)), + $(fields().should().haveRawType(String.class.getName()), ImmutableList.of(FIELD_B, FIELD_C, FIELD_D)), + $(fields().should().haveRawType(equivalentTo(String.class).as(String.class.getName())), ImmutableList.of(FIELD_B, FIELD_C, FIELD_D)), + $(fields().should().notHaveRawType(String.class), ImmutableList.of(FIELD_A)), + $(fields().should().notHaveRawType(String.class.getName()), ImmutableList.of(FIELD_A)), + $(fields().should().notHaveRawType(equivalentTo(String.class).as(String.class.getName())), ImmutableList.of(FIELD_A))); } @Test @UseDataProvider("restricted_property_rule_ends") - public void property_predicates(ArchRule rule) { + public void property_predicates(ArchRule rule, Collection expectedViolatingFields) { EvaluationResult result = rule .evaluate(importClasses(ClassWithVariousMembers.class)); Set actualFields = parseMembers(ClassWithVariousMembers.class, result.getFailureReport().getDetails()); - assertThat(actualFields).containsOnly(FIELD_B, FIELD_C, FIELD_D); - } - - @DataProvider - public static Object[][] restricted_property_rule_ends2() { - return testForEach( - fields().should().notHaveRawType(String.class), - fields().should().notHaveRawType(String.class.getName()), - fields().should().notHaveRawType(equivalentTo(String.class).as(String.class.getName()))); - } - - @Test - @UseDataProvider("restricted_property_rule_ends2") - public void property_predicates2(ArchRule rule) { - EvaluationResult result = rule - .evaluate(importClasses(ClassWithVariousMembers.class)); - - Set actualFields = parseMembers(ClassWithVariousMembers.class, result.getFailureReport().getDetails()); - assertThat(actualFields).containsOnly(FIELD_A); + assertThat(actualFields).containsOnlyElementsOf(expectedViolatingFields); } private static final String FIELD_A = "fieldA"; From ce6618a8f5a30f07e18997c59a4645cbe8e51de4 Mon Sep 17 00:00:00 2001 From: Manfred Hanke Date: Tue, 19 Mar 2019 18:29:01 +0100 Subject: [PATCH 04/10] add archunit-example with fields() Signed-off-by: Manfred Hanke --- .../exampletest/junit4/CodingRulesTest.java | 12 +++++++ .../exampletest/junit5/CodingRulesTest.java | 12 +++++++ .../example/ClassViolatingCodingRules.java | 2 +- .../archunit/exampletest/CodingRulesTest.java | 15 ++++++++- .../integration/ExamplesIntegrationTest.java | 7 ++++ .../archunit/testutils/ExpectedField.java | 32 +++++++++++++++++++ .../archunit/testutils/ExpectedMethod.java | 22 ++++++------- 7 files changed, 88 insertions(+), 14 deletions(-) create mode 100644 archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedField.java diff --git a/archunit-example/example-junit4/src/test/java/com/tngtech/archunit/exampletest/junit4/CodingRulesTest.java b/archunit-example/example-junit4/src/test/java/com/tngtech/archunit/exampletest/junit4/CodingRulesTest.java index 49fb6b4015..d0c11bfef0 100644 --- a/archunit-example/example-junit4/src/test/java/com/tngtech/archunit/exampletest/junit4/CodingRulesTest.java +++ b/archunit-example/example-junit4/src/test/java/com/tngtech/archunit/exampletest/junit4/CodingRulesTest.java @@ -1,6 +1,7 @@ package com.tngtech.archunit.exampletest.junit4; import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.domain.JavaModifier; import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; import com.tngtech.archunit.junit.ArchUnitRunner; @@ -9,6 +10,9 @@ import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; +import java.util.logging.Logger; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; import static com.tngtech.archunit.library.GeneralCodingRules.ACCESS_STANDARD_STREAMS; import static com.tngtech.archunit.library.GeneralCodingRules.NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS; @@ -35,6 +39,14 @@ private void no_access_to_standard_streams_as_method(JavaClasses classes) { @ArchTest private final ArchRule no_java_util_logging = NO_CLASSES_SHOULD_USE_JAVA_UTIL_LOGGING; + @ArchTest + private final ArchRule loggers_should_be_private_static_final = + fields().that().haveRawType(Logger.class) + .should().bePrivate() + .andShould().haveModifier(JavaModifier.STATIC) + .andShould().haveModifier(JavaModifier.FINAL) + .because("we agreed on this convention"); + @ArchTest private final ArchRule no_jodatime = NO_CLASSES_SHOULD_USE_JODATIME; diff --git a/archunit-example/example-junit5/src/test/java/com/tngtech/archunit/exampletest/junit5/CodingRulesTest.java b/archunit-example/example-junit5/src/test/java/com/tngtech/archunit/exampletest/junit5/CodingRulesTest.java index 90a1ff8646..7b3d57a8b6 100644 --- a/archunit-example/example-junit5/src/test/java/com/tngtech/archunit/exampletest/junit5/CodingRulesTest.java +++ b/archunit-example/example-junit5/src/test/java/com/tngtech/archunit/exampletest/junit5/CodingRulesTest.java @@ -1,12 +1,16 @@ package com.tngtech.archunit.exampletest.junit5; import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.domain.JavaModifier; import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTag; import com.tngtech.archunit.junit.ArchTest; import com.tngtech.archunit.lang.ArchRule; import com.tngtech.archunit.lang.CompositeArchRule; +import java.util.logging.Logger; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; import static com.tngtech.archunit.library.GeneralCodingRules.ACCESS_STANDARD_STREAMS; import static com.tngtech.archunit.library.GeneralCodingRules.NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS; @@ -32,6 +36,14 @@ private void no_access_to_standard_streams_as_method(JavaClasses classes) { @ArchTest private final ArchRule no_java_util_logging = NO_CLASSES_SHOULD_USE_JAVA_UTIL_LOGGING; + @ArchTest + private final ArchRule loggers_should_be_private_static_final = + fields().that().haveRawType(Logger.class) + .should().bePrivate() + .andShould().haveModifier(JavaModifier.STATIC) + .andShould().haveModifier(JavaModifier.FINAL) + .because("we agreed on this convention"); + @ArchTest private final ArchRule no_jodatime = NO_CLASSES_SHOULD_USE_JODATIME; diff --git a/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/ClassViolatingCodingRules.java b/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/ClassViolatingCodingRules.java index c47c4db5c2..a135821ba1 100644 --- a/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/ClassViolatingCodingRules.java +++ b/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/ClassViolatingCodingRules.java @@ -6,7 +6,7 @@ import java.util.logging.Logger; public class ClassViolatingCodingRules { - private static final Logger log = Logger.getLogger("Wrong Logger"); // Violates rule not to use java.util.logging + static Logger log = Logger.getLogger("Wrong Logger"); // Violates rules not to use java.util.logging & that loggers should be private static final public void printToStandardStream() throws FileNotFoundException { System.out.println("I'm gonna print to the command line"); // Violates rule not to write to standard streams diff --git a/archunit-example/example-plain/src/test/java/com/tngtech/archunit/exampletest/CodingRulesTest.java b/archunit-example/example-plain/src/test/java/com/tngtech/archunit/exampletest/CodingRulesTest.java index d95ba392fb..2f0d712039 100644 --- a/archunit-example/example-plain/src/test/java/com/tngtech/archunit/exampletest/CodingRulesTest.java +++ b/archunit-example/example-plain/src/test/java/com/tngtech/archunit/exampletest/CodingRulesTest.java @@ -1,12 +1,16 @@ package com.tngtech.archunit.exampletest; import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.domain.JavaModifier; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.example.ClassViolatingCodingRules; import com.tngtech.archunit.lang.CompositeArchRule; import org.junit.Test; import org.junit.experimental.categories.Category; +import java.util.logging.Logger; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; import static com.tngtech.archunit.library.GeneralCodingRules.ACCESS_STANDARD_STREAMS; import static com.tngtech.archunit.library.GeneralCodingRules.NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS; @@ -39,6 +43,16 @@ public void classes_should_not_use_java_util_logging() { NO_CLASSES_SHOULD_USE_JAVA_UTIL_LOGGING.check(classes); } + @Test + public void loggers_should_be_private_static_final() { + fields().that().haveRawType(Logger.class) + .should().bePrivate() + .andShould().haveModifier(JavaModifier.STATIC) + .andShould().haveModifier(JavaModifier.FINAL) + .because("we agreed on this convention") + .check(classes); + } + @Test public void classes_should_not_use_jodatime() { NO_CLASSES_SHOULD_USE_JODATIME.check(classes); @@ -49,5 +63,4 @@ public void no_classes_should_access_standard_streams_or_throw_generic_exception CompositeArchRule.of(NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS) .and(NO_CLASSES_SHOULD_THROW_GENERIC_EXCEPTIONS).check(classes); } - } diff --git a/archunit-integration-test/src/test/java/com/tngtech/archunit/integration/ExamplesIntegrationTest.java b/archunit-integration-test/src/test/java/com/tngtech/archunit/integration/ExamplesIntegrationTest.java index 3b0f759b25..6b9c24d1c3 100644 --- a/archunit-integration-test/src/test/java/com/tngtech/archunit/integration/ExamplesIntegrationTest.java +++ b/archunit-integration-test/src/test/java/com/tngtech/archunit/integration/ExamplesIntegrationTest.java @@ -15,6 +15,7 @@ import com.google.common.base.Joiner; import com.google.common.collect.ImmutableSet; +import com.tngtech.archunit.core.domain.JavaModifier; import com.tngtech.archunit.example.AbstractController; import com.tngtech.archunit.example.ClassViolatingCodingRules; import com.tngtech.archunit.example.ClassViolatingSessionBeanRules; @@ -108,6 +109,7 @@ import com.tngtech.archunit.testutils.CyclicErrorMatcher; import com.tngtech.archunit.testutils.ExpectedClass; import com.tngtech.archunit.testutils.ExpectedConstructor; +import com.tngtech.archunit.testutils.ExpectedField; import com.tngtech.archunit.testutils.ExpectedMethod; import com.tngtech.archunit.testutils.ExpectedTestFailures; import com.tngtech.archunit.testutils.MessageAssertionChain; @@ -196,6 +198,11 @@ Stream CodingRulesTest() { expectAccessToStandardStreams(expectFailures); expectThrownGenericExceptions(expectFailures); + expectFailures.ofRule("fields that have raw type java.util.logging.Logger should be private " + + "and should have modifier STATIC and should have modifier FINAL, because we agreed on this convention") + .by(ExpectedField.of(ClassViolatingCodingRules.class, "log").doesNotHaveModifier(JavaModifier.PRIVATE)) + .by(ExpectedField.of(ClassViolatingCodingRules.class, "log").doesNotHaveModifier(JavaModifier.FINAL)); + return expectFailures.toDynamicTests(); } diff --git a/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedField.java b/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedField.java new file mode 100644 index 0000000000..e64dd586e7 --- /dev/null +++ b/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedField.java @@ -0,0 +1,32 @@ +package com.tngtech.archunit.testutils; + +import com.tngtech.archunit.core.domain.JavaModifier; + +import static java.lang.String.format; + +public class ExpectedField { + + public static ExpectedField.Creator of(Class owner, String fieldName) { + return new ExpectedField.Creator(owner, fieldName); + } + + public static class Creator { + private final Class clazz; + private final String fieldName; + + private Creator(Class clazz, String fieldName) { + this.clazz = clazz; + this.fieldName = fieldName; + } + + public ExpectedMessage doesNotHaveModifier(JavaModifier modifier) { + return field("does not have modifier " + modifier); + } + + private ExpectedMessage field(String message) { + String fieldDescription = format("Field <%s.%s>", clazz.getName(), fieldName); + String sourceCodeLocation = format("(%s.java:0)", clazz.getSimpleName()); + return new ExpectedMessage(format("%s %s in %s", fieldDescription, message, sourceCodeLocation)); + } + } +} diff --git a/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedMethod.java b/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedMethod.java index fb353f8eb1..8c65625edf 100644 --- a/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedMethod.java +++ b/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedMethod.java @@ -4,6 +4,7 @@ import com.tngtech.archunit.core.domain.JavaClass; +import static java.lang.String.format; import static com.tngtech.archunit.core.domain.Formatters.formatMethod; public class ExpectedMethod { @@ -23,24 +24,21 @@ private Creator(Class clazz, String methodName, Class[] params) { } public ExpectedMessage toNotHaveRawReturnType(Class type) { - return new ExpectedMessage(String.format("Method <%s> does not have raw return type %s in (%s.java:0)", - formatMethod(clazz.getName(), methodName, JavaClass.namesOf(params)), - type.getName(), - clazz.getSimpleName())); + return method("does not have raw return type " + type.getName()); } public ExpectedMessage throwsException(Class type) { - return new ExpectedMessage(String.format("Method <%s> does declare throwable of type %s in (%s.java:0)", - formatMethod(clazz.getName(), methodName, JavaClass.namesOf(params)), - type.getName(), - clazz.getSimpleName())); + return method("does declare throwable of type " + type.getName()); } public ExpectedMessage beingAnnotatedWith(Class annotationType) { - return new ExpectedMessage(String.format("Method <%s> is annotated with @%s in (%s.java:0)", - formatMethod(clazz.getName(), methodName, JavaClass.namesOf(params)), - annotationType.getSimpleName(), - clazz.getSimpleName())); + return method("is annotated with @" + annotationType.getSimpleName()); + } + + private ExpectedMessage method(String message) { + String methodDescription = format("Method <%s>", formatMethod(clazz.getName(), methodName, JavaClass.namesOf(params))); + String sourceCodeLocation = format("(%s.java:0)", clazz.getSimpleName()); + return new ExpectedMessage(format("%s %s in %s", methodDescription, message, sourceCodeLocation)); } } } From 85c5d06060c59021903142d781d90cd8fca04cc3 Mon Sep 17 00:00:00 2001 From: Manfred Hanke Date: Mon, 18 Mar 2019 23:01:22 +0100 Subject: [PATCH 05/10] improve userguide * replace reference to @Deprecated methods getType() & getParameters() * replace reference to @Deprecated ImportOptions DONT_INCLUDE_JARS & DONT_INCLUDE_TESTS * domain-overview diagram: - indicate that JavaStaticInitializer does not have a ThrowsClause - add multiplicities * mention ArchRuleDefinition's other methods to compose member rules * remove a bunch of (AFAIK) superfluous commas ;-) Signed-off-by: Manfred Hanke --- docs/userguide/005_Ideas_and_Concepts.adoc | 15 +- docs/userguide/006_The_Core_API.adoc | 53 ++++--- docs/userguide/007_The_Lang_API.adoc | 74 ++++----- docs/userguide/008_The_Library_API.adoc | 6 +- docs/userguide/009_JUnit_Support.adoc | 6 +- .../userguide/010_Advanced_Configuration.adoc | 4 +- docs/userguide/html/000_Index.html | 148 +++++++++--------- docs/userguide/html/domain-overview.png | Bin 30541 -> 40709 bytes 8 files changed, 159 insertions(+), 147 deletions(-) diff --git a/docs/userguide/005_Ideas_and_Concepts.adoc b/docs/userguide/005_Ideas_and_Concepts.adoc index 3543ff25c3..6ea48a07aa 100644 --- a/docs/userguide/005_Ideas_and_Concepts.adoc +++ b/docs/userguide/005_Ideas_and_Concepts.adoc @@ -9,13 +9,14 @@ section will explain these layers in more detail. === Core -Much of ArchUnit's core API resembles the Java Reflection API. There are classes -like `JavaMethod`, `JavaField`, and more, and the public API consists of methods like -`getName()`, `getMethods()`, `getType()` or `getParameters()`. Additionally ArchUnit extends -this API for concepts needed to talk about dependencies between code, like `JavaMethodCall`, -`JavaConstructorCall` or `JavaFieldAccess`. For example, it is possible to programmatically -iterate over `javaClass.getAccessesFromSelf()` and react to the imported accesses between this -Java class and other Java classes. +Much of ArchUnit's core API resembles the Java Reflection API. +There are classes like `JavaMethod`, `JavaField`, and more, +and the public API consists of methods like `getName()`, `getMethods()`, +`getRawType()` or `getRawParameterTypes()`. +Additionally ArchUnit extends this API for concepts needed to talk about dependencies between code, +like `JavaMethodCall`, `JavaConstructorCall` or `JavaFieldAccess`. +For example, it is possible to programmatically iterate over `javaClass.getAccessesFromSelf()` +and react to the imported accesses between this Java class and other Java classes. To import compiled Java class files, ArchUnit provides the `ClassFileImporter`, which can for example be used to import packages from the classpath: diff --git a/docs/userguide/006_The_Core_API.adoc b/docs/userguide/006_The_Core_API.adoc index 7bc044f1d0..b5ad00b512 100644 --- a/docs/userguide/006_The_Core_API.adoc +++ b/docs/userguide/006_The_Core_API.adoc @@ -33,7 +33,7 @@ is imported. This can be achieved by specifying `ImportOptions`: ImportOption ignoreTests = new ImportOption() { @Override public boolean includes(Location location) { - return !location.contains("/test/"); // ignore any URI to sources, that contains '/test/' + return !location.contains("/test/"); // ignore any URI to sources that contains '/test/' } }; @@ -52,21 +52,21 @@ there already exist predefined `ImportOptions`: [source,java,options="nowrap"] ---- new ClassFileImporter() - .withImportOption(ImportOption.Predefined.DONT_INCLUDE_JARS) - .withImportOption(ImportOption.Predefined.DONT_INCLUDE_TESTS) + .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS) + .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) .importClasspath(); ---- ==== Dealing with Missing Classes While importing the requested classes (e.g. `target/classes` or `target/test-classes`) -it can happen, that a class within the scope of the import has a reference to a class outside of the +it can happen that a class within the scope of the import has a reference to a class outside of the scope of the import. This will naturally happen, if the classes of the JDK are not imported, since then for example any dependency on `Object.class` will be unresolved within the import. At this point ArchUnit needs to decide how to treat these classes that are missing from the import. By default, ArchUnit searches within the classpath for missing classes and if found -imports them. This obviously has the advantage, that information about those classes +imports them. This obviously has the advantage that information about those classes (which interfaces they implement, how they are annotated) is present during rule evaluation. On the downside this additional lookup from the classpath will cost some performance and in some @@ -115,18 +115,19 @@ class JavaFieldAccess class JavaConstructorCall class JavaMethodCall -JavaPackage *--* JavaClass : has -JavaClass *-- JavaMember : has +JavaPackage *--* "1..*" JavaClass : has +JavaClass *-- "0..*" JavaMember : has JavaMember <|-- JavaField : extends JavaMember <|-- JavaCodeUnit : extends -JavaCodeUnit *-left- ThrowsClause : has JavaCodeUnit <|-- JavaConstructor : extends JavaCodeUnit <|-- JavaMethod : extends JavaCodeUnit <|-- JavaStaticInitializer : extends +JavaConstructor *-- "1" ThrowsClause : has +JavaMethod *-- "1" ThrowsClause : has -JavaCodeUnit *-- JavaFieldAccess : has -JavaCodeUnit *-- JavaMethodCall : has -JavaCodeUnit *-- JavaConstructorCall : has +JavaCodeUnit *-- "0..*" JavaFieldAccess : has +JavaCodeUnit *-- "0..*" JavaMethodCall : has +JavaCodeUnit *-- "0..*" JavaConstructorCall : has ---- Most objects resemble the Java Reflection API, including inheritance relations. Thus a `JavaClass` @@ -137,7 +138,7 @@ calls 'code unit', and is in fact either a method, a constructor (including the or a static initializer of a class (e.g. a `static { ... }` block, a static field assignment, etc.). -Furthermore one of the most interesting features of ArchUnit, that exceeds the Java Reflection API, +Furthermore one of the most interesting features of ArchUnit that exceeds the Java Reflection API, is the concept of accesses to another class. On the lowest level accesses can only take place from a code unit (as mentioned, any block of executable code) to either a field (`JavaFieldAccess`), a method (`JavaMethodCall`) or constructor (`JavaConstructorCall`). @@ -146,8 +147,8 @@ ArchUnit imports the whole graph of classes and their relationship to each other the accesses *from* a class is pretty isolated (the bytecode offers all this information), checking accesses *to* a class requires the whole graph to be built first. To distinguish which sort of access is referred to, methods will always clearly state *fromSelf* and *toSelf*. -For example, every `JavaField` allows to call `JavaField#getAccessesToSelf()`, to retrieve all -code units within the graph, that access this specific field. The resolution process through +For example, every `JavaField` allows to call `JavaField#getAccessesToSelf()` to retrieve all +code units within the graph that access this specific field. The resolution process through inheritance is not completely straight forward. Consider for example [plantuml, "resolution-example"] @@ -180,7 +181,7 @@ ClassAccessing o-- ClassBeingAccessed The bytecode will record a field access from `ClassAccessing.accessField()` to `ClassBeingAccessed.accessedField`. However, there is no such field, since the field is -actually declared in the superclass. This is the reason, that a `JavaFieldAccess` +actually declared in the superclass. This is the reason why a `JavaFieldAccess` has no `JavaField` as its target, but a `FieldAccessTarget`. In other words, ArchUnit models the situation, as it is found within the bytecode, and an access target is not an actual member within another class. If a member is queried for `accessesToSelf()` though, ArchUnit @@ -223,14 +224,14 @@ ConstructorCallTarget "1" -- "0..1" JavaConstructor : resolves to Two things might seem strange at the first look. -First, why can a target resolve to zero matching members? The reason is, that the set of classes +First, why can a target resolve to zero matching members? The reason is that the set of classes that was imported does not need to have all classes involved within this resolution process. Consider the above example, if `SuperClassBeingAccessed` would not be imported, ArchUnit would -have no way of knowing, where the actual targeted field resides. Thus in this case the +have no way of knowing where the actual targeted field resides. Thus in this case the resolution would return zero elements. Second, why can there be more than one resolved methods for method calls? -The reason for this is, that a call target might indeed match several methods in those +The reason for this is that a call target might indeed match several methods in those cases, for example: [plantuml, "diamond-example"] @@ -265,7 +266,7 @@ D -right- C : calls targetMethod() ---- While this situation will always be resolved in a specified way for a real program, -ArchUnit can not do the same. Instead, the resolution will report all candidates that match a +ArchUnit cannot do the same. Instead, the resolution will report all candidates that match a specific access target, so in the above example, the call target `C.targetMethod()` would in fact resolve to two `JavaMethods`, namely `A.targetMethod()` and `B.targetMethod()`. Likewise a check of either `A.targetMethod.getCallsToSelf()` or `B.targetMethod.getCallsToSelf()` would return @@ -273,13 +274,13 @@ the same call from `D.callTargetMethod()` to `C.targetMethod()`. ==== Domain Objects, Reflection and the Classpath -ArchUnit tries to offer a lot of information from the bytecode, for example a `JavaClass` -provides details like if it is an Enum or an Interface, modifiers like `public` or `abstract`, +ArchUnit tries to offer a lot of information from the bytecode. For example, a `JavaClass` +provides details like if it is an enum or an interface, modifiers like `public` or `abstract`, but also the source, where this class was imported from (namely the URI mentioned in the first section). However, if information if missing, and the classpath is correct, ArchUnit offers some convenience to rely on the reflection API for extended details. For this reason, most -`Java*`-Objects offer a method `reflect()`, which will in fact try to resolve the respective -object from the Reflection API. For example +`Java*` objects offer a method `reflect()`, which will in fact try to resolve the respective +object from the Reflection API. For example: [source,java,options="nowrap"] ---- @@ -299,8 +300,8 @@ Method lengthMethod = javaMethod.reflect(); However, this will throw an `Exception`, if the respective classes are missing on the classpath (e.g. because they were just imported from some file path). -This restriction also applies to handling Annotations in a more convenient way. -Consider some Annotation +This restriction also applies to handling annotations in a more convenient way. +Consider the following annotation: [source,java,options="nowrap"] ---- @@ -309,7 +310,7 @@ Consider some Annotation } ---- -If you need to access this annotation, without this annotation on the classpath you must rely on +If you need to access this annotation without it being on the classpath, you must rely on [source,java,options="nowrap"] ---- diff --git a/docs/userguide/007_The_Lang_API.adoc b/docs/userguide/007_The_Lang_API.adoc index ff78dd0b2d..2102b13b2e 100644 --- a/docs/userguide/007_The_Lang_API.adoc +++ b/docs/userguide/007_The_Lang_API.adoc @@ -33,11 +33,11 @@ for (JavaClass service : services) { What we want to express, is the rule _"no classes that reside in a package 'service' should access classes that reside in a package 'controller'"_. Nevertheless, it's hard to read through -that code and distill that information. And the same process has to be done every time, someone +that code and distill that information. And the same process has to be done every time someone needs to understand the semantics of this rule. To solve this shortcoming, ArchUnit offers a high level API to express architectural concepts -in a concise way. In fact, we can write code, that is almost equivalent to the prose rule text +in a concise way. In fact, we can write code that is almost equivalent to the prose rule text mentioned before: [source,java,options="nowrap"] @@ -49,9 +49,9 @@ ArchRule rule = ArchRuleDefinition.noClasses() rule.check(importedClasses); ---- -The only difference to colloquial language, are the ".." in the package notation, +The only difference to colloquial language is the ".." in the package notation, which refers to any number of packages. Thus "..service.." just expresses -"any package that contains some sub-package 'service'", e.g. `com.myapp.service.any`. +_"any package that contains some sub-package 'service'"_, e.g. `com.myapp.service.any`. If this test fails, it will report an `AssertionError` with the following message: [source,bash] @@ -82,8 +82,8 @@ rule.check(importedClasses); === Composing Member Rules In addition to a predefined API to write rules about Java classes and their relations, there is -an extended API to define rules for members of Java classes. This might be relevant for example, -if methods in a certain context need to be annotated with a specific annotation or return +an extended API to define rules for members of Java classes. This might be relevant, for example, +if methods in a certain context need to be annotated with a specific annotation, or return types implementing a certain interface. The entry point is again `ArchRuleDefinition`, e.g. [source,java,options="nowrap"] @@ -96,6 +96,9 @@ ArchRule rule = ArchRuleDefinition.methods() rule.check(importedClasses); ---- +Besides `methods()`, `ArchRuleDefinition` offers the methods `members()`, `fields()`, `codeUnits()`, `constructors()` +– and the corresponding negations `noMembers()`, `noFields()`, `noMethods()`, etc. + === Creating Custom Rules In fact, most architectural rules take the form @@ -105,10 +108,10 @@ In fact, most architectural rules take the form classes that ${PREDICATE} should ${CONDITION} ---- -In other words, we always want to limit imported classes to a relevant subset, and then -evaluate some condition to see that all those classes satisfy it. -ArchUnit's API allows you, to do just that, by exposing the concepts of `DescribedPredicate` -and `ArchCondition`. So the rule above, is just an application of this generic API: +In other words, we always want to limit imported classes to a relevant subset, +and then evaluate some condition to see that all those classes satisfy it. +ArchUnit's API allows you to do just that, by exposing the concepts of `DescribedPredicate` and `ArchCondition`. +So the rule above is just an application of this generic API: [source,java,options="nowrap"] ---- @@ -119,8 +122,9 @@ noClasses().that(resideInAPackageService) .should(accessClassesThatResideInAPackageController); ---- -Thus, if the predefined API does not allow to express some concept, it is possible to extend -it in any custom way, for example: +Thus, if the predefined API does not allow to express some concept, +it is possible to extend it in any custom way. +For example: [source,java,options="nowrap"] ---- @@ -160,10 +164,9 @@ classes that have a field annotated with @Payload should only be accessed by @Se === Predefined Predicates and Conditions -Often custom predicates and conditions like in the last section can be composed from -predefined elements. ArchUnit's basic convention for predicates is, that they are defined -in an inner class `Predicates` within the type they target. For example, one can find the -predicate to check for the simple name of a `JavaClass` as +Custom predicates and conditions like in the last section can often be composed from predefined elements. +ArchUnit's basic convention for predicates is that they are defined in an inner class `Predicates` within the type they target. +For example, one can find the predicate to check for the simple name of a `JavaClass` as [source,java,options="nowrap"] ---- @@ -184,9 +187,10 @@ DescribedPredicate serializableNamedFoo = ---- Note that for some properties, there exist interfaces with predicates defined for them. -For example the property to have a name is represented by the interface `HasName`, consequently -the predicate to check the name of a `JavaClass`, is the same as the predicate to check the name -of a `JavaMethod` and resides within +For example the property to have a name is represented by the interface `HasName`; +consequently the predicate to check the name of a `JavaClass` +is the same as the predicate to check the name of a `JavaMethod`, +and resides within [source,java,options="nowrap"] ---- @@ -210,12 +214,12 @@ DescribedPredicate name = HasName.Predicates.name("").forSubType(); name.and(JavaClass.Predicates.type(Serializable.class)); ---- -This behavior is somewhat tedious, but unfortunately it is a shortcoming of the Java -type system, that cannot be circumvented in a satisfying way. +This behavior is somewhat tedious, but unfortunately it is a shortcoming of the Java type system +that cannot be circumvented in a satisfying way. -Just like predicates, there exist predefined conditions, that can be combined in a similar -way. Since `ArchCondition` is a less generic concept, all predefined conditions can be found -within `ArchConditions`: +Just like predicates, there exist predefined conditions that can be combined in a similar way. +Since `ArchCondition` is a less generic concept, all predefined conditions can be found within `ArchConditions`. +Examples: [source,java,options="nowrap"] ---- @@ -229,7 +233,7 @@ ArchCondition callEqualsOrHashCode = callEquals.or(callHashCode); === Rules with Custom Concepts -Earlier we stated, that most architectural rules take the form +Earlier we stated that most architectural rules take the form [source] ---- @@ -240,7 +244,7 @@ However, we do not always talk about classes, if we express architectural concep have custom language, we might talk about modules, about slices, or on the other hand more detailed about fields, methods or constructors. A generic API will never be able to support every imaginable concept out of the box. Thus ArchUnit's rule API has at its foundation -a more generic API, that controls the types of objects that our concept targets. +a more generic API that controls the types of objects that our concept targets. [plantuml, "import-vs-lang"] ---- @@ -262,7 +266,7 @@ CustomObjects -right->[passed to] "ArchRule and ArchCondition" ---- -To achieve this, any rule definition is based on a `ClassesTransformer` that defines, how +To achieve this, any rule definition is based on a `ClassesTransformer` that defines how `JavaClasses` are to be transformed to the desired rule input. In many cases, like the ones mentioned in the sections above, this is the identity transformation, passing classes on to the rule as they are. However, one can supply any custom transformation to express a rule about a @@ -307,18 +311,18 @@ all(businessModules).that(dealWithOrders).should(beIndependentOfPayment); If the rule is straight forward, the rule text that is created automatically should be sufficient in many cases. However, for rules that are not common knowledge, it is good practice -to document the reason for this rule. This can be done the following way: +to document the reason for this rule. This can be done in the following way: [source,java,options="nowrap"] ---- classes().that(haveAFieldAnnotatedWithPayload).should(onlyBeAccessedBySecuredMethods) - .because("@Secured methods will be intercepted, checking for increased priviledges " + + .because("@Secured methods will be intercepted, checking for increased privileges " + "and obfuscating sensitive auditing information"); ---- -Nevertheless sometimes the generated rule text might not convey the real intention -concisely enough (e.g. if multiple predicates or conditions are joined). In those cases -it is possible, to completely override the rule text: +Nevertheless, the generated rule text might sometimes not convey the real intention +concisely enough, e.g. if multiple predicates or conditions are joined. +It is possible to completely overwrite the rule description in those cases: [source,java,options="nowrap"] ---- @@ -329,10 +333,10 @@ classes().that(haveAFieldAnnotatedWithPayload).should(onlyBeAccessedBySecuredMet === Ignoring Violations In legacy projects there might be too many violations to fix at once. Nevertheless, that code -should be covered completely by architecture tests, to ensure that no further violations will +should be covered completely by architecture tests to ensure that no further violations will be added to the existing code. One approach to ignore existing violations is -to tailor the `that(..)` clause of the rules in question, to ignore certain violations. -A more generic approach is, to ignore violations based on simple regex matches. +to tailor the `that(..)` clause of the rules in question to ignore certain violations. +A more generic approach is to ignore violations based on simple regex matches. For this one can put a file named `archunit_ignore_patterns.txt` in the root of the classpath. Every line will be interpreted as a regular expression and checked against reported violations. Violations with a message matching the pattern will be ignored. If no violations are left, diff --git a/docs/userguide/008_The_Library_API.adoc b/docs/userguide/008_The_Library_API.adoc index 0d75300d60..afbd77cff0 100644 --- a/docs/userguide/008_The_Library_API.adoc +++ b/docs/userguide/008_The_Library_API.adoc @@ -1,12 +1,12 @@ == The Library API -The Library API offers a growing collection of predefined rules, that offer a more concise API +The Library API offers a growing collection of predefined rules, which offer a more concise API for more complex but common patterns, like a layered architecture or checks for cycles between slices (compare <>). === Architectures -The entrance point for checks of common architectural styles is +The entrance point for checks of common architectural styles is: [source,java,options="nowrap"] ---- @@ -20,7 +20,7 @@ architecture, pipes and filters, separation of business logic and technical infr === Slices Currently there are two "slice" rules offered by the Library API. These are basically rules -that slice the code by packages, and contain assertions on those slices. The entrance point is +that slice the code by packages, and contain assertions on those slices. The entrance point is: [source,java,options="nowrap"] ---- diff --git a/docs/userguide/009_JUnit_Support.adoc b/docs/userguide/009_JUnit_Support.adoc index e448903431..a1010ee72c 100644 --- a/docs/userguide/009_JUnit_Support.adoc +++ b/docs/userguide/009_JUnit_Support.adoc @@ -65,7 +65,7 @@ public class ArchitectureTest { The `JavaClass` cache will work in two ways. On the one hand it will cache the classes by test, so they can be reused by several rules declared within the same class. On the other hand, it -will cache the classes by location, so a second test, that wants to import classes from the same +will cache the classes by location, so a second test that wants to import classes from the same URLs will reuse the classes previously imported as well. Note that this second caching uses soft references, so the classes will be dropped from memory, if the heap runs low. For further information see <>. @@ -110,7 +110,7 @@ production code, and only consider code that is directly supplied and does not c [source,java,options="nowrap"] ---- -@AnalyzeClasses(importOptions = {DontIncludeTests.class, DontIncludeJars.class}) +@AnalyzeClasses(importOptions = {DoNotIncludeTests.class, DoNotIncludeJars.class}) ---- As explained in <>, you can write your own custom implementation of `ImportOption` @@ -123,7 +123,7 @@ test class runs imported Java classes will be reused, if the exact combination o been imported. If the heap runs low, and thus the garbage collector has to do a big sweep in one run, -this can cause a noticeable delay. On the other hand, if it is known, that no other test class will +this can cause a noticeable delay. On the other hand, if it is known that no other test class will reuse the imported Java classes, it would make sense to deactivate this cache. This can be achieved by configuring `CacheMode.PER_CLASS`, e.g. diff --git a/docs/userguide/010_Advanced_Configuration.adoc b/docs/userguide/010_Advanced_Configuration.adoc index b971cf155f..010ad84841 100644 --- a/docs/userguide/010_Advanced_Configuration.adoc +++ b/docs/userguide/010_Advanced_Configuration.adoc @@ -18,7 +18,7 @@ resolveMissingDependenciesFromClassPath=false If you want to resolve just some classes from the classpath (e.g. to import missing classes from your own organization but avoid the performance impact of importing classes from 3rd party packages), -it is possible, to configure only specific packages to be resolved from the classpath: +it is possible to configure only specific packages to be resolved from the classpath: [source,options="nowrap"] .archunit.properties @@ -61,7 +61,7 @@ For further details, compare the sources of `SelectedClassResolverFromClasspath` === MD5 Sums of Classes -Sometimes it can be valuable to record the MD5 sums of classes being imported, to track +Sometimes it can be valuable to record the MD5 sums of classes being imported to track unexpected behavior. Since this has a performance impact, it is disabled by default, but it can be activated the following way: diff --git a/docs/userguide/html/000_Index.html b/docs/userguide/html/000_Index.html index 12cf53c6a9..45eb8302e2 100644 --- a/docs/userguide/html/000_Index.html +++ b/docs/userguide/html/000_Index.html @@ -1016,13 +1016,14 @@

5

5.1. Core

-

Much of ArchUnit’s core API resembles the Java Reflection API. There are classes -like JavaMethod, JavaField, and more, and the public API consists of methods like -getName(), getMethods(), getType() or getParameters(). Additionally ArchUnit extends -this API for concepts needed to talk about dependencies between code, like JavaMethodCall, -JavaConstructorCall or JavaFieldAccess. For example, it is possible to programmatically -iterate over javaClass.getAccessesFromSelf() and react to the imported accesses between this -Java class and other Java classes.

+

Much of ArchUnit’s core API resembles the Java Reflection API. +There are classes like JavaMethod, JavaField, and more, +and the public API consists of methods like getName(), getMethods(), +getRawType() or getRawParameterTypes(). +Additionally ArchUnit extends this API for concepts needed to talk about dependencies between code, +like JavaMethodCall, JavaConstructorCall or JavaFieldAccess. +For example, it is possible to programmatically iterate over javaClass.getAccessesFromSelf() +and react to the imported accesses between this Java class and other Java classes.

To import compiled Java class files, ArchUnit provides the ClassFileImporter, which can @@ -1127,7 +1128,7 @@

6.1. Import

ImportOption ignoreTests = new ImportOption() {
     @Override
     public boolean includes(Location location) {
-        return !location.contains("/test/"); // ignore any URI to sources, that contains '/test/'
+        return !location.contains("/test/"); // ignore any URI to sources that contains '/test/'
     }
 };
 
@@ -1155,8 +1156,8 @@ 

6.1. Import

new ClassFileImporter()
-    .withImportOption(ImportOption.Predefined.DONT_INCLUDE_JARS)
-    .withImportOption(ImportOption.Predefined.DONT_INCLUDE_TESTS)
+    .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS)
+    .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
     .importClasspath();
@@ -1164,14 +1165,14 @@

6.1. Import

6.1.1. Dealing with Missing Classes

While importing the requested classes (e.g. target/classes or target/test-classes) -it can happen, that a class within the scope of the import has a reference to a class outside of the +it can happen that a class within the scope of the import has a reference to a class outside of the scope of the import. This will naturally happen, if the classes of the JDK are not imported, since then for example any dependency on Object.class will be unresolved within the import.

At this point ArchUnit needs to decide how to treat these classes that are missing from the import. By default, ArchUnit searches within the classpath for missing classes and if found -imports them. This obviously has the advantage, that information about those classes +imports them. This obviously has the advantage that information about those classes (which interfaces they implement, how they are annotated) is present during rule evaluation.

@@ -1198,7 +1199,7 @@

6.2. Domain

-domain overview +domain overview
@@ -1211,7 +1212,7 @@

6.2. Domain

etc.).

-

Furthermore one of the most interesting features of ArchUnit, that exceeds the Java Reflection API, +

Furthermore one of the most interesting features of ArchUnit that exceeds the Java Reflection API, is the concept of accesses to another class. On the lowest level accesses can only take place from a code unit (as mentioned, any block of executable code) to either a field (JavaFieldAccess), a method (JavaMethodCall) or constructor (JavaConstructorCall).

@@ -1221,8 +1222,8 @@

6.2. Domain

the accesses from a class is pretty isolated (the bytecode offers all this information), checking accesses to a class requires the whole graph to be built first. To distinguish which sort of access is referred to, methods will always clearly state fromSelf and toSelf. -For example, every JavaField allows to call JavaField#getAccessesToSelf(), to retrieve all -code units within the graph, that access this specific field. The resolution process through +For example, every JavaField allows to call JavaField#getAccessesToSelf() to retrieve all +code units within the graph that access this specific field. The resolution process through inheritance is not completely straight forward. Consider for example

@@ -1233,7 +1234,7 @@

6.2. Domain

The bytecode will record a field access from ClassAccessing.accessField() to ClassBeingAccessed.accessedField. However, there is no such field, since the field is -actually declared in the superclass. This is the reason, that a JavaFieldAccess +actually declared in the superclass. This is the reason why a JavaFieldAccess has no JavaField as its target, but a FieldAccessTarget. In other words, ArchUnit models the situation, as it is found within the bytecode, and an access target is not an actual member within another class. If a member is queried for accessesToSelf() though, ArchUnit @@ -1249,15 +1250,15 @@

6.2. Domain

Two things might seem strange at the first look.

-

First, why can a target resolve to zero matching members? The reason is, that the set of classes +

First, why can a target resolve to zero matching members? The reason is that the set of classes that was imported does not need to have all classes involved within this resolution process. Consider the above example, if SuperClassBeingAccessed would not be imported, ArchUnit would -have no way of knowing, where the actual targeted field resides. Thus in this case the +have no way of knowing where the actual targeted field resides. Thus in this case the resolution would return zero elements.

Second, why can there be more than one resolved methods for method calls? -The reason for this is, that a call target might indeed match several methods in those +The reason for this is that a call target might indeed match several methods in those cases, for example:

@@ -1267,7 +1268,7 @@

6.2. Domain

While this situation will always be resolved in a specified way for a real program, -ArchUnit can not do the same. Instead, the resolution will report all candidates that match a +ArchUnit cannot do the same. Instead, the resolution will report all candidates that match a specific access target, so in the above example, the call target C.targetMethod() would in fact resolve to two JavaMethods, namely A.targetMethod() and B.targetMethod(). Likewise a check of either A.targetMethod.getCallsToSelf() or B.targetMethod.getCallsToSelf() would return @@ -1276,13 +1277,13 @@

6.2. Domain

6.2.1. Domain Objects, Reflection and the Classpath

-

ArchUnit tries to offer a lot of information from the bytecode, for example a JavaClass -provides details like if it is an Enum or an Interface, modifiers like public or abstract, +

ArchUnit tries to offer a lot of information from the bytecode. For example, a JavaClass +provides details like if it is an enum or an interface, modifiers like public or abstract, but also the source, where this class was imported from (namely the URI mentioned in the first section). However, if information if missing, and the classpath is correct, ArchUnit offers some convenience to rely on the reflection API for extended details. For this reason, most -Java*-Objects offer a method reflect(), which will in fact try to resolve the respective -object from the Reflection API. For example

+Java* objects offer a method reflect(), which will in fact try to resolve the respective +object from the Reflection API. For example:

To solve this shortcoming, ArchUnit offers a high level API to express architectural concepts -in a concise way. In fact, we can write code, that is almost equivalent to the prose rule text +in a concise way. In fact, we can write code that is almost equivalent to the prose rule text mentioned before:

-

The only difference to colloquial language, are the ".." in the package notation, +

The only difference to colloquial language is the ".." in the package notation, which refers to any number of packages. Thus "..service.." just expresses -"any package that contains some sub-package 'service'", e.g. com.myapp.service.any. +"any package that contains some sub-package 'service'", e.g. com.myapp.service.any. If this test fails, it will report an AssertionError with the following message:

@@ -1436,8 +1437,8 @@

7.2. Composing Member Rules

In addition to a predefined API to write rules about Java classes and their relations, there is -an extended API to define rules for members of Java classes. This might be relevant for example, -if methods in a certain context need to be annotated with a specific annotation or return +an extended API to define rules for members of Java classes. This might be relevant, for example, +if methods in a certain context need to be annotated with a specific annotation, or return types implementing a certain interface. The entry point is again ArchRuleDefinition, e.g.

7.3. Creating Custom Rules

@@ -1462,10 +1467,10 @@

-

In other words, we always want to limit imported classes to a relevant subset, and then -evaluate some condition to see that all those classes satisfy it. -ArchUnit’s API allows you, to do just that, by exposing the concepts of DescribedPredicate -and ArchCondition. So the rule above, is just an application of this generic API:

+

In other words, we always want to limit imported classes to a relevant subset, +and then evaluate some condition to see that all those classes satisfy it. +ArchUnit’s API allows you to do just that, by exposing the concepts of DescribedPredicate and ArchCondition. +So the rule above is just an application of this generic API:

-

Thus, if the predefined API does not allow to express some concept, it is possible to extend -it in any custom way, for example:

+

Thus, if the predefined API does not allow to express some concept, +it is possible to extend it in any custom way. +For example:

@@ -1521,10 +1527,9 @@

7.4. Predefined Predicates and Conditions

-

Often custom predicates and conditions like in the last section can be composed from -predefined elements. ArchUnit’s basic convention for predicates is, that they are defined -in an inner class Predicates within the type they target. For example, one can find the -predicate to check for the simple name of a JavaClass as

+

Custom predicates and conditions like in the last section can often be composed from predefined elements. +ArchUnit’s basic convention for predicates is that they are defined in an inner class Predicates within the type they target. +For example, one can find the predicate to check for the simple name of a JavaClass as

-

Just like predicates, there exist predefined conditions, that can be combined in a similar -way. Since ArchCondition is a less generic concept, all predefined conditions can be found -within ArchConditions:

+

Just like predicates, there exist predefined conditions that can be combined in a similar way. +Since ArchCondition is a less generic concept, all predefined conditions can be found within ArchConditions. +Examples:

@@ -1597,7 +1603,7 @@

7.5. Rules with Custom Concepts

-

Earlier we stated, that most architectural rules take the form

+

Earlier we stated that most architectural rules take the form

@@ -1609,7 +1615,7 @@

classes().that(haveAFieldAnnotatedWithPayload).should(onlyBeAccessedBySecuredMethods)
-    .because("@Secured methods will be intercepted, checking for increased priviledges " +
+    .because("@Secured methods will be intercepted, checking for increased privileges " +
         "and obfuscating sensitive auditing information");
-

Nevertheless sometimes the generated rule text might not convey the real intention -concisely enough (e.g. if multiple predicates or conditions are joined). In those cases -it is possible, to completely override the rule text:

+

Nevertheless, the generated rule text might sometimes not convey the real intention +concisely enough, e.g. if multiple predicates or conditions are joined. +It is possible to completely overwrite the rule description in those cases:

@@ -1690,10 +1696,10 @@

7.7. Ignoring Violations

In legacy projects there might be too many violations to fix at once. Nevertheless, that code -should be covered completely by architecture tests, to ensure that no further violations will +should be covered completely by architecture tests to ensure that no further violations will be added to the existing code. One approach to ignore existing violations is -to tailor the that(..) clause of the rules in question, to ignore certain violations. -A more generic approach is, to ignore violations based on simple regex matches. +to tailor the that(..) clause of the rules in question to ignore certain violations. +A more generic approach is to ignore violations based on simple regex matches. For this one can put a file named archunit_ignore_patterns.txt in the root of the classpath. Every line will be interpreted as a regular expression and checked against reported violations. Violations with a message matching the pattern will be ignored. If no violations are left, @@ -1730,14 +1736,14 @@

8. The Library API

-

The Library API offers a growing collection of predefined rules, that offer a more concise API +

The Library API offers a growing collection of predefined rules, which offer a more concise API for more complex but common patterns, like a layered architecture or checks for cycles between slices (compare What to Check).

8.1. Architectures

-

The entrance point for checks of common architectural styles is

+

The entrance point for checks of common architectural styles is:

@@ -1754,7 +1760,7 @@

8.1. Archit

8.2. Slices

Currently there are two "slice" rules offered by the Library API. These are basically rules -that slice the code by packages, and contain assertions on those slices. The entrance point is

+that slice the code by packages, and contain assertions on those slices. The entrance point is:

@@ -2021,7 +2027,7 @@

9.1.1. Writ

The JavaClass cache will work in two ways. On the one hand it will cache the classes by test, so they can be reused by several rules declared within the same class. On the other hand, it -will cache the classes by location, so a second test, that wants to import classes from the same +will cache the classes by location, so a second test that wants to import classes from the same URLs will reuse the classes previously imported as well. Note that this second caching uses soft references, so the classes will be dropped from memory, if the heap runs low. For further information see Controlling the Cache.

@@ -2071,7 +2077,7 @@

-
@AnalyzeClasses(importOptions = {DontIncludeTests.class, DontIncludeJars.class})
+
@AnalyzeClasses(importOptions = {DoNotIncludeTests.class, DoNotIncludeJars.class})

@@ -2088,7 +2094,7 @@

If the heap runs low, and thus the garbage collector has to do a big sweep in one run, -this can cause a noticeable delay. On the other hand, if it is known, that no other test class will +this can cause a noticeable delay. On the other hand, if it is known that no other test class will reuse the imported Java classes, it would make sense to deactivate this cache.

archunit.properties
@@ -2246,7 +2252,7 @@

10.2. MD5 Sums of Classes

-

Sometimes it can be valuable to record the MD5 sums of classes being imported, to track +

Sometimes it can be valuable to record the MD5 sums of classes being imported to track unexpected behavior. Since this has a performance impact, it is disabled by default, but it can be activated the following way:

diff --git a/docs/userguide/html/domain-overview.png b/docs/userguide/html/domain-overview.png index 5a0dfe44247402184860db5ba8c5417b70912129..91325e8990541cddbfdb624a202daeeee55a2adf 100644 GIT binary patch literal 40709 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 literal 30541 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 uutfsF#GuY8X@old^XHdx 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

A!q1xIBv^35|U8-hrZCo^ovQ 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 From c590ec8cd94f4b84eac84553863cbbb07ad969ef Mon Sep 17 00:00:00 2001 From: Manfred Hanke Date: Wed, 20 Mar 2019 18:02:35 +0100 Subject: [PATCH 06/10] add fields().that().are[Not]{Static,Final} syntax Signed-off-by: Manfred Hanke --- .../lang/syntax/MembersThatInternal.java | 20 ++++++++++++ .../lang/syntax/SyntaxPredicates.java | 18 +++++++++++ .../lang/syntax/elements/FieldsThat.java | 32 +++++++++++++++++++ .../lang/syntax/elements/GivenFieldsTest.java | 15 ++++++--- 4 files changed, 81 insertions(+), 4 deletions(-) diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MembersThatInternal.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MembersThatInternal.java index 8ec874f21c..d4a35062f7 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MembersThatInternal.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MembersThatInternal.java @@ -111,6 +111,26 @@ public CONJUNCTION areNotPrivate() { return givenWith(SyntaxPredicates.areNotPrivate()); } + // only applicable to fields and methods; therefore not exposed via MembersThat + public CONJUNCTION areStatic() { + return givenWith(SyntaxPredicates.areStatic()); + } + + // only applicable to fields and methods; therefore not exposed via MembersThat + public CONJUNCTION areNotStatic() { + return givenWith(SyntaxPredicates.areNotStatic()); + } + + // only applicable to (classes,) fields and methods; therefore not exposed via MembersThat + public CONJUNCTION areFinal() { + return givenWith(SyntaxPredicates.areFinal()); + } + + // only applicable to (classes,) fields and methods; therefore not exposed via MembersThat + public CONJUNCTION areNotFinal() { + return givenWith(SyntaxPredicates.areNotFinal()); + } + @Override public CONJUNCTION haveModifier(JavaModifier modifier) { return givenWith(SyntaxPredicates.haveModifier(modifier)); diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/SyntaxPredicates.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/SyntaxPredicates.java index 260e3451b0..7852eaa482 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/SyntaxPredicates.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/SyntaxPredicates.java @@ -28,9 +28,11 @@ import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleNameContaining; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleNameEndingWith; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleNameStartingWith; +import static com.tngtech.archunit.core.domain.JavaModifier.FINAL; import static com.tngtech.archunit.core.domain.JavaModifier.PRIVATE; import static com.tngtech.archunit.core.domain.JavaModifier.PROTECTED; import static com.tngtech.archunit.core.domain.JavaModifier.PUBLIC; +import static com.tngtech.archunit.core.domain.JavaModifier.STATIC; import static com.tngtech.archunit.core.domain.properties.HasModifiers.Predicates.modifier; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameMatching; import static com.tngtech.archunit.lang.conditions.ArchConditions.fullyQualifiedName; @@ -85,6 +87,22 @@ static DescribedPredicate areNotPrivate() { return not(modifier(PRIVATE)).as("are not private"); } + static DescribedPredicate areStatic() { + return modifier(STATIC).as("are static"); + } + + static DescribedPredicate areNotStatic() { + return not(modifier(STATIC)).as("are not static"); + } + + static DescribedPredicate areFinal() { + return modifier(FINAL).as("are final"); + } + + static DescribedPredicate areNotFinal() { + return not(modifier(FINAL)).as("are not final"); + } + static DescribedPredicate haveFullyQualifiedName(String name) { return have(fullyQualifiedName(name)); } diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/FieldsThat.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/FieldsThat.java index 9632ca4c17..f59764430c 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/FieldsThat.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/FieldsThat.java @@ -137,4 +137,36 @@ public interface FieldsThat extends */ @PublicAPI(usage = ACCESS) CONJUNCTION doNotHaveRawType(DescribedPredicate predicate); + + /** + * Matches static fields. + * + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION areStatic(); + + /** + * Matches non-static fields. + * + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION areNotStatic(); + + /** + * Matches final fields. + * + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION areFinal(); + + /** + * Matches non-final fields. + * + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION areNotFinal(); } diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenFieldsTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenFieldsTest.java index 349876b564..cfb0537717 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenFieldsTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenFieldsTest.java @@ -55,7 +55,14 @@ public static Object[][] restricted_property_rule_starts() { $(described(fields().that().doNotHaveRawType(String.class)), allFieldsExcept(FIELD_A)), $(described(fields().that().doNotHaveRawType(String.class.getName())), allFieldsExcept(FIELD_A)), - $(described(fields().that().doNotHaveRawType(equivalentTo(String.class))), allFieldsExcept(FIELD_A)) + $(described(fields().that().doNotHaveRawType(equivalentTo(String.class))), allFieldsExcept(FIELD_A)), + + $(described(fields().that().areFinal()), ImmutableSet.of(FIELD_A, FIELD_B)), + $(described(fields().that().areNotFinal()), ImmutableSet.of(FIELD_C, FIELD_D)), + $(described(fields().that().areStatic()), ImmutableSet.of(FIELD_B, FIELD_D)), + $(described(fields().that().areNotStatic()), ImmutableSet.of(FIELD_A, FIELD_C)), + + $(described(fields().that().areStatic().and().areFinal()), ImmutableSet.of(FIELD_B)) ); } @@ -80,11 +87,11 @@ private static Set allFieldsExcept(String... fieldNames) { @SuppressWarnings({"unused"}) private static class ClassWithVariousMembers { - private String fieldA; + private final String fieldA = "A"; @A - protected Object fieldB; + protected static final Object fieldB = 'B'; public List fieldC; - Map fieldD; + static Map fieldD; } private @interface A { From efbbc3255c9505a050488d25bf7fbb1c0ad5c679 Mon Sep 17 00:00:00 2001 From: Manfred Hanke Date: Thu, 28 Mar 2019 08:11:09 +0100 Subject: [PATCH 07/10] add fields().should().{be,notBe}{Static,Final} syntax Signed-off-by: Manfred Hanke --- .../exampletest/junit4/CodingRulesTest.java | 4 +-- .../exampletest/junit5/CodingRulesTest.java | 4 +-- .../archunit/exampletest/CodingRulesTest.java | 4 +-- .../integration/ExamplesIntegrationTest.java | 2 +- .../lang/syntax/FieldsShouldInternal.java | 21 ++++++++++++ .../lang/syntax/elements/FieldsShould.java | 32 +++++++++++++++++++ .../syntax/elements/FieldsShouldTest.java | 14 +++++--- 7 files changed, 70 insertions(+), 11 deletions(-) diff --git a/archunit-example/example-junit4/src/test/java/com/tngtech/archunit/exampletest/junit4/CodingRulesTest.java b/archunit-example/example-junit4/src/test/java/com/tngtech/archunit/exampletest/junit4/CodingRulesTest.java index d0c11bfef0..298ccdf1d1 100644 --- a/archunit-example/example-junit4/src/test/java/com/tngtech/archunit/exampletest/junit4/CodingRulesTest.java +++ b/archunit-example/example-junit4/src/test/java/com/tngtech/archunit/exampletest/junit4/CodingRulesTest.java @@ -43,8 +43,8 @@ private void no_access_to_standard_streams_as_method(JavaClasses classes) { private final ArchRule loggers_should_be_private_static_final = fields().that().haveRawType(Logger.class) .should().bePrivate() - .andShould().haveModifier(JavaModifier.STATIC) - .andShould().haveModifier(JavaModifier.FINAL) + .andShould().beStatic() + .andShould().beFinal() .because("we agreed on this convention"); @ArchTest diff --git a/archunit-example/example-junit5/src/test/java/com/tngtech/archunit/exampletest/junit5/CodingRulesTest.java b/archunit-example/example-junit5/src/test/java/com/tngtech/archunit/exampletest/junit5/CodingRulesTest.java index 7b3d57a8b6..88e37d9d98 100644 --- a/archunit-example/example-junit5/src/test/java/com/tngtech/archunit/exampletest/junit5/CodingRulesTest.java +++ b/archunit-example/example-junit5/src/test/java/com/tngtech/archunit/exampletest/junit5/CodingRulesTest.java @@ -40,8 +40,8 @@ private void no_access_to_standard_streams_as_method(JavaClasses classes) { private final ArchRule loggers_should_be_private_static_final = fields().that().haveRawType(Logger.class) .should().bePrivate() - .andShould().haveModifier(JavaModifier.STATIC) - .andShould().haveModifier(JavaModifier.FINAL) + .andShould().beStatic() + .andShould().beFinal() .because("we agreed on this convention"); @ArchTest diff --git a/archunit-example/example-plain/src/test/java/com/tngtech/archunit/exampletest/CodingRulesTest.java b/archunit-example/example-plain/src/test/java/com/tngtech/archunit/exampletest/CodingRulesTest.java index 2f0d712039..7b349e4c2e 100644 --- a/archunit-example/example-plain/src/test/java/com/tngtech/archunit/exampletest/CodingRulesTest.java +++ b/archunit-example/example-plain/src/test/java/com/tngtech/archunit/exampletest/CodingRulesTest.java @@ -47,8 +47,8 @@ public void classes_should_not_use_java_util_logging() { public void loggers_should_be_private_static_final() { fields().that().haveRawType(Logger.class) .should().bePrivate() - .andShould().haveModifier(JavaModifier.STATIC) - .andShould().haveModifier(JavaModifier.FINAL) + .andShould().beStatic() + .andShould().beFinal() .because("we agreed on this convention") .check(classes); } diff --git a/archunit-integration-test/src/test/java/com/tngtech/archunit/integration/ExamplesIntegrationTest.java b/archunit-integration-test/src/test/java/com/tngtech/archunit/integration/ExamplesIntegrationTest.java index 6b9c24d1c3..1a876b74a3 100644 --- a/archunit-integration-test/src/test/java/com/tngtech/archunit/integration/ExamplesIntegrationTest.java +++ b/archunit-integration-test/src/test/java/com/tngtech/archunit/integration/ExamplesIntegrationTest.java @@ -199,7 +199,7 @@ Stream CodingRulesTest() { expectThrownGenericExceptions(expectFailures); expectFailures.ofRule("fields that have raw type java.util.logging.Logger should be private " + - "and should have modifier STATIC and should have modifier FINAL, because we agreed on this convention") + "and should be static and should be final, because we agreed on this convention") .by(ExpectedField.of(ClassViolatingCodingRules.class, "log").doesNotHaveModifier(JavaModifier.PRIVATE)) .by(ExpectedField.of(ClassViolatingCodingRules.class, "log").doesNotHaveModifier(JavaModifier.FINAL)); diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/FieldsShouldInternal.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/FieldsShouldInternal.java index f65db9b1cd..08ed812781 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/FieldsShouldInternal.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/FieldsShouldInternal.java @@ -19,6 +19,7 @@ import com.tngtech.archunit.base.Function; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaField; +import com.tngtech.archunit.core.domain.JavaModifier; import com.tngtech.archunit.lang.ArchCondition; import com.tngtech.archunit.lang.ClassesTransformer; import com.tngtech.archunit.lang.Priority; @@ -92,4 +93,24 @@ public FieldsShouldInternal haveRawType(DescribedPredicate pr public FieldsShouldInternal notHaveRawType(DescribedPredicate predicate) { return addCondition(not(ArchConditions.haveRawType(predicate))); } + + @Override + public FieldsShouldInternal beStatic() { + return addCondition(ArchConditions.haveModifier(JavaModifier.STATIC).as("be static")); + } + + @Override + public FieldsShouldInternal notBeStatic() { + return addCondition(not(ArchConditions.haveModifier(JavaModifier.STATIC)).as("not be static")); + } + + @Override + public FieldsShouldInternal beFinal() { + return addCondition(ArchConditions.haveModifier(JavaModifier.FINAL).as("be final")); + } + + @Override + public FieldsShouldInternal notBeFinal() { + return addCondition(not(ArchConditions.haveModifier(JavaModifier.FINAL)).as("not be final")); + } } diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/FieldsShould.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/FieldsShould.java index 772039de22..a7919d8de3 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/FieldsShould.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/FieldsShould.java @@ -143,4 +143,36 @@ public interface FieldsShould exten */ @PublicAPI(usage = ACCESS) CONJUNCTION notHaveRawType(DescribedPredicate predicate); + + /** + * Asserts that fields are static. + * + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION beStatic(); + + /** + * Asserts that fields are non-static. + * + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION notBeStatic(); + + /** + * Asserts that fields are final. + * + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION beFinal(); + + /** + * Asserts that fields are non-final. + * + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION notBeFinal(); } diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/FieldsShouldTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/FieldsShouldTest.java index 8ccb082487..89093112a0 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/FieldsShouldTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/FieldsShouldTest.java @@ -5,6 +5,7 @@ import java.util.Map; import java.util.Set; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList; import com.tngtech.archunit.lang.ArchRule; import com.tngtech.archunit.lang.EvaluationResult; @@ -53,7 +54,12 @@ public static Object[][] restricted_property_rule_ends() { $(fields().should().haveRawType(equivalentTo(String.class).as(String.class.getName())), ImmutableList.of(FIELD_B, FIELD_C, FIELD_D)), $(fields().should().notHaveRawType(String.class), ImmutableList.of(FIELD_A)), $(fields().should().notHaveRawType(String.class.getName()), ImmutableList.of(FIELD_A)), - $(fields().should().notHaveRawType(equivalentTo(String.class).as(String.class.getName())), ImmutableList.of(FIELD_A))); + $(fields().should().notHaveRawType(equivalentTo(String.class).as(String.class.getName())), ImmutableList.of(FIELD_A)), + $(fields().should().beFinal(), ImmutableList.of(FIELD_C, FIELD_D)), + $(fields().should().notBeFinal(), ImmutableList.of(FIELD_A, FIELD_B)), + $(fields().should().beStatic(), ImmutableList.of(FIELD_A, FIELD_C)), + $(fields().should().notBeStatic(), ImmutableList.of(FIELD_B, FIELD_D)) + ); } @Test @@ -73,11 +79,11 @@ public void property_predicates(ArchRule rule, Collection expectedViolat @SuppressWarnings({"unused"}) private static class ClassWithVariousMembers { - private String fieldA; + private final String fieldA = "A"; @A - protected Object fieldB; + protected static final Object fieldB = 'B'; public List fieldC; - Map fieldD; + static Map fieldD; } private @interface A { From 6b13196835cbf4de188e294291b4b8002d62f90a Mon Sep 17 00:00:00 2001 From: Manfred Hanke Date: Thu, 21 Mar 2019 20:42:31 +0100 Subject: [PATCH 08/10] add methods().that().are[Not]{Static,Final} syntax Signed-off-by: Manfred Hanke --- .../lang/syntax/GivenMethodsInternal.java | 15 +++++ .../lang/syntax/MethodsThatInternal.java | 28 +++++++++ .../lang/syntax/elements/GivenMethods.java | 4 ++ .../elements/GivenMethodsConjunction.java | 8 +++ .../lang/syntax/elements/MethodsThat.java | 55 ++++++++++++++++ .../syntax/elements/GivenMethodsTest.java | 62 +++++++++++++++++++ 6 files changed, 172 insertions(+) create mode 100644 archunit/src/main/java/com/tngtech/archunit/lang/syntax/MethodsThatInternal.java create mode 100644 archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MethodsThat.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMethodsTest.java diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/GivenMethodsInternal.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/GivenMethodsInternal.java index a05018124f..d1dd49c355 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/GivenMethodsInternal.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/GivenMethodsInternal.java @@ -57,6 +57,21 @@ private GivenMethodsInternal( super(factory, priority, classesTransformer, prepareCondition, relevantObjectsPredicates, overriddenDescription); } + @Override + public MethodsThatInternal that() { + return new MethodsThatInternal(this, currentPredicate()); + } + + @Override + public MethodsThatInternal and() { + return new MethodsThatInternal(this, currentPredicate().thatANDs()); + } + + @Override + public MethodsThatInternal or() { + return new MethodsThatInternal(this, currentPredicate().thatORs()); + } + @Override public MethodsShouldInternal should() { return new MethodsShouldInternal(finishedClassesTransformer(), priority, prepareCondition); diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MethodsThatInternal.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MethodsThatInternal.java new file mode 100644 index 0000000000..49376f9978 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MethodsThatInternal.java @@ -0,0 +1,28 @@ +/* + * Copyright 2019 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.lang.syntax; + +import com.tngtech.archunit.core.domain.JavaMethod; +import com.tngtech.archunit.lang.syntax.elements.MethodsThat; + +class MethodsThatInternal + extends CodeUnitsThatInternal + implements MethodsThat { + + MethodsThatInternal(GivenMethodsInternal givenMethods, PredicateAggregator currentPredicate) { + super(givenMethods, currentPredicate); + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/GivenMethods.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/GivenMethods.java index 6176d688f3..f7e5ff5577 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/GivenMethods.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/GivenMethods.java @@ -23,6 +23,10 @@ public interface GivenMethods extends GivenCodeUnits { + @Override + @PublicAPI(usage = ACCESS) + MethodsThat that(); + @Override @PublicAPI(usage = ACCESS) GivenMethodsConjunction that(DescribedPredicate predicate); diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/GivenMethodsConjunction.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/GivenMethodsConjunction.java index 73b2e60b3d..f0267dc493 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/GivenMethodsConjunction.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/GivenMethodsConjunction.java @@ -23,6 +23,14 @@ public interface GivenMethodsConjunction extends GivenCodeUnitsConjunction { + @Override + @PublicAPI(usage = ACCESS) + MethodsThat and(); + + @Override + @PublicAPI(usage = ACCESS) + MethodsThat or(); + @Override @PublicAPI(usage = ACCESS) GivenMethodsConjunction and(DescribedPredicate predicate); diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MethodsThat.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MethodsThat.java new file mode 100644 index 0000000000..0a23a6fa43 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MethodsThat.java @@ -0,0 +1,55 @@ +/* + * Copyright 2019 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.lang.syntax.elements; + +import com.tngtech.archunit.PublicAPI; + +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; + +public interface MethodsThat extends CodeUnitsThat { + + /** + * Matches static methods. + * + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION areStatic(); + + /** + * Matches non-static methods. + * + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION areNotStatic(); + + /** + * Matches final methods. + * + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION areFinal(); + + /** + * Matches non-final methods. + * + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION areNotFinal(); +} diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMethodsTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMethodsTest.java new file mode 100644 index 0000000000..3adbb41295 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMethodsTest.java @@ -0,0 +1,62 @@ +package com.tngtech.archunit.lang.syntax.elements; + +import com.google.common.collect.ImmutableSet; +import com.tngtech.archunit.lang.EvaluationResult; +import com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.*; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Collection; + +import static com.tngtech.archunit.core.domain.TestUtils.importClasses; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods; +import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.*; +import static com.tngtech.java.junit.dataprovider.DataProviders.$; +import static com.tngtech.java.junit.dataprovider.DataProviders.$$; +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(DataProviderRunner.class) +public class GivenMethodsTest { + + @DataProvider + public static Object[][] restricted_property_rule_starts() { + return $$( + $(described(methods().that().areFinal()), ImmutableSet.of(METHOD_A, METHOD_B)), + $(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().areFinal().and().areStatic()), ImmutableSet.of(METHOD_B)), + $(described(methods().that().areFinal().or().areStatic()), ImmutableSet.of(METHOD_A, METHOD_B, METHOD_D)) + ); + } + + @Test + @UseDataProvider("restricted_property_rule_starts") + public void property_predicates(DescribedRuleStart ruleStart, Collection expectedMembers) { + EvaluationResult result = ruleStart.should(everythingViolationPrintMemberName()) + .evaluate(importClasses(ClassWithVariousMembers.class)); + + assertThat(result.getFailureReport().getDetails()).containsOnlyElementsOf(expectedMembers); + } + + private static final String METHOD_A = "methodA([I)"; + private static final String METHOD_B = "methodB(boolean)"; + private static final String METHOD_C = "methodC(char)"; + private static final String METHOD_D = "methodD()"; + + @SuppressWarnings({"unused"}) + private static class ClassWithVariousMembers { + public final void methodA(int[] array) { + } + protected static final void methodB(boolean flag) { + } + private void methodC(char ch) { + } + static int methodD() { + return 0; + } + } +} From ca18b17df44fe0d73720c4a6bc763e29366d1a78 Mon Sep 17 00:00:00 2001 From: Manfred Hanke Date: Sun, 24 Mar 2019 23:58:52 +0100 Subject: [PATCH 09/10] add methods().should().{be,notBe}{Static,Final} syntax Signed-off-by: Manfred Hanke --- .../lang/syntax/MethodsShouldInternal.java | 30 ++++++++- .../lang/syntax/elements/GivenMethods.java | 9 +++ .../elements/GivenMethodsConjunction.java | 9 +++ .../lang/syntax/elements/MethodsShould.java | 55 ++++++++++++++++ .../elements/MethodsShouldConjunction.java | 41 ++++++++++++ .../syntax/elements/MethodsShouldTest.java | 63 +++++++++++++++++++ 6 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MethodsShould.java create mode 100644 archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MethodsShouldConjunction.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/MethodsShouldTest.java diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MethodsShouldInternal.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MethodsShouldInternal.java index dc47b41723..a9662efced 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MethodsShouldInternal.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MethodsShouldInternal.java @@ -17,11 +17,19 @@ import com.tngtech.archunit.base.Function; import com.tngtech.archunit.core.domain.JavaMethod; +import com.tngtech.archunit.core.domain.JavaModifier; import com.tngtech.archunit.lang.ArchCondition; import com.tngtech.archunit.lang.ClassesTransformer; import com.tngtech.archunit.lang.Priority; +import com.tngtech.archunit.lang.conditions.ArchConditions; +import com.tngtech.archunit.lang.syntax.elements.MethodsShould; +import com.tngtech.archunit.lang.syntax.elements.MethodsShouldConjunction; -class MethodsShouldInternal extends AbstractCodeUnitsShouldInternal { +import static com.tngtech.archunit.lang.conditions.ArchConditions.not; + +class MethodsShouldInternal + extends AbstractCodeUnitsShouldInternal + implements MethodsShould, MethodsShouldConjunction { MethodsShouldInternal( ClassesTransformer classesTransformer, @@ -53,4 +61,24 @@ private MethodsShouldInternal( MethodsShouldInternal copyWithNewCondition(ConditionAggregator newCondition) { return new MethodsShouldInternal(classesTransformer, priority, newCondition, prepareCondition); } + + @Override + public MethodsShouldInternal beStatic() { + return addCondition(ArchConditions.haveModifier(JavaModifier.STATIC).as("be static")); + } + + @Override + public MethodsShouldInternal notBeStatic() { + return addCondition(not(ArchConditions.haveModifier(JavaModifier.STATIC)).as("not be static")); + } + + @Override + public MethodsShouldInternal beFinal() { + return addCondition(ArchConditions.haveModifier(JavaModifier.FINAL).as("be final")); + } + + @Override + public MethodsShouldInternal notBeFinal() { + return addCondition(not(ArchConditions.haveModifier(JavaModifier.FINAL)).as("not be final")); + } } diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/GivenMethods.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/GivenMethods.java index f7e5ff5577..de861b1414 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/GivenMethods.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/GivenMethods.java @@ -18,6 +18,7 @@ import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.JavaMethod; +import com.tngtech.archunit.lang.ArchCondition; import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; @@ -30,4 +31,12 @@ public interface GivenMethods extends GivenCodeUnits { @Override @PublicAPI(usage = ACCESS) GivenMethodsConjunction that(DescribedPredicate predicate); + + @Override + @PublicAPI(usage = ACCESS) + MethodsShould should(); + + @Override + @PublicAPI(usage = ACCESS) + MethodsShouldConjunction should(ArchCondition condition); } diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/GivenMethodsConjunction.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/GivenMethodsConjunction.java index f0267dc493..1bb4574dd4 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/GivenMethodsConjunction.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/GivenMethodsConjunction.java @@ -18,6 +18,7 @@ import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.JavaMethod; +import com.tngtech.archunit.lang.ArchCondition; import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; @@ -38,4 +39,12 @@ public interface GivenMethodsConjunction extends GivenCodeUnitsConjunction predicate); + + @Override + @PublicAPI(usage = ACCESS) + MethodsShould should(); + + @Override + @PublicAPI(usage = ACCESS) + MethodsShouldConjunction should(ArchCondition condition); } diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MethodsShould.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MethodsShould.java new file mode 100644 index 0000000000..fd2c9c3462 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MethodsShould.java @@ -0,0 +1,55 @@ +/* + * Copyright 2019 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.lang.syntax.elements; + +import com.tngtech.archunit.PublicAPI; + +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; + +public interface MethodsShould extends CodeUnitsShould { + + /** + * Asserts that methods are static. + * + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION beStatic(); + + /** + * Asserts that methods are non-static. + * + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION notBeStatic(); + + /** + * Asserts that methods are final. + * + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION beFinal(); + + /** + * Asserts that methods are non-final. + * + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION notBeFinal(); +} diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MethodsShouldConjunction.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MethodsShouldConjunction.java new file mode 100644 index 0000000000..9865df6038 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MethodsShouldConjunction.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.lang.syntax.elements; + +import com.tngtech.archunit.PublicAPI; +import com.tngtech.archunit.core.domain.JavaMethod; +import com.tngtech.archunit.lang.ArchCondition; + +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; + +public interface MethodsShouldConjunction extends CodeUnitsShouldConjunction { + + @Override + @PublicAPI(usage = ACCESS) + MethodsShouldConjunction andShould(ArchCondition condition); + + @Override + @PublicAPI(usage = ACCESS) + MethodsShould andShould(); + + @Override + @PublicAPI(usage = ACCESS) + MethodsShouldConjunction orShould(ArchCondition condition); + + @Override + @PublicAPI(usage = ACCESS) + MethodsShould orShould(); +} diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/MethodsShouldTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/MethodsShouldTest.java new file mode 100644 index 0000000000..d33b8f1eb0 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/MethodsShouldTest.java @@ -0,0 +1,63 @@ +package com.tngtech.archunit.lang.syntax.elements; + +import com.google.common.collect.ImmutableSet; +import com.tngtech.archunit.lang.ArchRule; +import com.tngtech.archunit.lang.EvaluationResult; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Collection; +import java.util.Set; + +import static com.tngtech.archunit.core.domain.TestUtils.importClasses; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods; +import static com.tngtech.archunit.lang.syntax.elements.MembersShouldTest.parseMembers; +import static com.tngtech.java.junit.dataprovider.DataProviders.$; +import static com.tngtech.java.junit.dataprovider.DataProviders.$$; +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(DataProviderRunner.class) +public class MethodsShouldTest { + + @DataProvider + public static Object[][] restricted_property_rule_ends() { + return $$( + $(methods().should().beFinal(), ImmutableSet.of(METHOD_C, METHOD_D)), + $(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().notBeFinal().andShould().notBeStatic(), ImmutableSet.of(METHOD_A, METHOD_B, METHOD_D)), + $(methods().should().notBeFinal().orShould().notBeStatic(), ImmutableSet.of(METHOD_B)) + ); + } + + @Test + @UseDataProvider("restricted_property_rule_ends") + public void property_predicates(ArchRule ruleStart, Collection expectedMembers) { + EvaluationResult result = ruleStart.evaluate(importClasses(ClassWithVariousMembers.class)); + + Set actualMethods = parseMembers(ClassWithVariousMembers.class, result.getFailureReport().getDetails()); + assertThat(actualMethods).containsOnlyElementsOf(expectedMembers); + } + + private static final String METHOD_A = "methodA([I)"; + private static final String METHOD_B = "methodB(boolean)"; + private static final String METHOD_C = "methodC(char)"; + private static final String METHOD_D = "methodD()"; + + @SuppressWarnings({"unused"}) + private static class ClassWithVariousMembers { + public final void methodA(int[] array) { + } + protected static final void methodB(boolean flag) { + } + private void methodC(char ch) { + } + static int methodD() { + return 0; + } + } +} From f145cbdb33c7c6da6f92b44384713f04cb803580 Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Sun, 31 Mar 2019 13:45:05 +0200 Subject: [PATCH 10/10] Review: Pulled the common "beStatic", ... methods up to AbstractMembersShouldInternal (analogously to MembersThat). Also extracted the conditions to ArchConditions to be consistent with the rest (like bePublic(), etc.) Signed-off-by: Peter Gafert --- .../lang/conditions/ArchConditions.java | 31 ++++++++++++++++--- .../syntax/AbstractMembersShouldInternal.java | 20 ++++++++++++ .../lang/syntax/FieldsShouldInternal.java | 21 ------------- .../lang/syntax/MethodsShouldInternal.java | 24 -------------- 4 files changed, 46 insertions(+), 50 deletions(-) diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ArchConditions.java b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ArchConditions.java index 2e932319dd..0919b80d99 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ArchConditions.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ArchConditions.java @@ -52,9 +52,9 @@ import com.tngtech.archunit.core.domain.properties.HasAnnotations; import com.tngtech.archunit.core.domain.properties.HasModifiers; import com.tngtech.archunit.core.domain.properties.HasName; -import com.tngtech.archunit.core.domain.properties.HasSourceCodeLocation; import com.tngtech.archunit.core.domain.properties.HasOwner.Functions.Get; import com.tngtech.archunit.core.domain.properties.HasOwner.Predicates.With; +import com.tngtech.archunit.core.domain.properties.HasSourceCodeLocation; import com.tngtech.archunit.core.domain.properties.HasThrowsClause; import com.tngtech.archunit.lang.ArchCondition; import com.tngtech.archunit.lang.ConditionEvents; @@ -90,6 +90,7 @@ import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME; import static com.tngtech.archunit.core.domain.JavaMember.Predicates.declaredIn; import static com.tngtech.archunit.core.domain.JavaModifier.FINAL; +import static com.tngtech.archunit.core.domain.JavaModifier.STATIC; import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates.annotatedWith; import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates.metaAnnotatedWith; import static com.tngtech.archunit.core.domain.properties.HasModifiers.Predicates.modifier; @@ -522,22 +523,22 @@ public static resideInAPackage(final String packageIdentifier) { - return new DoesConditionByPredicate(JavaClass.Predicates.resideInAPackage(packageIdentifier)); + return new DoesConditionByPredicate<>(JavaClass.Predicates.resideInAPackage(packageIdentifier)); } @PublicAPI(usage = ACCESS) public static ArchCondition resideInAnyPackage(String... packageIdentifiers) { - return new DoesConditionByPredicate(JavaClass.Predicates.resideInAnyPackage(packageIdentifiers)); + return new DoesConditionByPredicate<>(JavaClass.Predicates.resideInAnyPackage(packageIdentifiers)); } @PublicAPI(usage = ACCESS) public static ArchCondition resideOutsideOfPackage(String packageIdentifier) { - return new DoesConditionByPredicate(JavaClass.Predicates.resideOutsideOfPackage(packageIdentifier)); + return new DoesConditionByPredicate<>(JavaClass.Predicates.resideOutsideOfPackage(packageIdentifier)); } @PublicAPI(usage = ACCESS) public static ArchCondition resideOutsideOfPackages(String... packageIdentifiers) { - return new DoesConditionByPredicate(JavaClass.Predicates.resideOutsideOfPackages(packageIdentifiers)); + return new DoesConditionByPredicate<>(JavaClass.Predicates.resideOutsideOfPackages(packageIdentifiers)); } @PublicAPI(usage = ACCESS) @@ -595,6 +596,26 @@ public static haveModifier(JavaModifier.PRIVATE)).as("not be private"); } + @PublicAPI(usage = ACCESS) + public static ArchCondition beStatic() { + return ArchConditions.haveModifier(STATIC).as("be static"); + } + + @PublicAPI(usage = ACCESS) + public static ArchCondition notBeStatic() { + return not(ArchConditions.haveModifier(STATIC).as("be static")); + } + + @PublicAPI(usage = ACCESS) + public static ArchCondition beFinal() { + return ArchConditions.haveModifier(FINAL).as("be final"); + } + + @PublicAPI(usage = ACCESS) + public static ArchCondition notBeFinal() { + return not(ArchConditions.haveModifier(FINAL).as("be final")); + } + @PublicAPI(usage = ACCESS) public static ArchCondition haveOnlyFinalFields() { return new HaveOnlyFinalFieldsCondition(); diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/AbstractMembersShouldInternal.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/AbstractMembersShouldInternal.java index 11e6a78c03..0b5e6bb313 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/AbstractMembersShouldInternal.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/AbstractMembersShouldInternal.java @@ -119,6 +119,26 @@ public SELF notBePrivate() { return addCondition(ArchConditions.notBePrivate()); } + // only applicable to fields and methods; therefore not exposed via MembersShould + public SELF beStatic() { + return addCondition(ArchConditions.beStatic()); + } + + // only applicable to fields and methods; therefore not exposed via MembersShould + public SELF notBeStatic() { + return addCondition(ArchConditions.notBeStatic()); + } + + // only applicable to fields and methods; therefore not exposed via MembersShould + public SELF beFinal() { + return addCondition(ArchConditions.beFinal()); + } + + // only applicable to fields and methods; therefore not exposed via MembersShould + public SELF notBeFinal() { + return addCondition(ArchConditions.notBeFinal()); + } + @Override public SELF haveModifier(JavaModifier modifier) { return addCondition(ArchConditions.haveModifier(modifier)); diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/FieldsShouldInternal.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/FieldsShouldInternal.java index 08ed812781..f65db9b1cd 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/FieldsShouldInternal.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/FieldsShouldInternal.java @@ -19,7 +19,6 @@ import com.tngtech.archunit.base.Function; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaField; -import com.tngtech.archunit.core.domain.JavaModifier; import com.tngtech.archunit.lang.ArchCondition; import com.tngtech.archunit.lang.ClassesTransformer; import com.tngtech.archunit.lang.Priority; @@ -93,24 +92,4 @@ public FieldsShouldInternal haveRawType(DescribedPredicate pr public FieldsShouldInternal notHaveRawType(DescribedPredicate predicate) { return addCondition(not(ArchConditions.haveRawType(predicate))); } - - @Override - public FieldsShouldInternal beStatic() { - return addCondition(ArchConditions.haveModifier(JavaModifier.STATIC).as("be static")); - } - - @Override - public FieldsShouldInternal notBeStatic() { - return addCondition(not(ArchConditions.haveModifier(JavaModifier.STATIC)).as("not be static")); - } - - @Override - public FieldsShouldInternal beFinal() { - return addCondition(ArchConditions.haveModifier(JavaModifier.FINAL).as("be final")); - } - - @Override - public FieldsShouldInternal notBeFinal() { - return addCondition(not(ArchConditions.haveModifier(JavaModifier.FINAL)).as("not be final")); - } } diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MethodsShouldInternal.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MethodsShouldInternal.java index a9662efced..15cee9ec86 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MethodsShouldInternal.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MethodsShouldInternal.java @@ -17,16 +17,12 @@ import com.tngtech.archunit.base.Function; import com.tngtech.archunit.core.domain.JavaMethod; -import com.tngtech.archunit.core.domain.JavaModifier; import com.tngtech.archunit.lang.ArchCondition; import com.tngtech.archunit.lang.ClassesTransformer; import com.tngtech.archunit.lang.Priority; -import com.tngtech.archunit.lang.conditions.ArchConditions; import com.tngtech.archunit.lang.syntax.elements.MethodsShould; import com.tngtech.archunit.lang.syntax.elements.MethodsShouldConjunction; -import static com.tngtech.archunit.lang.conditions.ArchConditions.not; - class MethodsShouldInternal extends AbstractCodeUnitsShouldInternal implements MethodsShould, MethodsShouldConjunction { @@ -61,24 +57,4 @@ private MethodsShouldInternal( MethodsShouldInternal copyWithNewCondition(ConditionAggregator newCondition) { return new MethodsShouldInternal(classesTransformer, priority, newCondition, prepareCondition); } - - @Override - public MethodsShouldInternal beStatic() { - return addCondition(ArchConditions.haveModifier(JavaModifier.STATIC).as("be static")); - } - - @Override - public MethodsShouldInternal notBeStatic() { - return addCondition(not(ArchConditions.haveModifier(JavaModifier.STATIC)).as("not be static")); - } - - @Override - public MethodsShouldInternal beFinal() { - return addCondition(ArchConditions.haveModifier(JavaModifier.FINAL).as("be final")); - } - - @Override - public MethodsShouldInternal notBeFinal() { - return addCondition(not(ArchConditions.haveModifier(JavaModifier.FINAL)).as("not be final")); - } }