diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..2253d48622 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +version: 2 +updates: + - package-ecosystem: "gradle" + directory: "/" + schedule: + interval: "daily" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + - package-ecosystem: "bundler" + directory: "/docs" + schedule: + interval: "daily" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6931bc4f07..f16b5b3b41 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,29 +4,122 @@ on: push: branches: - master + - release-* pull_request: +env: + build_java_version: 11 + jobs: build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Java + uses: actions/setup-java@v1 + with: + java-version: ${{ env.build_java_version }} + - name: Build + uses: eskatos/gradle-command-action@v1 + with: + arguments: build + + test: strategy: matrix: os: - ubuntu-latest - macos-latest - windows-latest - java_version: + test_java_version: + - 7 + - 8 + - 9 + - 10 - 11 - + - 12 + - 13 + - 14 runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Build JDK + uses: actions/setup-java@v1 + with: + java-version: ${{ env.build_java_version }} + - name: Set up Test JDK + uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.test_java_version }} + - name: Provide installed JDKs + uses: actions/github-script@v3 + with: + script: | + for ( let envVarName in process.env ) { + if (/JAVA_HOME_\d.*64/.test(envVarName)) { + const version = envVarName.match(/JAVA_HOME_(\d+).*64/)[1]; + if (version === "${{ matrix.test_java_version }}") { + core.exportVariable('test_jdk_path', process.env[envVarName]); + } else if (version === "${{ env.build_java_version }}") { + core.exportVariable('build_jdk_path', process.env[envVarName]); + } + } + } + - name: Test + uses: eskatos/gradle-command-action@v1 + env: + JAVA_HOME: ${{ env.build_jdk_path }} + with: + arguments: testJre${{ matrix.test_java_version }} jdk9TestJre${{ matrix.test_java_version }} -PallTests -PmultiJdkTest -Pjava${{ matrix.test_java_version }}Home="${{ env.test_jdk_path }}" + integration-test: + strategy: + matrix: + os: + - ubuntu-latest + - macos-latest + - windows-latest + java_version: + - 7 + - 8 + - 9 + - 10 + - 11 + - 12 + - 13 + - 14 + runs-on: ${{ matrix.os }} steps: - name: Checkout uses: actions/checkout@v2 - - name: Set up Java + - name: Set up Build JDK + uses: actions/setup-java@v1 + with: + java-version: ${{ env.build_java_version }} + - name: Set up Test JDK uses: actions/setup-java@v1 with: java-version: ${{ matrix.java_version }} - - name: Build + - name: Provide installed JDKs + uses: actions/github-script@v3 + id: provideJdkPaths + with: + script: | + for ( let envVarName in process.env ) { + if (/JAVA_HOME_\d.*64/.test(envVarName)) { + const version = envVarName.match(/JAVA_HOME_(\d+).*64/)[1]; + if (version === "${{ matrix.test_java_version }}") { + core.exportVariable('test_jdk_path', process.env[envVarName]); + } else if (version === "${{ env.build_java_version }}") { + core.exportVariable('build_jdk_path', process.env[envVarName]); + } + } + } + - name: Integration test uses: eskatos/gradle-command-action@v1 + env: + JAVA_HOME: ${{ env.build_jdk_path }} with: - arguments: build -PallTests + arguments: build -xtest -xspotbugsMain -xjavadoc publishToMavenLocal runMavenTest -Pjava${{ matrix.test_java_version }}Home="${{ env.test_jdk_path }}" diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index ce08c174ec..7151048d9e 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - release-* pull_request: jobs: diff --git a/.github/workflows/update-gradle-wrapper.yml b/.github/workflows/update-gradle-wrapper.yml new file mode 100644 index 0000000000..65bd1f53ff --- /dev/null +++ b/.github/workflows/update-gradle-wrapper.yml @@ -0,0 +1,17 @@ +name: Update Gradle Wrapper + +on: + schedule: + - cron: "0 0 * * *" + +jobs: + update-gradle-wrapper: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Update Gradle Wrapper + uses: gradle-update/update-gradle-wrapper-action@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 928fcc5a99..2cb51f6eea 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,6 @@ out _site .sass-cache .jekyll-metadata +.jekyll-cache .asciidoctor -verification-results \ No newline at end of file +verification-results diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c79d0f6468..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -language: java - -script: -- ./gradlew --no-daemon --scan --info -PallTests clean build publishToMavenLocal runMavenTest - -jdk: - - openjdk11 - -sudo: required diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4a4ba746a3..d006179a12 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ Windows users should use `gradlew.bat` instead. ``` $ cd /path/to/git/clone/of/ArchUnit $ ./gradlew showJdkVersion -Configured JDK: 1.9 +Configured JDK: 14 $ ./gradlew build ``` diff --git a/README.md b/README.md index f6aa767c65..90a5c4c91f 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ framework. ###### Gradle ``` -testImplementation 'com.tngtech.archunit:archunit:0.14.1' +testImplementation 'com.tngtech.archunit:archunit:0.15.0' ``` ###### Maven @@ -26,7 +26,7 @@ testImplementation 'com.tngtech.archunit:archunit:0.14.1' com.tngtech.archunit archunit - 0.14.1 + 0.15.0 test ``` diff --git a/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/persistence/second/dao/jpa/OtherJpa.java b/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/persistence/second/dao/jpa/OtherJpa.java index 443c838092..d5fcea2fbc 100644 --- a/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/persistence/second/dao/jpa/OtherJpa.java +++ b/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/persistence/second/dao/jpa/OtherJpa.java @@ -9,6 +9,7 @@ import com.tngtech.archunit.example.layers.persistence.second.dao.OtherDao; import com.tngtech.archunit.example.layers.persistence.second.dao.domain.OtherPersistentObject; import com.tngtech.archunit.example.layers.security.Secured; +import com.tngtech.archunit.example.layers.service.ProxiedConnection; public class OtherJpa implements OtherDao { @PersistenceContext @@ -22,6 +23,9 @@ public OtherPersistentObject findById(long id) { @Override public void testConnection() throws SQLException { Connection conn = entityManager.unwrap(Connection.class); + if (conn instanceof ProxiedConnection) { + ((ProxiedConnection) conn).refresh(); + } conn.prepareStatement("SELECT 1 FROM DUAL"); } diff --git a/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/service/ProxiedConnection.java b/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/service/ProxiedConnection.java new file mode 100644 index 0000000000..404cb9ece3 --- /dev/null +++ b/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/service/ProxiedConnection.java @@ -0,0 +1,5 @@ +package com.tngtech.archunit.example.layers.service; + +public interface ProxiedConnection { + void refresh(); +} diff --git a/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/service/ServiceHelper.java b/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/service/ServiceHelper.java index 1210abe8a4..f72fb50ba5 100644 --- a/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/service/ServiceHelper.java +++ b/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/service/ServiceHelper.java @@ -1,11 +1,20 @@ package com.tngtech.archunit.example.layers.service; +import java.util.Map; +import java.util.Set; + +import com.tngtech.archunit.example.layers.controller.SomeUtility; +import com.tngtech.archunit.example.layers.controller.one.SomeEnum; import com.tngtech.archunit.example.layers.security.Secured; /** * Well modelled code always has lots of 'helpers' ;-) */ -public class ServiceHelper { +@SuppressWarnings("unused") +public class ServiceHelper< + TYPE_PARAMETER_VIOLATING_LAYER_RULE extends SomeUtility, + ANOTHER_TYPE_PARAMETER_VIOLATING_LAYER_RULE extends Map>> { + public Object insecure = new Object(); @Secured public Object properlySecured = new Object(); diff --git a/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/service/ServiceViolatingLayerRules.java b/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/service/ServiceViolatingLayerRules.java index 6382e2c781..01ef8a850d 100644 --- a/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/service/ServiceViolatingLayerRules.java +++ b/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/service/ServiceViolatingLayerRules.java @@ -9,6 +9,7 @@ import com.tngtech.archunit.example.layers.controller.two.UseCaseTwoController; import com.tngtech.archunit.example.layers.security.Secured; +@SuppressWarnings("unused") @MyService @ComplexServiceAnnotation( controllerAnnotation = @ComplexControllerAnnotation(simpleControllerAnnotation = @SimpleControllerAnnotation), @@ -17,9 +18,6 @@ serviceType = ServiceType.STANDARD ) public class ServiceViolatingLayerRules { - public static final String illegalAccessToController = "illegalAccessToController"; - public static final String doSomething = "doSomething"; - public static final String dependentMethod = "dependentMethod"; void illegalAccessToController() { System.out.println(UseCaseOneTwoController.someString); @@ -34,7 +32,16 @@ public SomeGuiController dependentMethod(UseCaseTwoController otherController) { return null; } + public SomeGuiController[][] dependentOnComponentTypeMethod(UseCaseTwoController[] otherController) { + return null; + } + @Secured public void properlySecured() { } + + public static final String illegalAccessToController = "illegalAccessToController"; + public static final String doSomething = "doSomething"; + public static final String dependentMethod = "dependentMethod"; + public static final String dependentOnComponentTypeMethod = "dependentOnComponentTypeMethod"; } diff --git a/archunit-integration-test/build.gradle b/archunit-integration-test/build.gradle index cb0b666974..ad506484ef 100644 --- a/archunit-integration-test/build.gradle +++ b/archunit-integration-test/build.gradle @@ -25,4 +25,5 @@ dependencies { test { useJUnitPlatform() -} \ No newline at end of file +} +addMultiJdkTestsFor project, test diff --git a/archunit-integration-test/src/test/java/com/tngtech/archunit/ImporterRules.java b/archunit-integration-test/src/test/java/com/tngtech/archunit/ImporterRules.java index a0b3befb06..320707e372 100644 --- a/archunit-integration-test/src/test/java/com/tngtech/archunit/ImporterRules.java +++ b/archunit-integration-test/src/test/java/com/tngtech/archunit/ImporterRules.java @@ -2,7 +2,7 @@ import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.JavaClass; -import com.tngtech.archunit.core.domain.JavaType; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.core.importer.DomainBuilders; import com.tngtech.archunit.junit.ArchTest; @@ -21,15 +21,15 @@ public class ImporterRules { .should().accessClassesThat(belong_to_the_import_context()); @ArchTest - public static final ArchRule ASM_type_is_only_accessed_within_JavaType_or_JavaTypeImporter = + public static final ArchRule ASM_type_is_only_accessed_within_JavaClassDescriptor_or_JavaClassDescriptorImporter = noClasses() .that().resideOutsideOfPackage(THIRDPARTY_PACKAGE_IDENTIFIER) - .and(not(belongToAnyOf(JavaType.class))) - .and().doNotHaveFullyQualifiedName("com.tngtech.archunit.core.importer.JavaTypeImporter") + .and(not(belongToAnyOf(JavaClassDescriptor.class))) + .and().doNotHaveFullyQualifiedName("com.tngtech.archunit.core.importer.JavaClassDescriptorImporter") .should().dependOnClassesThat().haveNameMatching(".*\\.asm\\..*Type") - .as("org.objectweb.asm.Type should only be accessed within JavaType(Importer)") + .as("org.objectweb.asm.Type should only be accessed within JavaClassDescriptor(Importer)") .because("org.objectweb.asm.Type handles array types inconsistently (uses the canonical name instead of the class name), " - + "so the correct behavior is implemented only within JavaType"); + + "so the correct behavior is implemented only within JavaClassDescriptor"); private static DescribedPredicate belong_to_the_import_context() { return new DescribedPredicate("belong to the import context") { 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 06372b5d15..f0727d8c7d 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 @@ -94,6 +94,7 @@ import com.tngtech.archunit.example.layers.persistence.second.dao.jpa.OtherJpa; import com.tngtech.archunit.example.layers.security.Secured; import com.tngtech.archunit.example.layers.service.ComplexServiceAnnotation; +import com.tngtech.archunit.example.layers.service.ProxiedConnection; import com.tngtech.archunit.example.layers.service.ServiceHelper; import com.tngtech.archunit.example.layers.service.ServiceInterface; import com.tngtech.archunit.example.layers.service.ServiceViolatingDaoRules; @@ -154,6 +155,7 @@ import static com.tngtech.archunit.example.layers.core.VeryCentralCore.DO_CORE_STUFF_METHOD_NAME; import static com.tngtech.archunit.example.layers.persistence.layerviolation.DaoCallingService.violateLayerRules; import static com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules.dependentMethod; +import static com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules.dependentOnComponentTypeMethod; import static com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules.illegalAccessToController; import static com.tngtech.archunit.testutils.CyclicErrorMatcher.cycle; import static com.tngtech.archunit.testutils.ExpectedAccess.callFromConstructor; @@ -164,6 +166,7 @@ import static com.tngtech.archunit.testutils.ExpectedDependency.field; import static com.tngtech.archunit.testutils.ExpectedDependency.inheritanceFrom; import static com.tngtech.archunit.testutils.ExpectedDependency.method; +import static com.tngtech.archunit.testutils.ExpectedDependency.typeParameter; import static com.tngtech.archunit.testutils.ExpectedLocation.javaClass; import static com.tngtech.archunit.testutils.ExpectedNaming.simpleNameOf; import static com.tngtech.archunit.testutils.ExpectedNaming.simpleNameOfAnonymousClassOf; @@ -250,7 +253,7 @@ private static void expectAccessToStandardStreams(ExpectedTestFailures expectFai .inLine(14)) .by(callFromMethod(ServiceViolatingLayerRules.class, "illegalAccessToController") .getting().field(System.class, "out") - .inLine(25)); + .inLine(23)); } private static void expectThrownGenericExceptions(ExpectedTestFailures expectFailures) { @@ -631,6 +634,13 @@ Stream FrozenRulesTest() { toMethod(ServiceViolatingLayerRules.class, "properlySecured") .inLine(15) .asDependency()) + .by(callFromMethod(OtherJpa.class, "testConnection") + .toMethod(ProxiedConnection.class, "refresh") + .inLine(27) + .asDependency()) + .by(method(OtherJpa.class, "testConnection") + .checkingInstanceOf(ProxiedConnection.class) + .inLine(26)) .ofRule("no classes should depend on classes that are assignable to javax.persistence.EntityManager") .by(callFromMethod(ServiceViolatingDaoRules.class, "illegallyUseEntityManager"). @@ -678,19 +688,22 @@ Stream LayerDependencyRulesTest() { "should access classes that reside in a package '..controller..'") .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .getting().field(UseCaseOneTwoController.class, someString) - .inLine(25)) + .inLine(23)) .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .toConstructor(UseCaseTwoController.class) - .inLine(26)) + .inLine(24)) .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .toMethod(UseCaseTwoController.class, doSomethingTwo) - .inLine(27)) + .inLine(25)) .ofRule("no classes that reside in a package '..persistence..' should " + "access classes that reside in a package '..service..'") .by(callFromMethod(DaoCallingService.class, violateLayerRules) .toMethod(ServiceViolatingLayerRules.class, ServiceViolatingLayerRules.doSomething) .inLine(14)) + .by(callFromMethod(OtherJpa.class, "testConnection") + .toMethod(ProxiedConnection.class, "refresh") + .inLine(27)) .ofRule("classes that reside in a package '..service..' should " + "only be accessed by any package ['..controller..', '..service..']") @@ -700,32 +713,42 @@ Stream LayerDependencyRulesTest() { .by(callFromMethod(SomeMediator.class, violateLayerRulesIndirectly) .toMethod(ServiceViolatingLayerRules.class, ServiceViolatingLayerRules.doSomething) .inLine(15)) + .by(callFromMethod(OtherJpa.class, "testConnection") + .toMethod(ProxiedConnection.class, "refresh") + .inLine(27)) .ofRule("classes that reside in a package '..service..' should " + "only access classes that reside in any package ['..service..', '..persistence..', 'java..']") .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .getting().field(UseCaseOneTwoController.class, UseCaseOneTwoController.someString) - .inLine(25)) + .inLine(23)) .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .toConstructor(UseCaseTwoController.class) - .inLine(26)) + .inLine(24)) .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .toMethod(UseCaseTwoController.class, UseCaseTwoController.doSomethingTwo) - .inLine(27)) + .inLine(25)) .ofRule("no classes that reside in a package '..service..' " + "should depend on classes that reside in a package '..controller..'") .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .getting().field(UseCaseOneTwoController.class, someString) - .inLine(25).asDependency()) + .inLine(23).asDependency()) .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .toConstructor(UseCaseTwoController.class) - .inLine(26).asDependency()) + .inLine(24).asDependency()) .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .toMethod(UseCaseTwoController.class, doSomethingTwo) - .inLine(27).asDependency()) + .inLine(25).asDependency()) + .by(typeParameter(ServiceHelper.class, "TYPE_PARAMETER_VIOLATING_LAYER_RULE").dependingOn(SomeUtility.class)) + .by(typeParameter(ServiceHelper.class, "ANOTHER_TYPE_PARAMETER_VIOLATING_LAYER_RULE").dependingOn(SomeEnum.class)) .by(method(ServiceViolatingLayerRules.class, dependentMethod).withParameter(UseCaseTwoController.class)) .by(method(ServiceViolatingLayerRules.class, dependentMethod).withReturnType(SomeGuiController.class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).withParameter(UseCaseTwoController[].class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).dependingOnComponentType(UseCaseTwoController.class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).withReturnType(SomeGuiController[][].class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).dependingOnComponentType(SomeGuiController[].class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).dependingOnComponentType(SomeGuiController.class)) .by(annotatedClass(ServiceViolatingLayerRules.class).withAnnotationParameterType(ComplexControllerAnnotation.class)) .by(annotatedClass(ServiceViolatingLayerRules.class).withAnnotationParameterType(SimpleControllerAnnotation.class)) .by(annotatedClass(ServiceViolatingLayerRules.class).withAnnotationParameterType(SomeEnum.class)) @@ -737,8 +760,14 @@ Stream LayerDependencyRulesTest() { .by(callFromMethod(DaoCallingService.class, violateLayerRules) .toMethod(ServiceViolatingLayerRules.class, ServiceViolatingLayerRules.doSomething) .inLine(14).asDependency()) + .by(callFromMethod(OtherJpa.class, "testConnection") + .toMethod(ProxiedConnection.class, "refresh") + .inLine(27).asDependency()) .by(field(DaoCallingService.class, "service").ofType(ServiceViolatingLayerRules.class)) .by(inheritanceFrom(DaoCallingService.class).implementing(ServiceInterface.class)) + .by(method(OtherJpa.class, "testConnection") + .checkingInstanceOf(ProxiedConnection.class) + .inLine(26)) .ofRule("classes that reside in a package '..service..' should " + "only have dependent classes that reside in any package ['..controller..', '..service..']") @@ -748,32 +777,40 @@ Stream LayerDependencyRulesTest() { .by(callFromMethod(SomeMediator.class, violateLayerRulesIndirectly) .toMethod(ServiceViolatingLayerRules.class, ServiceViolatingLayerRules.doSomething) .inLine(15).asDependency()) + .by(callFromMethod(OtherJpa.class, "testConnection") + .toMethod(ProxiedConnection.class, "refresh") + .inLine(27).asDependency()) .by(inheritanceFrom(DaoCallingService.class).implementing(ServiceInterface.class)) .by(constructor(SomeMediator.class).withParameter(ServiceViolatingLayerRules.class)) .by(field(SomeMediator.class, "service").ofType(ServiceViolatingLayerRules.class)) .by(field(DaoCallingService.class, "service").ofType(ServiceViolatingLayerRules.class)) + .by(method(OtherJpa.class, "testConnection") + .checkingInstanceOf(ProxiedConnection.class) + .inLine(26)) .ofRule("classes that reside in a package '..service..' should " + "only depend on classes that reside in any package ['..service..', '..persistence..', 'java..', 'javax..']") .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .getting().field(UseCaseOneTwoController.class, someString) - .inLine(25).asDependency()) + .inLine(23).asDependency()) .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .toConstructor(UseCaseTwoController.class) - .inLine(26).asDependency()) + .inLine(24).asDependency()) .by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController) .toMethod(UseCaseTwoController.class, doSomethingTwo) - .inLine(27).asDependency()) - .by(method(ServiceViolatingLayerRules.class, dependentMethod) - .withParameter(UseCaseTwoController.class)) - .by(method(ServiceViolatingLayerRules.class, dependentMethod) - .withReturnType(SomeGuiController.class)) - .by(field(ServiceHelper.class, "properlySecured") - .withAnnotationType(Secured.class)) - .by(method(ServiceViolatingLayerRules.class, "properlySecured") - .withAnnotationType(Secured.class)) - .by(constructor(ServiceHelper.class) - .withAnnotationType(Secured.class)) + .inLine(25).asDependency()) + .by(typeParameter(ServiceHelper.class, "TYPE_PARAMETER_VIOLATING_LAYER_RULE").dependingOn(SomeUtility.class)) + .by(typeParameter(ServiceHelper.class, "ANOTHER_TYPE_PARAMETER_VIOLATING_LAYER_RULE").dependingOn(SomeEnum.class)) + .by(method(ServiceViolatingLayerRules.class, dependentMethod).withParameter(UseCaseTwoController.class)) + .by(method(ServiceViolatingLayerRules.class, dependentMethod).withReturnType(SomeGuiController.class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).withParameter(UseCaseTwoController[].class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).dependingOnComponentType(UseCaseTwoController.class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).withReturnType(SomeGuiController[][].class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).dependingOnComponentType(SomeGuiController[].class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).dependingOnComponentType(SomeGuiController.class)) + .by(field(ServiceHelper.class, "properlySecured").withAnnotationType(Secured.class)) + .by(method(ServiceViolatingLayerRules.class, "properlySecured").withAnnotationType(Secured.class)) + .by(constructor(ServiceHelper.class).withAnnotationType(Secured.class)) .by(annotatedClass(ServiceViolatingDaoRules.class).annotatedWith(MyService.class)) .by(annotatedClass(ServiceViolatingLayerRules.class).annotatedWith(MyService.class)) .by(annotatedClass(ServiceImplementation.class).annotatedWith(MyService.class)) @@ -813,28 +850,41 @@ Stream LayeredArchitectureTest() { .by(callFromMethod(ServiceViolatingLayerRules.class, "illegalAccessToController") .toConstructor(UseCaseTwoController.class) - .inLine(26) + .inLine(24) .asDependency()) .by(callFromMethod(ServiceViolatingLayerRules.class, "illegalAccessToController") .toMethod(UseCaseTwoController.class, "doSomethingTwo") - .inLine(27) + .inLine(25) .asDependency()) .by(callFromMethod(ServiceViolatingLayerRules.class, "illegalAccessToController") .getting().field(UseCaseOneTwoController.class, "someString") - .inLine(25) + .inLine(23) .asDependency()) - .by(method(ServiceViolatingLayerRules.class, dependentMethod).withParameter(UseCaseTwoController.class)) + .by(callFromMethod(OtherJpa.class, "testConnection") + .toMethod(ProxiedConnection.class, "refresh") + .inLine(27) + .asDependency()) + .by(typeParameter(ServiceHelper.class, "TYPE_PARAMETER_VIOLATING_LAYER_RULE").dependingOn(SomeUtility.class)) + .by(typeParameter(ServiceHelper.class, "ANOTHER_TYPE_PARAMETER_VIOLATING_LAYER_RULE").dependingOn(SomeEnum.class)) + .by(method(ServiceViolatingLayerRules.class, dependentMethod).withParameter(UseCaseTwoController.class)) .by(method(ServiceViolatingLayerRules.class, dependentMethod).withReturnType(SomeGuiController.class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod) + .dependingOnComponentType(UseCaseTwoController.class)) + .by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod) + .dependingOnComponentType(SomeGuiController.class)) .by(annotatedClass(ServiceViolatingLayerRules.class).withAnnotationParameterType(ComplexControllerAnnotation.class)) .by(annotatedClass(ServiceViolatingLayerRules.class).withAnnotationParameterType(SimpleControllerAnnotation.class)) .by(annotatedClass(ServiceViolatingLayerRules.class).withAnnotationParameterType(SomeEnum.class)) .by(method(ComplexServiceAnnotation.class, "controllerAnnotation").withReturnType(ComplexControllerAnnotation.class)) - .by(method(ComplexServiceAnnotation.class, "controllerEnum").withReturnType(SomeEnum.class)); + .by(method(ComplexServiceAnnotation.class, "controllerEnum").withReturnType(SomeEnum.class)) + .by(method(OtherJpa.class, "testConnection") + .checkingInstanceOf(ProxiedConnection.class) + .inLine(26)); ExpectedTestFailures expectedTestFailures = ExpectedTestFailures .forTests( @@ -935,7 +985,7 @@ Stream MethodsTest() { .ofRule("no code units that are declared in classes that reside in a package '..persistence..' " + "should be annotated with @" + Secured.class.getSimpleName()) .by(ExpectedConstructor.of(SomeJpa.class).inLine(15).beingAnnotatedWith(Secured.class)) - .by(ExpectedMethod.of(OtherJpa.class, "getEntityManager").inLine(31).beingAnnotatedWith(Secured.class)) + .by(ExpectedMethod.of(OtherJpa.class, "getEntityManager").inLine(35).beingAnnotatedWith(Secured.class)) .toDynamicTests(); } diff --git a/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedDependency.java b/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedDependency.java index 50239d8574..7a19fce21d 100644 --- a/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedDependency.java +++ b/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedDependency.java @@ -25,6 +25,10 @@ public static InheritanceCreator inheritanceFrom(Class clazz) { return new InheritanceCreator(clazz); } + public static TypeParameterCreator typeParameter(Class clazz, String typeParameterName) { + return new TypeParameterCreator(clazz, typeParameterName); + } + public static AnnotationDependencyCreator annotatedClass(Class clazz) { return new AnnotationDependencyCreator(clazz); } @@ -85,6 +89,21 @@ public ExpectedDependency implementing(Class anInterface) { } } + public static class TypeParameterCreator { + private final Class clazz; + private final String typeParameterName; + + private TypeParameterCreator(Class clazz, String typeParameterName) { + this.clazz = clazz; + this.typeParameterName = typeParameterName; + } + + public ExpectedDependency dependingOn(Class typeParameterDependency) { + return new ExpectedDependency(clazz, typeParameterDependency, + getDependencyPattern(clazz.getName(), "has type parameter '" + typeParameterName + "' depending on", typeParameterDependency.getName(), 0)); + } + } + public static class AccessCreator { private final Class originClass; @@ -139,13 +158,39 @@ public ExpectedDependency withReturnType(Class returnType) { return new ExpectedDependency(owner, returnType, dependencyPattern); } + public ExpectedDependency dependingOnComponentType(Class componentType) { + String dependencyPattern = getDependencyPattern(getOriginName(), "depends on component type", componentType.getName(), 0); + return new ExpectedDependency(owner, componentType, dependencyPattern); + } + public ExpectedDependency withAnnotationType(Class annotationType) { return new ExpectedDependency(owner, annotationType, getDependencyPattern(getOriginName(), "is annotated with", annotationType.getName(), 0)); } + public AddsLineNumber checkingInstanceOf(Class target) { + return new AddsLineNumber(owner, getOriginName(), target); + } + private String getOriginName() { return owner.getName() + "." + memberName; } + + public static class AddsLineNumber { + private final Class owner; + private final String origin; + private final Class target; + + private AddsLineNumber(Class owner, String origin, Class target) { + this.owner = owner; + this.origin = origin; + this.target = target; + } + + public ExpectedDependency inLine(int lineNumber) { + String dependencyPattern = getDependencyPattern(origin, "checks instanceof", target.getName(), lineNumber); + return new ExpectedDependency(owner, target, dependencyPattern); + } + } } public static class AnnotationDependencyCreator { diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/ArchUnitTestEngine.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/ArchUnitTestEngine.java index bbd3e9f4db..fa01f573fa 100644 --- a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/ArchUnitTestEngine.java +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/ArchUnitTestEngine.java @@ -22,12 +22,10 @@ import java.util.Optional; import java.util.function.Predicate; import java.util.stream.Stream; -import java.util.stream.StreamSupport; import com.tngtech.archunit.Internal; import com.tngtech.archunit.core.MayResolveTypesViaReflection; import com.tngtech.archunit.core.domain.JavaClass; -import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.ExecutionRequest; @@ -143,15 +141,11 @@ private void resolveRequestedUniqueIds(EngineDiscoveryRequest discoveryRequest, } private Stream getContainedClasses(String[] packages) { - return stream(new ClassFileImporter().importPackages(packages)); + return new ClassFileImporter().importPackages(packages).stream(); } private Stream getContainedClasses(ClasspathRootSelector selector) { - return stream(new ClassFileImporter().importUrl(toUrl(selector.getClasspathRoot()))); - } - - private Stream stream(JavaClasses classes) { - return StreamSupport.stream(classes.spliterator(), false); + return new ClassFileImporter().importUrl(toUrl(selector.getClasspathRoot())).stream(); } private Predicate isAllowedBy(EngineDiscoveryRequest discoveryRequest) { diff --git a/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/testexamples/TestFieldWithMetaTags.java b/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/testexamples/TestFieldWithMetaTags.java index a4e626d674..3f2c5475cb 100644 --- a/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/testexamples/TestFieldWithMetaTags.java +++ b/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/testexamples/TestFieldWithMetaTags.java @@ -11,7 +11,6 @@ import java.lang.annotation.Target; import static java.lang.annotation.ElementType.*; -import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; @AnalyzeClasses diff --git a/archunit-junit/src/test/java/com/tngtech/archunit/junit/ClassCacheTest.java b/archunit-junit/src/test/java/com/tngtech/archunit/junit/ClassCacheTest.java index 8c34920ba1..5a9a42a281 100644 --- a/archunit-junit/src/test/java/com/tngtech/archunit/junit/ClassCacheTest.java +++ b/archunit-junit/src/test/java/com/tngtech/archunit/junit/ClassCacheTest.java @@ -28,7 +28,7 @@ import org.mockito.junit.MockitoRule; import static com.tngtech.archunit.junit.CacheMode.PER_CLASS; -import static com.tngtech.archunit.testutil.Assertions.assertThatClasses; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -114,13 +114,13 @@ public void get_all_classes_by_LocationProvider() { .withPackagesRoots(ClassCacheTest.class) .withLocationProviders(TestLocationProviderOfClass_String.class, TestLocationProviderOfClass_Rule.class)); - assertThatClasses(classes).contain(String.class, Rule.class, getClass()); + assertThatTypes(classes).contain(String.class, Rule.class, getClass()); classes = cache.getClassesToAnalyzeFor(TestClassWithLocationProviderUsingTestClass.class, analyzeLocation(LocationOfClass.Provider.class)); - assertThatClasses(classes).contain(String.class); - assertThatClasses(classes).doNotContain(getClass()); + assertThatTypes(classes).contain(String.class); + assertThatTypes(classes).doNotContain(getClass()); } @Test @@ -275,4 +275,4 @@ public Set get(Class testClass) { return Collections.emptySet(); } } -} \ No newline at end of file +} diff --git a/archunit-maven-test/pom.xml.template b/archunit-maven-test/pom.xml.template index f7055ec34a..edf40686ed 100644 --- a/archunit-maven-test/pom.xml.template +++ b/archunit-maven-test/pom.xml.template @@ -78,7 +78,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.7.0 + 3.8.1 true #{javaVersion} diff --git a/archunit/build.gradle b/archunit/build.gradle index fbcb0dac9e..df0a62a3ba 100644 --- a/archunit/build.gradle +++ b/archunit/build.gradle @@ -65,6 +65,7 @@ sourceSets { dependencies { jdk9mainImplementation sourceSets.main.output jdk9testImplementation sourceSets.test.output + jdk9testImplementation sourceSets.test.compileClasspath jdk9testImplementation sourceSets.jdk9main.output runtimeOnly sourceSets.jdk9main.output @@ -89,6 +90,11 @@ task jdk9Test(type: Test) { [jar, test]*.dependsOn compileJdk9mainJava +[test, jdk9Test].each { testTask -> + addMultiJdkTestsFor project, testTask +} +test.finalizedBy(jdk9Test) + spotbugsJdk9test.enabled = false idea { diff --git a/archunit/src/jdk9main/java/com/tngtech/archunit/core/domain/Java9DomainPlugin.java b/archunit/src/jdk9main/java/com/tngtech/archunit/core/domain/Java9DomainPlugin.java index 5ed77359a2..387fb66010 100644 --- a/archunit/src/jdk9main/java/com/tngtech/archunit/core/domain/Java9DomainPlugin.java +++ b/archunit/src/jdk9main/java/com/tngtech/archunit/core/domain/Java9DomainPlugin.java @@ -16,7 +16,6 @@ package com.tngtech.archunit.core.domain; import com.tngtech.archunit.Internal; -import com.tngtech.archunit.base.Function; import com.tngtech.archunit.core.InitialConfiguration; import com.tngtech.archunit.core.PluginLoader; @@ -27,8 +26,8 @@ @Internal public class Java9DomainPlugin implements DomainPlugin { @Override - public void plugInAnnotationValueFormatter(InitialConfiguration> valueFormatter) { - valueFormatter.set(AnnotationValueFormatter.configure() + public void plugInAnnotationPropertiesFormatter(InitialConfiguration propertiesFormatter) { + propertiesFormatter.set(AnnotationPropertiesFormatter.configure() .formattingArraysWithCurlyBrackets() .formattingTypesAsClassNames() .quotingStrings() diff --git a/archunit/src/main/java/com/tngtech/archunit/ArchConfiguration.java b/archunit/src/main/java/com/tngtech/archunit/ArchConfiguration.java index 626426ab60..6cb161cd08 100644 --- a/archunit/src/main/java/com/tngtech/archunit/ArchConfiguration.java +++ b/archunit/src/main/java/com/tngtech/archunit/ArchConfiguration.java @@ -273,15 +273,16 @@ private static class PropertiesOverwritableBySystemProperties { ENABLE_MD5_IN_CLASS_SOURCES, Boolean.FALSE.toString() )); - private final Properties properties = createProperties(PROPERTY_DEFAULTS); + private final Properties baseProperties = createProperties(PROPERTY_DEFAULTS); + private final Properties overwrittenProperties = new Properties(); void clear() { - properties.clear(); - properties.putAll(PROPERTY_DEFAULTS); + replaceProperties(baseProperties, PROPERTY_DEFAULTS); + overwrittenProperties.clear(); } void load(InputStream inputStream) throws IOException { - properties.load(inputStream); + baseProperties.load(inputStream); } Set stringPropertyNames() { @@ -301,23 +302,33 @@ String getProperty(String propertyName, String defaultValue) { } void setProperty(String propertyName, String value) { - properties.setProperty(propertyName, value); + baseProperties.setProperty(propertyName, value); } void remove(String propertyName) { - properties.remove(propertyName); + baseProperties.remove(propertyName); } Properties getMergedProperties() { - Properties result = createProperties(this.properties); - Properties overwritten = getSubProperties("archunit", System.getProperties()); - if (!overwritten.isEmpty()) { - LOG.info("Merging properties: The following properties have been overwritten by system properties: {}", overwritten); + Properties result = createProperties(baseProperties); + Properties currentlyOverwritten = getSubProperties("archunit", System.getProperties()); + result.putAll(currentlyOverwritten); + + if (!overwrittenProperties.equals(currentlyOverwritten)) { + replaceProperties(overwrittenProperties, currentlyOverwritten); + if (!currentlyOverwritten.isEmpty()) { + LOG.info("Merging properties: The following properties have been overwritten by system properties: {}", currentlyOverwritten); + } } - result.putAll(overwritten); + return result; } + private static void replaceProperties(Properties properties, Properties newProperties) { + properties.clear(); + properties.putAll(newProperties); + } + private static Properties createProperties(Map entries) { Properties result = new Properties(); result.putAll(entries); diff --git a/archunit/src/main/java/com/tngtech/archunit/base/Optional.java b/archunit/src/main/java/com/tngtech/archunit/base/Optional.java index 6fc4637a02..6200b3fdf8 100644 --- a/archunit/src/main/java/com/tngtech/archunit/base/Optional.java +++ b/archunit/src/main/java/com/tngtech/archunit/base/Optional.java @@ -42,7 +42,6 @@ public static Optional fromNullable(T object) { } @PublicAPI(usage = ACCESS) - @SuppressWarnings("unchecked") public static Optional absent() { return Absent.getInstance(); } @@ -53,9 +52,16 @@ public static Optional absent() { @PublicAPI(usage = ACCESS) public abstract T get(); + /** + * @deprecated Use {@link #getOrThrow(Supplier)} instead (this version always instantiates the exception, no matter if needed) + */ + @Deprecated @PublicAPI(usage = ACCESS) public abstract T getOrThrow(RuntimeException e); + @PublicAPI(usage = ACCESS) + public abstract T getOrThrow(Supplier exceptionSupplier); + @PublicAPI(usage = ACCESS) public abstract Optional transform(Function function); @@ -114,6 +120,11 @@ public T getOrThrow(RuntimeException e) { throw e; } + @Override + public T getOrThrow(Supplier exceptionSupplier) { + throw exceptionSupplier.get(); + } + @Override public Optional transform(Function function) { return absent(); @@ -172,6 +183,11 @@ public T getOrThrow(RuntimeException e) { return object; } + @Override + public T getOrThrow(Supplier exceptionSupplier) { + return object; + } + @Override public Optional transform(Function function) { return Optional.of(function.apply(object)); diff --git a/archunit/src/main/java/com/tngtech/archunit/base/Supplier.java b/archunit/src/main/java/com/tngtech/archunit/base/Supplier.java new file mode 100644 index 0000000000..2e2d5fb3d6 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/base/Supplier.java @@ -0,0 +1,25 @@ +/* + * Copyright 2014-2020 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.base; + +import com.tngtech.archunit.PublicAPI; + +import static com.tngtech.archunit.PublicAPI.Usage.INHERITANCE; + +@PublicAPI(usage = INHERITANCE) +public interface Supplier { + T get(); +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/PluginLoader.java b/archunit/src/main/java/com/tngtech/archunit/core/PluginLoader.java index 0f5fd42289..244240b9ce 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/PluginLoader.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/PluginLoader.java @@ -133,30 +133,46 @@ public Creator load(String pluginClassName) { @Internal public enum JavaVersion { - JAVA_9 { - @Override - public boolean isLessOrEqualThan(String version) { - // The new versioning scheme starting with JDK 9 is 9.x, before it was sth. like 1.8.0_122 - return parseFirstGroupOfJavaVersion(version) >= 9; - } - private int parseFirstGroupOfJavaVersion(String javaVersion) { - Matcher matcher = VERSION_PATTERN.matcher(javaVersion); - if (!matcher.matches()) { - throw new IllegalArgumentException("Can't parse Java version " + javaVersion); - } - return Integer.parseInt(matcher.group(1)); - } - }; + JAVA_9(9), + JAVA_14(14); + + private final int releaseVersion; + + JavaVersion(int releaseVersion) { + this.releaseVersion = releaseVersion; + } - private static final Ordering FROM_NEWEST_TO_OLDEST_ORDERING = Ordering.explicit(JAVA_9); + private static final Ordering FROM_NEWEST_TO_OLDEST_ORDERING = Ordering.natural().reverse(); private static final Pattern VERSION_PATTERN = Pattern.compile("^(\\d+).*"); - public abstract boolean isLessOrEqualThan(String version); + public boolean isLessOrEqualThan(String version) { + return this.releaseVersion <= parseJavaReleaseVersion(version); + } static List sortFromNewestToOldest(Set javaVersions) { return FROM_NEWEST_TO_OLDEST_ORDERING.sortedCopy(javaVersions); } + + private static int parseJavaReleaseVersion(String version) { + String versionToParse; + if (version.startsWith("1.")) { + // Up to JDK 8 the versioning scheme was sth. like 1.8.0_122 + versionToParse = version.substring(2); + } else { + // The new versioning scheme starting with JDK 9 is + // - for major releases: 9 + // - for patches: 9.x + // - for early access builds: 9-ea + versionToParse = version; + } + + Matcher matcher = VERSION_PATTERN.matcher(versionToParse); + if (!matcher.matches()) { + throw new IllegalArgumentException("Can't parse Java version " + version); + } + return Integer.parseInt(matcher.group(1)); + } } static class PluginLoadingFailedException extends RuntimeException { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/AccessTarget.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/AccessTarget.java index 9206752cf4..a0cf420e3a 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/AccessTarget.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/AccessTarget.java @@ -251,6 +251,12 @@ public static final class FieldAccessTarget extends AccessTarget implements HasT this.field = Suppliers.memoize(builder.getField()); } + @Override + @PublicAPI(usage = ACCESS) + public JavaType getType() { + return type; + } + @Override @PublicAPI(usage = ACCESS) public JavaClass getRawType() { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationValueFormatter.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationPropertiesFormatter.java similarity index 65% rename from archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationValueFormatter.java rename to archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationPropertiesFormatter.java index 178b05174b..2594901541 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationValueFormatter.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationPropertiesFormatter.java @@ -17,7 +17,10 @@ import java.lang.reflect.Array; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import com.google.common.base.Joiner; import com.tngtech.archunit.base.Function; @@ -25,19 +28,33 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.tngtech.archunit.base.Function.Functions.identity; -class AnnotationValueFormatter implements Function { +class AnnotationPropertiesFormatter { private final Function, String> arrayFormatter; private final Function, String> typeFormatter; private final Function stringFormatter; + private final boolean omitOptionalIdentifierForSingleElementAnnotations; - private AnnotationValueFormatter(Builder builder) { + private AnnotationPropertiesFormatter(Builder builder) { this.arrayFormatter = checkNotNull(builder.arrayFormatter); this.typeFormatter = checkNotNull(builder.typeFormatter); this.stringFormatter = checkNotNull(builder.stringFormatter); + this.omitOptionalIdentifierForSingleElementAnnotations = builder.omitOptionalIdentifierForSingleElementAnnotations; } - @Override - public String apply(Object input) { + String formatProperties(Map properties) { + // see Builder#omitOptionalIdentifierForSingleElementAnnotations() for documentation + if (properties.size() == 1 && properties.containsKey("value") && omitOptionalIdentifierForSingleElementAnnotations) { + return formatValue(properties.get("value")); + } + + Set formattedProperties = new HashSet<>(); + for (Map.Entry entry : properties.entrySet()) { + formattedProperties.add(entry.getKey() + "=" + formatValue(entry.getValue())); + } + return Joiner.on(", ").join(formattedProperties); + } + + String formatValue(Object input) { if (input instanceof Class) { return typeFormatter.apply((Class) input); } @@ -50,7 +67,7 @@ public String apply(Object input) { List elemToString = new ArrayList<>(); for (int i = 0; i < Array.getLength(input); i++) { - elemToString.add("" + apply(Array.get(input, i))); + elemToString.add(formatValue(Array.get(input, i))); } return arrayFormatter.apply(elemToString); } @@ -63,6 +80,7 @@ static class Builder { private Function, String> arrayFormatter; private Function, String> typeFormatter; private Function stringFormatter = identity(); + private boolean omitOptionalIdentifierForSingleElementAnnotations = false; Builder formattingArraysWithSquareBrackets() { arrayFormatter = new Function, String>() { @@ -114,8 +132,21 @@ public String apply(String input) { return this; } - AnnotationValueFormatter build() { - return new AnnotationValueFormatter(this); + /** + * Configures that the identifier is omitted if the annotation is a + * single-element annotation + * and the identifier of the only element is "value". + * + *
  • Example with this configuration: {@code @Copyright("2020 Acme Corporation")}
  • + *
  • Example without this configuration: {@code @Copyright(value="2020 Acme Corporation")}
+ */ + Builder omitOptionalIdentifierForSingleElementAnnotations() { + omitOptionalIdentifierForSingleElementAnnotations = true; + return this; + } + + AnnotationPropertiesFormatter build() { + return new AnnotationPropertiesFormatter(this); } } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationProxy.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationProxy.java index 4401ddc152..28247930b7 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationProxy.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationProxy.java @@ -20,17 +20,15 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; -import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.tngtech.archunit.base.Function; +import com.google.common.collect.Maps; import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.InitialConfiguration; import com.tngtech.archunit.core.MayResolveTypesViaReflection; @@ -39,10 +37,10 @@ @MayResolveTypesViaReflection(reason = "We depend on the classpath, if we proxy an annotation type") class AnnotationProxy { - private static final InitialConfiguration> valueFormatter = new InitialConfiguration<>(); + private static final InitialConfiguration propertiesFormatter = new InitialConfiguration<>(); static { - DomainPlugin.Loader.loadForCurrentPlatform().plugInAnnotationValueFormatter(valueFormatter); + DomainPlugin.Loader.loadForCurrentPlatform().plugInAnnotationPropertiesFormatter(propertiesFormatter); } public static A of(Class annotationType, JavaAnnotation toProxy) { @@ -122,7 +120,7 @@ private JavaClassConversion(ClassLoader classLoader) { @Override public Class convert(JavaClass input, Class returnType) { - return JavaType.From.javaClass(input).resolveClass(classLoader); + return JavaClassDescriptor.From.javaClass(input).resolveClass(classLoader); } @Override @@ -152,7 +150,7 @@ public boolean canHandle(Class returnType) { private static class JavaEnumConstantConversion implements Conversion { @Override public Enum convert(JavaEnumConstant input, Class returnType) { - for (Object constant : JavaType.From.javaClass(input.getDeclaringClass()).resolveClass().getEnumConstants()) { + for (Object constant : JavaClassDescriptor.From.javaClass(input.getDeclaringClass()).resolveClass().getEnumConstants()) { Enum anEnum = (Enum) constant; if (anEnum.name().equals(input.name())) { return anEnum; @@ -198,7 +196,7 @@ public Annotation convert(JavaAnnotation input, Class returnType) { // JavaAnnotation.getType() will return the type name of a Class @SuppressWarnings("unchecked") Class type = (Class) - JavaType.From.javaClass(input.getRawType()).resolveClass(classLoader); + JavaClassDescriptor.From.javaClass(input.getRawType()).resolveClass(classLoader); return AnnotationProxy.of(type, input); } @@ -279,17 +277,22 @@ private ToStringHandler(Class annotationType, JavaAnnotation toProxy, Conv @Override public Object handle(Object proxy, Method method, Object[] args) { - return String.format("@%s(%s)", toProxy.getRawType().getName(), propertyStrings()); + return String.format("@%s(%s)", toProxy.getRawType().getName(), propertiesString()); } - private String propertyStrings() { - Set properties = new HashSet<>(); - for (Map.Entry entry : toProxy.getProperties().entrySet()) { - Class returnType = getDeclaredMethod(entry.getKey()).getReturnType(); - String value = valueFormatter.get().apply(conversions.convertIfNecessary(entry.getValue(), returnType)); - properties.add(entry.getKey() + "=" + value); - } - return Joiner.on(", ").join(properties); + private String propertiesString() { + Map unwrappedProperties = unwrapProxiedProperties(); + return propertiesFormatter.get().formatProperties(unwrappedProperties); + } + + private Map unwrapProxiedProperties() { + return Maps.transformEntries(toProxy.getProperties(), new Maps.EntryTransformer() { + @Override + public Object transformEntry(String key, Object value) { + Class returnType = getDeclaredMethod(key).getReturnType(); + return conversions.convertIfNecessary(value, returnType); + } + }); } private Method getDeclaredMethod(String name) { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/Dependency.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/Dependency.java index 44e7c34bdf..e3de3ea06e 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/Dependency.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/Dependency.java @@ -21,6 +21,7 @@ import com.google.common.base.MoreObjects; import com.google.common.collect.ComparisonChain; +import com.google.common.collect.ImmutableSet; import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.base.ChainableFunction; import com.tngtech.archunit.base.DescribedPredicate; @@ -53,26 +54,31 @@ public class Dependency implements HasDescription, Comparable, HasSo private final int lineNumber; private final String description; private final SourceCodeLocation sourceCodeLocation; + private final int hashCode; private Dependency(JavaClass originClass, JavaClass targetClass, int lineNumber, String description) { + checkArgument(!originClass.equals(targetClass) || targetClass.isPrimitive(), + "Tried to create illegal dependency '%s' (%s -> %s), this is likely a bug!", + description, originClass.getSimpleName(), targetClass.getSimpleName()); + this.originClass = originClass; this.targetClass = targetClass; this.lineNumber = lineNumber; this.description = description; this.sourceCodeLocation = SourceCodeLocation.of(originClass, lineNumber); + hashCode = Objects.hash(originClass, targetClass, lineNumber, description); } - static Optional tryCreateFromAccess(JavaAccess access) { - if (access.getOriginOwner().equals(access.getTargetOwner()) || access.getTargetOwner().isPrimitive()) { - return Optional.absent(); - } - return Optional.of(new Dependency(access.getOriginOwner(), access.getTargetOwner(), access.getLineNumber(), access.getDescription())); + static Set tryCreateFromAccess(JavaAccess access) { + JavaClass originOwner = access.getOriginOwner(); + JavaClass targetOwner = access.getTargetOwner(); + ImmutableSet.Builder dependencies = ImmutableSet.builder() + .addAll(createComponentTypeDependencies(originOwner, access.getOrigin().getDescription(), targetOwner, access.getSourceCodeLocation())); + dependencies.addAll(tryCreateDependency(originOwner, targetOwner, access.getDescription(), access.getLineNumber()).asSet()); + return dependencies.build(); } static Dependency fromInheritance(JavaClass origin, JavaClass targetSuperType) { - checkArgument(!origin.equals(targetSuperType) && !targetSuperType.isPrimitive(), - "It should never be possible to create an inheritance dependency to self or any primitive"); - String originType = origin.isInterface() ? "Interface" : "Class"; String originDescription = originType + " " + bracketFormat(origin.getName()); @@ -84,63 +90,106 @@ static Dependency fromInheritance(JavaClass origin, JavaClass targetSuperType) { String dependencyDescription = originDescription + " " + dependencyType + " " + targetType + " " + targetDescription; String description = dependencyDescription + " in " + origin.getSourceCodeLocation(); - return new Dependency(origin, targetSuperType, 0, description); + Optional result = tryCreateDependency(origin, targetSuperType, description, 0); + + if (!result.isPresent()) { + throw new IllegalStateException(String.format("Tried to create illegal inheritance dependency '%s' (%s -> %s), this is likely a bug!", + description, origin.getSimpleName(), targetSuperType.getSimpleName())); + } + return result.get(); } - static Optional tryCreateFromField(JavaField field) { + static Set tryCreateFromField(JavaField field) { return tryCreateDependencyFromJavaMember(field, "has type", field.getRawType()); } - static Optional tryCreateFromReturnType(JavaMethod method) { + static Set tryCreateFromReturnType(JavaMethod method) { return tryCreateDependencyFromJavaMember(method, "has return type", method.getRawReturnType()); } - static Optional tryCreateFromParameter(JavaCodeUnit codeUnit, JavaClass parameter) { + static Set tryCreateFromParameter(JavaCodeUnit codeUnit, JavaClass parameter) { return tryCreateDependencyFromJavaMember(codeUnit, "has parameter of type", parameter); } - static Optional tryCreateFromThrowsDeclaration(ThrowsDeclaration declaration) { + static Set tryCreateFromThrowsDeclaration(ThrowsDeclaration declaration) { return tryCreateDependencyFromJavaMember(declaration.getLocation(), "throws type", declaration.getRawType()); } - static Optional tryCreateFromAnnotation(JavaAnnotation target) { - Origin origin = findSuitableOrigin(target); + static Set tryCreateFromInstanceofCheck(InstanceofCheck instanceofCheck) { + return tryCreateDependencyFromJavaMemberWithLocation(instanceofCheck.getOwner(), "checks instanceof", instanceofCheck.getRawType(), instanceofCheck.getLineNumber()); + } + + static Set tryCreateFromAnnotation(JavaAnnotation target) { + Origin origin = findSuitableOrigin(target, target.getAnnotatedElement()); return tryCreateDependency(origin.originClass, origin.originDescription, "is annotated with", target.getRawType()); } - static Optional tryCreateFromAnnotationMember(JavaAnnotation annotation, JavaClass memberType) { - Origin origin = findSuitableOrigin(annotation); + static Set tryCreateFromAnnotationMember(JavaAnnotation annotation, JavaClass memberType) { + Origin origin = findSuitableOrigin(annotation, annotation.getAnnotatedElement()); return tryCreateDependency(origin.originClass, origin.originDescription, "has annotation member of type", memberType); } - private static Origin findSuitableOrigin(JavaAnnotation annotation) { - Object annotatedElement = annotation.getAnnotatedElement(); - if (annotatedElement instanceof JavaMember) { - JavaMember member = (JavaMember) annotatedElement; + static Set tryCreateFromTypeParameter(JavaTypeVariable typeParameter, JavaClass typeParameterDependency) { + String dependencyType = "has type parameter '" + typeParameter.getName() + "' depending on"; + Origin origin = findSuitableOrigin(typeParameter, typeParameter.getOwner()); + return tryCreateDependency(origin.originClass, origin.originDescription, dependencyType, typeParameterDependency); + } + + private static Origin findSuitableOrigin(Object dependencyCause, Object originCandidate) { + if (originCandidate instanceof JavaMember) { + JavaMember member = (JavaMember) originCandidate; return new Origin(member.getOwner(), member.getDescription()); } - if (annotatedElement instanceof JavaClass) { - JavaClass clazz = (JavaClass) annotatedElement; + if (originCandidate instanceof JavaClass) { + JavaClass clazz = (JavaClass) originCandidate; return new Origin(clazz, clazz.getDescription()); } - throw new IllegalStateException("Could not find suitable dependency origin for " + annotation); + throw new IllegalStateException("Could not find suitable dependency origin for " + dependencyCause); } - private static Optional tryCreateDependencyFromJavaMember(JavaMember origin, String dependencyType, JavaClass target) { + private static Set tryCreateDependencyFromJavaMember(JavaMember origin, String dependencyType, JavaClass target) { return tryCreateDependency(origin.getOwner(), origin.getDescription(), dependencyType, target); } - private static Optional tryCreateDependency( + private static Set tryCreateDependencyFromJavaMemberWithLocation(JavaMember origin, String dependencyType, JavaClass target, int lineNumber) { + return tryCreateDependency(origin.getOwner(), origin.getDescription(), dependencyType, target, SourceCodeLocation.of(origin.getOwner(), lineNumber)); + } + + private static Set tryCreateDependency( JavaClass originClass, String originDescription, String dependencyType, JavaClass targetClass) { - if (originClass.equals(targetClass) || targetClass.isPrimitive()) { - return Optional.absent(); - } + return tryCreateDependency(originClass, originDescription, dependencyType, targetClass, originClass.getSourceCodeLocation()); + } + private static Set tryCreateDependency( + JavaClass originClass, String originDescription, String dependencyType, JavaClass targetClass, SourceCodeLocation sourceCodeLocation) { + ImmutableSet.Builder dependencies = ImmutableSet.builder() + .addAll(createComponentTypeDependencies(originClass, originDescription, targetClass, sourceCodeLocation)); String targetDescription = bracketFormat(targetClass.getName()); String dependencyDescription = originDescription + " " + dependencyType + " " + targetDescription; - String description = dependencyDescription + " in " + originClass.getSourceCodeLocation(); - return Optional.of(new Dependency(originClass, targetClass, 0, description)); + String description = dependencyDescription + " in " + sourceCodeLocation; + dependencies.addAll(tryCreateDependency(originClass, targetClass, description, sourceCodeLocation.getLineNumber()).asSet()); + return dependencies.build(); + } + + private static Set createComponentTypeDependencies(JavaClass originClass, String originDescription, JavaClass targetClass, SourceCodeLocation sourceCodeLocation) { + ImmutableSet.Builder result = ImmutableSet.builder(); + Optional componentType = targetClass.tryGetComponentType(); + while (componentType.isPresent()) { + String componentTypeTargetDescription = bracketFormat(componentType.get().getName()); + String componentTypeDependencyDescription = originDescription + " depends on component type " + componentTypeTargetDescription; + String componentTypeDescription = componentTypeDependencyDescription + " in " + sourceCodeLocation; + result.addAll(tryCreateDependency(originClass, componentType.get(), componentTypeDescription, sourceCodeLocation.getLineNumber()).asSet()); + componentType = componentType.get().tryGetComponentType(); + } + return result.build(); + } + + private static Optional tryCreateDependency(JavaClass originClass, JavaClass targetClass, String description, int lineNumber) { + if (originClass.equals(targetClass) || targetClass.isPrimitive()) { + return Optional.absent(); + } + return Optional.of(new Dependency(originClass, targetClass, lineNumber, description)); } private static String bracketFormat(String name) { @@ -180,7 +229,7 @@ public int compareTo(Dependency o) { @Override public int hashCode() { - return Objects.hash(originClass, targetClass, lineNumber, description); + return hashCode; } @Override diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java index 7b9075505e..61b468e376 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java @@ -17,17 +17,9 @@ import java.net.URI; import java.util.Collection; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Multimap; -import com.google.common.collect.SetMultimap; import com.tngtech.archunit.Internal; import com.tngtech.archunit.base.Function; import com.tngtech.archunit.base.HasDescription; @@ -45,8 +37,11 @@ import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodCallBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaStaticInitializerBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.JavaWildcardTypeBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.MethodCallTargetBuilder; +import static com.google.common.base.Preconditions.checkArgument; + /** * Together with {@link DomainBuilders}, this class is the link to create domain objects from the import * context. To make the API clear, we try to keep only those methods public, which are really meant to be used. @@ -62,7 +57,7 @@ @Internal public class DomainObjectCreationContext { public static JavaClasses createJavaClasses( - Map selectedClasses, Map allClasses, ImportContext importContext) { + Map selectedClasses, Collection allClasses, ImportContext importContext) { return JavaClasses.of(selectedClasses, allClasses, importContext); } @@ -75,6 +70,14 @@ public static void completeClassHierarchy(JavaClass javaClass, ImportContext imp javaClass.completeClassHierarchyFrom(importContext); } + public static void completeEnclosingClass(JavaClass javaClass, ImportContext importContext) { + javaClass.completeEnclosingClassFrom(importContext); + } + + public static void completeTypeParameters(JavaClass javaClass, ImportContext importContext) { + javaClass.completeTypeParametersFrom(importContext); + } + public static void completeMembers(JavaClass javaClass, ImportContext importContext) { javaClass.completeMembers(importContext); } @@ -115,8 +118,8 @@ public static AccessTarget.ConstructorCallTarget createConstructorCallTarget(Con return new AccessTarget.ConstructorCallTarget(builder); } - public static JavaMethod createJavaMethod(JavaMethodBuilder builder) { - return new JavaMethod(builder); + public static JavaMethod createJavaMethod(JavaMethodBuilder builder, Function> createAnnotationDefaultValue) { + return new JavaMethod(builder, createAnnotationDefaultValue); } public static JavaMethodCall createJavaMethodCall(JavaMethodCallBuilder builder) { @@ -135,149 +138,33 @@ public static JavaEnumConstant createJavaEnumConstant(JavaEnumConstantBuilder bu return new JavaEnumConstant(builder); } - public static Source createSource(URI uri, Optional sourceFileName) { - return new Source(uri, sourceFileName); + public static Source createSource(URI uri, Optional sourceFileName, boolean md5InClassSourcesEnabled) { + return new Source(uri, sourceFileName, md5InClassSourcesEnabled); } public static ThrowsClause createThrowsClause(CODE_UNIT codeUnit, List types) { return ThrowsClause.from(codeUnit, types); } - static class AccessContext { - final SetMultimap fieldAccessesByTarget = HashMultimap.create(); - final SetMultimap methodCallsByTarget = HashMultimap.create(); - final SetMultimap constructorCallsByTarget = HashMultimap.create(); - - private AccessContext() { - } - - void mergeWith(AccessContext other) { - fieldAccessesByTarget.putAll(other.fieldAccessesByTarget); - methodCallsByTarget.putAll(other.methodCallsByTarget); - constructorCallsByTarget.putAll(other.constructorCallsByTarget); - } - - static class Part extends AccessContext { - Part() { - } - - Part(JavaCodeUnit codeUnit) { - for (JavaFieldAccess access : codeUnit.getFieldAccesses()) { - fieldAccessesByTarget.put(access.getTarget().getOwner(), access); - } - for (JavaMethodCall call : codeUnit.getMethodCallsFromSelf()) { - methodCallsByTarget.put(call.getTarget().getOwner(), call); - } - for (JavaConstructorCall call : codeUnit.getConstructorCallsFromSelf()) { - constructorCallsByTarget.put(call.getTarget().getFullName(), call); - } - } - } - - static class TopProcess extends AccessContext { - private final Collection classes; - - TopProcess(Collection classes) { - this.classes = classes; - } - - void finish() { - for (JavaClass clazz : classes) { - for (JavaField field : clazz.getFields()) { - field.registerAccessesToField(getFieldAccessesTo(field)); - } - for (JavaMethod method : clazz.getMethods()) { - method.registerCallsToMethod(getMethodCallsOf(method)); - } - for (final JavaConstructor constructor : clazz.getConstructors()) { - constructor.registerCallsToConstructor(constructorCallsByTarget.get(constructor.getFullName())); - } - } - } - - private Supplier> getFieldAccessesTo(final JavaField field) { - return Suppliers.memoize(new AccessSupplier<>(field.getOwner(), fieldAccessTargetResolvesTo(field))); - } - - private Function> fieldAccessTargetResolvesTo(final JavaField field) { - return new ClassToFieldAccessesToSelf(fieldAccessesByTarget, field); - } - - private Supplier> getMethodCallsOf(final JavaMethod method) { - return Suppliers.memoize(new AccessSupplier<>(method.getOwner(), methodCallTargetResolvesTo(method))); - } - - private Function> methodCallTargetResolvesTo(final JavaMethod method) { - return new ClassToMethodCallsToSelf(methodCallsByTarget, method); - } - - private static class ClassToFieldAccessesToSelf implements Function> { - private final Multimap fieldAccessesByTarget; - private final JavaField field; - - ClassToFieldAccessesToSelf(SetMultimap fieldAccessesByTarget, JavaField field) { - this.fieldAccessesByTarget = fieldAccessesByTarget; - this.field = field; - } - - @Override - public Set apply(JavaClass input) { - Set result = new HashSet<>(); - for (JavaFieldAccess access : fieldAccessesByTarget.get(input)) { - if (access.getTarget().resolveField().asSet().contains(field)) { - result.add(access); - } - } - return result; - } - } - - private static class ClassToMethodCallsToSelf implements Function> { - private final Multimap methodCallsByTarget; - private final JavaMethod method; - - ClassToMethodCallsToSelf(SetMultimap methodCallsByTarget, JavaMethod method) { - this.methodCallsByTarget = methodCallsByTarget; - this.method = method; - } - - @Override - public Set apply(JavaClass input) { - Set result = new HashSet<>(); - for (JavaMethodCall call : methodCallsByTarget.get(input)) { - if (call.getTarget().resolve().contains(method)) { - result.add(call); - } - } - return result; - } - } + public static InstanceofCheck createInstanceofCheck(JavaCodeUnit codeUnit, JavaClass target, int lineNumber) { + return InstanceofCheck.from(codeUnit, target, lineNumber); + } - private static class AccessSupplier> implements Supplier> { - private final JavaClass owner; - private final Function> mapToAccesses; + public static JavaTypeVariable createTypeVariable(String name, OWNER owner, JavaClass erasure) { + return new JavaTypeVariable<>(name, owner, erasure); + } - AccessSupplier(JavaClass owner, Function> mapToAccesses) { - this.owner = owner; - this.mapToAccesses = mapToAccesses; - } + public static void completeTypeVariable(JavaTypeVariable variable, List upperBounds) { + variable.setUpperBounds(upperBounds); + } - @Override - public Set get() { - ImmutableSet.Builder result = ImmutableSet.builder(); - for (final JavaClass javaClass : getPossibleTargetClassesForAccess()) { - result.addAll(mapToAccesses.apply(javaClass)); - } - return result.build(); - } + public static JavaGenericArrayType createGenericArrayType(JavaType componentType, JavaClass erasure) { + checkArgument(componentType instanceof JavaTypeVariable || componentType instanceof JavaGenericArrayType, + "Component type of a generic array type can only be a type variable or a generic array type. This is most likely a bug."); + return new JavaGenericArrayType(componentType.getName() + "[]", componentType, erasure); + } - private Set getPossibleTargetClassesForAccess() { - return ImmutableSet.builder() - .add(owner) - .addAll(owner.getAllSubClasses()) - .build(); - } - } - } + public static JavaWildcardType createWildcardType(JavaWildcardTypeBuilder builder) { + return new JavaWildcardType(builder); } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainPlugin.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainPlugin.java index 085f0d2dc5..dfb17e2caa 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainPlugin.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainPlugin.java @@ -16,20 +16,21 @@ package com.tngtech.archunit.core.domain; import com.tngtech.archunit.Internal; -import com.tngtech.archunit.base.Function; import com.tngtech.archunit.core.InitialConfiguration; import com.tngtech.archunit.core.PluginLoader; +import static com.tngtech.archunit.core.PluginLoader.JavaVersion.JAVA_14; import static com.tngtech.archunit.core.PluginLoader.JavaVersion.JAVA_9; interface DomainPlugin { - void plugInAnnotationValueFormatter(InitialConfiguration> valueFormatter); + void plugInAnnotationPropertiesFormatter(InitialConfiguration valueFormatter); @Internal class Loader { private static final PluginLoader pluginLoader = PluginLoader .forType(DomainPlugin.class) .ifVersionGreaterOrEqualTo(JAVA_9).load("com.tngtech.archunit.core.domain.Java9DomainPlugin") + .ifVersionGreaterOrEqualTo(JAVA_14).load("com.tngtech.archunit.core.domain.Java14DomainPlugin") .fallback(new LegacyDomainPlugin()); static DomainPlugin loadForCurrentPlatform() { @@ -38,8 +39,8 @@ static DomainPlugin loadForCurrentPlatform() { private static class LegacyDomainPlugin implements DomainPlugin { @Override - public void plugInAnnotationValueFormatter(InitialConfiguration> valueFormatter) { - valueFormatter.set(AnnotationValueFormatter.configure() + public void plugInAnnotationPropertiesFormatter(InitialConfiguration valueFormatter) { + valueFormatter.set(AnnotationPropertiesFormatter.configure() .formattingArraysWithSquareBrackets() .formattingTypesToString() .build()); diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java index 3b5b4fff9d..2e8e7a8248 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java @@ -15,6 +15,7 @@ */ package com.tngtech.archunit.core.domain; +import java.util.List; import java.util.Map; import java.util.Set; @@ -27,6 +28,8 @@ public interface ImportContext { Set createInterfaces(JavaClass owner); + List> createTypeParameters(JavaClass owner); + Set createFields(JavaClass owner); Set createMethods(JavaClass owner); @@ -37,29 +40,17 @@ public interface ImportContext { Map> createAnnotations(JavaClass owner); - Optional createEnclosingClass(JavaClass owner); - - Set getFieldAccessesFor(JavaCodeUnit codeUnit); - - Set getMethodCallsFor(JavaCodeUnit codeUnit); - - Set getConstructorCallsFor(JavaCodeUnit codeUnit); - - Set getFieldsOfType(JavaClass javaClass); + Map> createAnnotations(JavaMember owner); - Set getMethodsWithParameterOfType(JavaClass javaClass); - - Set getMethodsWithReturnType(JavaClass javaClass); - - Set> getMethodThrowsDeclarationsOfType(JavaClass javaClass); - - Set getConstructorsWithParameterOfType(JavaClass javaClass); + Optional createEnclosingClass(JavaClass owner); - Set> getConstructorThrowsDeclarationsOfType(JavaClass javaClass); + Set createFieldAccessesFor(JavaCodeUnit codeUnit); - Set> getAnnotationsOfType(JavaClass javaClass); + Set createMethodCallsFor(JavaCodeUnit codeUnit); - Set> getAnnotationsWithParameterOfType(JavaClass javaClass); + Set createConstructorCallsFor(JavaCodeUnit codeUnit); JavaClass resolveClass(String fullyQualifiedClassName); + + Optional getMethodReturnType(String declaringClassName, String methodName); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/InstanceofCheck.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/InstanceofCheck.java new file mode 100644 index 0000000000..02d15ec58e --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/InstanceofCheck.java @@ -0,0 +1,63 @@ +/* + * Copyright 2014-2020 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.core.domain; + +import com.tngtech.archunit.PublicAPI; +import com.tngtech.archunit.core.domain.properties.HasOwner; +import com.tngtech.archunit.core.domain.properties.HasType; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; + +public final class InstanceofCheck implements HasType, HasOwner { + + private final JavaCodeUnit owner; + private final JavaClass target; + private final int lineNumber; + + private InstanceofCheck(JavaCodeUnit owner, JavaClass target, int lineNumber) { + this.owner = checkNotNull(owner); + this.target = checkNotNull(target); + this.lineNumber = lineNumber; + } + + @Override + @PublicAPI(usage = ACCESS) + public JavaClass getRawType() { + return target; + } + + @Override + @PublicAPI(usage = ACCESS) + public JavaType getType() { + return target; + } + + @Override + @PublicAPI(usage = ACCESS) + public JavaCodeUnit getOwner() { + return owner; + } + + @PublicAPI(usage = ACCESS) + public int getLineNumber() { + return lineNumber; + } + + static InstanceofCheck from(JavaCodeUnit owner, JavaClass target, int lineNumber) { + return new InstanceofCheck(owner, target, lineNumber); + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/Java14DomainPlugin.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/Java14DomainPlugin.java new file mode 100644 index 0000000000..14992c62bc --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/Java14DomainPlugin.java @@ -0,0 +1,37 @@ +/* + * Copyright 2014-2020 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.core.domain; + +import com.tngtech.archunit.Internal; +import com.tngtech.archunit.core.InitialConfiguration; +import com.tngtech.archunit.core.PluginLoader; + +/** + * Resolved via {@link PluginLoader} + */ +@SuppressWarnings("unused") +@Internal +public class Java14DomainPlugin implements DomainPlugin { + @Override + public void plugInAnnotationPropertiesFormatter(InitialConfiguration propertiesFormatter) { + propertiesFormatter.set(AnnotationPropertiesFormatter.configure() + .formattingArraysWithCurlyBrackets() + .formattingTypesAsClassNames() + .quotingStrings() + .omitOptionalIdentifierForSingleElementAnnotations() + .build()); + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAnnotation.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAnnotation.java index 25ad9806a7..882f7fee95 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAnnotation.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAnnotation.java @@ -18,6 +18,7 @@ import java.lang.annotation.Annotation; import java.util.Map; +import com.google.common.collect.ImmutableMap; import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.base.HasDescription; import com.tngtech.archunit.base.Optional; @@ -119,6 +120,11 @@ private String startWithLowerCase(HasDescription annotatedElement) { return annotatedElement.getDescription().substring(0, 1).toLowerCase() + annotatedElement.getDescription().substring(1); } + @Override + public JavaType getType() { + return type; + } + @Override @PublicAPI(usage = ACCESS) public JavaClass getRawType() { @@ -188,7 +194,17 @@ public CanBeAnnotated getAnnotatedElement() { */ @PublicAPI(usage = ACCESS) public Optional get(String property) { - return Optional.fromNullable(values.get(property)); + Object directResult = values.get(property); + return directResult != null + ? Optional.of(directResult) + : tryGetDefaultValue(property); + } + + private Optional tryGetDefaultValue(String property) { + Optional method = type.tryGetMethod(property); + return method.isPresent() + ? method.get().getDefaultValue() + : Optional.absent(); } /** @@ -196,7 +212,14 @@ public Optional get(String property) { */ @PublicAPI(usage = ACCESS) public Map getProperties() { - return values; + ImmutableMap.Builder result = ImmutableMap.builder(); + result.putAll(values); + for (JavaMethod method : type.getMethods()) { + if (!values.containsKey(method.getName()) && method.getDefaultValue().isPresent()) { + result.put(method.getName(), method.getDefaultValue().get()); + } + } + return result.build(); } /** diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java index 5c7022d1e3..74686e19b7 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java @@ -36,7 +36,6 @@ import com.tngtech.archunit.base.PackageMatcher; import com.tngtech.archunit.core.MayResolveTypesViaReflection; import com.tngtech.archunit.core.ResolvesTypesViaReflection; -import com.tngtech.archunit.core.domain.DomainObjectCreationContext.AccessContext; import com.tngtech.archunit.core.domain.properties.CanBeAnnotated; import com.tngtech.archunit.core.domain.properties.HasAnnotations; import com.tngtech.archunit.core.domain.properties.HasModifiers; @@ -58,19 +57,22 @@ import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Utils.toAnnotationOfType; import static com.tngtech.archunit.core.domain.properties.HasName.Functions.GET_NAME; import static com.tngtech.archunit.core.domain.properties.HasType.Functions.GET_RAW_TYPE; +import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Collections.emptySet; -public class JavaClass implements HasName.AndFullName, HasAnnotations, HasModifiers, HasSourceCodeLocation { +public class JavaClass implements JavaType, HasName.AndFullName, HasAnnotations, HasModifiers, HasSourceCodeLocation { private final Optional source; private final SourceCodeLocation sourceCodeLocation; - private final JavaType javaType; + private final JavaClassDescriptor descriptor; private JavaPackage javaPackage; private final boolean isInterface; private final boolean isEnum; + private final boolean isAnnotation; private final boolean isAnonymousClass; private final boolean isMemberClass; private final Set modifiers; + private List> typeParameters = emptyList(); private final Supplier> reflectSupplier; private Set fields = emptySet(); private Set codeUnits = emptySet(); @@ -79,8 +81,54 @@ public class JavaClass implements HasName.AndFullName, HasAnnotations private Set constructors = emptySet(); private Optional staticInitializer = Optional.absent(); private Optional superClass = Optional.absent(); + private final Supplier> allSuperClasses = Suppliers.memoize(new Supplier>() { + @Override + public List get() { + ImmutableList.Builder result = ImmutableList.builder(); + JavaClass current = JavaClass.this; + while (current.getSuperClass().isPresent()) { + current = current.getSuperClass().get(); + result.add(current); + } + return result.build(); + } + }); private final Set interfaces = new HashSet<>(); + private final Supplier> allInterfaces = Suppliers.memoize(new Supplier>() { + @Override + public Set get() { + ImmutableSet.Builder result = ImmutableSet.builder(); + for (JavaClass i : interfaces) { + result.add(i); + result.addAll(i.getAllInterfaces()); + } + if (superClass.isPresent()) { + result.addAll(superClass.get().getAllInterfaces()); + } + return result.build(); + } + }); + private final Supplier> classHierarchy = Suppliers.memoize(new Supplier>() { + @Override + public List get() { + ImmutableList.Builder result = ImmutableList.builder(); + result.add(JavaClass.this); + result.addAll(getAllSuperClasses()); + return result.build(); + } + }); private final Set subClasses = new HashSet<>(); + private final Supplier> allSubClasses = Suppliers.memoize(new Supplier>() { + @Override + public Set get() { + Set result = new HashSet<>(); + for (JavaClass subClass : subClasses) { + result.add(subClass); + result.addAll(subClass.getAllSubClasses()); + } + return result; + } + }); private Optional enclosingClass = Optional.absent(); private Optional componentType = Optional.absent(); private Map> annotations = emptyMap(); @@ -97,13 +145,16 @@ public Set get() { .build(); } }); - private JavaClassDependencies javaClassDependencies; + private JavaClassDependencies javaClassDependencies = new JavaClassDependencies(this); // just for stubs; will be overwritten for imported classes + private ReverseDependencies reverseDependencies = ReverseDependencies.EMPTY; // just for stubs; will be overwritten for imported classes + private boolean fullyImported = false; JavaClass(JavaClassBuilder builder) { source = checkNotNull(builder.getSource()); - javaType = checkNotNull(builder.getJavaType()); + descriptor = checkNotNull(builder.getDescriptor()); isInterface = builder.isInterface(); isEnum = builder.isEnum(); + isAnnotation = builder.isAnnotation(); isAnonymousClass = builder.isAnonymousClass(); isMemberClass = builder.isMemberClass(); modifiers = checkNotNull(builder.getModifiers()); @@ -138,7 +189,7 @@ public String getDescription() { @Override @PublicAPI(usage = ACCESS) public String getName() { - return javaType.getName(); + return descriptor.getFullyQualifiedClassName(); } /** @@ -152,7 +203,7 @@ public String getFullName() { @PublicAPI(usage = ACCESS) public String getSimpleName() { - return javaType.getSimpleName(); + return descriptor.getSimpleClassName(); } @PublicAPI(usage = ACCESS) @@ -166,12 +217,12 @@ void setPackage(JavaPackage javaPackage) { @PublicAPI(usage = ACCESS) public String getPackageName() { - return javaType.getPackageName(); + return descriptor.getPackageName(); } @PublicAPI(usage = ACCESS) public boolean isPrimitive() { - return javaType.isPrimitive(); + return descriptor.isPrimitive(); } @PublicAPI(usage = ACCESS) @@ -184,6 +235,11 @@ public boolean isEnum() { return isEnum; } + @PublicAPI(usage = ACCESS) + public boolean isAnnotation() { + return isAnnotation; + } + @PublicAPI(usage = ACCESS) public Optional tryGetEnumConstant(String name) { Optional field = tryGetField(name); @@ -213,19 +269,22 @@ public Set getEnumConstants() { @PublicAPI(usage = ACCESS) public boolean isArray() { - return javaType.isArray(); + return descriptor.isArray(); } /** * This is a convenience method for {@link #tryGetComponentType()} in cases where * clients know that this type is certainly an array type and thus the component type present. - * @throws IllegalArgumentException if this class is no array + * @throws IllegalStateException if this class is no array * @return The result of {@link #tryGetComponentType()} */ @PublicAPI(usage = ACCESS) public JavaClass getComponentType() { - return tryGetComponentType().getOrThrow(new IllegalArgumentException( - String.format("Type %s is no array", getSimpleName()))); + Optional componentType = tryGetComponentType(); + if (!componentType.isPresent()) { + throw new IllegalStateException(String.format("Type %s is no array", getSimpleName())); + } + return componentType.get(); } /** @@ -240,6 +299,22 @@ Optional tryGetComponentType() { return componentType; } + /** + * The base component type is the class' {@link #getComponentType() component type} if it is a one-dimensional array, + * the repeated application of {@link #getComponentType()} if it is a multi-dimensional array, + * or the class itself if it is no array. + * For example, the base component type of {@code int}, {@code int[]}, {@code int[][]}, ... is always {@code int}. + * @return The base component type of this class + */ + @PublicAPI(usage = ACCESS) + JavaClass getBaseComponentType() { + JavaClass type = this; + while (type.isArray()) { + type = type.getComponentType(); + } + return type; + } + /** * A top level class is a class that is not a nested class, i.e. not declared within the body * of another class.

@@ -260,7 +335,7 @@ Optional tryGetComponentType() { * Of all these class declarations only {@code TopLevel} is a top level class, since all * other classes are declared within the body of {@code TopLevel} and are thereby nested classes. *

- * Compare e.g. + * Compare e.g. * Java Language Specification * * @see #isNestedClass() @@ -298,7 +373,7 @@ public boolean isTopLevelClass() { * (which will have some generated name like {@code TopLevel$1}) * are considered nested classes. {@code TopLevel} on the other side is no nested class. *

- * Compare e.g. + * Compare e.g. * Java Language Specification * * @see #isTopLevelClass() @@ -338,7 +413,7 @@ public boolean isNestedClass() { * (which will have some generated name like {@code TopLevel$1}), as well as {@code TopLevel} * itself, are not considered member classes. *

- * Compare e.g. + * Compare e.g. * Java Language Specification * * @see #isTopLevelClass() @@ -379,7 +454,7 @@ public boolean isMemberClass() { * are no inner classes, because the former two explicitly or implicitly have the * {@code static} modifier while the latter one is a top level class. *

- * Compare e.g. + * Compare e.g. * Java Language Specification * * @see #isTopLevelClass() @@ -419,7 +494,7 @@ public boolean isInnerClass() { * either are top level, directly declared within the body of {@code TopLevel} or are anonymous * and thus have no name. *

- * Compare e.g. + * Compare e.g. * Java Language Specification * * @see #isTopLevelClass() @@ -460,7 +535,7 @@ public boolean isLocalClass() { * All the other classes {@code TopLevel}, {@code InnerClass}, {@code NestedStaticClass} and * {@code LocalClass} are considered non-anonymous. *

- * Compare e.g. + * Compare e.g. * Java Language Specification * * @see #isTopLevelClass() @@ -534,8 +609,11 @@ public A getAnnotationOfType(Class type) { @Override @PublicAPI(usage = ACCESS) public JavaAnnotation getAnnotationOfType(String typeName) { - return tryGetAnnotationOfType(typeName).getOrThrow(new IllegalArgumentException( - String.format("Type %s is not annotated with @%s", getSimpleName(), typeName))); + Optional> annotation = tryGetAnnotationOfType(typeName); + if (!annotation.isPresent()) { + throw new IllegalArgumentException(String.format("Type %s is not annotated with @%s", getSimpleName(), typeName)); + } + return annotation.get(); } @Override @@ -566,6 +644,17 @@ public Optional> tryGetAnnotationOfType(String typeNam return Optional.fromNullable(annotations.get(typeName)); } + @PublicAPI(usage = ACCESS) + public List> getTypeParameters() { + return typeParameters; + } + + @Override + @PublicAPI(usage = ACCESS) + public JavaClass toErasure() { + return this; + } + @PublicAPI(usage = ACCESS) public Optional getSuperClass() { return superClass; @@ -576,10 +665,7 @@ public Optional getSuperClass() { */ @PublicAPI(usage = ACCESS) public List getClassHierarchy() { - ImmutableList.Builder result = ImmutableList.builder(); - result.add(this); - result.addAll(getAllSuperClasses()); - return result.build(); + return classHierarchy.get(); } /** @@ -588,13 +674,7 @@ public List getClassHierarchy() { */ @PublicAPI(usage = ACCESS) public List getAllSuperClasses() { - ImmutableList.Builder result = ImmutableList.builder(); - JavaClass current = this; - while (current.getSuperClass().isPresent()) { - current = current.getSuperClass().get(); - result.add(current); - } - return result.build(); + return allSuperClasses.get(); } @PublicAPI(usage = ACCESS) @@ -609,15 +689,7 @@ public Set getInterfaces() { @PublicAPI(usage = ACCESS) public Set getAllInterfaces() { - ImmutableSet.Builder result = ImmutableSet.builder(); - for (JavaClass i : interfaces) { - result.add(i); - result.addAll(i.getAllInterfaces()); - } - if (superClass.isPresent()) { - result.addAll(superClass.get().getAllInterfaces()); - } - return result.build(); + return allInterfaces.get(); } /** @@ -644,12 +716,7 @@ public Optional getEnclosingClass() { @PublicAPI(usage = ACCESS) public Set getAllSubClasses() { - Set result = new HashSet<>(); - for (JavaClass subClass : subClasses) { - result.add(subClass); - result.addAll(subClass.getAllSubClasses()); - } - return result; + return allSubClasses.get(); } @PublicAPI(usage = ACCESS) @@ -673,11 +740,22 @@ public Set getAllFields() { return allFields.get(); } + /** + * @return The field with the given name. + * @throws IllegalArgumentException If this class does not have such a field. + */ @PublicAPI(usage = ACCESS) public JavaField getField(String name) { - return tryGetField(name).getOrThrow(new IllegalArgumentException("No field with name '" + name + " in class " + getName())); + Optional field = tryGetField(name); + if (!field.isPresent()) { + throw new IllegalArgumentException("No field with name '" + name + " in class " + getName()); + } + return field.get(); } + /** + * @return The field with the given name, if this class has such a field, otherwise {@link Optional#absent()}. + */ @PublicAPI(usage = ACCESS) public Optional tryGetField(String name) { for (JavaField field : fields) { @@ -748,31 +826,53 @@ private Optional tryFindMatchingCodeUnit(Set code return Optional.absent(); } + /** + * @return The method with the given name and with zero parameters. + * @throws IllegalArgumentException If this class does not have such a method. + */ @PublicAPI(usage = ACCESS) public JavaMethod getMethod(String name) { return findMatchingCodeUnit(methods, name, Collections.emptyList()); } + /** + * @return The method with the given name and the given parameter types. + * @throws IllegalArgumentException If this class does not have such a method. + */ @PublicAPI(usage = ACCESS) public JavaMethod getMethod(String name, Class... parameters) { return findMatchingCodeUnit(methods, name, namesOf(parameters)); } + /** + * Same as {@link #getMethod(String, Class[])}, but with parameter signature specified as fully qualified class names. + */ @PublicAPI(usage = ACCESS) public JavaMethod getMethod(String name, String... parameters) { return findMatchingCodeUnit(methods, name, ImmutableList.copyOf(parameters)); } + /** + * @return The method with the given name and with zero parameters, + * if this class has such a method, otherwise {@link Optional#absent()}. + */ @PublicAPI(usage = ACCESS) public Optional tryGetMethod(String name) { return tryFindMatchingCodeUnit(methods, name, Collections.emptyList()); } + /** + * @return The method with the given name and the given parameter types, + * if this class has such a method, otherwise {@link Optional#absent()}. + */ @PublicAPI(usage = ACCESS) public Optional tryGetMethod(String name, Class... parameters) { return tryFindMatchingCodeUnit(methods, name, namesOf(parameters)); } + /** + * Same as {@link #tryGetMethod(String, Class[])}, but with parameter signature specified as fully qualified class names. + */ @PublicAPI(usage = ACCESS) public Optional tryGetMethod(String name, String... parameters) { return tryFindMatchingCodeUnit(methods, name, ImmutableList.copyOf(parameters)); @@ -789,21 +889,58 @@ public Set getAllMethods() { return allMethods.get(); } + /** + * @return The constructor with zero parameters. + * @throws IllegalArgumentException If this class does not have such a constructor. + */ @PublicAPI(usage = ACCESS) public JavaConstructor getConstructor() { return findMatchingCodeUnit(constructors, CONSTRUCTOR_NAME, Collections.emptyList()); } + /** + * @return The constructor with the given parameter types. + * @throws IllegalArgumentException If this class does not have a constructor with the given parameter types. + */ @PublicAPI(usage = ACCESS) public JavaConstructor getConstructor(Class... parameters) { return findMatchingCodeUnit(constructors, CONSTRUCTOR_NAME, namesOf(parameters)); } + /** + * Same as {@link #getConstructor(Class[])}, but with parameter signature specified as full class names. + */ @PublicAPI(usage = ACCESS) public JavaConstructor getConstructor(String... parameters) { return findMatchingCodeUnit(constructors, CONSTRUCTOR_NAME, ImmutableList.copyOf(parameters)); } + /** + * @return The constructor with zero parameters, + * if this class has such a constructor, otherwise {@link Optional#absent()}. + */ + @PublicAPI(usage = ACCESS) + public Optional tryGetConstructor() { + return tryFindMatchingCodeUnit(constructors, CONSTRUCTOR_NAME, Collections.emptyList()); + } + + /** + * @return The constructor with the given parameter types, + * if this class has such a constructor, otherwise {@link Optional#absent()}. + */ + @PublicAPI(usage = ACCESS) + public Optional tryGetConstructor(Class... parameters) { + return tryFindMatchingCodeUnit(constructors, CONSTRUCTOR_NAME, namesOf(parameters)); + } + + /** + * Same as {@link #tryGetConstructor(Class[])}, but with parameter signature specified as fully qualified class names. + */ + @PublicAPI(usage = ACCESS) + public Optional tryGetConstructor(String... parameters) { + return tryFindMatchingCodeUnit(constructors, CONSTRUCTOR_NAME, ImmutableList.copyOf(parameters)); + } + @PublicAPI(usage = ACCESS) public Set getConstructors() { return constructors; @@ -894,6 +1031,17 @@ public Set getDirectDependenciesFromSelf() { return javaClassDependencies.getDirectDependenciesFromClass(); } + /** + * Returns the transitive closure of all dependencies originating from this class, i.e. its direct dependencies + * and the dependencies from all imported target classes. + * @return all transitive dependencies (including direct dependencies) from this class + * @see #getDirectDependenciesFromSelf() + */ + @PublicAPI(usage = ACCESS) + public Set getTransitiveDependenciesFromSelf() { + return JavaClassTransitiveDependencies.findTransitiveDependenciesFrom(this); + } + /** * Like {@link #getDirectDependenciesFromSelf()}, but instead returns all dependencies where this class * is target. @@ -902,7 +1050,7 @@ public Set getDirectDependenciesFromSelf() { */ @PublicAPI(usage = ACCESS) public Set getDirectDependenciesToSelf() { - return javaClassDependencies.getDirectDependenciesToClass(); + return reverseDependencies.getDirectDependenciesTo(this); } @PublicAPI(usage = ACCESS) @@ -946,7 +1094,7 @@ public Set> getAccessesToSelf() { */ @PublicAPI(usage = ACCESS) public Set getFieldsWithTypeOfSelf() { - return javaClassDependencies.getFieldsWithTypeOfClass(); + return reverseDependencies.getFieldsWithTypeOf(this); } /** @@ -954,7 +1102,7 @@ public Set getFieldsWithTypeOfSelf() { */ @PublicAPI(usage = ACCESS) public Set getMethodsWithParameterTypeOfSelf() { - return javaClassDependencies.getMethodsWithParameterTypeOfClass(); + return reverseDependencies.getMethodsWithParameterTypeOf(this); } /** @@ -962,7 +1110,7 @@ public Set getMethodsWithParameterTypeOfSelf() { */ @PublicAPI(usage = ACCESS) public Set getMethodsWithReturnTypeOfSelf() { - return javaClassDependencies.getMethodsWithReturnTypeOfClass(); + return reverseDependencies.getMethodsWithReturnTypeOf(this); } /** @@ -970,7 +1118,7 @@ public Set getMethodsWithReturnTypeOfSelf() { */ @PublicAPI(usage = ACCESS) public Set> getMethodThrowsDeclarationsWithTypeOfSelf() { - return javaClassDependencies.getMethodThrowsDeclarationsWithTypeOfClass(); + return reverseDependencies.getMethodThrowsDeclarationsWithTypeOf(this); } /** @@ -978,7 +1126,7 @@ public Set> getMethodThrowsDeclarationsWithTypeOfS */ @PublicAPI(usage = ACCESS) public Set getConstructorsWithParameterTypeOfSelf() { - return javaClassDependencies.getConstructorsWithParameterTypeOfClass(); + return reverseDependencies.getConstructorsWithParameterTypeOf(this); } /** @@ -986,7 +1134,7 @@ public Set getConstructorsWithParameterTypeOfSelf() { */ @PublicAPI(usage = ACCESS) public Set> getConstructorsWithThrowsDeclarationTypeOfSelf() { - return javaClassDependencies.getConstructorsWithThrowsDeclarationTypeOfClass(); + return reverseDependencies.getConstructorsWithThrowsDeclarationTypeOf(this); } /** @@ -994,7 +1142,34 @@ public Set> getConstructorsWithThrowsDeclarat */ @PublicAPI(usage = ACCESS) public Set> getAnnotationsWithTypeOfSelf() { - return javaClassDependencies.getAnnotationsWithTypeOfClass(); + return reverseDependencies.getAnnotationsWithTypeOf(this); + } + + /** + * @return All imported {@link JavaAnnotation JavaAnnotations} that have a parameter with type of this class. + */ + @PublicAPI(usage = ACCESS) + public Set> getAnnotationsWithParameterTypeOfSelf() { + return reverseDependencies.getAnnotationsWithParameterTypeOf(this); + } + + /** + * @return All imported {@link InstanceofCheck InstanceofChecks} that check if another class is an instance of this class. + */ + @PublicAPI(usage = ACCESS) + public Set getInstanceofChecksWithTypeOfSelf() { + return reverseDependencies.getInstanceofChecksWithTypeOf(this); + } + + /** + * @return Whether this class has been fully imported, including all dependencies.
+ * Classes that are only transitively imported are not necessarily fully imported.

+ * Suppose you only import a class {@code Foo} that calls a method of class {@code Bar}. + * Then {@code Bar} is, as a dependency of the fully imported class {@code Foo}, only transitively imported. + */ + @PublicAPI(usage = ACCESS) + public boolean isFullyImported() { + return fullyImported; } /** @@ -1113,6 +1288,14 @@ private void completeInterfacesFrom(ImportContext context) { } } + void completeEnclosingClassFrom(ImportContext context) { + enclosingClass = context.createEnclosingClass(this); + } + + void completeTypeParametersFrom(ImportContext context) { + typeParameters = context.createTypeParameters(this); + } + void completeMembers(final ImportContext context) { fields = context.createFields(this); methods = context.createMethods(this); @@ -1129,28 +1312,41 @@ void completeMembers(final ImportContext context) { } void completeAnnotations(final ImportContext context) { - this.annotations = context.createAnnotations(this); + annotations = context.createAnnotations(this); + for (JavaMember member : members) { + member.completeAnnotations(context); + } } - CompletionProcess completeFrom(ImportContext context) { + JavaClassDependencies completeFrom(ImportContext context) { completeComponentType(context); - enclosingClass = context.createEnclosingClass(this); - javaClassDependencies = new JavaClassDependencies(this, context); - return new CompletionProcess(); + for (JavaCodeUnit codeUnit : codeUnits) { + codeUnit.completeFrom(context); + } + javaClassDependencies = new JavaClassDependencies(this); + fullyImported = true; + return javaClassDependencies; } private void completeComponentType(ImportContext context) { JavaClass current = this; while (current.isArray() && !current.componentType.isPresent()) { - JavaClass componentType = context.resolveClass(current.javaType.tryGetComponentType().get().getName()); + JavaClass componentType = context.resolveClass(current.descriptor.tryGetComponentType().get().getFullyQualifiedClassName()); current.componentType = Optional.of(componentType); current = componentType; } } + void setReverseDependencies(ReverseDependencies reverseDependencies) { + this.reverseDependencies = reverseDependencies; + for (JavaMember member : members) { + member.setReverseDependencies(reverseDependencies); + } + } + @Override public String toString() { - return "JavaClass{name='" + javaType.getName() + "'}"; + return "JavaClass{name='" + descriptor.getFullyQualifiedClassName() + "'}"; } @PublicAPI(usage = ACCESS) @@ -1159,7 +1355,7 @@ public static List namesOf(Class... paramTypes) { } @PublicAPI(usage = ACCESS) - public static List namesOf(List> paramTypes) { + public static List namesOf(Iterable> paramTypes) { ArrayList result = new ArrayList<>(); for (Class paramType : paramTypes) { result.add(paramType.getName()); @@ -1339,6 +1535,14 @@ public boolean apply(JavaClass input) { } }; + @PublicAPI(usage = ACCESS) + public static final DescribedPredicate ANNOTATIONS = new DescribedPredicate("annotations") { + @Override + public boolean apply(JavaClass input) { + return input.isAnnotation(); + } + }; + @PublicAPI(usage = ACCESS) public static final DescribedPredicate TOP_LEVEL_CLASSES = new DescribedPredicate("top level classes") { @Override @@ -1470,7 +1674,7 @@ public static DescribedPredicate implement(final DescribedPredicate> { @Override public Class get() { - return javaType.resolveClass(getCurrentClassLoader(getClass())); + return descriptor.resolveClass(getCurrentClassLoader(getClass())); } } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDependencies.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDependencies.java index 695041c3d1..8038cd3b76 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDependencies.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDependencies.java @@ -15,7 +15,6 @@ */ package com.tngtech.archunit.core.domain; -import java.util.HashSet; import java.util.Set; import com.google.common.base.Supplier; @@ -26,36 +25,18 @@ import com.tngtech.archunit.core.domain.properties.HasAnnotations; import static com.google.common.base.Suppliers.memoize; -import static com.google.common.collect.Sets.union; +import static com.google.common.collect.Iterables.concat; class JavaClassDependencies { private final JavaClass javaClass; - private final Set fieldsWithTypeOfClass; - private final Set methodsWithParameterTypeOfClass; - private final Set methodsWithReturnTypeOfClass; - private final Set> methodsWithThrowsDeclarationTypeOfClass; - private final Set constructorsWithParameterTypeOfClass; - private final Set> constructorsWithThrowsDeclarationTypeOfClass; - private final Set> annotationsWithTypeOfClass; - private final Set> annotationsWithParameterTypeOfClass; private final Supplier> directDependenciesFromClass; - private final Supplier> directDependenciesToClass; - JavaClassDependencies(JavaClass javaClass, ImportContext context) { + JavaClassDependencies(JavaClass javaClass) { this.javaClass = javaClass; - this.fieldsWithTypeOfClass = context.getFieldsOfType(javaClass); - this.methodsWithParameterTypeOfClass = context.getMethodsWithParameterOfType(javaClass); - this.methodsWithReturnTypeOfClass = context.getMethodsWithReturnType(javaClass); - this.methodsWithThrowsDeclarationTypeOfClass = context.getMethodThrowsDeclarationsOfType(javaClass); - this.constructorsWithParameterTypeOfClass = context.getConstructorsWithParameterOfType(javaClass); - this.constructorsWithThrowsDeclarationTypeOfClass = context.getConstructorThrowsDeclarationsOfType(javaClass); - this.annotationsWithTypeOfClass = context.getAnnotationsOfType(javaClass); - this.annotationsWithParameterTypeOfClass = context.getAnnotationsWithParameterOfType(javaClass); - this.directDependenciesFromClass = getDirectDependenciesFromClassSupplier(); - this.directDependenciesToClass = getDirectDependenciesToClassSupplier(); + this.directDependenciesFromClass = createDirectDependenciesFromClassSupplier(); } - private Supplier> getDirectDependenciesFromClassSupplier() { + private Supplier> createDirectDependenciesFromClassSupplier() { return memoize(new Supplier>() { @Override public Set get() { @@ -68,24 +49,8 @@ public Set get() { result.addAll(throwsDeclarationDependenciesFromSelf()); result.addAll(constructorParameterDependenciesFromSelf()); result.addAll(annotationDependenciesFromSelf()); - return result.build(); - } - }); - } - - private Supplier> getDirectDependenciesToClassSupplier() { - return memoize(new Supplier>() { - @Override - public Set get() { - ImmutableSet.Builder result = ImmutableSet.builder(); - result.addAll(dependenciesFromAccesses(javaClass.getAccessesToSelf())); - result.addAll(inheritanceDependenciesToSelf()); - result.addAll(fieldDependenciesToSelf()); - result.addAll(returnTypeDependenciesToSelf()); - result.addAll(methodParameterDependenciesToSelf()); - result.addAll(throwsDeclarationDependenciesToSelf()); - result.addAll(constructorParameterDependenciesToSelf()); - result.addAll(annotationDependenciesToSelf()); + result.addAll(instanceofCheckDependenciesFromSelf()); + result.addAll(typeParameterDependenciesFromSelf()); return result.build(); } }); @@ -95,46 +60,10 @@ Set getDirectDependenciesFromClass() { return directDependenciesFromClass.get(); } - Set getDirectDependenciesToClass() { - return directDependenciesToClass.get(); - } - - Set getFieldsWithTypeOfClass() { - return fieldsWithTypeOfClass; - } - - Set getMethodsWithParameterTypeOfClass() { - return methodsWithParameterTypeOfClass; - } - - Set getMethodsWithReturnTypeOfClass() { - return methodsWithReturnTypeOfClass; - } - - Set> getMethodThrowsDeclarationsWithTypeOfClass() { - return methodsWithThrowsDeclarationTypeOfClass; - } - - Set getConstructorsWithParameterTypeOfClass() { - return constructorsWithParameterTypeOfClass; - } - - Set> getConstructorsWithThrowsDeclarationTypeOfClass() { - return constructorsWithThrowsDeclarationTypeOfClass; - } - - private Set> getThrowsDeclarationsWithTypeOfClass() { - return union(methodsWithThrowsDeclarationTypeOfClass, constructorsWithThrowsDeclarationTypeOfClass); - } - - Set> getAnnotationsWithTypeOfClass() { - return annotationsWithTypeOfClass; - } - private Set dependenciesFromAccesses(Set> accesses) { ImmutableSet.Builder result = ImmutableSet.builder(); for (JavaAccess access : accesses) { - result.addAll(Dependency.tryCreateFromAccess(access).asSet()); + result.addAll(Dependency.tryCreateFromAccess(access)); } return result.build(); } @@ -150,7 +79,7 @@ private Set inheritanceDependenciesFromSelf() { private Set fieldDependenciesFromSelf() { ImmutableSet.Builder result = ImmutableSet.builder(); for (JavaField field : javaClass.getFields()) { - result.addAll(Dependency.tryCreateFromField(field).asSet()); + result.addAll(Dependency.tryCreateFromField(field)); } return result.build(); } @@ -158,7 +87,7 @@ private Set fieldDependenciesFromSelf() { private Set returnTypeDependenciesFromSelf() { ImmutableSet.Builder result = ImmutableSet.builder(); for (JavaMethod method : javaClass.getMethods()) { - result.addAll(Dependency.tryCreateFromReturnType(method).asSet()); + result.addAll(Dependency.tryCreateFromReturnType(method)); } return result.build(); } @@ -167,7 +96,7 @@ private Set methodParameterDependenciesFromSelf() { ImmutableSet.Builder result = ImmutableSet.builder(); for (JavaMethod method : javaClass.getMethods()) { for (JavaClass parameter : method.getRawParameterTypes()) { - result.addAll(Dependency.tryCreateFromParameter(method, parameter).asSet()); + result.addAll(Dependency.tryCreateFromParameter(method, parameter)); } } return result.build(); @@ -177,7 +106,7 @@ private Set throwsDeclarationDependenciesFromSelf() { ImmutableSet.Builder result = ImmutableSet.builder(); for (JavaCodeUnit codeUnit : javaClass.getCodeUnits()) { for (ThrowsDeclaration throwsDeclaration : codeUnit.getThrowsClause()) { - result.addAll(Dependency.tryCreateFromThrowsDeclaration(throwsDeclaration).asSet()); + result.addAll(Dependency.tryCreateFromThrowsDeclaration(throwsDeclaration)); } } return result.build(); @@ -187,7 +116,7 @@ private Set constructorParameterDependenciesFromSelf() { ImmutableSet.Builder result = ImmutableSet.builder(); for (JavaConstructor constructor : javaClass.getConstructors()) { for (JavaClass parameter : constructor.getRawParameterTypes()) { - result.addAll(Dependency.tryCreateFromParameter(constructor, parameter).asSet()); + result.addAll(Dependency.tryCreateFromParameter(constructor, parameter)); } } return result.build(); @@ -202,6 +131,63 @@ private Set annotationDependenciesFromSelf() { .build(); } + private Set instanceofCheckDependenciesFromSelf() { + ImmutableSet.Builder result = ImmutableSet.builder(); + for (JavaCodeUnit codeUnit : javaClass.getCodeUnits()) { + for (InstanceofCheck instanceofCheck : codeUnit.getInstanceofChecks()) { + result.addAll(Dependency.tryCreateFromInstanceofCheck(instanceofCheck)); + } + } + return result.build(); + } + + private Set typeParameterDependenciesFromSelf() { + ImmutableSet.Builder result = ImmutableSet.builder(); + for (JavaTypeVariable typeVariable : javaClass.getTypeParameters()) { + result.addAll(getDependenciesFromTypeParameter(typeVariable)); + } + return result.build(); + } + + private Set getDependenciesFromTypeParameter(JavaTypeVariable typeVariable) { + ImmutableSet.Builder dependenciesBuilder = ImmutableSet.builder(); + for (JavaType bound : typeVariable.getUpperBounds()) { + for (JavaClass typeParameterDependency : dependenciesOfType(bound)) { + dependenciesBuilder.addAll(Dependency.tryCreateFromTypeParameter(typeVariable, typeParameterDependency)); + } + } + return dependenciesBuilder.build(); + } + + private static Iterable dependenciesOfType(JavaType javaType) { + ImmutableSet.Builder result = ImmutableSet.builder(); + if (javaType instanceof JavaClass) { + result.add((JavaClass) javaType); + } else if (javaType instanceof JavaParameterizedType) { + result.addAll(dependenciesOfParameterizedType((JavaParameterizedType) javaType)); + } else if (javaType instanceof JavaWildcardType) { + result.addAll(dependenciesOfWildcardType((JavaWildcardType) javaType)); + } + return result.build(); + } + + private static Set dependenciesOfParameterizedType(JavaParameterizedType parameterizedType) { + ImmutableSet.Builder result = ImmutableSet.builder() + .add(parameterizedType.toErasure()); + for (JavaType typeArgument : parameterizedType.getActualTypeArguments()) { + result.addAll(dependenciesOfType(typeArgument)); + } + return result.build(); + } + + private static Set dependenciesOfWildcardType(JavaWildcardType javaType) { + ImmutableSet.Builder result = ImmutableSet.builder(); + for (JavaType bound : concat(javaType.getUpperBounds(), javaType.getLowerBounds())) { + result.addAll(dependenciesOfType(bound)); + } + return result.build(); + } + private > Set annotationDependencies(Set annotatedObjects) { ImmutableSet.Builder result = ImmutableSet.builder(); for (T annotated : annotatedObjects) { @@ -213,84 +199,25 @@ private > Set annotatio private > Set annotationDependencies(T annotated) { final ImmutableSet.Builder result = ImmutableSet.builder(); for (final JavaAnnotation annotation : annotated.getAnnotations()) { - result.addAll(Dependency.tryCreateFromAnnotation(annotation).asSet()); + result.addAll(Dependency.tryCreateFromAnnotation(annotation)); annotation.accept(new DefaultParameterVisitor() { @Override public void visitClass(String propertyName, JavaClass javaClass) { - result.addAll(Dependency.tryCreateFromAnnotationMember(annotation, javaClass).asSet()); + result.addAll(Dependency.tryCreateFromAnnotationMember(annotation, javaClass)); } @Override public void visitEnumConstant(String propertyName, JavaEnumConstant enumConstant) { - result.addAll(Dependency.tryCreateFromAnnotationMember(annotation, enumConstant.getDeclaringClass()).asSet()); + result.addAll(Dependency.tryCreateFromAnnotationMember(annotation, enumConstant.getDeclaringClass())); } @Override public void visitAnnotation(String propertyName, JavaAnnotation memberAnnotation) { - result.addAll(Dependency.tryCreateFromAnnotationMember(annotation, memberAnnotation.getRawType()).asSet()); + result.addAll(Dependency.tryCreateFromAnnotationMember(annotation, memberAnnotation.getRawType())); memberAnnotation.accept(this); } }); } return result.build(); } - - private Set inheritanceDependenciesToSelf() { - Set result = new HashSet<>(); - for (JavaClass subClass : javaClass.getSubClasses()) { - result.add(Dependency.fromInheritance(subClass, javaClass)); - } - return result; - } - - private Set fieldDependenciesToSelf() { - Set result = new HashSet<>(); - for (JavaField field : javaClass.getFieldsWithTypeOfSelf()) { - result.addAll(Dependency.tryCreateFromField(field).asSet()); - } - return result; - } - - private Set returnTypeDependenciesToSelf() { - Set result = new HashSet<>(); - for (JavaMethod method : javaClass.getMethodsWithReturnTypeOfSelf()) { - result.addAll(Dependency.tryCreateFromReturnType(method).asSet()); - } - return result; - } - - private Set methodParameterDependenciesToSelf() { - Set result = new HashSet<>(); - for (JavaMethod method : javaClass.getMethodsWithParameterTypeOfSelf()) { - result.addAll(Dependency.tryCreateFromParameter(method, javaClass).asSet()); - } - return result; - } - - private Set throwsDeclarationDependenciesToSelf() { - Set result = new HashSet<>(); - for (ThrowsDeclaration throwsDeclaration : getThrowsDeclarationsWithTypeOfClass()) { - result.addAll(Dependency.tryCreateFromThrowsDeclaration(throwsDeclaration).asSet()); - } - return result; - } - - private Set constructorParameterDependenciesToSelf() { - Set result = new HashSet<>(); - for (JavaConstructor constructor : javaClass.getConstructorsWithParameterTypeOfSelf()) { - result.addAll(Dependency.tryCreateFromParameter(constructor, javaClass).asSet()); - } - return result; - } - - private Iterable annotationDependenciesToSelf() { - Set result = new HashSet<>(); - for (JavaAnnotation annotation : annotationsWithTypeOfClass) { - result.addAll(Dependency.tryCreateFromAnnotation(annotation).asSet()); - } - for (JavaAnnotation annotation : annotationsWithParameterTypeOfClass) { - result.addAll(Dependency.tryCreateFromAnnotationMember(annotation, javaClass).asSet()); - } - return result; - } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDescriptor.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDescriptor.java new file mode 100644 index 0000000000..01d459594a --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDescriptor.java @@ -0,0 +1,321 @@ +/* + * Copyright 2014-2020 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.core.domain; + +import java.util.Map; +import java.util.Objects; + +import com.google.common.base.CharMatcher; +import com.google.common.base.Function; +import com.google.common.base.Strings; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.tngtech.archunit.Internal; +import com.tngtech.archunit.base.ArchUnitException.ReflectionException; +import com.tngtech.archunit.base.Optional; +import com.tngtech.archunit.core.MayResolveTypesViaReflection; +import com.tngtech.archunit.core.ResolvesTypesViaReflection; +import org.objectweb.asm.Type; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.primitives.Primitives.allPrimitiveTypes; +import static com.tngtech.archunit.base.ClassLoaders.getCurrentClassLoader; +import static com.tngtech.archunit.core.domain.Formatters.ensureSimpleName; + +@Internal +public interface JavaClassDescriptor { + String getFullyQualifiedClassName(); + + String getSimpleClassName(); + + String getPackageName(); + + @ResolvesTypesViaReflection + Class resolveClass(); + + @ResolvesTypesViaReflection + Class resolveClass(ClassLoader classLoader); + + Optional tryGetComponentType(); + + boolean isPrimitive(); + + boolean isArray(); + + JavaClassDescriptor withSimpleClassName(String simpleName); + + JavaClassDescriptor toArrayDescriptor(); + + @Internal + final class From { + private static final LoadingCache descriptorCache = + CacheBuilder.newBuilder().build(new CacheLoader() { + @Override + public JavaClassDescriptor load(String typeName) { + if (primitiveClassesByNameOrDescriptor.containsKey(typeName)) { + return new PrimitiveClassDescriptor(Type.getType(primitiveClassesByNameOrDescriptor.get(typeName)).getClassName()); + } + if (isArray(typeName)) { + // NOTE: ASM uses the canonical name for arrays (i.e. java.lang.Object[]), but we want the class name, + // i.e. [Ljava.lang.Object; + return new ArrayClassDescriptor(ensureCorrectArrayTypeName(typeName)); + } + return new ObjectClassDescriptor(typeName); + } + }); + private static final ImmutableMap> primitiveClassesByName = + Maps.uniqueIndex(allPrimitiveTypes(), new Function, String>() { + @Override + public String apply(Class input) { + return input.getName(); + } + }); + private static final ImmutableBiMap> primitiveClassesByDescriptor = + ImmutableBiMap.copyOf(Maps.uniqueIndex(allPrimitiveTypes(), new Function, String>() { + @Override + public String apply(Class input) { + return Type.getType(input).getDescriptor(); + } + })); + private static final Map> primitiveClassesByNameOrDescriptor = + ImmutableMap.>builder() + .putAll(primitiveClassesByName) + .putAll(primitiveClassesByDescriptor) + .build(); + + public static JavaClassDescriptor name(String typeName) { + return descriptorCache.getUnchecked(typeName); + } + + private static boolean isArray(String typeName) { + // We support class name ([Ljava.lang.Object;) and canonical name java.lang.Object[] + return typeName.startsWith("[") || typeName.endsWith("]"); + } + + private static String ensureCorrectArrayTypeName(String name) { + return name.endsWith("[]") ? convertCanonicalArrayNameToClassName(name) : name; + } + + private static String convertCanonicalArrayNameToClassName(String name) { + String arrayDesignator = Strings.repeat("[", CharMatcher.is('[').countIn(name)); + return arrayDesignator + createComponentTypeName(name); + } + + private static String createComponentTypeName(String name) { + String baseName = name.substring(0, name.indexOf("[]")); + + return primitiveClassesByName.containsKey(baseName) ? + createPrimitiveComponentType(baseName) : + createObjectComponentType(baseName); + } + + private static String createPrimitiveComponentType(String componentTypeName) { + return primitiveClassesByDescriptor.inverse().get(primitiveClassesByName.get(componentTypeName)); + } + + private static String createObjectComponentType(String componentTypeName) { + return "L" + componentTypeName + ";"; + } + + public static JavaClassDescriptor javaClass(JavaClass javaClass) { + return name(javaClass.getName()); + } + + private abstract static class AbstractClassDescriptor implements JavaClassDescriptor { + private final String name; + private final String simpleName; + private final String javaPackage; + + private AbstractClassDescriptor(String name, String simpleName, String javaPackage) { + this.name = name; + this.simpleName = simpleName; + this.javaPackage = javaPackage; + } + + @Override + public String getFullyQualifiedClassName() { + return name; + } + + @Override + public String getSimpleClassName() { + return simpleName; + } + + @Override + public String getPackageName() { + return javaPackage; + } + + @Override + public Class resolveClass() { + return resolveClass(getCurrentClassLoader(getClass())); + } + + @Override + public Class resolveClass(ClassLoader classLoader) { + try { + return classForName(classLoader); + } catch (ClassNotFoundException e) { + throw new ReflectionException(e); + } + } + + @MayResolveTypesViaReflection(reason = "This method is one of the known sources for resolving via reflection") + Class classForName(ClassLoader classLoader) throws ClassNotFoundException { + return Class.forName(getFullyQualifiedClassName(), false, classLoader); + } + + @Override + public Optional tryGetComponentType() { + return Optional.absent(); + } + + @Override + public boolean isPrimitive() { + return false; + } + + @Override + public boolean isArray() { + return false; + } + + @Override + public int hashCode() { + return Objects.hash(getFullyQualifiedClassName()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final JavaClassDescriptor other = (JavaClassDescriptor) obj; + return Objects.equals(this.getFullyQualifiedClassName(), other.getFullyQualifiedClassName()); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{" + getFullyQualifiedClassName() + "}"; + } + } + + private static class ObjectClassDescriptor extends AbstractClassDescriptor { + ObjectClassDescriptor(String fullName) { + this(fullName, ensureSimpleName(fullName), createPackage(fullName)); + } + + private ObjectClassDescriptor(String fullName, String simpleName, String packageName) { + super(fullName, simpleName, packageName); + } + + @Override + public JavaClassDescriptor withSimpleClassName(String simpleName) { + return new ObjectClassDescriptor(getFullyQualifiedClassName(), simpleName, getPackageName()); + } + + @Override + public JavaClassDescriptor toArrayDescriptor() { + return From.name(getFullyQualifiedClassName() + "[]"); + } + } + + private static class PrimitiveClassDescriptor extends AbstractClassDescriptor { + PrimitiveClassDescriptor(String fullName) { + super(fullName, fullName, ""); + checkArgument(primitiveClassesByName.containsKey(fullName), "'%s' must be a primitive name", fullName); + } + + @Override + Class classForName(ClassLoader classLoader) { + return primitiveClassesByName.get(getFullyQualifiedClassName()); + } + + @Override + public boolean isPrimitive() { + return true; + } + + @Override + public JavaClassDescriptor withSimpleClassName(String simpleName) { + throw new UnsupportedOperationException("It should never make sense to override the simple type of a primitive"); + } + + @Override + public JavaClassDescriptor toArrayDescriptor() { + return From.name(getFullyQualifiedClassName() + "[]"); + } + } + + private static class ArrayClassDescriptor extends AbstractClassDescriptor { + ArrayClassDescriptor(String fullName) { + this(fullName, createSimpleName(fullName), createPackageOfComponentType(fullName)); + } + + private ArrayClassDescriptor(String fullName, String simpleName, String packageName) { + super(fullName, simpleName, packageName); + } + + private static String createPackageOfComponentType(String fullName) { + String componentType = getCanonicalName(fullName).replace("[]", ""); + return createPackage(componentType); + } + + private static String createSimpleName(String fullName) { + return ensureSimpleName(getCanonicalName(fullName)); + } + + private static String getCanonicalName(String fullName) { + return Type.getType(fullName).getClassName(); + } + + @Override + public boolean isArray() { + return true; + } + + @Override + public JavaClassDescriptor withSimpleClassName(String simpleName) { + return new ArrayClassDescriptor(getFullyQualifiedClassName(), simpleName, getPackageName()); + } + + @Override + public Optional tryGetComponentType() { + String canonicalName = getCanonicalName(getFullyQualifiedClassName()); + String componentTypeName = canonicalName.substring(0, canonicalName.lastIndexOf("[")); + return Optional.of(JavaClassDescriptor.From.name(componentTypeName)); + } + + @Override + public JavaClassDescriptor toArrayDescriptor() { + return new ArrayClassDescriptor("[" + getFullyQualifiedClassName()); + } + } + + private static String createPackage(String fullName) { + int packageEnd = fullName.lastIndexOf('.'); + return packageEnd >= 0 ? fullName.substring(0, packageEnd) : ""; + } + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassTransitiveDependencies.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassTransitiveDependencies.java new file mode 100644 index 0000000000..2a4e53b304 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassTransitiveDependencies.java @@ -0,0 +1,47 @@ +/* + * Copyright 2014-2020 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.core.domain; + +import com.google.common.collect.ImmutableSet; + +import java.util.HashSet; +import java.util.Set; + +class JavaClassTransitiveDependencies { + private JavaClassTransitiveDependencies() { + } + + static Set findTransitiveDependenciesFrom(JavaClass javaClass) { + ImmutableSet.Builder transitiveDependencies = ImmutableSet.builder(); + Set analyzedClasses = new HashSet<>(); // to avoid infinite recursion for cyclic dependencies + addTransitiveDependenciesFrom(javaClass, transitiveDependencies, analyzedClasses); + return transitiveDependencies.build(); + } + + private static void addTransitiveDependenciesFrom(JavaClass javaClass, ImmutableSet.Builder transitiveDependencies, Set analyzedClasses) { + analyzedClasses.add(javaClass); // currently being analyzed + Set targetClassesToRecurse = new HashSet<>(); + for (Dependency dependency : javaClass.getDirectDependenciesFromSelf()) { + transitiveDependencies.add(dependency); + targetClassesToRecurse.add(dependency.getTargetClass().getBaseComponentType()); + } + for (JavaClass targetClass : targetClassesToRecurse) { + if (!analyzedClasses.contains(targetClass)) { + addTransitiveDependenciesFrom(targetClass, transitiveDependencies, analyzedClasses); + } + } + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClasses.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClasses.java index 02fd2029f1..b763d8f450 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClasses.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClasses.java @@ -17,10 +17,8 @@ import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Objects; -import java.util.Set; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; @@ -29,7 +27,6 @@ import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.base.ForwardingCollection; import com.tngtech.archunit.base.Guava; -import com.tngtech.archunit.core.domain.DomainObjectCreationContext.AccessContext; import com.tngtech.archunit.core.domain.properties.CanOverrideDescription; import static com.google.common.base.Preconditions.checkArgument; @@ -194,15 +191,16 @@ private static JavaPackage getRoot(JavaPackage javaPackage) { } static JavaClasses of( - Map selectedClasses, Map allClasses, ImportContext importContext) { + Map selectedClasses, Collection allClasses, ImportContext importContext) { - CompletionProcess completionProcess = new CompletionProcess(allClasses.values(), importContext); - JavaPackage defaultPackage = JavaPackage.from(allClasses.values()); - for (JavaClass clazz : allClasses.values()) { + ReverseDependencies.Creation reverseDependenciesCreation = new ReverseDependencies.Creation(); + JavaPackage defaultPackage = JavaPackage.from(allClasses); + for (JavaClass clazz : allClasses) { setPackage(clazz, defaultPackage); - completionProcess.completeClass(clazz); + JavaClassDependencies classDependencies = clazz.completeFrom(importContext); + reverseDependenciesCreation.registerDependenciesOf(clazz, classDependencies); } - completionProcess.finish(); + reverseDependenciesCreation.finish(allClasses); return new JavaClasses(defaultPackage, selectedClasses); } @@ -212,27 +210,4 @@ private static void setPackage(JavaClass clazz, JavaPackage defaultPackage) { : defaultPackage.getPackage(clazz.getPackageName()); clazz.setPackage(javaPackage); } - - private static class CompletionProcess { - private final Set classCompletionProcesses = new HashSet<>(); - private final Collection classes; - private final ImportContext context; - - CompletionProcess(Collection classes, ImportContext context) { - this.classes = classes; - this.context = context; - } - - void completeClass(JavaClass clazz) { - classCompletionProcesses.add(clazz.completeFrom(context)); - } - - void finish() { - AccessContext.TopProcess accessCompletionProcess = new AccessContext.TopProcess(classes); - for (JavaClass.CompletionProcess process : classCompletionProcesses) { - accessCompletionProcess.mergeWith(process.completeCodeUnitsFrom(context)); - } - accessCompletionProcess.finish(); - } - } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java index 9ba03da3a7..a8082c65d4 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java @@ -28,7 +28,6 @@ import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.MayResolveTypesViaReflection; import com.tngtech.archunit.core.ResolvesTypesViaReflection; -import com.tngtech.archunit.core.domain.DomainObjectCreationContext.AccessContext; import com.tngtech.archunit.core.domain.properties.HasParameterTypes; import com.tngtech.archunit.core.domain.properties.HasReturnType; import com.tngtech.archunit.core.domain.properties.HasThrowsClause; @@ -51,6 +50,7 @@ public abstract class JavaCodeUnit extends JavaMember implements HasParameterTyp private final JavaClass returnType; private final JavaClassList parameters; private final String fullName; + private final Set instanceofChecks; private Set fieldAccesses = Collections.emptySet(); private Set methodCalls = Collections.emptySet(); @@ -61,6 +61,7 @@ public abstract class JavaCodeUnit extends JavaMember implements HasParameterTyp this.returnType = builder.getReturnType(); this.parameters = builder.getParameters(); fullName = formatMethod(getOwner().getName(), getName(), getRawParameterTypes()); + instanceofChecks = builder.getInstanceofChecks(this); } /** @@ -111,6 +112,11 @@ public Set getConstructorCallsFromSelf() { return constructorCalls; } + @PublicAPI(usage = ACCESS) + public Set getInstanceofChecks() { + return instanceofChecks; + } + @PublicAPI(usage = ACCESS) public Set> getCallsFromSelf() { return ImmutableSet.>builder() @@ -150,12 +156,10 @@ public Optional> tryGetAnnotati return (Optional>) super.tryGetAnnotationOfType(typeName); } - AccessContext.Part completeFrom(ImportContext context) { - fieldAccesses = context.getFieldAccessesFor(this); - methodCalls = context.getMethodCallsFor(this); - constructorCalls = context.getConstructorCallsFor(this); - - return new AccessContext.Part(this); + void completeFrom(ImportContext context) { + fieldAccesses = context.createFieldAccessesFor(this); + methodCalls = context.createMethodCallsFor(this); + constructorCalls = context.createConstructorCallsFor(this); } @ResolvesTypesViaReflection diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaConstructor.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaConstructor.java index 773fc09aa8..a64b41d153 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaConstructor.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaConstructor.java @@ -16,13 +16,10 @@ package com.tngtech.archunit.core.domain; import java.lang.reflect.Constructor; -import java.util.Collection; -import java.util.Collections; import java.util.Set; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; -import com.google.common.collect.ImmutableSet; import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.base.ArchUnitException.InconsistentClassPathException; import com.tngtech.archunit.base.Optional; @@ -36,7 +33,6 @@ public final class JavaConstructor extends JavaCodeUnit { private final Supplier> constructorSupplier; private final ThrowsClause throwsClause; - private Set callsToSelf = Collections.emptySet(); @PublicAPI(usage = ACCESS) public static final String CONSTRUCTOR_NAME = ""; @@ -67,7 +63,7 @@ public Set getCallsOfSelf() { @Override @PublicAPI(usage = ACCESS) public Set getAccessesToSelf() { - return callsToSelf; + return getReverseDependencies().getCallsTo(this); } @Override @@ -102,10 +98,6 @@ public String getDescription() { return "Constructor <" + getFullName() + ">"; } - void registerCallsToConstructor(Collection calls) { - this.callsToSelf = ImmutableSet.copyOf(calls); - } - @ResolvesTypesViaReflection @MayResolveTypesViaReflection(reason = "Just part of a bigger resolution process") private class ReflectConstructorSupplier implements Supplier> { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaField.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaField.java index 2b7b2dc54b..ee4e667f59 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaField.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaField.java @@ -16,7 +16,6 @@ package com.tngtech.archunit.core.domain; import java.lang.reflect.Field; -import java.util.Collections; import java.util.Set; import com.google.common.base.Supplier; @@ -29,13 +28,11 @@ import com.tngtech.archunit.core.domain.properties.HasType; import com.tngtech.archunit.core.importer.DomainBuilders; -import static com.google.common.base.Preconditions.checkNotNull; import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; public class JavaField extends JavaMember implements HasType { private final JavaClass type; private final Supplier fieldSupplier; - private Supplier> accessesToSelf = Suppliers.ofInstance(Collections.emptySet()); JavaField(DomainBuilders.JavaFieldBuilder builder) { super(builder); @@ -52,6 +49,16 @@ public String getFullName() { return getOwner().getName() + "." + getName(); } + /** + * Note: This is still work in progress and thus does not support generic types at the moment. + * In the future the result can possibly also be a {@link JavaParameterizedType} or {@link JavaTypeVariable} + */ + @Override + @PublicAPI(usage = ACCESS) + public JavaType getType() { + return type; + } + @Override @PublicAPI(usage = ACCESS) public JavaClass getRawType() { @@ -61,7 +68,7 @@ public JavaClass getRawType() { @Override @PublicAPI(usage = ACCESS) public Set getAccessesToSelf() { - return accessesToSelf.get(); + return getReverseDependencies().getAccessesTo(this); } @Override @@ -96,10 +103,6 @@ public String getDescription() { return "Field <" + getFullName() + ">"; } - void registerAccessesToField(Supplier> accesses) { - this.accessesToSelf = checkNotNull(accesses); - } - @ResolvesTypesViaReflection @MayResolveTypesViaReflection(reason = "Just part of a bigger resolution process") private class ReflectFieldSupplier implements Supplier { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaGenericArrayType.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaGenericArrayType.java new file mode 100644 index 0000000000..05fa5bef51 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaGenericArrayType.java @@ -0,0 +1,74 @@ +/* + * Copyright 2014-2020 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.core.domain; + +import com.tngtech.archunit.PublicAPI; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; + +/** + * Represents a generic array type used in signatures of parameterized types.
+ * E.g. for {@code MyClass>} the upper bound {@code List} + * would have one {@link JavaGenericArrayType} {@code A[]} as its type parameter.
+ * Like its concrete counterpart a {@link JavaGenericArrayType} can be queried for its + * {@link #getComponentType() component type}, which will by definition be a + * {@link JavaTypeVariable} or a {@link JavaGenericArrayType} corresponding to a lower dimensional array. + */ +@PublicAPI(usage = ACCESS) +public final class JavaGenericArrayType implements JavaType { + private final String name; + private final JavaType componentType; + private final JavaClass erasure; + + JavaGenericArrayType(String name, JavaType componentType, JavaClass erasure) { + this.name = checkNotNull(name); + this.componentType = checkNotNull(componentType); + this.erasure = checkNotNull(erasure); + } + + /** + * @return The name of this {@link JavaGenericArrayType}, e.g. for {@code A[]} within + * signature {@code MyClass>} the name would be "A[]" + */ + @Override + @PublicAPI(usage = ACCESS) + public String getName() { + return name; + } + + /** + * @return The component type of this {@link JavaGenericArrayType}, e.g. for {@code A[]} within + * signature {@code MyClass>} the component type would be {@code A}, + * while for {@code A[][]} within {@code MyClass>} the component + * type would be {@code A[]}. + */ + @PublicAPI(usage = ACCESS) + public JavaType getComponentType() { + return componentType; + } + + @Override + @PublicAPI(usage = ACCESS) + public JavaClass toErasure() { + return erasure; + } + + @Override + public String toString() { + return getClass().getSimpleName() + '{' + getName() + '}'; + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaMember.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaMember.java index ebd7f5dfe2..0b50bcd50f 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaMember.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaMember.java @@ -17,10 +17,10 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Member; +import java.util.Collections; import java.util.Map; import java.util.Set; -import com.google.common.base.Supplier; import com.google.common.collect.ImmutableSet; import com.tngtech.archunit.Internal; import com.tngtech.archunit.PublicAPI; @@ -48,15 +48,15 @@ public abstract class JavaMember implements private final String name; private final String descriptor; - private final Supplier>> annotations; + private Map> annotations = Collections.emptyMap(); private final JavaClass owner; private final SourceCodeLocation sourceCodeLocation; private final Set modifiers; + private ReverseDependencies reverseDependencies = ReverseDependencies.EMPTY; JavaMember(JavaMemberBuilder builder) { this.name = checkNotNull(builder.getName()); this.descriptor = checkNotNull(builder.getDescriptor()); - this.annotations = builder.getAnnotations(this); this.owner = checkNotNull(builder.getOwner()); this.sourceCodeLocation = SourceCodeLocation.of(owner, builder.getFirstLineNumber()); this.modifiers = checkNotNull(builder.getModifiers()); @@ -65,7 +65,7 @@ public abstract class JavaMember implements @Override @PublicAPI(usage = ACCESS) public Set> getAnnotations() { - return ImmutableSet.copyOf(annotations.get().values()); + return ImmutableSet.copyOf(annotations.values()); } /** @@ -82,9 +82,11 @@ public
A getAnnotationOfType(Class type) { @Override @PublicAPI(usage = ACCESS) public JavaAnnotation getAnnotationOfType(String typeName) { - return tryGetAnnotationOfType(typeName).getOrThrow(new IllegalArgumentException(String.format( - "Member %s is not annotated with @%s", - getFullName(), typeName))); + Optional> annotation = tryGetAnnotationOfType(typeName); + if (!annotation.isPresent()) { + throw new IllegalArgumentException(String.format("Member %s is not annotated with @%s", getFullName(), typeName)); + } + return annotation.get(); } @Override @@ -96,7 +98,7 @@ public Optional tryGetAnnotationOfType(Class type) @Override @PublicAPI(usage = ACCESS) public Optional> tryGetAnnotationOfType(String typeName) { - return Optional.fromNullable(annotations.get().get(typeName)); + return Optional.fromNullable(annotations.get(typeName)); } @Override @@ -108,13 +110,13 @@ public boolean isAnnotatedWith(Class type) { @Override @PublicAPI(usage = ACCESS) public boolean isAnnotatedWith(String typeName) { - return annotations.get().containsKey(typeName); + return annotations.containsKey(typeName); } @Override @PublicAPI(usage = ACCESS) public boolean isAnnotatedWith(DescribedPredicate> predicate) { - return CanBeAnnotated.Utils.isAnnotatedWith(annotations.get().values(), predicate); + return CanBeAnnotated.Utils.isAnnotatedWith(annotations.values(), predicate); } @Override @@ -132,7 +134,7 @@ public boolean isMetaAnnotatedWith(String typeName) { @Override @PublicAPI(usage = ACCESS) public boolean isMetaAnnotatedWith(DescribedPredicate> predicate) { - return CanBeAnnotated.Utils.isMetaAnnotatedWith(annotations.get().values(), predicate); + return CanBeAnnotated.Utils.isMetaAnnotatedWith(annotations.values(), predicate); } @Override @@ -178,6 +180,18 @@ public String getDescriptor() { @PublicAPI(usage = ACCESS) public abstract Member reflect(); + void completeAnnotations(ImportContext context) { + annotations = context.createAnnotations(this); + } + + protected ReverseDependencies getReverseDependencies() { + return reverseDependencies; + } + + void setReverseDependencies(ReverseDependencies reverseDependencies) { + this.reverseDependencies = reverseDependencies; + } + @Override public String toString() { return getClass().getSimpleName() + '{' + getFullName() + '}'; diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaMethod.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaMethod.java index 428de09cfd..a43402fce4 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaMethod.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaMethod.java @@ -16,33 +16,31 @@ package com.tngtech.archunit.core.domain; import java.lang.reflect.Method; -import java.util.Collections; import java.util.Set; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.base.ArchUnitException.InconsistentClassPathException; +import com.tngtech.archunit.base.Function; import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.MayResolveTypesViaReflection; import com.tngtech.archunit.core.ResolvesTypesViaReflection; import com.tngtech.archunit.core.importer.DomainBuilders; -import static com.google.common.base.Preconditions.checkNotNull; import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; import static com.tngtech.archunit.core.domain.Formatters.formatMethod; public class JavaMethod extends JavaCodeUnit { private final Supplier methodSupplier; private final ThrowsClause throwsClause; - private Supplier> callsToSelf = Suppliers.ofInstance(Collections.emptySet()); - private final Supplier> annotationDefaultValue; + private final Optional annotationDefaultValue; - JavaMethod(DomainBuilders.JavaMethodBuilder builder) { + JavaMethod(DomainBuilders.JavaMethodBuilder builder, Function> createAnnotationDefaultValue) { super(builder); throwsClause = builder.getThrowsClause(this); methodSupplier = Suppliers.memoize(new ReflectMethodSupplier()); - annotationDefaultValue = builder.getAnnotationDefaultValue(); + annotationDefaultValue = createAnnotationDefaultValue.apply(this); } @Override @@ -60,7 +58,7 @@ public ThrowsClause getThrowsClause() { */ @PublicAPI(usage = ACCESS) public Optional getDefaultValue() { - return annotationDefaultValue.get(); + return annotationDefaultValue; } @PublicAPI(usage = ACCESS) @@ -71,7 +69,7 @@ public Set getCallsOfSelf() { @Override @PublicAPI(usage = ACCESS) public Set getAccessesToSelf() { - return callsToSelf.get(); + return getReverseDependencies().getCallsTo(this); } @Override @@ -106,10 +104,6 @@ public String getDescription() { return "Method <" + getFullName() + ">"; } - void registerCallsToMethod(Supplier> calls) { - this.callsToSelf = checkNotNull(calls); - } - @ResolvesTypesViaReflection @MayResolveTypesViaReflection(reason = "Just part of a bigger resolution process") private class ReflectMethodSupplier implements Supplier { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaPackage.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaPackage.java index ad13fb57db..fabf3a9d2c 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaPackage.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaPackage.java @@ -86,8 +86,11 @@ public String getRelativeName() { @PublicAPI(usage = ACCESS) public HasAnnotations getPackageInfo() { - return tryGetPackageInfo().getOrThrow( - new IllegalArgumentException(String.format("%s does not contain a package-info.java", getDescription()))); + Optional> packageInfo = tryGetPackageInfo(); + if (!packageInfo.isPresent()) { + throw new IllegalArgumentException(String.format("%s does not contain a package-info.java", getDescription())); + } + return packageInfo.get(); } @PublicAPI(usage = ACCESS) @@ -113,8 +116,11 @@ public A getAnnotationOfType(Class type) { @Override @PublicAPI(usage = ACCESS) public JavaAnnotation getAnnotationOfType(String typeName) { - return tryGetAnnotationOfType(typeName).getOrThrow(new IllegalArgumentException( - String.format("%s is not annotated with @%s", getDescription(), typeName))); + Optional> annotation = tryGetAnnotationOfType(typeName); + if (!annotation.isPresent()) { + throw new IllegalArgumentException(String.format("%s is not annotated with @%s", getDescription(), typeName)); + } + return annotation.get(); } @Override diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaParameterizedType.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaParameterizedType.java new file mode 100644 index 0000000000..7aa12185a8 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaParameterizedType.java @@ -0,0 +1,38 @@ +/* + * Copyright 2014-2020 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.core.domain; + +import java.util.List; + +import com.tngtech.archunit.PublicAPI; + +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; + +/** + * A {@link JavaParameterizedType} represents a concrete parameterization of a generic type. + * Consider the generic type {@code List}, then {@code List} would be a parameterized type + * where the concrete type {@code java.lang.String} has been assigned to the type variable {@code T}. + * The concrete type {@code java.lang.String} is then an "actual type argument" of this {@link JavaParameterizedType} + * (see {@link JavaParameterizedType#getActualTypeArguments()}). + */ +@PublicAPI(usage = ACCESS) +public interface JavaParameterizedType extends JavaType { + /** + * @return The actual type arguments of this parameterized type (compare {@link JavaParameterizedType}). + */ + @PublicAPI(usage = ACCESS) + List getActualTypeArguments(); +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java index b844d3518a..06b9bc1a0a 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java @@ -15,289 +15,26 @@ */ package com.tngtech.archunit.core.domain; -import java.util.Map; -import java.util.Objects; - -import com.google.common.base.CharMatcher; -import com.google.common.base.Function; -import com.google.common.base.Strings; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import com.google.common.collect.ImmutableBiMap; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; -import com.tngtech.archunit.Internal; -import com.tngtech.archunit.base.ArchUnitException.ReflectionException; -import com.tngtech.archunit.base.Optional; -import com.tngtech.archunit.core.MayResolveTypesViaReflection; -import com.tngtech.archunit.core.ResolvesTypesViaReflection; -import org.objectweb.asm.Type; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.primitives.Primitives.allPrimitiveTypes; -import static com.tngtech.archunit.base.ClassLoaders.getCurrentClassLoader; -import static com.tngtech.archunit.core.domain.Formatters.ensureSimpleName; - -@Internal -public interface JavaType { - String getName(); - - String getSimpleName(); - - String getPackageName(); - - @ResolvesTypesViaReflection - Class resolveClass(); - - @ResolvesTypesViaReflection - Class resolveClass(ClassLoader classLoader); - - Optional tryGetComponentType(); - - boolean isPrimitive(); - - boolean isArray(); - - JavaType withSimpleName(String simpleName); - - @Internal - final class From { - private static final LoadingCache typeCache = CacheBuilder.newBuilder().build(new CacheLoader() { - @Override - public JavaType load(String typeName) { - if (primitiveClassesByNameOrDescriptor.containsKey(typeName)) { - return new PrimitiveType(Type.getType(primitiveClassesByNameOrDescriptor.get(typeName)).getClassName()); - } - if (isArray(typeName)) { - // NOTE: ASM uses the canonical name for arrays (i.e. java.lang.Object[]), but we want the class name, - // i.e. [Ljava.lang.Object; - return new ArrayType(ensureCorrectArrayTypeName(typeName)); - } - return new ObjectType(typeName); - } - }); - private static final ImmutableMap> primitiveClassesByName = - Maps.uniqueIndex(allPrimitiveTypes(), new Function, String>() { - @Override - public String apply(Class input) { - return input.getName(); - } - }); - private static final ImmutableBiMap> primitiveClassesByDescriptor = - ImmutableBiMap.copyOf(Maps.uniqueIndex(allPrimitiveTypes(), new Function, String>() { - @Override - public String apply(Class input) { - return Type.getType(input).getDescriptor(); - } - })); - private static final Map> primitiveClassesByNameOrDescriptor = - ImmutableMap.>builder() - .putAll(primitiveClassesByName) - .putAll(primitiveClassesByDescriptor) - .build(); - - public static JavaType name(String typeName) { - return typeCache.getUnchecked(typeName); - } - - private static boolean isArray(String typeName) { - // We support class name ([Ljava.lang.Object;) and canonical name java.lang.Object[] - return typeName.startsWith("[") || typeName.endsWith("]"); - } - - private static String ensureCorrectArrayTypeName(String name) { - return name.endsWith("[]") ? convertCanonicalArrayNameToClassName(name) : name; - } - - private static String convertCanonicalArrayNameToClassName(String name) { - String arrayDesignator = Strings.repeat("[", CharMatcher.is('[').countIn(name)); - return arrayDesignator + createComponentTypeName(name); - } - - private static String createComponentTypeName(String name) { - String baseName = name.substring(0, name.indexOf("[]")); - - return primitiveClassesByName.containsKey(baseName) ? - createPrimitiveComponentType(baseName) : - createObjectComponentType(baseName); - } - - private static String createPrimitiveComponentType(String componentTypeName) { - return primitiveClassesByDescriptor.inverse().get(primitiveClassesByName.get(componentTypeName)); - } - - private static String createObjectComponentType(String componentTypeName) { - return "L" + componentTypeName + ";"; - } - - static JavaType javaClass(JavaClass javaClass) { - return name(javaClass.getName()); - } - - private abstract static class AbstractType implements JavaType { - private final String name; - private final String simpleName; - private final String javaPackage; - - private AbstractType(String name, String simpleName, String javaPackage) { - this.name = name; - this.simpleName = simpleName; - this.javaPackage = javaPackage; - } - - @Override - public String getName() { - return name; - } - - @Override - public String getSimpleName() { - return simpleName; - } - - @Override - public String getPackageName() { - return javaPackage; - } - - @Override - public Class resolveClass() { - return resolveClass(getCurrentClassLoader(getClass())); - } - - @Override - public Class resolveClass(ClassLoader classLoader) { - try { - return classForName(classLoader); - } catch (ClassNotFoundException e) { - throw new ReflectionException(e); - } - } - - @MayResolveTypesViaReflection(reason = "This method is one of the known sources for resolving via reflection") - Class classForName(ClassLoader classLoader) throws ClassNotFoundException { - return Class.forName(getName(), false, classLoader); - } - - @Override - public Optional tryGetComponentType() { - return Optional.absent(); - } - - @Override - public boolean isPrimitive() { - return false; - } - - @Override - public boolean isArray() { - return false; - } - - @Override - public int hashCode() { - return Objects.hash(getName()); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - final JavaType other = (JavaType) obj; - return Objects.equals(this.getName(), other.getName()); - } - - @Override - public String toString() { - return getClass().getSimpleName() + "{" + getName() + "}"; - } - } - - private static class ObjectType extends AbstractType { - ObjectType(String fullName) { - this(fullName, ensureSimpleName(fullName), createPackage(fullName)); - } - - private ObjectType(String fullName, String simpleName, String packageName) { - super(fullName, simpleName, packageName); - } - - @Override - public JavaType withSimpleName(String simpleName) { - return new ObjectType(getName(), simpleName, getPackageName()); - } - } - - private static String createPackage(String fullName) { - int packageEnd = fullName.lastIndexOf('.'); - return packageEnd >= 0 ? fullName.substring(0, packageEnd) : ""; - } - - private static class PrimitiveType extends AbstractType { - PrimitiveType(String fullName) { - super(fullName, fullName, ""); - checkArgument(primitiveClassesByName.containsKey(fullName), "'%s' must be a primitive name", fullName); - } - - @Override - Class classForName(ClassLoader classLoader) { - return primitiveClassesByName.get(getName()); - } - - @Override - public boolean isPrimitive() { - return true; - } - - @Override - public JavaType withSimpleName(String simpleName) { - throw new UnsupportedOperationException("It should never make sense to override the simple type of a primitive"); - } - } - - private static class ArrayType extends AbstractType { - ArrayType(String fullName) { - this(fullName, createSimpleName(fullName), createPackageOfComponentType(fullName)); - } - - private ArrayType(String fullName, String simpleName, String packageName) { - super(fullName, simpleName, packageName); - } - - private static String createPackageOfComponentType(String fullName) { - String componentType = getCanonicalName(fullName).replace("[]", ""); - return createPackage(componentType); - } - - private static String createSimpleName(String fullName) { - return ensureSimpleName(getCanonicalName(fullName)); - } - - private static String getCanonicalName(String fullName) { - return Type.getType(fullName).getClassName(); - } - - @Override - public boolean isArray() { - return true; - } - - @Override - public JavaType withSimpleName(String simpleName) { - return new ArrayType(getName(), simpleName, getPackageName()); - } - - @Override - public Optional tryGetComponentType() { - String canonicalName = getCanonicalName(getName()); - String componentTypeName = canonicalName.substring(0, canonicalName.lastIndexOf("[")); - return Optional.of(JavaType.From.name(componentTypeName)); - } - } - } +import com.tngtech.archunit.PublicAPI; +import com.tngtech.archunit.core.domain.properties.HasName; + +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; + +@PublicAPI(usage = ACCESS) +public interface JavaType extends HasName { + /** + * Converts this {@link JavaType} into the erased type + * (compare the Java Language Specification). + * In particular this will result in + *
    + *
  • the class itself, if this type is a {@link JavaClass}
  • + *
  • the {@link JavaClass} equivalent to {@link Object}, if this type is an unbound {@link JavaTypeVariable}
  • + *
  • the {@link JavaClass} equivalent to the erasure of the left most bound, if this type is a bound {@link JavaTypeVariable}
  • + *
  • if this type is a {@link JavaGenericArrayType}, the erasure will be the {@link JavaClass} + * equivalent to the array type that has the erasure of the generic component type of this type as its component type; + * e.g. take the generic array type {@code T[][]} where {@code T} is unbound, then the erasure will be the array type {@code Object[][]}
  • + *
+ */ + @PublicAPI(usage = ACCESS) + JavaClass toErasure(); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java new file mode 100644 index 0000000000..b0f1af0b54 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java @@ -0,0 +1,143 @@ +/* + * Copyright 2014-2020 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.core.domain; + +import java.lang.reflect.TypeVariable; +import java.util.List; + +import com.google.common.base.Joiner; +import com.google.common.collect.FluentIterable; +import com.tngtech.archunit.PublicAPI; +import com.tngtech.archunit.base.HasDescription; +import com.tngtech.archunit.core.domain.properties.HasOwner; +import com.tngtech.archunit.core.domain.properties.HasUpperBounds; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; +import static com.tngtech.archunit.base.Guava.toGuava; +import static com.tngtech.archunit.core.domain.properties.HasName.Functions.GET_NAME; +import static java.util.Collections.emptyList; + +/** + * Represents a type variable used by generic types and members.
+ * E.g. {@code class MyClass} would have one {@link JavaTypeVariable} with name "T" + * and unbound, i.e. only bound by {@link Object}.
+ * A type variable can have several bounds, where only one bound may be a class bound + * while all further bounds must be interfaces (compare the JLS).
+ * Example: {@code class MyClass} + * would declare one {@link JavaTypeVariable} {@code T} which is bound by {@code SomeClass}, + * {@code SomeInterfaceOne} and {@code SomeInterfaceTwo}. I.e. any concrete class + * substituted for the type variable must extend {@code SomeClass} and implement + * {@code SomeInterfaceOne} and {@code SomeInterfaceTwo}. + */ +@PublicAPI(usage = ACCESS) +public final class JavaTypeVariable implements JavaType, HasOwner, HasUpperBounds { + private final String name; + private final OWNER owner; + private List upperBounds = emptyList(); + private JavaClass erasure; + + JavaTypeVariable(String name, OWNER owner, JavaClass erasure) { + this.name = name; + this.owner = owner; + this.erasure = erasure; + } + + void setUpperBounds(List upperBounds) { + this.upperBounds = upperBounds; + erasure = upperBounds.isEmpty() ? erasure : upperBounds.get(0).toErasure(); + } + + /** + * @return The name of this {@link JavaTypeVariable}, e.g. for {@code class MyClass} + * the name would be "T" + */ + @Override + @PublicAPI(usage = ACCESS) + public String getName() { + return name; + } + + /** + * This method is simply an alias for {@link #getOwner()} that is more familiar to users + * of the Java Reflection API. + * + * @see TypeVariable#getGenericDeclaration() + */ + @PublicAPI(usage = ACCESS) + public OWNER getGenericDeclaration() { + return getOwner(); + } + + /** + * @return The 'owner' of this type parameter, i.e. the Java object that declared this + * {@link TypeVariable} as a type parameter. For type parameter {@code T} of + * {@code SomeClass} this would be the {@code JavaClass} representing {@code SomeClass} + */ + @Override + public OWNER getOwner() { + return owner; + } + + /** + * This method is simply an alias for {@link #getUpperBounds()} that is more familiar to users + * of the Java Reflection API. + * + * @see TypeVariable#getBounds() + */ + @PublicAPI(usage = ACCESS) + public List getBounds() { + return getUpperBounds(); + } + + /** + * @return All upper bounds of this {@link JavaTypeVariable}, i.e. super types any substitution + * of this variable must extend. E.g. for + * {@code class MyClass} the upper bounds would be + * {@code SomeClass} and {@code SomeInterface} + */ + @Override + @PublicAPI(usage = ACCESS) + public List getUpperBounds() { + return upperBounds; + } + + @Override + @PublicAPI(usage = ACCESS) + public JavaClass toErasure() { + return erasure; + } + + @Override + public String toString() { + String bounds = printExtendsClause() ? " extends " + joinTypeNames(upperBounds) : ""; + return getClass().getSimpleName() + '{' + getName() + bounds + '}'; + } + + private boolean printExtendsClause() { + if (upperBounds.isEmpty()) { + return false; + } + if (upperBounds.size() > 1) { + return true; + } + return !getOnlyElement(upperBounds).getName().equals(Object.class.getName()); + } + + private String joinTypeNames(List types) { + return FluentIterable.from(types).transform(toGuava(GET_NAME)).join(Joiner.on(" & ")); + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaWildcardType.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaWildcardType.java new file mode 100644 index 0000000000..101b84152f --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaWildcardType.java @@ -0,0 +1,112 @@ +/* + * Copyright 2014-2020 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.core.domain; + +import java.lang.reflect.WildcardType; +import java.util.List; + +import com.google.common.base.Joiner; +import com.google.common.collect.FluentIterable; +import com.tngtech.archunit.PublicAPI; +import com.tngtech.archunit.core.domain.properties.HasUpperBounds; +import com.tngtech.archunit.core.importer.DomainBuilders.JavaWildcardTypeBuilder; + +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; +import static com.tngtech.archunit.base.Guava.toGuava; +import static com.tngtech.archunit.core.domain.properties.HasName.Functions.GET_NAME; + +/** + * Represents a wildcard type in a type signature (compare the JLS). + * Consider the generic type {@code List}, then the parameterized type + * {@code List} would have the wildcard {@code ?} as its type argument + * (also see {@link JavaParameterizedType}).
+ * According to the JLS a wildcard may have upper and lower bounds.
+ * An upper bound denotes a common supertype any substitution of this wildcard must + * be assignable to. It is denoted by {@code ? extends SomeType}.
+ * A lower bound denotes a common subtype that must be assignable to all substitutions + * of this wildcard type. It is denoted by {@code ? super SomeType}. + */ +public class JavaWildcardType implements JavaType, HasUpperBounds { + private static final String WILDCARD_TYPE_NAME = "?"; + + private final List upperBounds; + private final List lowerBounds; + private final JavaClass erasure; + + JavaWildcardType(JavaWildcardTypeBuilder builder) { + upperBounds = builder.getUpperBounds(); + lowerBounds = builder.getLowerBounds(); + erasure = builder.getUnboundErasureType(upperBounds); + } + + /** + * @return The name of this {@link JavaWildcardType}, which is always "?" + */ + @Override + @PublicAPI(usage = ACCESS) + public String getName() { + return WILDCARD_TYPE_NAME; + } + + /** + * @return All upper bounds of this {@link JavaWildcardType}, i.e. supertypes any substitution + * of this variable must extend. E.g. for + * {@code List} the upper bounds would be {@code [SomeClass]}
+ * Note that the JLS currently only allows a single upper bound for a wildcard type, + * but we follow the Reflection API here and support a collection + * (compare {@link WildcardType#getUpperBounds()}). + */ + @Override + @PublicAPI(usage = ACCESS) + public List getUpperBounds() { + return upperBounds; + } + + /** + * @return All lower bounds of this {@link JavaWildcardType}, i.e. any substitution for this + * {@link JavaWildcardType} must be a supertype of all lower bounds. E.g. for + * {@code Handler>} the lower bounds would be {@code [SomeClass]}.
+ * Note that the JLS currently only allows a single lower bound for a wildcard type, + * but we follow the Reflection API here and support a collection + * (compare {@link WildcardType#getLowerBounds()}). + */ + @PublicAPI(usage = ACCESS) + public List getLowerBounds() { + return lowerBounds; + } + + @Override + @PublicAPI(usage = ACCESS) + public JavaClass toErasure() { + return erasure; + } + + @Override + public String toString() { + String bounds = boundsToString(); + return getClass().getSimpleName() + '{' + getName() + bounds + '}'; + } + + private String boundsToString() { + String upperBoundsString = !upperBounds.isEmpty() ? " extends " + joinTypeNames(upperBounds) : ""; + String lowerBoundsString = !lowerBounds.isEmpty() ? " super " + joinTypeNames(lowerBounds) : ""; + return upperBoundsString + lowerBoundsString; + } + + private String joinTypeNames(List types) { + return FluentIterable.from(types).transform(toGuava(GET_NAME)).join(Joiner.on(" & ")); + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/ReverseDependencies.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/ReverseDependencies.java new file mode 100644 index 0000000000..3d93bb5f5c --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/ReverseDependencies.java @@ -0,0 +1,294 @@ +/* + * Copyright 2014-2020 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.core.domain; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.SetMultimap; +import com.google.common.collect.Sets; + +final class ReverseDependencies { + + private final LoadingCache> accessToFieldCache; + private final LoadingCache> callToMethodCache; + private final LoadingCache> callToConstructorCache; + private final SetMultimap fieldTypeDependencies; + private final SetMultimap methodParameterTypeDependencies; + private final SetMultimap methodReturnTypeDependencies; + private final SetMultimap> methodsThrowsDeclarationDependencies; + private final SetMultimap constructorParameterTypeDependencies; + private final SetMultimap> constructorThrowsDeclarationDependencies; + private final SetMultimap> annotationTypeDependencies; + private final SetMultimap> annotationParameterTypeDependencies; + private final SetMultimap instanceofCheckDependencies; + private final Supplier> directDependenciesToClass; + + private ReverseDependencies(ReverseDependencies.Creation creation) { + accessToFieldCache = CacheBuilder.newBuilder().build(new ResolvingAccessLoader<>(creation.fieldAccessDependencies.build())); + callToMethodCache = CacheBuilder.newBuilder().build(new ResolvingAccessLoader<>(creation.methodCallDependencies.build())); + callToConstructorCache = CacheBuilder.newBuilder().build(new ConstructorCallLoader(creation.constructorCallDependencies.build())); + this.fieldTypeDependencies = creation.fieldTypeDependencies.build(); + this.methodParameterTypeDependencies = creation.methodParameterTypeDependencies.build(); + this.methodReturnTypeDependencies = creation.methodReturnTypeDependencies.build(); + this.methodsThrowsDeclarationDependencies = creation.methodsThrowsDeclarationDependencies.build(); + this.constructorParameterTypeDependencies = creation.constructorParameterTypeDependencies.build(); + this.constructorThrowsDeclarationDependencies = creation.constructorThrowsDeclarationDependencies.build(); + this.annotationTypeDependencies = creation.annotationTypeDependencies.build(); + this.annotationParameterTypeDependencies = creation.annotationParameterTypeDependencies.build(); + this.instanceofCheckDependencies = creation.instanceofCheckDependencies.build(); + this.directDependenciesToClass = createDirectDependenciesToClassSupplier(creation.allDependencies); + } + + private static Supplier> createDirectDependenciesToClassSupplier(final List allDependencies) { + return Suppliers.memoize(new Supplier>() { + @Override + public SetMultimap get() { + ImmutableSetMultimap.Builder result = ImmutableSetMultimap.builder(); + for (JavaClassDependencies dependencies : allDependencies) { + for (Dependency dependency : dependencies.getDirectDependenciesFromClass()) { + result.put(dependency.getTargetClass(), dependency); + } + } + return result.build(); + } + }); + } + + Set getAccessesTo(JavaField field) { + return accessToFieldCache.getUnchecked(field); + } + + Set getCallsTo(JavaMethod method) { + return callToMethodCache.getUnchecked(method); + } + + Set getCallsTo(JavaConstructor constructor) { + return callToConstructorCache.getUnchecked(constructor); + } + + Set getFieldsWithTypeOf(JavaClass clazz) { + return fieldTypeDependencies.get(clazz); + } + + Set getMethodsWithParameterTypeOf(JavaClass clazz) { + return methodParameterTypeDependencies.get(clazz); + } + + Set getMethodsWithReturnTypeOf(JavaClass clazz) { + return methodReturnTypeDependencies.get(clazz); + } + + Set> getMethodThrowsDeclarationsWithTypeOf(JavaClass clazz) { + return methodsThrowsDeclarationDependencies.get(clazz); + } + + Set getConstructorsWithParameterTypeOf(JavaClass clazz) { + return constructorParameterTypeDependencies.get(clazz); + } + + Set> getConstructorsWithThrowsDeclarationTypeOf(JavaClass clazz) { + return constructorThrowsDeclarationDependencies.get(clazz); + } + + Set> getAnnotationsWithTypeOf(JavaClass clazz) { + return annotationTypeDependencies.get(clazz); + } + + Set> getAnnotationsWithParameterTypeOf(JavaClass clazz) { + return annotationParameterTypeDependencies.get(clazz); + } + + Set getInstanceofChecksWithTypeOf(JavaClass clazz) { + return instanceofCheckDependencies.get(clazz); + } + + Set getDirectDependenciesTo(JavaClass clazz) { + return directDependenciesToClass.get().get(clazz); + } + + static final ReverseDependencies EMPTY = new ReverseDependencies(new Creation()); + + static class Creation { + private final ImmutableSetMultimap.Builder fieldAccessDependencies = ImmutableSetMultimap.builder(); + private final ImmutableSetMultimap.Builder methodCallDependencies = ImmutableSetMultimap.builder(); + private final ImmutableSetMultimap.Builder constructorCallDependencies = ImmutableSetMultimap.builder(); + private final ImmutableSetMultimap.Builder fieldTypeDependencies = ImmutableSetMultimap.builder(); + private final ImmutableSetMultimap.Builder methodParameterTypeDependencies = ImmutableSetMultimap.builder(); + private final ImmutableSetMultimap.Builder methodReturnTypeDependencies = ImmutableSetMultimap.builder(); + private final ImmutableSetMultimap.Builder> methodsThrowsDeclarationDependencies = ImmutableSetMultimap.builder(); + private final ImmutableSetMultimap.Builder constructorParameterTypeDependencies = ImmutableSetMultimap.builder(); + private final ImmutableSetMultimap.Builder> constructorThrowsDeclarationDependencies = ImmutableSetMultimap.builder(); + private final ImmutableSetMultimap.Builder> annotationTypeDependencies = ImmutableSetMultimap.builder(); + private final ImmutableSetMultimap.Builder> annotationParameterTypeDependencies = ImmutableSetMultimap.builder(); + private final ImmutableSetMultimap.Builder instanceofCheckDependencies = ImmutableSetMultimap.builder(); + private final List allDependencies = new ArrayList<>(); + + public void registerDependenciesOf(JavaClass clazz, JavaClassDependencies classDependencies) { + registerAccesses(clazz); + registerFields(clazz); + registerMethods(clazz); + registerConstructors(clazz); + registerAnnotations(clazz); + registerStaticInitializer(clazz); + allDependencies.add(classDependencies); + } + + private void registerAccesses(JavaClass clazz) { + for (JavaFieldAccess access : clazz.getFieldAccessesFromSelf()) { + fieldAccessDependencies.put(access.getTargetOwner(), access); + } + for (JavaMethodCall call : clazz.getMethodCallsFromSelf()) { + methodCallDependencies.put(call.getTargetOwner(), call); + } + for (JavaConstructorCall call : clazz.getConstructorCallsFromSelf()) { + constructorCallDependencies.put(call.getTarget().getFullName(), call); + } + } + + private void registerFields(JavaClass clazz) { + for (JavaField field : clazz.getFields()) { + fieldTypeDependencies.put(field.getRawType(), field); + } + } + + private void registerMethods(JavaClass clazz) { + for (JavaMethod method : clazz.getMethods()) { + for (JavaClass parameter : method.getRawParameterTypes()) { + methodParameterTypeDependencies.put(parameter, method); + } + methodReturnTypeDependencies.put(method.getRawReturnType(), method); + for (ThrowsDeclaration throwsDeclaration : method.getThrowsClause()) { + methodsThrowsDeclarationDependencies.put(throwsDeclaration.getRawType(), throwsDeclaration); + } + for (InstanceofCheck instanceofCheck : method.getInstanceofChecks()) { + instanceofCheckDependencies.put(instanceofCheck.getRawType(), instanceofCheck); + } + } + } + + private void registerConstructors(JavaClass clazz) { + for (JavaConstructor constructor : clazz.getConstructors()) { + for (JavaClass parameter : constructor.getRawParameterTypes()) { + constructorParameterTypeDependencies.put(parameter, constructor); + } + for (ThrowsDeclaration throwsDeclaration : constructor.getThrowsClause()) { + constructorThrowsDeclarationDependencies.put(throwsDeclaration.getRawType(), throwsDeclaration); + } + for (InstanceofCheck instanceofCheck : constructor.getInstanceofChecks()) { + instanceofCheckDependencies.put(instanceofCheck.getRawType(), instanceofCheck); + } + } + } + + private void registerAnnotations(JavaClass clazz) { + for (final JavaAnnotation annotation : findAnnotations(clazz)) { + annotationTypeDependencies.put(annotation.getRawType(), annotation); + annotation.accept(new JavaAnnotation.DefaultParameterVisitor() { + @Override + public void visitClass(String propertyName, JavaClass javaClass) { + annotationParameterTypeDependencies.put(javaClass, annotation); + } + + @Override + public void visitEnumConstant(String propertyName, JavaEnumConstant enumConstant) { + annotationParameterTypeDependencies.put(enumConstant.getDeclaringClass(), annotation); + } + + @Override + public void visitAnnotation(String propertyName, JavaAnnotation memberAnnotation) { + annotationParameterTypeDependencies.put(memberAnnotation.getRawType(), annotation); + memberAnnotation.accept(this); + } + }); + } + } + + private Set> findAnnotations(JavaClass clazz) { + Set> result = Sets.>newHashSet(clazz.getAnnotations()); + for (JavaMember member : clazz.getMembers()) { + result.addAll(member.getAnnotations()); + } + return result; + } + + private void registerStaticInitializer(JavaClass clazz) { + if (clazz.getStaticInitializer().isPresent()) { + for (InstanceofCheck instanceofCheck : clazz.getStaticInitializer().get().getInstanceofChecks()) { + instanceofCheckDependencies.put(instanceofCheck.getRawType(), instanceofCheck); + } + } + } + + void finish(Iterable classes) { + ReverseDependencies reverseDependencies = new ReverseDependencies(this); + for (JavaClass clazz : classes) { + clazz.setReverseDependencies(reverseDependencies); + } + } + } + + private static class ResolvingAccessLoader> extends CacheLoader> { + private final SetMultimap accessesToSelf; + + private ResolvingAccessLoader(SetMultimap accessesToSelf) { + this.accessesToSelf = accessesToSelf; + } + + @Override + public Set load(MEMBER member) { + ImmutableSet.Builder result = ImmutableSet.builder(); + for (final JavaClass javaClass : getPossibleTargetClassesForAccess(member.getOwner())) { + for (ACCESS access : this.accessesToSelf.get(javaClass)) { + if (access.getTarget().resolve().contains(member)) { + result.add(access); + } + } + } + return result.build(); + } + + private Set getPossibleTargetClassesForAccess(JavaClass owner) { + return ImmutableSet.builder() + .add(owner) + .addAll(owner.getAllSubClasses()) + .build(); + } + } + + private static class ConstructorCallLoader extends CacheLoader> { + private final SetMultimap accessesToSelf; + + private ConstructorCallLoader(SetMultimap accessesToSelf) { + this.accessesToSelf = accessesToSelf; + } + + @Override + public Set load(JavaConstructor member) { + ImmutableSet.Builder result = ImmutableSet.builder(); + result.addAll(accessesToSelf.get(member.getFullName())); + return result.build(); + } + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/Source.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/Source.java index ba4e0ce61f..726a562f22 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/Source.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/Source.java @@ -48,10 +48,10 @@ public class Source { private final Optional fileName; private final Md5sum md5sum; - Source(URI uri, Optional fileName) { + Source(URI uri, Optional fileName, boolean md5InClassSourcesEnabled) { this.uri = checkNotNull(uri); this.fileName = checkNotNull(fileName); - md5sum = Md5sum.of(uri); + md5sum = md5InClassSourcesEnabled ? Md5sum.of(uri) : Md5sum.DISABLED; } @PublicAPI(usage = ACCESS) @@ -167,21 +167,13 @@ private static MessageDigest getMd5Digest() { } } - static Md5sum of(byte[] input) { + private static Md5sum of(URI uri) { if (MD5_DIGEST == null) { return NOT_SUPPORTED; } - return ArchConfiguration.get().md5InClassSourcesEnabled() ? new Md5sum(input, MD5_DIGEST) : DISABLED; - } - - private static Md5sum of(URI uri) { - if (!ArchConfiguration.get().md5InClassSourcesEnabled()) { - return DISABLED; - } - Optional bytesFromUri = read(uri); - return bytesFromUri.isPresent() ? Md5sum.of(bytesFromUri.get()) : UNDETERMINED; + return bytesFromUri.isPresent() ? new Md5sum(bytesFromUri.get(), MD5_DIGEST) : UNDETERMINED; } private static Optional read(URI uri) { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/SourceCodeLocation.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/SourceCodeLocation.java index 95252079c7..dcb7410e1e 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/SourceCodeLocation.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/SourceCodeLocation.java @@ -40,7 +40,6 @@ */ @PublicAPI(usage = ACCESS) public final class SourceCodeLocation { - private static final String LOCATION_TEMPLATE = "(%s:%d)"; @PublicAPI(usage = ACCESS) public static SourceCodeLocation of(JavaClass sourceClass) { @@ -57,7 +56,7 @@ private static String formatLocation(JavaClass sourceClass, int lineNumber) { ? sourceClass.getSource().get().getFileName() : Optional.absent(); String sourceFileName = recordedSourceFileName.isPresent() ? recordedSourceFileName.get() : guessSourceFileName(sourceClass); - return String.format(LOCATION_TEMPLATE, sourceFileName, lineNumber); + return "(" + sourceFileName + ":" + lineNumber + ")"; } private static String guessSourceFileName(JavaClass location) { @@ -78,6 +77,10 @@ private SourceCodeLocation(JavaClass sourceClass, int lineNumber) { description = formatLocation(sourceClass, lineNumber); } + int getLineNumber() { + return lineNumber; + } + @Override public int hashCode() { return Objects.hash(sourceClass, lineNumber); diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/ThrowsDeclaration.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/ThrowsDeclaration.java index cefcfe42c1..3895845675 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/ThrowsDeclaration.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/ThrowsDeclaration.java @@ -93,6 +93,12 @@ public JavaClass getDeclaringClass() { return getLocation().getOwner(); } + @Override + @PublicAPI(usage = ACCESS) + public JavaType getType() { + return type; + } + /** * @return The type of this {@link ThrowsDeclaration}, e.g. for a method *
void method() throws SomeException {...}
diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/CanBeAnnotated.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/CanBeAnnotated.java index ee76d27942..ffd8b73fda 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/CanBeAnnotated.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/CanBeAnnotated.java @@ -19,6 +19,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Collection; +import java.util.HashSet; +import java.util.Set; import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.base.ArchUnitException.InvalidSyntaxUsageException; @@ -157,7 +159,24 @@ public static boolean isMetaAnnotatedWith( DescribedPredicate> predicate) { for (JavaAnnotation annotation : annotations) { - if (annotation.getRawType().isAnnotatedWith(predicate) || annotation.getRawType().isMetaAnnotatedWith(predicate)) { + if (isMetaAnnotatedWith(annotation, predicate, new HashSet())) { + return true; + } + } + return false; + } + + private static boolean isMetaAnnotatedWith( + JavaAnnotation annotation, + DescribedPredicate> predicate, + Set visitedAnnotations) { + + if (!visitedAnnotations.add(annotation.getRawType().getName())) { + return false; + } + + for (JavaAnnotation metaAnnotation : annotation.getRawType().getAnnotations()) { + if (predicate.apply(metaAnnotation) || isMetaAnnotatedWith(metaAnnotation, predicate, visitedAnnotations)) { return true; } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasName.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasName.java index 8c0ba9888e..1543cbeaaf 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasName.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasName.java @@ -111,6 +111,21 @@ public static DescribedPredicate nameMatching(final String regex) { return new NameMatchingPredicate(regex); } + @PublicAPI(usage = ACCESS) + public static DescribedPredicate nameStartingWith(final String prefix) { + return new NameStartingWithPredicate(prefix); + } + + @PublicAPI(usage = ACCESS) + public static DescribedPredicate nameContaining(final String infix) { + return new NameContainingPredicate(infix); + } + + @PublicAPI(usage = ACCESS) + public static DescribedPredicate nameEndingWith(final String postfix) { + return new NameEndingWithPredicate(postfix); + } + private static class NameEqualsPredicate extends DescribedPredicate { private final String name; @@ -138,6 +153,49 @@ public boolean apply(HasName input) { return pattern.matcher(input.getName()).matches(); } } + + private static class NameStartingWithPredicate extends DescribedPredicate { + private final String prefix; + + NameStartingWithPredicate(String prefix) { + super(String.format("name starting with '%s'", prefix)); + this.prefix = prefix; + } + + @Override + public boolean apply(HasName input) { + return input.getName().startsWith(prefix); + } + + } + + private static class NameContainingPredicate extends DescribedPredicate { + private final String infix; + + NameContainingPredicate(String infix) { + super(String.format("name containing '%s'", infix)); + this.infix = infix; + } + + @Override + public boolean apply(HasName input) { + return input.getName().contains(infix); + } + } + + private static class NameEndingWithPredicate extends DescribedPredicate { + private final String suffix; + + NameEndingWithPredicate(String suffix) { + super(String.format("name ending with '%s'", suffix)); + this.suffix = suffix; + } + + @Override + public boolean apply(HasName input) { + return input.getName().endsWith(suffix); + } + } } final class Functions { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasType.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasType.java index d835f9ba98..66f7d82dae 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasType.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasType.java @@ -19,6 +19,7 @@ import com.tngtech.archunit.base.ChainableFunction; import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaType; import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; import static com.tngtech.archunit.base.DescribedPredicate.equalTo; @@ -27,6 +28,9 @@ public interface HasType { + @PublicAPI(usage = ACCESS) + JavaType getType(); + @PublicAPI(usage = ACCESS) JavaClass getRawType(); @@ -48,7 +52,6 @@ public static DescribedPredicate rawType(String typeName) { public static DescribedPredicate rawType(DescribedPredicate predicate) { return GET_RAW_TYPE.is(predicate).as("raw type " + predicate.getDescription()); } - } final class Functions { @@ -62,6 +65,5 @@ public JavaClass apply(HasType input) { return input.getRawType(); } }; - } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasUpperBounds.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasUpperBounds.java new file mode 100644 index 0000000000..6027e8a443 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasUpperBounds.java @@ -0,0 +1,29 @@ +/* + * Copyright 2014-2020 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.core.domain.properties; + +import java.util.List; + +import com.tngtech.archunit.PublicAPI; +import com.tngtech.archunit.core.domain.JavaType; + +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; + +@PublicAPI(usage = ACCESS) +public interface HasUpperBounds { + @PublicAPI(usage = ACCESS) + List getUpperBounds(); +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/AccessRecord.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/AccessRecord.java index eaffa473b6..2436fcd7bd 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/AccessRecord.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/AccessRecord.java @@ -17,11 +17,16 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.tngtech.archunit.Internal; import com.tngtech.archunit.base.Optional; @@ -30,16 +35,14 @@ import com.tngtech.archunit.core.domain.AccessTarget.FieldAccessTarget; import com.tngtech.archunit.core.domain.AccessTarget.MethodCallTarget; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.domain.JavaClassList; import com.tngtech.archunit.core.domain.JavaCodeUnit; import com.tngtech.archunit.core.domain.JavaConstructor; import com.tngtech.archunit.core.domain.JavaField; import com.tngtech.archunit.core.domain.JavaFieldAccess.AccessType; +import com.tngtech.archunit.core.domain.JavaMember; import com.tngtech.archunit.core.domain.JavaMethod; -import com.tngtech.archunit.core.domain.JavaType; -import com.tngtech.archunit.core.domain.properties.HasDescriptor; -import com.tngtech.archunit.core.domain.properties.HasName; -import com.tngtech.archunit.core.domain.properties.HasOwner; import com.tngtech.archunit.core.importer.DomainBuilders.ConstructorCallTargetBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.FieldAccessTargetBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.MethodCallTargetBuilder; @@ -48,6 +51,7 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createJavaClassList; +import static java.util.Collections.singletonList; interface AccessRecord { JavaCodeUnit getCaller(); @@ -102,7 +106,7 @@ private static class RawConstructorCallRecordProcessed implements AccessRecord get() { - return uniqueTargetIn(tryFindMatchingTargets(targetOwner.getAllConstructors(), target)); + for (JavaConstructor constructor : targetOwner.getConstructors()) { + if (constructor.getDescriptor().equals(target.desc)) { + return Optional.of(constructor); + } + } + return Optional.absent(); } } } @@ -154,7 +163,7 @@ private static class RawMethodCallRecordProcessed implements AccessRecord> methodsSupplier = new MethodTargetSupplier(targetOwner.getAllMethods(), record.target); JavaClassList parameters = getArgumentTypesFrom(record.target.desc, classes); - JavaClass returnType = classes.getOrResolve(JavaTypeImporter.importAsmMethodReturnType(record.target.desc).getName()); + JavaClass returnType = classes.getOrResolve(JavaClassDescriptorImporter.importAsmMethodReturnType(record.target.desc).getFullyQualifiedClassName()); return new MethodCallTargetBuilder() .withOwner(targetOwner) .withName(record.target.name) @@ -193,7 +202,7 @@ private static class MethodTargetSupplier implements Supplier> { @Override public Set get() { - return tryFindMatchingTargets(allMethods, target); + return tryFindMatchingTargets(allMethods, target, METHOD_SIGNATURE_PREDICATE); } } } @@ -207,7 +216,7 @@ private static class RawFieldAccessRecordProcessed implements FieldAccessRecord RawFieldAccessRecordProcessed(RawAccessRecord.ForField record, ImportedClasses classes) { this.record = record; this.classes = classes; - targetOwner = this.classes.getOrResolve(record.target.owner.getName()); + targetOwner = this.classes.getOrResolve(record.target.owner.getFullyQualifiedClassName()); callerSupplier = createCallerSupplier(record.caller, classes); } @@ -224,7 +233,7 @@ public JavaCodeUnit getCaller() { @Override public FieldAccessTarget getTarget() { Supplier> fieldSupplier = new FieldTargetSupplier(targetOwner.getAllFields(), record.target); - JavaClass fieldType = classes.getOrResolve(JavaTypeImporter.importAsmType(record.target.desc).getName()); + JavaClass fieldType = classes.getOrResolve(JavaClassDescriptorImporter.importAsmType(record.target.desc).getFullyQualifiedClassName()); return new FieldAccessTargetBuilder() .withOwner(targetOwner) .withName(record.target.name) @@ -249,7 +258,7 @@ private static class FieldTargetSupplier implements Supplier @Override public Optional get() { - return uniqueTargetIn(tryFindMatchingTargets(allFields, target)); + return uniqueTargetIn(tryFindMatchingTargets(allFields, target, FIELD_SIGNATURE_PREDICATE)); } } } @@ -273,27 +282,216 @@ private static JavaCodeUnit getCaller(CodeUnit caller, ImportedClasses classes) " that matches supposed caller " + caller); } - private static > Set - tryFindMatchingTargets(Set possibleTargets, TargetInfo targetInfo) { - ImmutableSet.Builder result = ImmutableSet.builder(); - for (T possibleTarget : possibleTargets) { - if (targetInfo.matches(possibleTarget)) { + private static Set + tryFindMatchingTargets(Set possibleTargets, TARGET target, SignaturePredicate signaturePredicate) { + ImmutableSet.Builder result = ImmutableSet.builder(); + for (MEMBER possibleTarget : possibleTargets) { + if (matches(possibleTarget, target, signaturePredicate)) { result.add(possibleTarget); } } return result.build(); } + private static boolean matches(MEMBER member, TARGET target, SignaturePredicate signaturePredicate) { + if (!target.name.equals(member.getName()) || !target.desc.equals(member.getDescriptor())) { + return false; + } + return target.owner.getFullyQualifiedClassName().equals(member.getOwner().getName()) || + containsExactlyOneMatch(new ClassHierarchyPath(target.owner, member.getOwner()), target, signaturePredicate); + } + + private static boolean containsExactlyOneMatch(Iterable classes, TARGET target, SignaturePredicate signaturePredicate) { + Set matching = new HashSet<>(); + for (JavaClass javaClass : classes) { + if (signaturePredicate.exists(javaClass, target)) { + matching.add(javaClass); + } + } + return matching.size() == 1; + } + + private interface SignaturePredicate { + boolean exists(JavaClass clazz, TARGET target); + } + + private static final SignaturePredicate FIELD_SIGNATURE_PREDICATE = new SignaturePredicate() { + @Override + public boolean exists(JavaClass clazz, TargetInfo target) { + Optional field = clazz.tryGetField(target.name); + return field.isPresent() && target.desc.equals(field.get().getDescriptor()); + } + }; + + private static final SignaturePredicate METHOD_SIGNATURE_PREDICATE = new SignaturePredicate() { + @Override + public boolean exists(JavaClass clazz, TargetInfo target) { + for (JavaMethod method : clazz.getMethods()) { + if (method.getName().equals(target.name) && method.getDescriptor().equals(target.desc)) { + return true; + } + } + return false; + } + }; + private static Optional uniqueTargetIn(Collection collection) { return collection.size() == 1 ? Optional.of(getOnlyElement(collection)) : Optional.absent(); } private static JavaClassList getArgumentTypesFrom(String descriptor, ImportedClasses classes) { List paramTypes = new ArrayList<>(); - for (JavaType type : JavaTypeImporter.importAsmMethodArgumentTypes(descriptor)) { - paramTypes.add(classes.getOrResolve(type.getName())); + for (JavaClassDescriptor type : JavaClassDescriptorImporter.importAsmMethodArgumentTypes(descriptor)) { + paramTypes.add(classes.getOrResolve(type.getFullyQualifiedClassName())); } return createJavaClassList(paramTypes); } + + private static class ClassHierarchyPath implements Iterable { + private final List path; + + public ClassHierarchyPath(JavaClassDescriptor childType, JavaClass parent) { + Optional child = tryFindChildInHierarchy(childType, parent); + path = child.isPresent() ? createPath(parent, child.get()) : Collections.emptyList(); + } + + private Optional tryFindChildInHierarchy(JavaClassDescriptor childType, JavaClass parent) { + for (JavaClass subclass : parent.getAllSubClasses()) { + if (subclass.getName().equals(childType.getFullyQualifiedClassName())) { + return Optional.of(subclass); + } + } + return Optional.absent(); + } + + private List createPath(JavaClass parent, JavaClass child) { + ImmutableList.Builder pathBuilder = ImmutableList.builder().add(child); + HierarchyResolutionStrategy hierarchyResolutionStrategy = hierarchyResolutionStrategyFrom(child).to(parent); + while (hierarchyResolutionStrategy.hasNext()) { + pathBuilder.add(hierarchyResolutionStrategy.next()); + } + return pathBuilder.build(); + } + + private HierarchyResolutionStrategyCreator hierarchyResolutionStrategyFrom(JavaClass child) { + return new HierarchyResolutionStrategyCreator(child); + } + + @Override + public Iterator iterator() { + return path.iterator(); + } + + private interface HierarchyResolutionStrategy { + boolean hasNext(); + + JavaClass next(); + } + + private static class HierarchyResolutionStrategyCreator { + private final JavaClass child; + + private HierarchyResolutionStrategyCreator(JavaClass child) { + this.child = child; + } + + public HierarchyResolutionStrategy to(JavaClass parent) { + return parent.isInterface() ? + new InterfaceHierarchyResolutionStrategy(child, parent) : + new ClassHierarchyResolutionStrategy(child, parent); + } + } + + private static class ClassHierarchyResolutionStrategy implements HierarchyResolutionStrategy { + private final JavaClass parent; + private JavaClass current; + + private ClassHierarchyResolutionStrategy(JavaClass child, JavaClass parent) { + this.current = child; + this.parent = parent; + } + + @Override + public boolean hasNext() { + return !current.equals(parent) && current.getSuperClass().isPresent(); + } + + @Override + public JavaClass next() { + current = current.getSuperClass().get(); + return current; + } + } + + private static class InterfaceHierarchyResolutionStrategy implements HierarchyResolutionStrategy { + private final Iterator interfaces; + private final JavaClass parent; + private JavaClass current; + + private InterfaceHierarchyResolutionStrategy(JavaClass child, JavaClass parent) { + interfaces = interfacesBetween(child, parent); + this.parent = parent; + current = child; + } + + private Iterator interfacesBetween(JavaClass from, JavaClass target) { + Node node = new Node(from); + List result = new ArrayList<>(); + for (Node parent : node.parents) { + result.addAll(parent.to(target)); + } + return result.iterator(); + } + + @Override + public boolean hasNext() { + return !current.equals(parent) && interfaces.hasNext(); + } + + @Override + public JavaClass next() { + current = interfaces.next(); + return current; + } + } + + private static class Node { + private final JavaClass child; + private final Set parents = new HashSet<>(); + + private Node(JavaClass child) { + this.child = child; + for (JavaClass i : child.getInterfaces()) { + parents.add(new Node(i)); + } + } + + public List to(JavaClass target) { + if (child.equals(target)) { + return singletonList(child); + } + Set result = new LinkedHashSet<>(); + for (Node parent : parents) { + if (parent.contains(target)) { + result.add(child); + result.addAll(parent.to(target)); + } + } + return new ArrayList<>(result); + } + + public boolean contains(JavaClass target) { + if (child.equals(target)) { + return true; + } + for (Node parent : parents) { + if (parent.contains(target)) { + return true; + } + } + return false; + } + } + } } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java index dc992cec88..bd7f2bf211 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java @@ -16,6 +16,7 @@ package com.tngtech.archunit.core.importer; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -23,9 +24,14 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.google.common.collect.SetMultimap; import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaMember; +import com.tngtech.archunit.core.domain.JavaMethod; +import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeParameterBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.TypeParametersBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,15 +40,20 @@ class ClassFileImportRecord { private static final Logger LOG = LoggerFactory.getLogger(ClassFileImportRecord.class); + private static final TypeParametersBuilder NO_TYPE_PARAMETERS = + new TypeParametersBuilder(Collections.>emptySet()); + private final Map classes = new HashMap<>(); private final Map superClassNamesByOwner = new HashMap<>(); private final SetMultimap interfaceNamesByOwner = HashMultimap.create(); + private final Map typeParametersBuilderByOwner = new HashMap<>(); private final SetMultimap fieldBuildersByOwner = HashMultimap.create(); private final SetMultimap methodBuildersByOwner = HashMultimap.create(); private final SetMultimap constructorBuildersByOwner = HashMultimap.create(); private final Map staticInitializerBuildersByOwner = new HashMap<>(); private final SetMultimap annotationsByOwner = HashMultimap.create(); + private final Map annotationDefaultValuesByOwner = new HashMap<>(); private final EnclosingClassesByInnerClasses enclosingClassNamesByOwner = new EnclosingClassesByInnerClasses(); private final Set rawFieldAccessRecords = new HashSet<>(); @@ -60,6 +71,10 @@ void addInterfaces(String ownerName, Set interfaceNames) { interfaceNamesByOwner.putAll(ownerName, interfaceNames); } + public void addTypeParameters(String ownerName, TypeParametersBuilder builder) { + typeParametersBuilderByOwner.put(ownerName, builder); + } + void addField(String ownerName, DomainBuilders.JavaFieldBuilder fieldBuilder) { fieldBuildersByOwner.put(ownerName, fieldBuilder); } @@ -79,10 +94,18 @@ void setStaticInitializer(String ownerName, DomainBuilders.JavaStaticInitializer staticInitializerBuildersByOwner.put(ownerName, builder); } - void addAnnotations(String ownerName, Set annotations) { + void addClassAnnotations(String ownerName, Set annotations) { this.annotationsByOwner.putAll(ownerName, annotations); } + void addMemberAnnotations(String declaringClassName, String memberName, String descriptor, Set annotations) { + this.annotationsByOwner.putAll(getMemberKey(declaringClassName, memberName, descriptor), annotations); + } + + void addAnnotationDefaultValue(String declaringClassName, String methodName, String descriptor, DomainBuilders.JavaAnnotationBuilder.ValueBuilder valueBuilder) { + annotationDefaultValuesByOwner.put(getMemberKey(declaringClassName, methodName, descriptor), valueBuilder); + } + void setEnclosingClass(String ownerName, String enclosingClassName) { enclosingClassNamesByOwner.register(ownerName, enclosingClassName); } @@ -95,6 +118,13 @@ Set getInterfaceNamesFor(String ownerName) { return interfaceNamesByOwner.get(ownerName); } + TypeParametersBuilder getTypeParameterBuildersFor(String ownerName) { + if (!typeParametersBuilderByOwner.containsKey(ownerName)) { + return NO_TYPE_PARAMETERS; + } + return typeParametersBuilderByOwner.get(ownerName); + } + Set getFieldBuildersFor(String ownerName) { return fieldBuildersByOwner.get(ownerName); } @@ -111,8 +141,46 @@ Optional getStaticInitializerBuilde return Optional.fromNullable(staticInitializerBuildersByOwner.get(ownerName)); } - Set getAnnotationsFor(String ownerName) { - return annotationsByOwner.get(ownerName); + Set getAnnotationTypeNamesFor(JavaClass owner) { + ImmutableSet.Builder result = ImmutableSet.builder(); + for (DomainBuilders.JavaAnnotationBuilder annotationBuilder : annotationsByOwner.get(owner.getName())) { + result.add(annotationBuilder.getFullyQualifiedClassName()); + } + return result.build(); + } + + Set getAnnotationsFor(JavaClass owner) { + return annotationsByOwner.get(owner.getName()); + } + + Set getMemberAnnotationTypeNamesFor(JavaClass owner) { + Iterable> memberBuilders = Iterables.concat( + fieldBuildersByOwner.get(owner.getName()), + methodBuildersByOwner.get(owner.getName()), + constructorBuildersByOwner.get(owner.getName()), + nullToEmpty(staticInitializerBuildersByOwner.get(owner.getName()))); + + ImmutableSet.Builder result = ImmutableSet.builder(); + for (DomainBuilders.JavaMemberBuilder memberBuilder : memberBuilders) { + for (DomainBuilders.JavaAnnotationBuilder annotationBuilder : annotationsByOwner.get(getMemberKey(owner.getName(), memberBuilder.getName(), memberBuilder.getDescriptor()))) { + result.add(annotationBuilder.getFullyQualifiedClassName()); + } + } + return result.build(); + } + + private Iterable> nullToEmpty(DomainBuilders.JavaStaticInitializerBuilder staticInitializerBuilder) { + return staticInitializerBuilder != null + ? Collections.>singleton(staticInitializerBuilder) + : Collections.>emptySet(); + } + + Set getAnnotationsFor(JavaMember owner) { + return annotationsByOwner.get(getMemberKey(owner)); + } + + Optional getAnnotationDefaultValueBuilderFor(JavaMethod method) { + return Optional.fromNullable(annotationDefaultValuesByOwner.get(getMemberKey(method))); } Optional getEnclosingClassFor(String ownerName) { @@ -161,12 +229,20 @@ Set getAccessRecords() { .build(); } - Map getSuperClassNamesBySubClass() { - return superClassNamesByOwner; + Set getAllSuperClassNames() { + return ImmutableSet.copyOf(superClassNamesByOwner.values()); + } + + Set getAllSuperInterfaceNames() { + return ImmutableSet.copyOf(interfaceNamesByOwner.values()); + } + + private static String getMemberKey(JavaMember member) { + return getMemberKey(member.getOwner().getName(), member.getName(), member.getDescriptor()); } - SetMultimap getInterfaceNamesBySubInterface() { - return interfaceNamesByOwner; + private static String getMemberKey(String declaringClassName, String methodName, String descriptor) { + return declaringClassName + "|" + methodName + "|" + descriptor; } // NOTE: ASM calls visitInnerClass and visitOuterClass several times, sometimes when the outer class is imported diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java index 401eb09d1e..47153708d8 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java @@ -19,15 +19,15 @@ import java.net.URI; import java.util.Set; +import com.tngtech.archunit.ArchConfiguration; import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.domain.JavaFieldAccess.AccessType; +import com.tngtech.archunit.core.importer.DomainBuilders.TypeParametersBuilder; import com.tngtech.archunit.core.importer.JavaClassProcessor.AccessHandler; import com.tngtech.archunit.core.importer.JavaClassProcessor.DeclarationHandler; import com.tngtech.archunit.core.importer.RawAccessRecord.CodeUnit; -import com.tngtech.archunit.core.importer.RawAccessRecord.ConstructorTargetInfo; -import com.tngtech.archunit.core.importer.RawAccessRecord.MethodTargetInfo; import com.tngtech.archunit.core.importer.RawAccessRecord.TargetInfo; import com.tngtech.archunit.core.importer.resolvers.ClassResolver; import com.tngtech.archunit.core.importer.resolvers.ClassResolver.ClassUriImporter; @@ -43,6 +43,7 @@ class ClassFileProcessor { static final int ASM_API_VERSION = ASM7; + private final boolean md5InClassSourcesEnabled = ArchConfiguration.get().md5InClassSourcesEnabled(); private final ClassResolver.Factory classResolverFactory = new ClassResolver.Factory(); JavaClasses process(ClassFileSource source) { @@ -52,7 +53,7 @@ JavaClasses process(ClassFileSource source) { for (ClassFileLocation location : source) { try (InputStream s = location.openStream()) { JavaClassProcessor javaClassProcessor = - new JavaClassProcessor(location.getUri(), classDetailsRecorder, accessHandler); + new JavaClassProcessor(new SourceDescriptor(location.getUri(), md5InClassSourcesEnabled), classDetailsRecorder, accessHandler); new ClassReader(s).accept(javaClassProcessor, 0); importRecord.addAll(javaClassProcessor.createJavaClass().asSet()); } catch (Exception e) { @@ -84,29 +85,44 @@ public void onNewClass(String className, Optional superClassName, Set annotationBuilders) { + importRecord.addClassAnnotations(ownerName, annotationBuilders); } @Override - public void onDeclaredStaticInitializer(DomainBuilders.JavaStaticInitializerBuilder builder) { - importRecord.setStaticInitializer(ownerName, builder); + public void onDeclaredMemberAnnotations(String memberName, String descriptor, Set annotations) { + importRecord.addMemberAnnotations(ownerName, memberName, descriptor, annotations); } @Override - public void onDeclaredAnnotations(Set annotations) { - importRecord.addAnnotations(ownerName, annotations); + public void onDeclaredAnnotationDefaultValue(String methodName, String methodDescriptor, DomainBuilders.JavaAnnotationBuilder.ValueBuilder valueBuilder) { + importRecord.addAnnotationDefaultValue(ownerName, methodName, methodDescriptor, valueBuilder); } @Override @@ -140,7 +156,7 @@ public void setLineNumber(int lineNumber) { public void handleFieldInstruction(int opcode, String owner, String name, String desc) { AccessType accessType = AccessType.forOpCode(opcode); LOG.trace("Found {} access to field {}.{}:{} in line {}", accessType, owner, name, desc, lineNumber); - TargetInfo target = new RawAccessRecord.FieldTargetInfo(owner, name, desc); + TargetInfo target = new TargetInfo(owner, name, desc); importRecord.registerFieldAccess(filled(new RawAccessRecord.ForField.Builder(), target) .withAccessType(accessType) .build()); @@ -150,10 +166,10 @@ public void handleFieldInstruction(int opcode, String owner, String name, String public void handleMethodInstruction(String owner, String name, String desc) { LOG.trace("Found call of method {}.{}:{} in line {}", owner, name, desc, lineNumber); if (CONSTRUCTOR_NAME.equals(name)) { - TargetInfo target = new ConstructorTargetInfo(owner, name, desc); + TargetInfo target = new TargetInfo(owner, name, desc); importRecord.registerConstructorCall(filled(new RawAccessRecord.Builder(), target).build()); } else { - TargetInfo target = new MethodTargetInfo(owner, name, desc); + TargetInfo target = new TargetInfo(owner, name, desc); importRecord.registerMethodCall(filled(new RawAccessRecord.Builder(), target).build()); } } @@ -168,21 +184,23 @@ private > BUILDER filled(BU private ClassResolver getClassResolver(ClassDetailsRecorder classDetailsRecorder) { ClassResolver classResolver = classResolverFactory.create(); - classResolver.setClassUriImporter(new UriImporterOfProcessor(classDetailsRecorder)); + classResolver.setClassUriImporter(new UriImporterOfProcessor(classDetailsRecorder, md5InClassSourcesEnabled)); return classResolver; } private static class UriImporterOfProcessor implements ClassUriImporter { private final DeclarationHandler declarationHandler; + private final boolean md5InClassSourcesEnabled; - UriImporterOfProcessor(DeclarationHandler declarationHandler) { + UriImporterOfProcessor(DeclarationHandler declarationHandler, boolean md5InClassSourcesEnabled) { this.declarationHandler = declarationHandler; + this.md5InClassSourcesEnabled = md5InClassSourcesEnabled; } @Override public Optional tryImport(URI uri) { try (InputStream inputStream = uri.toURL().openStream()) { - JavaClassProcessor classProcessor = new JavaClassProcessor(uri, declarationHandler); + JavaClassProcessor classProcessor = new JavaClassProcessor(new SourceDescriptor(uri, md5InClassSourcesEnabled), declarationHandler); new ClassReader(inputStream).accept(classProcessor, 0); return classProcessor.createJavaClass(); } catch (Exception e) { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java index dd78c5da88..7f90453da7 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java @@ -15,7 +15,7 @@ */ package com.tngtech.archunit.core.importer; -import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.Set; @@ -24,35 +24,38 @@ import com.google.common.collect.Multimap; import com.google.common.collect.SetMultimap; import com.tngtech.archunit.base.Function; +import com.tngtech.archunit.base.HasDescription; import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.domain.AccessTarget; import com.tngtech.archunit.core.domain.AccessTarget.ConstructorCallTarget; import com.tngtech.archunit.core.domain.AccessTarget.MethodCallTarget; -import com.tngtech.archunit.core.domain.DomainObjectCreationContext; import com.tngtech.archunit.core.domain.ImportContext; import com.tngtech.archunit.core.domain.JavaAnnotation; -import com.tngtech.archunit.core.domain.JavaAnnotation.DefaultParameterVisitor; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.domain.JavaCodeUnit; import com.tngtech.archunit.core.domain.JavaConstructor; import com.tngtech.archunit.core.domain.JavaConstructorCall; -import com.tngtech.archunit.core.domain.JavaEnumConstant; import com.tngtech.archunit.core.domain.JavaField; import com.tngtech.archunit.core.domain.JavaFieldAccess; import com.tngtech.archunit.core.domain.JavaMember; import com.tngtech.archunit.core.domain.JavaMethod; import com.tngtech.archunit.core.domain.JavaMethodCall; import com.tngtech.archunit.core.domain.JavaStaticInitializer; -import com.tngtech.archunit.core.domain.ThrowsDeclaration; +import com.tngtech.archunit.core.domain.JavaTypeVariable; import com.tngtech.archunit.core.importer.AccessRecord.FieldAccessRecord; +import com.tngtech.archunit.core.importer.DomainBuilders.JavaAnnotationBuilder.ValueBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaConstructorCallBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaFieldAccessBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodCallBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.TypeParametersBuilder; import com.tngtech.archunit.core.importer.resolvers.ClassResolver; -import static com.google.common.collect.Iterables.concat; +import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.completeAnnotations; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.completeClassHierarchy; +import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.completeEnclosingClass; +import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.completeMembers; +import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.completeTypeParameters; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createJavaClasses; import static com.tngtech.archunit.core.importer.DomainBuilders.BuilderWithBuildParameter.BuildFinisher.build; import static com.tngtech.archunit.core.importer.DomainBuilders.buildAnnotations; @@ -67,7 +70,6 @@ class ClassGraphCreator implements ImportContext { private final SetMultimap> processedConstructorCallRecords = HashMultimap.create(); private final Function> superClassStrategy; private final Function> interfaceStrategy; - private final MemberDependenciesByTarget memberDependenciesByTarget = new MemberDependenciesByTarget(); ClassGraphCreator(ClassFileImportRecord importRecord, ClassResolver classResolver) { this.importRecord = importRecord; @@ -96,40 +98,25 @@ public Set apply(JavaClass input) { JavaClasses complete() { ensureCallTargetsArePresent(); - ensureClassHierarchies(); - completeMembers(); - completeAnnotations(); - for (RawAccessRecord.ForField fieldAccessRecord : importRecord.getRawFieldAccessRecords()) { - tryProcess(fieldAccessRecord, AccessRecord.Factory.forFieldAccessRecord(), processedFieldAccessRecords); - } - for (RawAccessRecord methodCallRecord : importRecord.getRawMethodCallRecords()) { - tryProcess(methodCallRecord, AccessRecord.Factory.forMethodCallRecord(), processedMethodCallRecords); - } - for (RawAccessRecord constructorCallRecord : importRecord.getRawConstructorCallRecords()) { - tryProcess(constructorCallRecord, AccessRecord.Factory.forConstructorCallRecord(), processedConstructorCallRecords); - } - return createJavaClasses(classes.getDirectlyImported(), classes.getAll(), this); + ensureClassesOfInheritanceHierarchiesArePresent(); + ensureMetaAnnotationsArePresent(); + completeClasses(); + completeAccesses(); + return createJavaClasses(classes.getDirectlyImported(), classes.getAllWithOuterClassesSortedBeforeInnerClasses(), this); } private void ensureCallTargetsArePresent() { for (RawAccessRecord record : importRecord.getAccessRecords()) { - classes.ensurePresent(record.target.owner.getName()); - } - } - - private void ensureClassHierarchies() { - ensureClassesOfHierarchyInContext(); - for (JavaClass javaClass : classes.getAll().values()) { - completeClassHierarchy(javaClass, this); + classes.ensurePresent(record.target.owner.getFullyQualifiedClassName()); } } - private void ensureClassesOfHierarchyInContext() { - for (String superClassName : ImmutableSet.copyOf(importRecord.getSuperClassNamesBySubClass().values())) { + private void ensureClassesOfInheritanceHierarchiesArePresent() { + for (String superClassName : importRecord.getAllSuperClassNames()) { resolveInheritance(superClassName, superClassStrategy); } - for (String superInterfaceName : ImmutableSet.copyOf(importRecord.getInterfaceNamesBySubInterface().values())) { + for (String superInterfaceName : importRecord.getAllSuperInterfaceNames()) { resolveInheritance(superInterfaceName, interfaceStrategy); } } @@ -140,21 +127,52 @@ private void resolveInheritance(String currentTypeName, Function getAnnotationTypeNamesToResolveFor(JavaClass javaClass) { + return ImmutableSet.builder() + .addAll(importRecord.getAnnotationTypeNamesFor(javaClass)) + .addAll(importRecord.getMemberAnnotationTypeNamesFor(javaClass)) + .build(); + } + private , B extends RawAccessRecord> void tryProcess( B rawRecord, AccessRecord.Factory factory, @@ -165,7 +183,7 @@ private , B extends RawAccessRecord> void tryProcess( } @Override - public Set getFieldAccessesFor(JavaCodeUnit codeUnit) { + public Set createFieldAccessesFor(JavaCodeUnit codeUnit) { ImmutableSet.Builder result = ImmutableSet.builder(); for (FieldAccessRecord record : processedFieldAccessRecords.get(codeUnit)) { result.add(accessBuilderFrom(new JavaFieldAccessBuilder(), record) @@ -176,7 +194,7 @@ public Set getFieldAccessesFor(JavaCodeUnit codeUnit) { } @Override - public Set getMethodCallsFor(JavaCodeUnit codeUnit) { + public Set createMethodCallsFor(JavaCodeUnit codeUnit) { ImmutableSet.Builder result = ImmutableSet.builder(); for (AccessRecord record : processedMethodCallRecords.get(codeUnit)) { result.add(accessBuilderFrom(new JavaMethodCallBuilder(), record).build()); @@ -185,7 +203,7 @@ public Set getMethodCallsFor(JavaCodeUnit codeUnit) { } @Override - public Set getConstructorCallsFor(JavaCodeUnit codeUnit) { + public Set createConstructorCallsFor(JavaCodeUnit codeUnit) { ImmutableSet.Builder result = ImmutableSet.builder(); for (AccessRecord record : processedConstructorCallRecords.get(codeUnit)) { result.add(accessBuilderFrom(new JavaConstructorCallBuilder(), record).build()); @@ -193,46 +211,6 @@ public Set getConstructorCallsFor(JavaCodeUnit codeUnit) { return result.build(); } - @Override - public Set getFieldsOfType(JavaClass javaClass) { - return memberDependenciesByTarget.getFieldsOfType(javaClass); - } - - @Override - public Set getMethodsWithParameterOfType(JavaClass javaClass) { - return memberDependenciesByTarget.getMethodsWithParameterOfType(javaClass); - } - - @Override - public Set getMethodsWithReturnType(JavaClass javaClass) { - return memberDependenciesByTarget.getMethodsWithReturnType(javaClass); - } - - @Override - public Set> getMethodThrowsDeclarationsOfType(JavaClass javaClass) { - return memberDependenciesByTarget.getMethodThrowsDeclarationsOfType(javaClass); - } - - @Override - public Set getConstructorsWithParameterOfType(JavaClass javaClass) { - return memberDependenciesByTarget.getConstructorsWithParameterOfType(javaClass); - } - - @Override - public Set> getConstructorThrowsDeclarationsOfType(JavaClass javaClass) { - return memberDependenciesByTarget.getConstructorThrowsDeclarationsOfType(javaClass); - } - - @Override - public Set> getAnnotationsOfType(JavaClass javaClass) { - return memberDependenciesByTarget.getAnnotationsOfType(javaClass); - } - - @Override - public Set> getAnnotationsWithParameterOfType(JavaClass javaClass) { - return memberDependenciesByTarget.getAnnotationsWithParameterOfType(javaClass); - } - private > B accessBuilderFrom(B builder, AccessRecord record) { return builder @@ -258,40 +236,61 @@ public Set createInterfaces(JavaClass owner) { return result.build(); } + @Override + public List> createTypeParameters(JavaClass owner) { + TypeParametersBuilder typeParametersBuilder = importRecord.getTypeParameterBuildersFor(owner.getName()); + return typeParametersBuilder.build(owner, classes.byTypeName()); + } + @Override public Set createFields(JavaClass owner) { - Set fields = build(importRecord.getFieldBuildersFor(owner.getName()), owner, classes.byTypeName()); - memberDependenciesByTarget.registerFields(fields); - return fields; + return build(importRecord.getFieldBuildersFor(owner.getName()), owner, classes.byTypeName()); } @Override public Set createMethods(JavaClass owner) { - Set methods = build(importRecord.getMethodBuildersFor(owner.getName()), owner, classes.byTypeName()); - memberDependenciesByTarget.registerMethods(methods); - return methods; + Set methodBuilders = importRecord.getMethodBuildersFor(owner.getName()); + if (owner.isAnnotation()) { + for (DomainBuilders.JavaMethodBuilder methodBuilder : methodBuilders) { + methodBuilder.withAnnotationDefaultValue(new Function>() { + @Override + public Optional apply(JavaMethod method) { + Optional defaultValueBuilder = importRecord.getAnnotationDefaultValueBuilderFor(method); + return defaultValueBuilder.isPresent() ? defaultValueBuilder.get().build(method, ClassGraphCreator.this) : Optional.absent(); + } + }); + } + } + return build(methodBuilders, owner, classes.byTypeName()); } @Override public Set createConstructors(JavaClass owner) { - Set constructors = build(importRecord.getConstructorBuildersFor(owner.getName()), owner, classes.byTypeName()); - memberDependenciesByTarget.registerConstructors(constructors); - return constructors; + return build(importRecord.getConstructorBuildersFor(owner.getName()), owner, classes.byTypeName()); } @Override public Optional createStaticInitializer(JavaClass owner) { Optional builder = importRecord.getStaticInitializerBuilderFor(owner.getName()); - return builder.isPresent() ? - Optional.of(builder.get().build(owner, classes.byTypeName())) : - Optional.absent(); + if (!builder.isPresent()) { + return Optional.absent(); + } + JavaStaticInitializer staticInitializer = builder.get().build(owner, classes.byTypeName()); + return Optional.of(staticInitializer); } @Override public Map> createAnnotations(JavaClass owner) { - Map> annotations = buildAnnotations(owner, importRecord.getAnnotationsFor(owner.getName()), classes.byTypeName()); - memberDependenciesByTarget.registerAnnotations(annotations.values()); - return annotations; + return createAnnotations(owner, importRecord.getAnnotationsFor(owner)); + } + + @Override + public Map> createAnnotations(JavaMember owner) { + return createAnnotations(owner, importRecord.getAnnotationsFor(owner)); + } + + private Map> createAnnotations(OWNER owner, Set annotationBuilders) { + return buildAnnotations(owner, annotationBuilders, this); } @Override @@ -307,98 +306,13 @@ public JavaClass resolveClass(String fullyQualifiedClassName) { return classes.getOrResolve(fullyQualifiedClassName); } - private static class MemberDependenciesByTarget { - private final SetMultimap fieldTypeDependencies = HashMultimap.create(); - private final SetMultimap methodParameterTypeDependencies = HashMultimap.create(); - private final SetMultimap methodReturnTypeDependencies = HashMultimap.create(); - private final SetMultimap> methodsThrowsDeclarationDependencies = HashMultimap.create(); - private final SetMultimap constructorParameterTypeDependencies = HashMultimap.create(); - private final SetMultimap> constructorThrowsDeclarationDependencies = HashMultimap.create(); - private final SetMultimap> annotationTypeDependencies = HashMultimap.create(); - private final SetMultimap> annotationParameterTypeDependencies = HashMultimap.create(); - - void registerFields(Set fields) { - for (JavaField field : fields) { - fieldTypeDependencies.put(field.getRawType(), field); - } - } - - void registerMethods(Set methods) { - for (JavaMethod method : methods) { - for (JavaClass parameter : method.getRawParameterTypes()) { - methodParameterTypeDependencies.put(parameter, method); - } - methodReturnTypeDependencies.put(method.getRawReturnType(), method); - for (ThrowsDeclaration throwsDeclaration : method.getThrowsClause()) { - methodsThrowsDeclarationDependencies.put(throwsDeclaration.getRawType(), throwsDeclaration); - } - } - } - - void registerConstructors(Set constructors) { - for (JavaConstructor constructor : constructors) { - for (JavaClass parameter : constructor.getRawParameterTypes()) { - constructorParameterTypeDependencies.put(parameter, constructor); - } - for (ThrowsDeclaration throwsDeclaration : constructor.getThrowsClause()) { - constructorThrowsDeclarationDependencies.put(throwsDeclaration.getRawType(), throwsDeclaration); - } - } - } - - void registerAnnotations(Collection> annotations) { - for (final JavaAnnotation annotation : annotations) { - annotationTypeDependencies.put(annotation.getRawType(), annotation); - annotation.accept(new DefaultParameterVisitor() { - @Override - public void visitClass(String propertyName, JavaClass javaClass) { - annotationParameterTypeDependencies.put(javaClass, annotation); - } - - @Override - public void visitEnumConstant(String propertyName, JavaEnumConstant enumConstant) { - annotationParameterTypeDependencies.put(enumConstant.getDeclaringClass(), annotation); - } - - @Override - public void visitAnnotation(String propertyName, JavaAnnotation memberAnnotation) { - annotationParameterTypeDependencies.put(memberAnnotation.getRawType(), annotation); - memberAnnotation.accept(this); - } - }); + @Override + public Optional getMethodReturnType(String declaringClassName, String methodName) { + for (DomainBuilders.JavaMethodBuilder methodBuilder : importRecord.getMethodBuildersFor(declaringClassName)) { + if (methodBuilder.getName().equals(methodName) && methodBuilder.hasNoParameters()) { + return Optional.of(classes.getOrResolve(methodBuilder.getReturnTypeName())); } } - - Set getFieldsOfType(JavaClass javaClass) { - return fieldTypeDependencies.get(javaClass); - } - - Set getMethodsWithParameterOfType(JavaClass javaClass) { - return methodParameterTypeDependencies.get(javaClass); - } - - Set getMethodsWithReturnType(JavaClass javaClass) { - return methodReturnTypeDependencies.get(javaClass); - } - - Set> getMethodThrowsDeclarationsOfType(JavaClass javaClass) { - return methodsThrowsDeclarationDependencies.get(javaClass); - } - - Set getConstructorsWithParameterOfType(JavaClass javaClass) { - return constructorParameterTypeDependencies.get(javaClass); - } - - Set> getConstructorThrowsDeclarationsOfType(JavaClass javaClass) { - return constructorThrowsDeclarationDependencies.get(javaClass); - } - - Set> getAnnotationsOfType(JavaClass javaClass) { - return annotationTypeDependencies.get(javaClass); - } - - Set> getAnnotationsWithParameterOfType(JavaClass javaClass) { - return annotationParameterTypeDependencies.get(javaClass); - } + return Optional.absent(); } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java index f2340d416c..1903985aab 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java @@ -15,7 +15,8 @@ */ package com.tngtech.archunit.core.importer; -import java.net.URI; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; @@ -23,12 +24,13 @@ import java.util.Map; import java.util.Set; +import com.google.common.base.Joiner; import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.tngtech.archunit.Internal; +import com.tngtech.archunit.base.Function; import com.tngtech.archunit.base.HasDescription; import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.domain.AccessTarget; @@ -37,8 +39,11 @@ import com.tngtech.archunit.core.domain.AccessTarget.MethodCallTarget; import com.tngtech.archunit.core.domain.DomainObjectCreationContext; import com.tngtech.archunit.core.domain.Formatters; +import com.tngtech.archunit.core.domain.ImportContext; +import com.tngtech.archunit.core.domain.InstanceofCheck; import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.domain.JavaClassList; import com.tngtech.archunit.core.domain.JavaCodeUnit; import com.tngtech.archunit.core.domain.JavaConstructor; @@ -47,20 +52,26 @@ import com.tngtech.archunit.core.domain.JavaField; import com.tngtech.archunit.core.domain.JavaFieldAccess; import com.tngtech.archunit.core.domain.JavaFieldAccess.AccessType; -import com.tngtech.archunit.core.domain.JavaMember; import com.tngtech.archunit.core.domain.JavaMethod; import com.tngtech.archunit.core.domain.JavaMethodCall; import com.tngtech.archunit.core.domain.JavaModifier; +import com.tngtech.archunit.core.domain.JavaParameterizedType; import com.tngtech.archunit.core.domain.JavaStaticInitializer; import com.tngtech.archunit.core.domain.JavaType; +import com.tngtech.archunit.core.domain.JavaTypeVariable; +import com.tngtech.archunit.core.domain.JavaWildcardType; import com.tngtech.archunit.core.domain.Source; import com.tngtech.archunit.core.domain.ThrowsClause; -import com.tngtech.archunit.core.importer.DomainBuilders.JavaAnnotationBuilder.ValueBuilder; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Sets.union; +import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.completeTypeVariable; +import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createInstanceofCheck; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createJavaClassList; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createSource; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createThrowsClause; +import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createTypeVariable; +import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createWildcardType; import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME; @Internal @@ -68,10 +79,10 @@ public final class DomainBuilders { private DomainBuilders() { } - static Map> buildAnnotations(T owner, Set annotations, ClassesByTypeName importedClasses) { + static Map> buildAnnotations(T owner, Set annotations, ImportContext importContext) { ImmutableMap.Builder> result = ImmutableMap.builder(); for (JavaAnnotationBuilder annotationBuilder : annotations) { - JavaAnnotation javaAnnotation = annotationBuilder.build(owner, importedClasses); + JavaAnnotation javaAnnotation = annotationBuilder.build(owner, importContext); result.put(javaAnnotation.getRawType().getName(), javaAnnotation); } return result.build(); @@ -114,7 +125,6 @@ public abstract static class JavaMemberBuilder annotations; private Set modifiers; private JavaClass owner; private ClassesByTypeName importedClasses; @@ -133,11 +143,6 @@ SELF withDescriptor(String descriptor) { return self(); } - SELF withAnnotations(Set annotations) { - this.annotations = annotations; - return self(); - } - SELF withModifiers(Set modifiers) { this.modifiers = modifiers; return self(); @@ -162,15 +167,6 @@ public String getName() { return name; } - public Supplier>> getAnnotations(final JavaMember owner) { - return Suppliers.memoize(new Supplier>>() { - @Override - public Map> get() { - return buildAnnotations(owner, annotations, importedClasses); - } - }); - } - public String getDescriptor() { return descriptor; } @@ -197,18 +193,18 @@ public final OUTPUT build(JavaClass owner, ClassesByTypeName importedClasses) { @Internal public static final class JavaFieldBuilder extends JavaMemberBuilder { - private JavaType type; + private JavaClassDescriptor type; JavaFieldBuilder() { } - JavaFieldBuilder withType(JavaType type) { + JavaFieldBuilder withType(JavaClassDescriptor type) { this.type = type; return self(); } public JavaClass getType() { - return get(type.getName()); + return get(type.getFullyQualifiedClassName()); } @Override @@ -219,30 +215,44 @@ JavaField construct(JavaFieldBuilder builder, ClassesByTypeName importedClasses) @Internal public abstract static class JavaCodeUnitBuilder> extends JavaMemberBuilder { - private JavaType returnType; - private List parameters; - private List throwsDeclarations; + private JavaClassDescriptor returnType; + private List parameters; + private List throwsDeclarations; + private final List instanceOfChecks = new ArrayList<>(); private JavaCodeUnitBuilder() { } - SELF withReturnType(JavaType type) { + SELF withReturnType(JavaClassDescriptor type) { returnType = type; return self(); } - SELF withParameters(List parameters) { + SELF withParameters(List parameters) { this.parameters = parameters; return self(); } - SELF withThrowsClause(List throwsDeclarations) { + SELF withThrowsClause(List throwsDeclarations) { this.throwsDeclarations = throwsDeclarations; return self(); } + SELF addInstanceOfCheck(RawInstanceofCheck rawInstanceOfChecks) { + this.instanceOfChecks.add(rawInstanceOfChecks); + return self(); + } + + String getReturnTypeName() { + return returnType.getFullyQualifiedClassName(); + } + + boolean hasNoParameters() { + return parameters.isEmpty(); + } + public JavaClass getReturnType() { - return get(returnType.getName()); + return get(returnType.getFullyQualifiedClassName()); } public JavaClassList getParameters() { @@ -253,10 +263,18 @@ public ThrowsClause getThrowsClause( return createThrowsClause(codeUnit, asJavaClasses(this.throwsDeclarations)); } - private List asJavaClasses(List javaTypes) { + public Set getInstanceofChecks(JavaCodeUnit codeUnit) { + ImmutableSet.Builder result = ImmutableSet.builder(); + for (RawInstanceofCheck instanceOfCheck : this.instanceOfChecks) { + result.add(createInstanceofCheck(codeUnit, get(instanceOfCheck.getTarget().getFullyQualifiedClassName()), instanceOfCheck.getLineNumber())); + } + return result.build(); + } + + private List asJavaClasses(List descriptors) { ImmutableList.Builder result = ImmutableList.builder(); - for (JavaType javaType : javaTypes) { - result.add(get(javaType.getName())); + for (JavaClassDescriptor javaClassDescriptor : descriptors) { + result.add(get(javaClassDescriptor.getFullyQualifiedClassName())); } return result.build(); } @@ -264,44 +282,24 @@ private List asJavaClasses(List javaTypes) { @Internal public static final class JavaMethodBuilder extends JavaCodeUnitBuilder { - private Optional annotationDefaultValueBuilder = Optional.absent(); - private Supplier> annotationDefaultValue = Suppliers.ofInstance(Optional.absent()); + private static final Function> NO_ANNOTATION_DEFAULT_VALUE = new Function>() { + @Override + public Optional apply(JavaMethod input) { + return Optional.absent(); + } + }; + private Function> createAnnotationDefaultValue = NO_ANNOTATION_DEFAULT_VALUE; JavaMethodBuilder() { } - JavaMethodBuilder withAnnotationDefaultValue(ValueBuilder defaultValue) { - annotationDefaultValueBuilder = Optional.of(defaultValue); - return this; - } - - public Supplier> getAnnotationDefaultValue() { - return annotationDefaultValue; + void withAnnotationDefaultValue(Function> createAnnotationDefaultValue) { + this.createAnnotationDefaultValue = createAnnotationDefaultValue; } @Override JavaMethod construct(JavaMethodBuilder builder, final ClassesByTypeName importedClasses) { - if (annotationDefaultValueBuilder.isPresent()) { - annotationDefaultValue = Suppliers.memoize(new DefaultValueSupplier(getOwner(), annotationDefaultValueBuilder.get(), importedClasses)); - } - return DomainObjectCreationContext.createJavaMethod(builder); - } - - private static class DefaultValueSupplier implements Supplier> { - private final JavaClass owner; - private final ValueBuilder valueBuilder; - private final ClassesByTypeName importedClasses; - - DefaultValueSupplier(JavaClass owner, ValueBuilder valueBuilder, ClassesByTypeName importedClasses) { - this.owner = owner; - this.valueBuilder = valueBuilder; - this.importedClasses = importedClasses; - } - - @Override - public Optional get() { - return valueBuilder.build(owner, importedClasses); - } + return DomainObjectCreationContext.createJavaMethod(builder, createAnnotationDefaultValue); } } @@ -318,11 +316,12 @@ JavaConstructor construct(JavaConstructorBuilder builder, ClassesByTypeName impo @Internal public static final class JavaClassBuilder { - private Optional sourceURI = Optional.absent(); + private Optional sourceDescriptor = Optional.absent(); private Optional sourceFileName = Optional.absent(); - private JavaType javaType; + private JavaClassDescriptor descriptor; private boolean isInterface; private boolean isEnum; + private boolean isAnnotation; private boolean isAnonymousClass; private boolean isMemberClass; private Set modifiers = new HashSet<>(); @@ -330,8 +329,8 @@ public static final class JavaClassBuilder { JavaClassBuilder() { } - JavaClassBuilder withSourceUri(URI sourceUri) { - this.sourceURI = Optional.of(sourceUri); + JavaClassBuilder withSourceDescriptor(SourceDescriptor sourceDescriptor) { + this.sourceDescriptor = Optional.of(sourceDescriptor); return this; } @@ -340,8 +339,8 @@ JavaClassBuilder withSourceFileName(String sourceFileName) { return this; } - JavaClassBuilder withType(JavaType javaType) { - this.javaType = javaType; + JavaClassBuilder withDescriptor(JavaClassDescriptor descriptor) { + this.descriptor = descriptor; return this; } @@ -365,13 +364,18 @@ JavaClassBuilder withEnum(boolean isEnum) { return this; } + public JavaClassBuilder withAnnotation(boolean isAnnotation) { + this.isAnnotation = isAnnotation; + return this; + } + JavaClassBuilder withModifiers(Set modifiers) { this.modifiers = modifiers; return this; } JavaClassBuilder withSimpleName(String simpleName) { - this.javaType = javaType.withSimpleName(simpleName); + this.descriptor = descriptor.withSimpleClassName(simpleName); return this; } @@ -380,11 +384,13 @@ JavaClass build() { } public Optional getSource() { - return sourceURI.isPresent() ? Optional.of(createSource(sourceURI.get(), sourceFileName)) : Optional.absent(); + return sourceDescriptor.isPresent() + ? Optional.of(createSource(sourceDescriptor.get().getUri(), sourceFileName, sourceDescriptor.get().isMd5InClassSourcesEnabled())) + : Optional.absent(); } - public JavaType getJavaType() { - return javaType; + public JavaClassDescriptor getDescriptor() { + return descriptor; } public boolean isInterface() { @@ -395,6 +401,10 @@ public boolean isEnum() { return isEnum; } + public boolean isAnnotation() { + return isAnnotation; + } + public boolean isAnonymousClass() { return isAnonymousClass; } @@ -410,63 +420,58 @@ public Set getModifiers() { @Internal public static final class JavaAnnotationBuilder { - private JavaType type; + private JavaClassDescriptor type; private final Map values = new LinkedHashMap<>(); - private ClassesByTypeName importedClasses; + private ImportContext importContext; JavaAnnotationBuilder() { } - JavaAnnotationBuilder withType(JavaType type) { + JavaAnnotationBuilder withType(JavaClassDescriptor type) { this.type = type; return this; } - JavaType getJavaType() { + JavaClassDescriptor getTypeDescriptor() { return type; } + String getFullyQualifiedClassName() { + return type.getFullyQualifiedClassName(); + } + JavaAnnotationBuilder addProperty(String key, ValueBuilder valueBuilder) { values.put(key, valueBuilder); return this; } public JavaClass getType() { - return importedClasses.get(type.getName()); + return importContext.resolveClass(type.getFullyQualifiedClassName()); } public Map getValues(T owner) { ImmutableMap.Builder result = ImmutableMap.builder(); for (Map.Entry entry : values.entrySet()) { - Optional value = entry.getValue().build(owner, importedClasses); + Optional value = entry.getValue().build(owner, importContext); if (value.isPresent()) { result.put(entry.getKey(), value.get()); } } - addDefaultValues(result, importedClasses); return result.build(); } - private void addDefaultValues(ImmutableMap.Builder result, ClassesByTypeName importedClasses) { - for (JavaMethod method : importedClasses.get(type.getName()).getMethods()) { - if (!values.containsKey(method.getName()) && method.getDefaultValue().isPresent()) { - result.put(method.getName(), method.getDefaultValue().get()); - } - } - } - - public JavaAnnotation build(T owner, ClassesByTypeName importedClasses) { - this.importedClasses = importedClasses; + public JavaAnnotation build(T owner, ImportContext importContext) { + this.importContext = importContext; return DomainObjectCreationContext.createJavaAnnotation(owner, this); } abstract static class ValueBuilder { - abstract Optional build(T owner, ClassesByTypeName importedClasses); + abstract Optional build(T owner, ImportContext importContext); static ValueBuilder ofFinished(final Object value) { return new ValueBuilder() { @Override - Optional build(T owner, ClassesByTypeName importedClasses) { + Optional build(T owner, ImportContext unused) { return Optional.of(value); } }; @@ -475,8 +480,8 @@ Optional build(T owner, ClassesByTypeName imp static ValueBuilder from(final JavaAnnotationBuilder builder) { return new ValueBuilder() { @Override - Optional build(T owner, ClassesByTypeName importedClasses) { - return Optional.of(builder.build(owner, importedClasses)); + Optional build(T owner, ImportContext importContext) { + return Optional.of(builder.build(owner, importContext)); } }; } @@ -486,13 +491,12 @@ Optional build(T owner, ClassesByTypeName imp @Internal public static final class JavaStaticInitializerBuilder extends JavaCodeUnitBuilder { JavaStaticInitializerBuilder() { - withReturnType(JavaType.From.name(void.class.getName())); - withParameters(Collections.emptyList()); + withReturnType(JavaClassDescriptor.From.name(void.class.getName())); + withParameters(Collections.emptyList()); withName(JavaStaticInitializer.STATIC_INITIALIZER_NAME); withDescriptor("()V"); - withAnnotations(Collections.emptySet()); withModifiers(Collections.emptySet()); - withThrowsClause(Collections.emptyList()); + withThrowsClause(Collections.emptyList()); } @Override @@ -501,6 +505,158 @@ JavaStaticInitializer construct(JavaStaticInitializerBuilder builder, ClassesByT } } + interface JavaTypeCreationProcess { + JavaType finish(OWNER owner, Iterable> allTypeParametersInContext, ClassesByTypeName classes); + } + + @Internal + public static final class JavaTypeParameterBuilder { + private final String name; + private final List> upperBounds = new ArrayList<>(); + private OWNER owner; + private ClassesByTypeName importedClasses; + + JavaTypeParameterBuilder(String name) { + this.name = checkNotNull(name); + } + + void addBound(JavaTypeCreationProcess bound) { + upperBounds.add(bound); + } + + public JavaTypeVariable build(OWNER owner, ClassesByTypeName importedClasses) { + this.owner = owner; + this.importedClasses = importedClasses; + return createTypeVariable(name, owner, this.importedClasses.get(Object.class.getName())); + } + + String getName() { + return name; + } + + @SuppressWarnings("unchecked") // Iterable is covariant + public List getUpperBounds(Iterable> allGenericParametersInContext) { + return buildJavaTypes(upperBounds, owner, (Iterable>) allGenericParametersInContext, importedClasses); + } + } + + static class TypeParametersBuilder { + private final Collection> typeParameterBuilders; + + TypeParametersBuilder(Collection> typeParameterBuilders) { + this.typeParameterBuilders = typeParameterBuilders; + } + + public List> build(JavaClass owner, ClassesByTypeName classesByTypeName) { + if (typeParameterBuilders.isEmpty()) { + return Collections.emptyList(); + } + + Map, JavaTypeParameterBuilder> typeArgumentsToBuilders = new LinkedHashMap<>(); + for (JavaTypeParameterBuilder builder : typeParameterBuilders) { + typeArgumentsToBuilders.put(builder.build(owner, classesByTypeName), builder); + } + Set> allGenericParametersInContext = union(allTypeParametersInEnclosingClassesOf(owner), typeArgumentsToBuilders.keySet()); + for (Map.Entry, JavaTypeParameterBuilder> typeParameterToBuilder : typeArgumentsToBuilders.entrySet()) { + List upperBounds = typeParameterToBuilder.getValue().getUpperBounds(allGenericParametersInContext); + completeTypeVariable(typeParameterToBuilder.getKey(), upperBounds); + } + return ImmutableList.copyOf(typeArgumentsToBuilders.keySet()); + } + + private Set> allTypeParametersInEnclosingClassesOf(JavaClass javaClass) { + Set> result = new HashSet<>(); + while (javaClass.getEnclosingClass().isPresent()) { + result.addAll(javaClass.getEnclosingClass().get().getTypeParameters()); + javaClass = javaClass.getEnclosingClass().get(); + } + return result; + } + } + + interface JavaTypeBuilder { + JavaType build(OWNER owner, Iterable> allTypeParametersInContext, ClassesByTypeName importedClasses); + } + + @Internal + public static final class JavaWildcardTypeBuilder implements JavaTypeBuilder { + private final List> lowerBoundCreationProcesses = new ArrayList<>(); + private final List> upperBoundCreationProcesses = new ArrayList<>(); + private OWNER owner; + private Iterable> allTypeParametersInContext; + private ClassesByTypeName importedClasses; + + JavaWildcardTypeBuilder() { + } + + public JavaWildcardTypeBuilder addLowerBound(JavaTypeCreationProcess boundCreationProcess) { + lowerBoundCreationProcesses.add(boundCreationProcess); + return this; + } + + public JavaWildcardTypeBuilder addUpperBound(JavaTypeCreationProcess boundCreationProcess) { + upperBoundCreationProcesses.add(boundCreationProcess); + return this; + } + + @Override + public JavaWildcardType build(OWNER owner, Iterable> allTypeParametersInContext, ClassesByTypeName importedClasses) { + this.owner = owner; + this.allTypeParametersInContext = allTypeParametersInContext; + this.importedClasses = importedClasses; + return createWildcardType(this); + } + + public List getUpperBounds() { + return buildJavaTypes(upperBoundCreationProcesses, owner, allTypeParametersInContext, importedClasses); + } + + public List getLowerBounds() { + return buildJavaTypes(lowerBoundCreationProcesses, owner, allTypeParametersInContext, importedClasses); + } + + public JavaClass getUnboundErasureType(List upperBounds) { + return DomainBuilders.getUnboundErasureType(upperBounds, importedClasses); + } + } + + static class JavaParameterizedTypeBuilder implements JavaTypeBuilder { + private final JavaClassDescriptor type; + private final List> typeArgumentCreationProcesses = new ArrayList<>(); + + JavaParameterizedTypeBuilder(JavaClassDescriptor type) { + this.type = type; + } + + void addTypeArgument(JavaTypeCreationProcess typeCreationProcess) { + typeArgumentCreationProcesses.add(typeCreationProcess); + } + + @Override + public JavaParameterizedType build(OWNER owner, Iterable> allTypeParametersInContext, ClassesByTypeName classes) { + List typeArguments = buildJavaTypes(typeArgumentCreationProcesses, owner, allTypeParametersInContext, classes); + return new ImportedParameterizedType(classes.get(type.getFullyQualifiedClassName()), typeArguments); + } + + String getTypeName() { + return type.getFullyQualifiedClassName(); + } + } + + private static List buildJavaTypes(List> typeCreationProcesses, OWNER owner, Iterable> allGenericParametersInContext, ClassesByTypeName classes) { + ImmutableList.Builder result = ImmutableList.builder(); + for (JavaTypeCreationProcess typeCreationProcess : typeCreationProcesses) { + result.add(typeCreationProcess.finish(owner, allGenericParametersInContext, classes)); + } + return result.build(); + } + + private static JavaClass getUnboundErasureType(List upperBounds, ClassesByTypeName importedClasses) { + return upperBounds.size() > 0 + ? upperBounds.get(0).toErasure() + : importedClasses.get(Object.class.getName()); + } + @Internal interface BuilderWithBuildParameter { VALUE build(PARAMETER parameter, ClassesByTypeName importedClasses); @@ -746,4 +902,38 @@ MethodCallTarget build() { return DomainObjectCreationContext.createMethodCallTarget(this); } } + + private static class ImportedParameterizedType implements JavaParameterizedType { + private final JavaType type; + private final List typeArguments; + + ImportedParameterizedType(JavaType type, List typeArguments) { + this.type = type; + this.typeArguments = typeArguments; + } + + @Override + public String getName() { + return type.getName(); + } + + @Override + public JavaClass toErasure() { + return type.toErasure(); + } + + @Override + public List getActualTypeArguments() { + return typeArguments; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{" + type.getName() + formatTypeArguments() + '}'; + } + + private String formatTypeArguments() { + return typeArguments.isEmpty() ? "" : "<" + Joiner.on(", ").join(typeArguments) + ">"; + } + } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ImportedClasses.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ImportedClasses.java index fe0730723c..701cc140e0 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ImportedClasses.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ImportedClasses.java @@ -15,16 +15,18 @@ */ package com.tngtech.archunit.core.importer; +import java.util.Collection; import java.util.HashMap; import java.util.Map; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Sets; import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.domain.JavaModifier; -import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.importer.resolvers.ClassResolver; import static com.tngtech.archunit.core.domain.JavaModifier.ABSTRACT; @@ -36,11 +38,12 @@ class ImportedClasses { Sets.immutableEnumSet(PUBLIC, ABSTRACT, FINAL); private final ImmutableMap directlyImported; - private final Map additionalClasses = new HashMap<>(); + private final Map allClasses = new HashMap<>(); private final ClassResolver resolver; ImportedClasses(Map directlyImported, ClassResolver resolver) { this.directlyImported = ImmutableMap.copyOf(directlyImported); + allClasses.putAll(directlyImported); this.resolver = resolver; } @@ -49,29 +52,25 @@ Map getDirectlyImported() { } JavaClass getOrResolve(String typeName) { - ensurePresent(typeName); - return directlyImported.containsKey(typeName) ? - directlyImported.get(typeName) : - additionalClasses.get(typeName); - } - - void ensurePresent(String typeName) { - if (!contain(typeName)) { + JavaClass javaClass = allClasses.get(typeName); + if (javaClass == null) { Optional resolved = resolver.tryResolve(typeName); - JavaClass newClass = resolved.isPresent() ? resolved.get() : simpleClassOf(typeName); - additionalClasses.put(typeName, newClass); + javaClass = resolved.isPresent() ? resolved.get() : simpleClassOf(typeName); + allClasses.put(typeName, javaClass); } + return javaClass; } - private boolean contain(String name) { - return directlyImported.containsKey(name) || additionalClasses.containsKey(name); + boolean isPresent(String typeName) { + return allClasses.containsKey(typeName); } - Map getAll() { - return ImmutableMap.builder() - .putAll(directlyImported) - .putAll(additionalClasses) - .build(); + void ensurePresent(String typeName) { + getOrResolve(typeName); + } + + Collection getAllWithOuterClassesSortedBeforeInnerClasses() { + return ImmutableSortedMap.copyOf(allClasses).values(); } ClassesByTypeName byTypeName() { @@ -84,16 +83,15 @@ public JavaClass get(String typeName) { } private static JavaClass simpleClassOf(String typeName) { - JavaType type = JavaType.From.name(typeName); - DomainBuilders.JavaClassBuilder builder = new DomainBuilders.JavaClassBuilder().withType(type); - addModifiersIfPossible(builder, type); + JavaClassDescriptor descriptor = JavaClassDescriptor.From.name(typeName); + DomainBuilders.JavaClassBuilder builder = new DomainBuilders.JavaClassBuilder().withDescriptor(descriptor); + addModifiersIfPossible(builder, descriptor); return builder.build(); } - private static void addModifiersIfPossible(DomainBuilders.JavaClassBuilder builder, JavaType type) { - if (type.isPrimitive() || type.isArray()) { + private static void addModifiersIfPossible(DomainBuilders.JavaClassBuilder builder, JavaClassDescriptor descriptor) { + if (descriptor.isPrimitive() || descriptor.isArray()) { builder.withModifiers(PRIMITIVE_AND_ARRAY_TYPE_MODIFIERS); } } - } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaTypeImporter.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassDescriptorImporter.java similarity index 64% rename from archunit/src/main/java/com/tngtech/archunit/core/importer/JavaTypeImporter.java rename to archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassDescriptorImporter.java index 7fd076bd75..f4217a007e 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaTypeImporter.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassDescriptorImporter.java @@ -18,39 +18,43 @@ import java.util.List; import com.google.common.collect.ImmutableList; -import com.tngtech.archunit.core.domain.JavaType; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; import org.objectweb.asm.Type; -class JavaTypeImporter { +class JavaClassDescriptorImporter { /** * Takes an 'internal' ASM object type name, i.e. the class name but with slashes instead of periods, * i.e. java/lang/Object (note that this is not a descriptor like Ljava/lang/Object;) */ - static JavaType createFromAsmObjectTypeName(String objectTypeName) { + static JavaClassDescriptor createFromAsmObjectTypeName(String objectTypeName) { return importAsmType(Type.getObjectType(objectTypeName)); } - static JavaType importAsmType(Type type) { - return JavaType.From.name(type.getClassName()); + static JavaClassDescriptor importAsmType(Type type) { + return JavaClassDescriptor.From.name(type.getClassName()); + } + + static boolean isAsmType(Object value) { + return value instanceof Type; } static Object importAsmTypeIfPossible(Object value) { - return value instanceof Type ? importAsmType((Type) value) : value; + return isAsmType(value) ? importAsmType((Type) value) : value; } - static JavaType importAsmType(String typeDescriptor) { + static JavaClassDescriptor importAsmType(String typeDescriptor) { return importAsmType(Type.getType(typeDescriptor)); } - static List importAsmMethodArgumentTypes(String methodDescriptor) { - ImmutableList.Builder result = ImmutableList.builder(); + static List importAsmMethodArgumentTypes(String methodDescriptor) { + ImmutableList.Builder result = ImmutableList.builder(); for (Type type : Type.getArgumentTypes(methodDescriptor)) { result.add(importAsmType(type)); } return result.build(); } - static JavaType importAsmMethodReturnType(String methodDescriptor) { + static JavaClassDescriptor importAsmMethodReturnType(String methodDescriptor) { return importAsmType(Type.getReturnType(methodDescriptor)); } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java index f02f30a956..a10828781a 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java @@ -16,7 +16,6 @@ package com.tngtech.archunit.core.importer; import java.lang.reflect.Array; -import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -37,12 +36,12 @@ import com.tngtech.archunit.base.HasDescription; import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.MayResolveTypesViaReflection; +import com.tngtech.archunit.core.domain.ImportContext; import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.domain.JavaEnumConstant; -import com.tngtech.archunit.core.domain.JavaMethod; import com.tngtech.archunit.core.domain.JavaModifier; -import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.importer.DomainBuilders.JavaAnnotationBuilder.ValueBuilder; import com.tngtech.archunit.core.importer.RawAccessRecord.CodeUnit; import org.objectweb.asm.AnnotationVisitor; @@ -51,7 +50,6 @@ import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,6 +59,7 @@ import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME; import static com.tngtech.archunit.core.domain.JavaStaticInitializer.STATIC_INITIALIZER_NAME; import static com.tngtech.archunit.core.importer.ClassFileProcessor.ASM_API_VERSION; +import static com.tngtech.archunit.core.importer.RawInstanceofCheck.from; class JavaClassProcessor extends ClassVisitor { private static final Logger LOG = LoggerFactory.getLogger(JavaClassProcessor.class); @@ -69,18 +68,18 @@ class JavaClassProcessor extends ClassVisitor { private DomainBuilders.JavaClassBuilder javaClassBuilder; private final Set annotations = new HashSet<>(); - private final URI sourceURI; + private final SourceDescriptor sourceDescriptor; private final DeclarationHandler declarationHandler; private final AccessHandler accessHandler; private String className; - JavaClassProcessor(URI sourceURI, DeclarationHandler declarationHandler) { - this(sourceURI, declarationHandler, NO_OP); + JavaClassProcessor(SourceDescriptor sourceDescriptor, DeclarationHandler declarationHandler) { + this(sourceDescriptor, declarationHandler, NO_OP); } - JavaClassProcessor(URI sourceURI, DeclarationHandler declarationHandler, AccessHandler accessHandler) { + JavaClassProcessor(SourceDescriptor sourceDescriptor, DeclarationHandler declarationHandler, AccessHandler accessHandler) { super(ASM_API_VERSION); - this.sourceURI = sourceURI; + this.sourceDescriptor = sourceDescriptor; this.declarationHandler = declarationHandler; this.accessHandler = accessHandler; } @@ -89,18 +88,11 @@ Optional createJavaClass() { return javaClassBuilder != null ? Optional.of(javaClassBuilder.build()) : Optional.absent(); } - @Override - public void visitSource(String source, String debug) { - if (!importAborted() && source != null) { - javaClassBuilder.withSourceFileName(source); - } - } - @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { LOG.debug("Analyzing class '{}'", name); - JavaType javaType = JavaTypeImporter.createFromAsmObjectTypeName(name); - if (alreadyImported(javaType)) { + JavaClassDescriptor descriptor = JavaClassDescriptorImporter.createFromAsmObjectTypeName(name); + if (alreadyImported(descriptor)) { return; } @@ -108,22 +100,25 @@ public void visit(int version, int access, String name, String signature, String LOG.trace("Found interfaces {} on class '{}'", interfaceNames, name); boolean opCodeForInterfaceIsPresent = (access & Opcodes.ACC_INTERFACE) != 0; boolean opCodeForEnumIsPresent = (access & Opcodes.ACC_ENUM) != 0; + boolean opCodeForAnnotationIsPresent = (access & Opcodes.ACC_ANNOTATION) != 0; Optional superClassName = getSuperClassName(superName, opCodeForInterfaceIsPresent); LOG.trace("Found superclass {} on class '{}'", superClassName.orNull(), name); javaClassBuilder = new DomainBuilders.JavaClassBuilder() - .withSourceUri(sourceURI) - .withType(javaType) + .withSourceDescriptor(sourceDescriptor) + .withDescriptor(descriptor) .withInterface(opCodeForInterfaceIsPresent) .withEnum(opCodeForEnumIsPresent) + .withAnnotation(opCodeForAnnotationIsPresent) .withModifiers(JavaModifier.getModifiersForClass(access)); - className = javaType.getName(); + className = descriptor.getFullyQualifiedClassName(); declarationHandler.onNewClass(className, superClassName, interfaceNames); + JavaGenericTypeImporter.parseAsmTypeSignature(signature, declarationHandler); } - private boolean alreadyImported(JavaType javaType) { - return !declarationHandler.isNew(javaType.getName()); + private boolean alreadyImported(JavaClassDescriptor descriptor) { + return !declarationHandler.isNew(descriptor.getFullyQualifiedClassName()); } // NOTE: For some reason ASM claims superName == java/lang/Object for Interfaces??? @@ -138,6 +133,13 @@ private boolean importAborted() { return javaClassBuilder == null; } + @Override + public void visitSource(String source, String debug) { + if (!importAborted() && source != null) { + javaClassBuilder.withSourceFileName(source); + } + } + @Override public void visitInnerClass(String name, String outerName, String innerName, int access) { if (importAborted()) { @@ -204,11 +206,11 @@ public FieldVisitor visitField(int access, String name, String desc, String sign DomainBuilders.JavaFieldBuilder fieldBuilder = new DomainBuilders.JavaFieldBuilder() .withName(name) - .withType(JavaTypeImporter.importAsmType(desc)) + .withType(JavaClassDescriptorImporter.importAsmType(desc)) .withModifiers(JavaModifier.getModifiersForField(access)) .withDescriptor(desc); declarationHandler.onDeclaredField(fieldBuilder); - return new FieldProcessor(fieldBuilder); + return new FieldProcessor(fieldBuilder, declarationHandler); } @Override @@ -218,7 +220,7 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si } LOG.trace("Analyzing method {}.{}:{}", className, name, desc); - List parameters = JavaTypeImporter.importAsmMethodArgumentTypes(desc); + List parameters = JavaClassDescriptorImporter.importAsmMethodArgumentTypes(desc); accessHandler.setContext(new CodeUnit(name, namesOf(parameters), className)); DomainBuilders.JavaCodeUnitBuilder codeUnitBuilder = addCodeUnitBuilder(name); @@ -226,18 +228,18 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si .withName(name) .withModifiers(JavaModifier.getModifiersForMethod(access)) .withParameters(parameters) - .withReturnType(JavaTypeImporter.importAsmMethodReturnType(desc)) + .withReturnType(JavaClassDescriptorImporter.importAsmMethodReturnType(desc)) .withDescriptor(desc) .withThrowsClause(typesFrom(exceptions)); - return new MethodProcessor(className, accessHandler, codeUnitBuilder); + return new MethodProcessor(className, accessHandler, codeUnitBuilder, declarationHandler); } - private List typesFrom(String[] throwsDeclarations) { - List result = new ArrayList<>(); + private List typesFrom(String[] throwsDeclarations) { + List result = new ArrayList<>(); if (throwsDeclarations != null) { for (String throwsDeclaration : throwsDeclarations) { - result.add(JavaTypeImporter.createFromAsmObjectTypeName(throwsDeclaration)); + result.add(JavaClassDescriptorImporter.createFromAsmObjectTypeName(throwsDeclaration)); } } return result; @@ -274,14 +276,14 @@ public void visitEnd() { return; } - declarationHandler.onDeclaredAnnotations(annotations); + declarationHandler.onDeclaredClassAnnotations(annotations); LOG.trace("Done analyzing {}", className); } - private static List namesOf(Iterable types) { + private static List namesOf(Iterable descriptors) { ImmutableList.Builder result = ImmutableList.builder(); - for (JavaType type : types) { - result.add(type.getName()); + for (JavaClassDescriptor descriptor : descriptors) { + result.add(descriptor.getFullyQualifiedClassName()); } return result.build(); } @@ -290,14 +292,16 @@ private static class MethodProcessor extends MethodVisitor { private final String declaringClassName; private final AccessHandler accessHandler; private final DomainBuilders.JavaCodeUnitBuilder codeUnitBuilder; + private final DeclarationHandler declarationHandler; private final Set annotations = new HashSet<>(); private int actualLineNumber; - MethodProcessor(String declaringClassName, AccessHandler accessHandler, DomainBuilders.JavaCodeUnitBuilder codeUnitBuilder) { + MethodProcessor(String declaringClassName, AccessHandler accessHandler, DomainBuilders.JavaCodeUnitBuilder codeUnitBuilder, DeclarationHandler declarationHandler) { super(ASM_API_VERSION); this.declaringClassName = declaringClassName; this.accessHandler = accessHandler; this.codeUnitBuilder = codeUnitBuilder; + this.declarationHandler = declarationHandler; } @Override @@ -324,6 +328,13 @@ public void visitMethodInsn(int opcode, String owner, String name, String desc, accessHandler.handleMethodInstruction(owner, name, desc); } + @Override + public void visitTypeInsn(int opcode, String type) { + if (opcode == Opcodes.INSTANCEOF) { + codeUnitBuilder.addInstanceOfCheck(from(JavaClassDescriptorImporter.createFromAsmObjectTypeName(type), actualLineNumber)); + } + } + @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { return new AnnotationProcessor(addAnnotationTo(annotations), annotationBuilderFor(desc)); @@ -331,21 +342,23 @@ public AnnotationVisitor visitAnnotation(String desc, boolean visible) { @Override public AnnotationVisitor visitAnnotationDefault() { - return new AnnotationDefaultProcessor(declaringClassName, codeUnitBuilder); + return new AnnotationDefaultProcessor(declaringClassName, codeUnitBuilder, declarationHandler); } @Override public void visitEnd() { - codeUnitBuilder.withAnnotations(annotations); + declarationHandler.onDeclaredMemberAnnotations(codeUnitBuilder.getName(), codeUnitBuilder.getDescriptor(), annotations); } private static class AnnotationDefaultProcessor extends AnnotationVisitor { private final String annotationTypeName; + private final DeclarationHandler declarationHandler; private final DomainBuilders.JavaMethodBuilder methodBuilder; - AnnotationDefaultProcessor(String annotationTypeName, DomainBuilders.JavaCodeUnitBuilder codeUnitBuilder) { + AnnotationDefaultProcessor(String annotationTypeName, DomainBuilders.JavaCodeUnitBuilder codeUnitBuilder, DeclarationHandler declarationHandler) { super(ClassFileProcessor.ASM_API_VERSION); this.annotationTypeName = annotationTypeName; + this.declarationHandler = declarationHandler; checkArgument(codeUnitBuilder instanceof DomainBuilders.JavaMethodBuilder, "tried to import annotation defaults for code unit '%s' that is not a method " + "(as any annotation.property() is assumed to be), " + @@ -356,34 +369,35 @@ private static class AnnotationDefaultProcessor extends AnnotationVisitor { @Override public void visit(String name, Object value) { - methodBuilder.withAnnotationDefaultValue(AnnotationTypeConversion.convert(value)); + declarationHandler.onDeclaredAnnotationDefaultValue(methodBuilder.getName(), methodBuilder.getDescriptor(), AnnotationTypeConversion.convert(value)); } @Override public void visitEnum(String name, String desc, String value) { - methodBuilder.withAnnotationDefaultValue(javaEnumBuilder(desc, value)); + declarationHandler.onDeclaredAnnotationDefaultValue(methodBuilder.getName(), methodBuilder.getDescriptor(), javaEnumBuilder(desc, value)); } @Override public AnnotationVisitor visitAnnotation(String name, String desc) { - return new AnnotationProcessor(new SetAsAnnotationDefault(annotationTypeName, methodBuilder), annotationBuilderFor(desc)); + return new AnnotationProcessor(new SetAsAnnotationDefault(annotationTypeName, methodBuilder, declarationHandler), annotationBuilderFor(desc)); } @Override public AnnotationVisitor visitArray(String name) { - return new AnnotationArrayProcessor(new SetAsAnnotationDefault(annotationTypeName, methodBuilder)); + return new AnnotationArrayProcessor(new SetAsAnnotationDefault(annotationTypeName, methodBuilder, declarationHandler)); } - } } private static class SetAsAnnotationDefault implements TakesAnnotationBuilder, AnnotationArrayContext { private final String annotationTypeName; private final DomainBuilders.JavaMethodBuilder methodBuilder; + private final DeclarationHandler declarationHandler; - private SetAsAnnotationDefault(String annotationTypeName, DomainBuilders.JavaMethodBuilder methodBuilder) { + private SetAsAnnotationDefault(String annotationTypeName, DomainBuilders.JavaMethodBuilder methodBuilder, DeclarationHandler declarationHandler) { this.annotationTypeName = annotationTypeName; this.methodBuilder = methodBuilder; + this.declarationHandler = declarationHandler; } @Override @@ -403,7 +417,7 @@ public String getDeclaringAnnotationMemberName() { @Override public void setArrayResult(ValueBuilder valueBuilder) { - methodBuilder.withAnnotationDefaultValue(valueBuilder); + declarationHandler.onDeclaredAnnotationDefaultValue(methodBuilder.getName(), methodBuilder.getDescriptor(), valueBuilder); } } @@ -412,15 +426,21 @@ interface DeclarationHandler { void onNewClass(String className, Optional superClassName, Set interfaceNames); + void onDeclaredTypeParameters(DomainBuilders.TypeParametersBuilder typeParametersBuilder); + void onDeclaredField(DomainBuilders.JavaFieldBuilder fieldBuilder); - void onDeclaredConstructor(DomainBuilders.JavaConstructorBuilder builder); + void onDeclaredConstructor(DomainBuilders.JavaConstructorBuilder constructorBuilder); + + void onDeclaredMethod(DomainBuilders.JavaMethodBuilder methodBuilder); + + void onDeclaredStaticInitializer(DomainBuilders.JavaStaticInitializerBuilder staticInitializerBuilder); - void onDeclaredMethod(DomainBuilders.JavaMethodBuilder builder); + void onDeclaredClassAnnotations(Set annotationBuilders); - void onDeclaredStaticInitializer(DomainBuilders.JavaStaticInitializerBuilder builder); + void onDeclaredMemberAnnotations(String memberName, String descriptor, Set annotations); - void onDeclaredAnnotations(Set annotations); + void onDeclaredAnnotationDefaultValue(String methodName, String methodDescriptor, ValueBuilder valueBuilder); void registerEnclosingClass(String ownerName, String enclosingClassName); } @@ -456,12 +476,14 @@ public void handleMethodInstruction(String owner, String name, String desc) { private static class FieldProcessor extends FieldVisitor { private final DomainBuilders.JavaFieldBuilder fieldBuilder; + private final DeclarationHandler declarationHandler; private final Set annotations = new HashSet<>(); - private FieldProcessor(DomainBuilders.JavaFieldBuilder fieldBuilder) { + private FieldProcessor(DomainBuilders.JavaFieldBuilder fieldBuilder, DeclarationHandler declarationHandler) { super(ASM_API_VERSION); this.fieldBuilder = fieldBuilder; + this.declarationHandler = declarationHandler; } @Override @@ -471,12 +493,12 @@ public AnnotationVisitor visitAnnotation(String desc, boolean visible) { @Override public void visitEnd() { - fieldBuilder.withAnnotations(annotations); + declarationHandler.onDeclaredMemberAnnotations(fieldBuilder.getName(), fieldBuilder.getDescriptor(), annotations); } } private static DomainBuilders.JavaAnnotationBuilder annotationBuilderFor(String desc) { - return new DomainBuilders.JavaAnnotationBuilder().withType(JavaTypeImporter.importAsmType(desc)); + return new DomainBuilders.JavaAnnotationBuilder().withType(JavaClassDescriptorImporter.importAsmType(desc)); } private static class AnnotationProcessor extends AnnotationVisitor { @@ -509,7 +531,7 @@ public AnnotationVisitor visitArray(final String name) { return new AnnotationArrayProcessor(new AnnotationArrayContext() { @Override public String getDeclaringAnnotationTypeName() { - return annotationBuilder.getJavaType().getName(); + return annotationBuilder.getFullyQualifiedClassName(); } @Override @@ -564,7 +586,7 @@ private AnnotationArrayProcessor(AnnotationArrayContext annotationArrayContext) @Override public void visit(String name, Object value) { - if (value instanceof Type) { + if (JavaClassDescriptorImporter.isAsmType(value)) { setDerivedComponentType(JavaClass.class); } else { setDerivedComponentType(value.getClass()); @@ -591,7 +613,7 @@ public void visitEnum(String name, final String desc, final String value) { // NOTE: If the declared annotation is not imported itself, we can still use this heuristic, // to determine additional information about the respective array. - // (It the annotation is imported itself, we could easily determine this from the respective + // (If the annotation is imported itself, we could easily determine this from the respective // JavaClass methods) private void setDerivedComponentType(Class type) { checkState(derivedComponentType == null || derivedComponentType.equals(type), @@ -607,13 +629,13 @@ public void visitEnd() { private class ArrayValueBuilder extends ValueBuilder { @Override - public Optional build(T owner, ClassesByTypeName importedClasses) { - Optional> componentType = determineComponentType(importedClasses); + public Optional build(T owner, ImportContext importContext) { + Optional> componentType = determineComponentType(importContext); if (!componentType.isPresent()) { return Optional.absent(); } - return Optional.of(toArray(componentType.get(), buildValues(owner, importedClasses))); + return Optional.of(toArray(componentType.get(), buildValues(owner, importContext))); } @SuppressWarnings({"unchecked", "rawtypes"}) // NOTE: We assume the component type matches the list @@ -638,45 +660,45 @@ private Object toArray(Class componentType, List values) { return values.toArray((Object[]) Array.newInstance(componentType, values.size())); } - private List buildValues(T owner, ClassesByTypeName importedClasses) { + private List buildValues(T owner, ImportContext importContext) { List result = new ArrayList<>(); for (ValueBuilder value : values) { - result.addAll(value.build(owner, importedClasses).asSet()); + result.addAll(value.build(owner, importContext).asSet()); } return result; } - private Optional> determineComponentType(ClassesByTypeName importedClasses) { + private Optional> determineComponentType(ImportContext importContext) { if (derivedComponentType != null) { return Optional.>of(derivedComponentType); } - JavaClass annotationType = importedClasses.get(annotationArrayContext.getDeclaringAnnotationTypeName()); - Optional method = annotationType - .tryGetMethod(annotationArrayContext.getDeclaringAnnotationMemberName()); + Optional returnType = importContext.getMethodReturnType( + annotationArrayContext.getDeclaringAnnotationTypeName(), + annotationArrayContext.getDeclaringAnnotationMemberName()); - return method.isPresent() ? - determineComponentTypeFromReturnValue(method.get()) : + return returnType.isPresent() ? + determineComponentTypeFromReturnValue(returnType.get()) : Optional.>absent(); } - private Optional> determineComponentTypeFromReturnValue(JavaMethod method) { - if (method.getRawReturnType().isEquivalentTo(Class[].class)) { + private Optional> determineComponentTypeFromReturnValue(JavaClass returnType) { + if (returnType.isEquivalentTo(Class[].class)) { return Optional.>of(JavaClass.class); } - return resolveComponentTypeFrom(method.getRawReturnType().getName()); + return resolveComponentTypeFrom(returnType.getName()); } @MayResolveTypesViaReflection(reason = "Resolving primitives does not really use reflection") private Optional> resolveComponentTypeFrom(String arrayTypeName) { - JavaType type = JavaType.From.name(arrayTypeName); - JavaType componentType = getComponentType(type); + JavaClassDescriptor descriptor = JavaClassDescriptor.From.name(arrayTypeName); + JavaClassDescriptor componentType = getComponentType(descriptor); if (componentType.isPrimitive()) { return Optional.>of(componentType.resolveClass()); } - if (String.class.getName().equals(componentType.getName())) { + if (String.class.getName().equals(componentType.getFullyQualifiedClassName())) { return Optional.>of(String.class); } @@ -688,10 +710,10 @@ private Optional> resolveComponentTypeFrom(String arrayTypeName) { return Optional.>of(Object.class); } - private JavaType getComponentType(JavaType type) { - Optional result = type.tryGetComponentType(); + private JavaClassDescriptor getComponentType(JavaClassDescriptor descriptor) { + Optional result = descriptor.tryGetComponentType(); checkState(result.isPresent(), "Couldn't determine component type of array return type %s, " + - "this is most likely a bug", type.getName()); + "this is most likely a bug", descriptor.getFullyQualifiedClassName()); return result.get(); } @@ -709,10 +731,10 @@ private interface AnnotationArrayContext { private static ValueBuilder javaEnumBuilder(final String desc, final String value) { return new ValueBuilder() { @Override - public Optional build(T owner, ClassesByTypeName importedClasses) { + public Optional build(T owner, ImportContext importContext) { return Optional.of( new DomainBuilders.JavaEnumConstantBuilder() - .withDeclaringClass(importedClasses.get(JavaTypeImporter.importAsmType(desc).getName())) + .withDeclaringClass(importContext.resolveClass(JavaClassDescriptorImporter.importAsmType(desc).getFullyQualifiedClassName())) .withName(value) .build()); } @@ -721,12 +743,12 @@ public Optional build(T owner, ClassesByTypeN private static class AnnotationTypeConversion { static ValueBuilder convert(Object input) { - final Object value = JavaTypeImporter.importAsmTypeIfPossible(input); - if (value instanceof JavaType) { + final Object value = JavaClassDescriptorImporter.importAsmTypeIfPossible(input); + if (value instanceof JavaClassDescriptor) { return new ValueBuilder() { @Override - public Optional build(T owner, ClassesByTypeName importedClasses) { - return Optional.of(importedClasses.get(((JavaType) value).getName())); + public Optional build(T owner, ImportContext importContext) { + return Optional.of(importContext.resolveClass(((JavaClassDescriptor) value).getFullyQualifiedClassName())); } }; } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java new file mode 100644 index 0000000000..6c89e6fa4b --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java @@ -0,0 +1,318 @@ +/* + * Copyright 2014-2020 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.core.importer; + +import java.util.ArrayList; +import java.util.List; + +import com.google.common.base.Function; +import com.google.common.base.Functions; +import com.tngtech.archunit.base.HasDescription; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; +import com.tngtech.archunit.core.domain.JavaType; +import com.tngtech.archunit.core.domain.JavaTypeVariable; +import com.tngtech.archunit.core.importer.DomainBuilders.JavaParameterizedTypeBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeCreationProcess; +import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeParameterBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.JavaWildcardTypeBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.TypeParametersBuilder; +import com.tngtech.archunit.core.importer.JavaClassProcessor.DeclarationHandler; +import org.objectweb.asm.signature.SignatureReader; +import org.objectweb.asm.signature.SignatureVisitor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static com.google.common.base.Functions.compose; +import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createGenericArrayType; +import static com.tngtech.archunit.core.importer.ClassFileProcessor.ASM_API_VERSION; + +class JavaGenericTypeImporter { + private static final Logger log = LoggerFactory.getLogger(JavaGenericTypeImporter.class); + + static void parseAsmTypeSignature(String signature, DeclarationHandler declarationHandler) { + if (signature == null) { + return; + } + + log.trace("Analyzing signature: {}", signature); + + JavaTypeVariableProcessor typeVariableProcessor = new JavaTypeVariableProcessor(); + new SignatureReader(signature).accept(typeVariableProcessor); + declarationHandler.onDeclaredTypeParameters(new TypeParametersBuilder(typeVariableProcessor.typeParameterBuilders)); + } + + private static class JavaTypeVariableProcessor extends SignatureVisitor { + private static final BoundProcessor boundProcessor = new BoundProcessor(); + + private final List> typeParameterBuilders = new ArrayList<>(); + + JavaTypeVariableProcessor() { + super(ASM_API_VERSION); + } + + @Override + public void visitFormalTypeParameter(String name) { + log.trace("Encountered type parameter {}", name); + JavaTypeParameterBuilder type = new JavaTypeParameterBuilder<>(name); + boundProcessor.setCurrentType(type); + typeParameterBuilders.add(type); + } + + @Override + public SignatureVisitor visitClassBound() { + return boundProcessor; + } + + @Override + public SignatureVisitor visitInterfaceBound() { + return boundProcessor; + } + + private static class BoundProcessor extends SignatureVisitor { + private JavaTypeParameterBuilder currentType; + private JavaParameterizedTypeBuilder currentBound; + + BoundProcessor() { + super(ASM_API_VERSION); + } + + void setCurrentType(JavaTypeParameterBuilder type) { + this.currentType = type; + } + + @Override + public void visitClassType(String internalObjectName) { + JavaClassDescriptor type = JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName); + log.trace("Encountered upper bound for {}: Class type {}", currentType.getName(), type.getFullyQualifiedClassName()); + this.currentBound = new JavaParameterizedTypeBuilder<>(type); + currentType.addBound(new NewJavaTypeCreationProcess<>(this.currentBound)); + } + + @Override + public void visitTypeArgument() { + log.trace("Encountered wildcard for {}", currentBound.getTypeName()); + currentBound.addTypeArgument(new NewJavaTypeCreationProcess<>(new JavaWildcardTypeBuilder())); + } + + @Override + public void visitTypeVariable(String name) { + log.trace("Encountered upper bound for {}: Type variable {}", currentType.getName(), name); + currentType.addBound(new ReferenceCreationProcess(name, ReferenceCreationProcess.JavaTypeVariableFinisher.IDENTITY)); + } + + @Override + public SignatureVisitor visitTypeArgument(char wildcard) { + return TypeArgumentProcessor.create(wildcard, currentBound, Functions.identity(), ReferenceCreationProcess.JavaTypeVariableFinisher.IDENTITY); + } + } + } + + private static class NewJavaTypeCreationProcess implements JavaTypeCreationProcess { + private final JavaTypeBuilder builder; + + NewJavaTypeCreationProcess(JavaTypeBuilder builder) { + this.builder = builder; + } + + @Override + public JavaType finish(OWNER owner, Iterable> allTypeParametersInContext, ClassesByTypeName classes) { + return builder.build(owner, allTypeParametersInContext, classes); + } + } + + private static class ReferenceCreationProcess implements JavaTypeCreationProcess { + private final String typeVariableName; + private final JavaTypeVariableFinisher finisher; + + ReferenceCreationProcess(String typeVariableName, JavaTypeVariableFinisher finisher) { + this.typeVariableName = typeVariableName; + this.finisher = finisher; + } + + @Override + public JavaType finish(OWNER owner, Iterable> allTypeParametersInContext, ClassesByTypeName classes) { + return finisher.finish(createTypeVariable(owner, allTypeParametersInContext, classes), classes); + } + + private JavaType createTypeVariable(OWNER owner, Iterable> allTypeParametersInContext, ClassesByTypeName classes) { + for (JavaTypeVariable existingTypeVariable : allTypeParametersInContext) { + if (existingTypeVariable.getName().equals(typeVariableName)) { + return existingTypeVariable; + } + } + // type variables can be missing from the import context -> create a simple unbound type variable since we have no more information + return new JavaTypeParameterBuilder<>(typeVariableName).build(owner, classes); + } + + abstract static class JavaTypeVariableFinisher { + abstract JavaType finish(JavaType input, ClassesByTypeName classes); + + abstract String getFinishedName(String name); + + JavaTypeVariableFinisher after(final JavaTypeVariableFinisher other) { + return new JavaTypeVariableFinisher() { + @Override + JavaType finish(JavaType input, ClassesByTypeName classes) { + return JavaTypeVariableFinisher.this.finish(other.finish(input, classes), classes); + } + + @Override + String getFinishedName(String name) { + return JavaTypeVariableFinisher.this.getFinishedName(other.getFinishedName(name)); + } + }; + } + + static JavaTypeVariableFinisher IDENTITY = new JavaTypeVariableFinisher() { + @Override + JavaType finish(JavaType input, ClassesByTypeName classes) { + return input; + } + + @Override + String getFinishedName(String name) { + return name; + } + }; + } + } + + private static class TypeArgumentProcessor extends SignatureVisitor { + private static final Function TO_ARRAY_TYPE = new Function() { + @Override + @SuppressWarnings("ConstantConditions") // we never return null by convention + public JavaClassDescriptor apply(JavaClassDescriptor input) { + return input.toArrayDescriptor(); + } + }; + private static final ReferenceCreationProcess.JavaTypeVariableFinisher GENERIC_ARRAY_CREATOR = new ReferenceCreationProcess.JavaTypeVariableFinisher() { + @Override + public JavaType finish(JavaType componentType, ClassesByTypeName classes) { + JavaClassDescriptor erasureType = JavaClassDescriptor.From.javaClass(componentType.toErasure()).toArrayDescriptor(); + JavaClass erasure = classes.get(erasureType.getFullyQualifiedClassName()); + return createGenericArrayType(componentType, erasure); + } + + @Override + String getFinishedName(String name) { + return name + "[]"; + } + }; + + private final TypeArgumentType typeArgumentType; + private final JavaParameterizedTypeBuilder parameterizedType; + private final Function typeMapping; + private final ReferenceCreationProcess.JavaTypeVariableFinisher typeVariableFinisher; + + private JavaParameterizedTypeBuilder currentTypeArgument; + + TypeArgumentProcessor( + TypeArgumentType typeArgumentType, + JavaParameterizedTypeBuilder parameterizedType, + Function typeMapping, + ReferenceCreationProcess.JavaTypeVariableFinisher typeVariableFinisher) { + super(ASM_API_VERSION); + this.typeArgumentType = typeArgumentType; + this.parameterizedType = parameterizedType; + this.typeMapping = typeMapping; + this.typeVariableFinisher = typeVariableFinisher; + } + + @Override + @SuppressWarnings("ConstantConditions") // we never return null by convention + public void visitClassType(String internalObjectName) { + JavaClassDescriptor type = typeMapping.apply(JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName)); + log.trace("Encountered {} for {}: Class type {}", typeArgumentType.description, parameterizedType.getTypeName(), type.getFullyQualifiedClassName()); + currentTypeArgument = new JavaParameterizedTypeBuilder<>(type); + typeArgumentType.addTypeArgumentToBuilder(parameterizedType, new NewJavaTypeCreationProcess<>(this.currentTypeArgument)); + } + + @Override + public void visitTypeArgument() { + log.trace("Encountered wildcard for {}", currentTypeArgument.getTypeName()); + currentTypeArgument.addTypeArgument(new NewJavaTypeCreationProcess<>(new JavaWildcardTypeBuilder())); + } + + @Override + public void visitTypeVariable(String name) { + if (log.isTraceEnabled()) { + log.trace("Encountered {} for {}: Type variable {}", typeArgumentType.description, parameterizedType.getTypeName(), typeVariableFinisher.getFinishedName(name)); + } + typeArgumentType.addTypeArgumentToBuilder(parameterizedType, new ReferenceCreationProcess(name, typeVariableFinisher)); + } + + @Override + public SignatureVisitor visitTypeArgument(char wildcard) { + return TypeArgumentProcessor.create(wildcard, currentTypeArgument, typeMapping, typeVariableFinisher); + } + + @Override + public SignatureVisitor visitArrayType() { + return new TypeArgumentProcessor(typeArgumentType, parameterizedType, compose(typeMapping, TO_ARRAY_TYPE), typeVariableFinisher.after(GENERIC_ARRAY_CREATOR)); + } + + static TypeArgumentProcessor create( + char identifier, + JavaParameterizedTypeBuilder parameterizedType, + Function typeMapping, + ReferenceCreationProcess.JavaTypeVariableFinisher typeVariableFinisher) { + + switch (identifier) { + case '=': + return new TypeArgumentProcessor(PARAMETERIZED_TYPE, parameterizedType, typeMapping, typeVariableFinisher); + case '+': + return new TypeArgumentProcessor(WILDCARD_WITH_UPPER_BOUND, parameterizedType, typeMapping, typeVariableFinisher); + case '-': + return new TypeArgumentProcessor(WILDCARD_WITH_LOWER_BOUND, parameterizedType, typeMapping, typeVariableFinisher); + default: + throw new IllegalStateException(String.format("Cannot handle asm type argument identifier '%s'", identifier)); + } + } + } + + private abstract static class TypeArgumentType { + private final String description; + + TypeArgumentType(String description) { + this.description = description; + } + + abstract void addTypeArgumentToBuilder(JavaParameterizedTypeBuilder parameterizedType, JavaTypeCreationProcess creationProcess); + } + + private static final TypeArgumentType PARAMETERIZED_TYPE = new TypeArgumentType("type argument") { + @Override + void addTypeArgumentToBuilder(JavaParameterizedTypeBuilder parameterizedType, JavaTypeCreationProcess typeCreationProcess) { + parameterizedType.addTypeArgument(typeCreationProcess); + } + }; + + private static final TypeArgumentType WILDCARD_WITH_UPPER_BOUND = new TypeArgumentType("wildcard with upper bound") { + @Override + void addTypeArgumentToBuilder(JavaParameterizedTypeBuilder parameterizedType, JavaTypeCreationProcess typeCreationProcess) { + parameterizedType.addTypeArgument(new NewJavaTypeCreationProcess<>(new JavaWildcardTypeBuilder().addUpperBound(typeCreationProcess))); + } + }; + + private static final TypeArgumentType WILDCARD_WITH_LOWER_BOUND = new TypeArgumentType("wildcard with lower bound") { + @Override + void addTypeArgumentToBuilder(JavaParameterizedTypeBuilder parameterizedType, JavaTypeCreationProcess typeCreationProcess) { + parameterizedType.addTypeArgument(new NewJavaTypeCreationProcess<>(new JavaWildcardTypeBuilder().addLowerBound(typeCreationProcess))); + } + }; +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/Location.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/Location.java index 36e0f76d46..1bafff9ae3 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/Location.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/Location.java @@ -90,7 +90,7 @@ public URI asURI() { /** * @param part A part to check the respective location {@link URI} for - * @return true, if the respective {@link URI} contains the given part + * @return {@code true}, if the respective {@link URI} contains the given part, {@code false} otherwise */ @PublicAPI(usage = ACCESS) public boolean contains(String part) { @@ -99,7 +99,7 @@ public boolean contains(String part) { /** * @param pattern A pattern to compare the respective location {@link URI} against - * @return true, if the respective {@link URI} matches the given pattern + * @return {@code true}, if the respective {@link URI} matches the given pattern, {@code false} otherwise */ @PublicAPI(usage = ACCESS) public boolean matches(Pattern pattern) { @@ -112,7 +112,8 @@ public boolean matches(Pattern pattern) { /** * This is a generalization of {@link #isJar()}. Before JDK 9, the only archives were Jar files, * starting with JDK 9, we also have JRTs (the JDK modules). - * @return true, iff this location represents an archive, like a JAR or JRT + * + * @return {@code true}, if this location represents an archive, like a JAR or JRT, {@code false} otherwise */ @PublicAPI(usage = ACCESS) public abstract boolean isArchive(); diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/RawAccessRecord.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/RawAccessRecord.java index 229fa06508..9476e3d744 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/RawAccessRecord.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/RawAccessRecord.java @@ -15,34 +15,14 @@ */ package com.tngtech.archunit.core.importer; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; -import java.util.Set; -import com.google.common.collect.Sets; -import com.tngtech.archunit.base.DescribedPredicate; -import com.tngtech.archunit.base.Optional; -import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.domain.JavaCodeUnit; -import com.tngtech.archunit.core.domain.JavaConstructor; -import com.tngtech.archunit.core.domain.JavaField; import com.tngtech.archunit.core.domain.JavaFieldAccess.AccessType; -import com.tngtech.archunit.core.domain.JavaMethod; -import com.tngtech.archunit.core.domain.JavaType; -import com.tngtech.archunit.core.domain.properties.HasDescriptor; -import com.tngtech.archunit.core.domain.properties.HasName; -import com.tngtech.archunit.core.domain.properties.HasOwner; import static com.google.common.base.Preconditions.checkNotNull; -import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME; -import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameMatching; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static java.util.regex.Pattern.quote; class RawAccessRecord { final CodeUnit caller; @@ -144,39 +124,17 @@ && getParameters().equals(method.getRawParameterTypes().getNames()) } } - abstract static class TargetInfo { - final JavaType owner; + static class TargetInfo { + final JavaClassDescriptor owner; final String name; final String desc; TargetInfo(String owner, String name, String desc) { - this.owner = JavaTypeImporter.createFromAsmObjectTypeName(owner); + this.owner = JavaClassDescriptorImporter.createFromAsmObjectTypeName(owner); this.name = name; this.desc = desc; } - > boolean matches(T member) { - if (!name.equals(member.getName()) || !desc.equals(member.getDescriptor())) { - return false; - } - return owner.getName().equals(member.getOwner().getName()) || - classHierarchyFrom(member).hasExactlyOneMatchFor(this); - } - - private > ClassHierarchyPath classHierarchyFrom(T member) { - return new ClassHierarchyPath(owner, member.getOwner()); - } - - protected abstract boolean signatureExistsIn(JavaClass javaClass); - - boolean hasMatchingSignatureTo(JavaMethod method) { - return method.getName().equals(name) && method.getDescriptor().equals(desc); - } - - boolean hasMatchingSignatureTo(JavaConstructor constructor) { - return CONSTRUCTOR_NAME.equals(name) && constructor.getDescriptor().equals(desc); - } - @Override public int hashCode() { return Objects.hash(owner, name, desc); @@ -198,205 +156,7 @@ public boolean equals(Object obj) { @Override public String toString() { - return getClass().getSimpleName() + "{owner='" + owner.getName() + "', name='" + name + "', desc='" + desc + "'}"; - } - - private static class ClassHierarchyPath { - private final List path = new ArrayList<>(); - - private ClassHierarchyPath(JavaType childType, JavaClass parent) { - Set classesToSearchForChild = Sets.union(singleton(parent), parent.getAllSubClasses()); - Optional child = tryFind(classesToSearchForChild, nameMatching(quote(childType.getName()))); - if (child.isPresent()) { - createPath(child.get(), parent); - } - } - - private static Optional tryFind(Iterable collection, DescribedPredicate predicate) { - for (T elem : collection) { - if (predicate.apply(elem)) { - return Optional.of(elem); - } - } - return Optional.absent(); - } - - private void createPath(JavaClass child, JavaClass parent) { - HierarchyResolutionStrategy hierarchyResolutionStrategy = hierarchyResolutionStrategyFrom(child).to(parent); - path.add(child); - while (hierarchyResolutionStrategy.hasNext()) { - path.add(hierarchyResolutionStrategy.next()); - } - } - - boolean hasExactlyOneMatchFor(final TargetInfo target) { - Set matching = new HashSet<>(); - for (JavaClass javaClass : path) { - if (target.signatureExistsIn(javaClass)) { - matching.add(javaClass); - } - } - return matching.size() == 1; - } - - private HierarchyResolutionStrategyCreator hierarchyResolutionStrategyFrom(JavaClass child) { - return new HierarchyResolutionStrategyCreator(child); - } - - private interface HierarchyResolutionStrategy { - boolean hasNext(); - - JavaClass next(); - } - - private static class HierarchyResolutionStrategyCreator { - private final JavaClass child; - - private HierarchyResolutionStrategyCreator(JavaClass child) { - this.child = child; - } - - public HierarchyResolutionStrategy to(JavaClass parent) { - return parent.isInterface() ? - new InterfaceHierarchyResolutionStrategy(child, parent) : - new ClassHierarchyResolutionStrategy(child, parent); - } - } - - private static class ClassHierarchyResolutionStrategy implements HierarchyResolutionStrategy { - private final JavaClass parent; - private JavaClass current; - - private ClassHierarchyResolutionStrategy(JavaClass child, JavaClass parent) { - this.current = child; - this.parent = parent; - } - - @Override - public boolean hasNext() { - return !current.equals(parent) && current.getSuperClass().isPresent(); - } - - @Override - public JavaClass next() { - current = current.getSuperClass().get(); - return current; - } - } - - private static class InterfaceHierarchyResolutionStrategy implements HierarchyResolutionStrategy { - private final Iterator interfaces; - private final JavaClass parent; - private JavaClass current; - - private InterfaceHierarchyResolutionStrategy(JavaClass child, JavaClass parent) { - interfaces = interfacesBetween(child, parent); - this.parent = parent; - current = child; - } - - private Iterator interfacesBetween(JavaClass from, JavaClass target) { - Node node = new Node(from); - List result = new ArrayList<>(); - for (Node parent : node.parents) { - result.addAll(parent.to(target)); - } - return result.iterator(); - } - - @Override - public boolean hasNext() { - return !current.equals(parent) && interfaces.hasNext(); - } - - @Override - public JavaClass next() { - current = interfaces.next(); - return current; - } - } - - private static class Node { - private final JavaClass child; - private final Set parents = new HashSet<>(); - - private Node(JavaClass child) { - this.child = child; - for (JavaClass i : child.getInterfaces()) { - parents.add(new Node(i)); - } - } - - public List to(JavaClass target) { - if (child.equals(target)) { - return singletonList(child); - } - Set result = new LinkedHashSet<>(); - for (Node parent : parents) { - if (parent.contains(target)) { - result.add(child); - result.addAll(parent.to(target)); - } - } - return new ArrayList<>(result); - } - - public boolean contains(JavaClass target) { - if (child.equals(target)) { - return true; - } - for (Node parent : parents) { - if (parent.contains(target)) { - return true; - } - } - return false; - } - } - } - } - - static class FieldTargetInfo extends TargetInfo { - FieldTargetInfo(String owner, String name, String desc) { - super(owner, name, desc); - } - - @Override - protected boolean signatureExistsIn(JavaClass javaClass) { - Optional field = javaClass.tryGetField(name); - return field.isPresent() && desc.equals(field.get().getDescriptor()); - } - } - - static class ConstructorTargetInfo extends TargetInfo { - ConstructorTargetInfo(String owner, String name, String desc) { - super(owner, name, desc); - } - - @Override - protected boolean signatureExistsIn(JavaClass javaClass) { - for (JavaConstructor constructor : javaClass.getConstructors()) { - if (hasMatchingSignatureTo(constructor)) { - return true; - } - } - return false; - } - } - - static class MethodTargetInfo extends TargetInfo { - MethodTargetInfo(String owner, String name, String desc) { - super(owner, name, desc); - } - - @Override - protected boolean signatureExistsIn(JavaClass javaClass) { - for (JavaMethod method : javaClass.getMethods()) { - if (hasMatchingSignatureTo(method)) { - return true; - } - } - return false; + return getClass().getSimpleName() + "{owner='" + owner.getFullyQualifiedClassName() + "', name='" + name + "', desc='" + desc + "'}"; } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/RawInstanceofCheck.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/RawInstanceofCheck.java new file mode 100644 index 0000000000..66a10e8cfd --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/RawInstanceofCheck.java @@ -0,0 +1,42 @@ +/* + * Copyright 2014-2020 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.core.importer; + +import com.tngtech.archunit.core.domain.JavaClassDescriptor; + +import static com.google.common.base.Preconditions.checkNotNull; + +class RawInstanceofCheck { + private final JavaClassDescriptor target; + private final int lineNumber; + + private RawInstanceofCheck(JavaClassDescriptor target, int lineNumber) { + this.target = checkNotNull(target); + this.lineNumber = lineNumber; + } + + public static RawInstanceofCheck from(JavaClassDescriptor target, int lineNumber) { + return new RawInstanceofCheck(target, lineNumber); + } + + public JavaClassDescriptor getTarget() { + return target; + } + + public int getLineNumber() { + return lineNumber; + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/SourceDescriptor.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/SourceDescriptor.java new file mode 100644 index 0000000000..feca31436d --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/SourceDescriptor.java @@ -0,0 +1,36 @@ +/* + * Copyright 2014-2020 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.core.importer; + +import java.net.URI; + +class SourceDescriptor { + private final URI sourceUri; + private final boolean md5InClassSourcesEnabled; + + SourceDescriptor(URI sourceUri, boolean md5InClassSourcesEnabled) { + this.sourceUri = sourceUri; + this.md5InClassSourcesEnabled = md5InClassSourcesEnabled; + } + + URI getUri() { + return sourceUri; + } + + boolean isMd5InClassSourcesEnabled() { + return md5InClassSourcesEnabled; + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/UrlSource.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/UrlSource.java index 670a99cc40..f0903f4891 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/UrlSource.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/UrlSource.java @@ -153,7 +153,7 @@ private static boolean isUrl(String classpathEntry) { private static Optional parseUrl(Path parent, String classpathUrlEntry) { try { - return Optional.of(convertToJarUrlIfNecessary(parent.toUri().resolve(URI.create(classpathUrlEntry).getSchemeSpecificPart()))); + return Optional.of(convertToJarUrlIfNecessary(parent.toUri().resolve(URI.create(classpathUrlEntry).getRawSchemeSpecificPart()))); } catch (Exception e) { LOG.warn("Cannot parse URL classpath entry " + classpathUrlEntry, e); return Optional.absent(); diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/CompositeArchRule.java b/archunit/src/main/java/com/tngtech/archunit/lang/CompositeArchRule.java index b71fe0eb43..85ccefdd4d 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/CompositeArchRule.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/CompositeArchRule.java @@ -15,6 +15,7 @@ */ package com.tngtech.archunit.lang; +import java.util.Iterator; import java.util.List; import com.google.common.collect.ImmutableList; @@ -43,6 +44,20 @@ public static CompositeArchRule of(ArchRule rule) { return priority(MEDIUM).of(rule); } + @PublicAPI(usage = ACCESS) + public static CompositeArchRule of(Iterable rules) { + Iterator iterator = rules.iterator(); + if (!iterator.hasNext()) { + throw new IllegalArgumentException("Iterable must be non-empty"); + } + + CompositeArchRule composite = of(iterator.next()); + while (iterator.hasNext()) { + composite = composite.and(iterator.next()); + } + return composite; + } + @PublicAPI(usage = ACCESS) public static Creator priority(Priority priority) { return new Creator(priority); diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/ConditionEvent.java b/archunit/src/main/java/com/tngtech/archunit/lang/ConditionEvent.java index 13052454be..c39883f1b7 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/ConditionEvent.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/ConditionEvent.java @@ -28,7 +28,7 @@ @PublicAPI(usage = INHERITANCE) public interface ConditionEvent { /** - * @return true, IFF this event represents a violation of an evaluated rule. + * @return {@code true} if this event represents a violation of an evaluated rule, {@code false} otherwise */ boolean isViolation(); diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/ConditionEvents.java b/archunit/src/main/java/com/tngtech/archunit/lang/ConditionEvents.java index 33009dea3c..705a9bc728 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/ConditionEvents.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/ConditionEvents.java @@ -18,12 +18,14 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; -import java.util.SortedSet; -import java.util.TreeSet; +import com.google.common.base.Function; import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; +import com.google.common.collect.Ordering; import com.google.common.reflect.TypeToken; import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.base.Optional; @@ -94,10 +96,9 @@ public List getFailureDescriptionLines() { */ @PublicAPI(usage = ACCESS) public FailureMessages getFailureMessages() { - SortedSet result = new TreeSet<>(); - for (ConditionEvent event : getViolating()) { - result.addAll(event.getDescriptionLines()); - } + ImmutableList result = FluentIterable.from(getViolating()) + .transformAndConcat(TO_DESCRIPTION_LINES) + .toSortedList(Ordering.natural()); return new FailureMessages(result, informationAboutNumberOfViolations); } @@ -160,6 +161,13 @@ public String toString() { '}'; } + private static final Function> TO_DESCRIPTION_LINES = new Function>() { + @Override + public Iterable apply(ConditionEvent input) { + return input.getDescriptionLines(); + } + }; + private enum Type { ALLOWED, VIOLATION; diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/FailureMessages.java b/archunit/src/main/java/com/tngtech/archunit/lang/FailureMessages.java index b3faecc8f8..3cd7a2e71f 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/FailureMessages.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/FailureMessages.java @@ -15,7 +15,6 @@ */ package com.tngtech.archunit.lang; -import java.util.Collection; import java.util.List; import com.google.common.base.Predicate; @@ -31,8 +30,8 @@ public class FailureMessages extends ForwardingList { private final List failures; private final Optional informationAboutNumberOfViolations; - FailureMessages(Collection failures, Optional informationAboutNumberOfViolations) { - this.failures = ImmutableList.copyOf(failures); + FailureMessages(ImmutableList failures, Optional informationAboutNumberOfViolations) { + this.failures = failures; this.informationAboutNumberOfViolations = informationAboutNumberOfViolations; } 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 1ca9cdd525..44057d2654 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 @@ -102,7 +102,10 @@ import static com.tngtech.archunit.core.domain.properties.HasName.AndFullName.Predicates.fullName; import static com.tngtech.archunit.core.domain.properties.HasName.AndFullName.Predicates.fullNameMatching; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameContaining; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameEndingWith; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameMatching; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameStartingWith; import static com.tngtech.archunit.core.domain.properties.HasOwner.Predicates.With.owner; import static com.tngtech.archunit.core.domain.properties.HasParameterTypes.Predicates.rawParameterTypes; import static com.tngtech.archunit.core.domain.properties.HasReturnType.Predicates.rawReturnType; @@ -553,6 +556,48 @@ ArchCondition haveFullNameMatching(String regex) { return not(ArchConditions.haveFullNameMatching(regex)).as("have full name not matching '%s'", regex); } + @PublicAPI(usage = ACCESS) + public static ArchCondition + haveNameStartingWith(String prefix) { + final DescribedPredicate haveNameStartingWith = have(nameStartingWith(prefix)).forSubType(); + return new StartingCondition<>(haveNameStartingWith, prefix); + } + + @PublicAPI(usage = ACCESS) + public static ArchCondition + haveNameNotStartingWith(String prefix) { + final DescribedPredicate haveNameStartingWith = have(nameStartingWith(prefix)).forSubType(); + return not(new StartingCondition<>(haveNameStartingWith, prefix)).as("have name not starting with '%s'", prefix); + } + + @PublicAPI(usage = ACCESS) + public static ArchCondition + haveNameContaining(String infix) { + final DescribedPredicate haveNameContaining = have(nameContaining(infix)).forSubType(); + return new ContainingCondition<>(haveNameContaining, infix); + } + + @PublicAPI(usage = ACCESS) + public static ArchCondition + haveNameNotContaining(String infix) { + final DescribedPredicate haveNameContaining = have(nameContaining(infix)).forSubType(); + return not(new ContainingCondition<>(haveNameContaining, infix)).as("have name not containing '%s'", infix); + } + + @PublicAPI(usage = ACCESS) + public static ArchCondition + haveNameEndingWith(String suffix) { + final DescribedPredicate haveNameEndingWith = have(nameEndingWith(suffix)).forSubType(); + return new EndingCondition<>(haveNameEndingWith, suffix); + } + + @PublicAPI(usage = ACCESS) + public static ArchCondition + haveNameNotEndingWith(String suffix) { + final DescribedPredicate haveNameEndingWith = have(nameEndingWith(suffix)).forSubType(); + return not(new EndingCondition<>(haveNameEndingWith, suffix)).as("have name not ending with '%s'", suffix); + } + @PublicAPI(usage = ACCESS) public static ArchCondition resideInAPackage(final String packageIdentifier) { return new DoesConditionByPredicate<>(JavaClass.Predicates.resideInAPackage(packageIdentifier)); @@ -1243,6 +1288,63 @@ public void check(T item, ConditionEvents events) { } } + private static class StartingCondition extends ArchCondition { + private final DescribedPredicate startingWith; + private final String prefix; + + StartingCondition(DescribedPredicate startingWith, String prefix) { + super(startingWith.getDescription()); + this.startingWith = startingWith; + this.prefix = prefix; + } + + @Override + public void check(T item, ConditionEvents events) { + boolean satisfied = startingWith.apply(item); + String message = createMessage(item, + String.format("name %s '%s'", satisfied ? "starts with" : "does not start with", prefix)); + events.add(new SimpleConditionEvent(item, satisfied, message)); + } + } + + private static class ContainingCondition extends ArchCondition { + private final DescribedPredicate containing; + private final String infix; + + ContainingCondition(DescribedPredicate containing, String infix) { + super(containing.getDescription()); + this.containing = containing; + this.infix = infix; + } + + @Override + public void check(T item, ConditionEvents events) { + boolean satisfied = containing.apply(item); + String message = createMessage(item, + String.format("name %s '%s'", satisfied ? "contains" : "does not contain", infix)); + events.add(new SimpleConditionEvent(item, satisfied, message)); + } + } + + private static class EndingCondition extends ArchCondition { + private final DescribedPredicate endingWith; + private final String suffix; + + EndingCondition(DescribedPredicate endingWith, String suffix) { + super(endingWith.getDescription()); + this.endingWith = endingWith; + this.suffix = suffix; + } + + @Override + public void check(T item, ConditionEvents events) { + boolean satisfied = endingWith.apply(item); + String message = createMessage(item, + String.format("name %s '%s'", satisfied ? "ends with" : "does not end with", suffix)); + events.add(new SimpleConditionEvent(item, satisfied, message)); + } + } + private static class DoesConditionByPredicate extends ArchCondition { private final DescribedPredicate predicate; 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 a6d385be53..a096526380 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 @@ -98,6 +98,37 @@ public SELF haveFullNameMatching(String regex) { public SELF haveFullNameNotMatching(String regex) { return addCondition(ArchConditions.haveFullNameNotMatching(regex)); } + + @Override + public SELF haveNameStartingWith(String prefix) { + return addCondition(ArchConditions.haveNameStartingWith(prefix)); + } + + @Override + public SELF haveNameNotStartingWith(String prefix) { + return addCondition(ArchConditions.haveNameNotStartingWith(prefix)); + } + + @Override + public SELF haveNameContaining(String infix) { + return addCondition(ArchConditions.haveNameContaining(infix)); + } + + @Override + public SELF haveNameNotContaining(String infix) { + return addCondition(ArchConditions.haveNameNotContaining(infix)); + } + + @Override + public SELF haveNameEndingWith(String suffix) { + return addCondition(ArchConditions.haveNameEndingWith(suffix)); + } + + @Override + public SELF haveNameNotEndingWith(String suffix) { + return addCondition(ArchConditions.haveNameNotEndingWith(suffix)); + } + @Override public SELF bePublic() { return addCondition(ArchConditions.bePublic()); diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/ClassesThatInternal.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/ClassesThatInternal.java index 727ecf7125..e4449ae0b5 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/ClassesThatInternal.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/ClassesThatInternal.java @@ -27,6 +27,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.tngtech.archunit.base.DescribedPredicate.doNot; import static com.tngtech.archunit.base.DescribedPredicate.not; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.ANNOTATIONS; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.ANONYMOUS_CLASSES; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.ENUMS; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.INNER_CLASSES; @@ -282,6 +283,16 @@ public CONJUNCTION areNotEnums() { return givenWith(are(not(ENUMS))); } + @Override + public CONJUNCTION areAnnotations() { + return givenWith(are(ANNOTATIONS)); + } + + @Override + public CONJUNCTION areNotAnnotations() { + return givenWith(are(not(ANNOTATIONS))); + } + @Override public CONJUNCTION areTopLevelClasses() { return givenWith(are(TOP_LEVEL_CLASSES)); @@ -347,6 +358,11 @@ public CONJUNCTION belongToAnyOf(Class... classes) { return givenWith(JavaClass.Predicates.belongToAnyOf(classes)); } + @Override + public CONJUNCTION doNotBelongToAnyOf(Class... classes) { + return givenWith(doNot(JavaClass.Predicates.belongToAnyOf(classes))); + } + @Override public CONJUNCTION arePublic() { return givenWith(SyntaxPredicates.arePublic()); diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MembersDeclaredInClassesThat.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MembersDeclaredInClassesThat.java index 8ef5348628..132a4fedd6 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MembersDeclaredInClassesThat.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MembersDeclaredInClassesThat.java @@ -28,6 +28,7 @@ import static com.tngtech.archunit.base.DescribedPredicate.doNot; import static com.tngtech.archunit.base.DescribedPredicate.not; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.ANNOTATIONS; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.ANONYMOUS_CLASSES; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.ENUMS; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.INNER_CLASSES; @@ -355,6 +356,16 @@ public CONJUNCTION areNotEnums() { return givenWith(are(not(ENUMS))); } + @Override + public CONJUNCTION areAnnotations() { + return givenWith(are(ANNOTATIONS)); + } + + @Override + public CONJUNCTION areNotAnnotations() { + return givenWith(are(not(ANNOTATIONS))); + } + @Override public CONJUNCTION areTopLevelClasses() { return givenWith(are(TOP_LEVEL_CLASSES)); @@ -420,6 +431,11 @@ public CONJUNCTION belongToAnyOf(final Class... classes) { return givenWith(JavaClass.Predicates.belongToAnyOf(classes)); } + @Override + public CONJUNCTION doNotBelongToAnyOf(Class... classes) { + return givenWith(doNot(JavaClass.Predicates.belongToAnyOf(classes))); + } + private CONJUNCTION givenWith(DescribedPredicate predicate) { return predicateAggregator.apply(predicate); } 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 e46fcb9a85..2ce73b302c 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 @@ -34,7 +34,10 @@ import static com.tngtech.archunit.core.domain.properties.HasName.AndFullName.Predicates.fullName; import static com.tngtech.archunit.core.domain.properties.HasName.AndFullName.Predicates.fullNameMatching; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameContaining; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameEndingWith; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameMatching; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameStartingWith; import static com.tngtech.archunit.lang.conditions.ArchPredicates.are; import static com.tngtech.archunit.lang.conditions.ArchPredicates.have; @@ -93,6 +96,36 @@ public CONJUNCTION haveFullNameNotMatching(String regex) { return givenWith(have(not(fullNameMatching(regex)).as("full name not matching '%s'", regex))); } + @Override + public CONJUNCTION haveNameStartingWith(String prefix) { + return givenWith(have(nameStartingWith(prefix))); + } + + @Override + public CONJUNCTION haveNameNotStartingWith(String prefix) { + return givenWith(have(not(nameStartingWith(prefix)).as("name not starting with '%s'", prefix))); + } + + @Override + public CONJUNCTION haveNameContaining(String infix) { + return givenWith(have(nameContaining(infix))); + } + + @Override + public CONJUNCTION haveNameNotContaining(String infix) { + return givenWith(have(not(nameContaining(infix)).as("name not containing '%s'", infix))); + } + + @Override + public CONJUNCTION haveNameEndingWith(String suffix) { + return givenWith(have(nameEndingWith(suffix))); + } + + @Override + public CONJUNCTION haveNameNotEndingWith(String suffix) { + return givenWith(have(not(nameEndingWith(suffix)).as("name not ending with '%s'", suffix))); + } + @Override public CONJUNCTION arePublic() { return givenWith(SyntaxPredicates.arePublic()); diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/ClassesShould.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/ClassesShould.java index f9d4c1a6b8..c0d243269c 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/ClassesShould.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/ClassesShould.java @@ -821,6 +821,9 @@ public interface ClassesShould { * {@link ArchRuleDefinition#noClasses() noClasses()}.{@link GivenClasses#should() should()}.{@link #accessClassesThat()}.{@link ClassesThat#haveFullyQualifiedName(String) haveFullyQualifiedName(String)} * * + * NOTE: 'access' refers only to violations by real accesses, i.e. accessing a field, and calling a method. + * Compare with {@link #dependOnClassesThat()} that catches a wider variety of violations. + * * @return A syntax element that allows choosing which classes should be accessed */ @PublicAPI(usage = ACCESS) @@ -834,6 +837,9 @@ public interface ClassesShould { * {@link ArchRuleDefinition#noClasses() noClasses()}.{@link GivenClasses#should() should()}.{@link #accessClassesThat(DescribedPredicate) accessClassesThat(myPredicate)} * * + * NOTE: 'access' refers only to violations by real accesses, i.e. accessing a field, and calling a method. + * Compare with {@link #dependOnClassesThat(DescribedPredicate)} that catches a wider variety of violations. + * * @param predicate Determines which {@link JavaClass JavaClasses} match the access target * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule */ @@ -848,6 +854,9 @@ public interface ClassesShould { * {@link ArchRuleDefinition#noClasses() classes()}.{@link GivenClasses#should() should()}.{@link #onlyAccessClassesThat()}.{@link ClassesThat#haveFullyQualifiedName(String) haveFullyQualifiedName(String)} * * + * NOTE: 'access' refers only to violations by real accesses, i.e. accessing a field, and calling a method. + * Compare with {@link #onlyDependOnClassesThat()}) that catches a wider variety of violations. + * * @return A syntax element that allows choosing which classes should only be accessed */ @PublicAPI(usage = ACCESS) @@ -861,6 +870,9 @@ public interface ClassesShould { * {@link ArchRuleDefinition#noClasses() classes()}.{@link GivenClasses#should() should()}.{@link #onlyAccessClassesThat(DescribedPredicate) onlyAccessClassesThat(myPredicate)} * * + * NOTE: 'access' refers only to violations by real accesses, i.e. accessing a field, and calling a method. + * Compare with {@link #onlyDependOnClassesThat(DescribedPredicate)} that catches a wider variety of violations. + * * @param predicate Determines which {@link JavaClass JavaClasses} match the access target * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule */ @@ -875,6 +887,9 @@ public interface ClassesShould { * {@link ArchRuleDefinition#noClasses() noClasses()}.{@link GivenClasses#should() should()}.{@link #dependOnClassesThat()}.{@link ClassesThat#haveFullyQualifiedName(String) haveFullyQualifiedName(String)} * * + * NOTE: 'dependOn' catches wide variety of violations, e.g. having fields of type, having method parameters of type, extending type etc... + * Compare with {@link #accessClassesThat()} that catches violations only by real accesses. + * * @return A syntax element that allows choosing to which classes a dependency should exist */ @PublicAPI(usage = ACCESS) @@ -888,6 +903,9 @@ public interface ClassesShould { * {@link ArchRuleDefinition#noClasses() noClasses()}.{@link GivenClasses#should() should()}.{@link #dependOnClassesThat(DescribedPredicate) dependOnClassesThat(myPredicate)} * * + * NOTE: 'dependOn' catches wide variety of violations, e.g. having fields of type, having method parameters of type, extending type etc... + * Compare with {@link #accessClassesThat(DescribedPredicate)} that catches violations only by real accesses. + * * @param predicate Determines which {@link JavaClass JavaClasses} match the dependency target * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule */ @@ -902,6 +920,9 @@ public interface ClassesShould { * {@link ArchRuleDefinition#classes() classes()}.{@link GivenClasses#should() should()}.{@link #onlyDependOnClassesThat()}.{@link ClassesThat#haveFullyQualifiedName(String) haveFullyQualifiedName(String)} * * + * NOTE: 'dependOn' catches wide variety of violations, e.g. having fields of type, having method parameters of type, extending type etc... + * Compare with {@link #onlyAccessClassesThat()} that catches violations only by real accesses. + * * @return A syntax element that allows choosing to which classes a dependency should only exist */ @PublicAPI(usage = ACCESS) @@ -915,6 +936,9 @@ public interface ClassesShould { * {@link ArchRuleDefinition#classes() classes()}.{@link GivenClasses#should() should()}.{@link #onlyDependOnClassesThat(DescribedPredicate) onlyDependOnClassesThat(myPredicate)} * * + * NOTE: 'dependOn' catches wide variety of violations, e.g. having fields of type, having method parameters of type, extending type etc... + * Compare with {@link #onlyAccessClassesThat(DescribedPredicate)} that catches violations only by real accesses. + * * @param predicate Determines which {@link JavaClass JavaClasses} match the dependency target * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule */ @@ -922,11 +946,16 @@ public interface ClassesShould { ClassesShouldConjunction onlyDependOnClassesThat(DescribedPredicate predicate); /** - * @return A syntax element that allows restricting how classes should be accessed + * Asserts that only certain classes access the classes selected by this rule.
*
E.g. *

      * {@link #onlyBeAccessed()}.{@link OnlyBeAccessedSpecification#byAnyPackage(String...) byAnyPackage(String...)}
      * 
+ * + * NOTE: 'access' refers only to violations by real accesses, i.e. accessing a field, and calling a method. + * Compare with {@link #onlyHaveDependentClassesThat()} that catches a wider variety of violations. + * + * @return A syntax element that allows restricting how classes should be accessed */ @PublicAPI(usage = ACCESS) OnlyBeAccessedSpecification onlyBeAccessed(); @@ -938,6 +967,9 @@ public interface ClassesShould { * {@link ArchRuleDefinition#classes() classes()}.{@link GivenClasses#should() should()}.{@link #onlyHaveDependentClassesThat()}.{@link ClassesThat#haveFullyQualifiedName(String) haveFullyQualifiedName(String)} * * + * NOTE: 'depends' catches wide variety of violations, e.g. having fields of type, having method parameters of type, extending type etc... + * Compare with {@link #onlyBeAccessed()} that catches violations only by real accesses. + * * @return A syntax element that allows choosing from which classes a dependency to these classes may exist */ @PublicAPI(usage = ACCESS) @@ -950,6 +982,10 @@ public interface ClassesShould { * {@link ArchRuleDefinition#classes() classes()}.{@link GivenClasses#should() should()}.{@link #onlyHaveDependentClassesThat(DescribedPredicate) onlyHaveDependentClassesThat(myPredicate)} * * + * NOTE: 'depends' catches wide variety of violations, e.g. having fields of type, having method parameters of type, extending type etc... + * Compare with {@link #onlyBeAccessed()}.{@link OnlyBeAccessedSpecification#byClassesThat(DescribedPredicate) byClassesThat(DescribedPredicate)} + * that catches violations only by real accesses. + * * @param predicate Determines which {@link JavaClass JavaClasses} match the dependency origin * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule */ diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/ClassesThat.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/ClassesThat.java index 6b3e11f6ee..6579a9dcb8 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/ClassesThat.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/ClassesThat.java @@ -600,6 +600,22 @@ public interface ClassesThat { @PublicAPI(usage = ACCESS) CONJUNCTION areNotEnums(); + /** + * Matches annotations. + * + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION areAnnotations(); + + /** + * Matches everything except annotations. + * + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION areNotAnnotations(); + @PublicAPI(usage = ACCESS) CONJUNCTION areTopLevelClasses(); @@ -659,4 +675,12 @@ public interface ClassesThat { @PublicAPI(usage = ACCESS) CONJUNCTION belongToAnyOf(Class... classes); + /** + * Inverted form of {@link #belongToAnyOf belongToAnyOf(Outer.class)} + * + * @param classes List of {@link Class} objects. + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION doNotBelongToAnyOf(Class... classes); } diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersShould.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersShould.java index eaeb3542b1..744ff441e9 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersShould.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersShould.java @@ -107,6 +107,60 @@ public interface MembersShould> @PublicAPI(usage = ACCESS) CONJUNCTION haveFullNameNotMatching(String regex); + /** + * Asserts that members have a name starting with the specified prefix. + * + * @param prefix A prefix the member name should start with + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameStartingWith(String prefix); + + /** + * Asserts that members have a name not starting with the specified prefix. + * + * @param prefix A prefix the member name should not start with + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameNotStartingWith(String prefix); + + /** + * Asserts that members have a name containing the specified infix. + * + * @param infix An infix the member name should contain + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameContaining(String infix); + + /** + * Asserts that members have a name not containing the specified infix. + * + * @param infix An infix the member name should not contain + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameNotContaining(String infix); + + /** + * Asserts that members have a name ending with the specified suffix. + * + * @param suffix A suffix the member name should end with + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameEndingWith(String suffix); + + /** + * Asserts that members have a name not ending with the specified suffix. + * + * @param suffix A suffix the member name should not end with + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameNotEndingWith(String suffix); + /** * Asserts that members are public. * diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersThat.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersThat.java index 8f7b45c243..56f99ac7eb 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersThat.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersThat.java @@ -103,6 +103,60 @@ public interface MembersThat> { @PublicAPI(usage = ACCESS) CONJUNCTION haveFullNameNotMatching(String regex); + /** + * Matches members with a name starting with the specified prefix. + * + * @param prefix A prefix the member name should start with + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameStartingWith(String prefix); + + /** + * Matches members with a name not starting with the specified prefix. + * + * @param prefix A prefix the member name should not start with + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameNotStartingWith(String prefix); + + /** + * Matches members with a name containing the specified infix. + * + * @param infix An infix the member name should contain + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameContaining(String infix); + + /** + * Matches members with a name not containing the specified infix. + * + * @param infix An infix the member name should not contain + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameNotContaining(String infix); + + /** + * Matches members with a name ending with the specified suffix. + * + * @param suffix A suffix the member name should end with + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameEndingWith(String suffix); + + /** + * Matches members with a name not ending with the specified suffix. + * + * @param suffix A suffix the member name should not end with + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameNotEndingWith(String suffix); + /** * Matches public members. * diff --git a/archunit/src/main/java/com/tngtech/archunit/library/dependencies/SliceCycleArchCondition.java b/archunit/src/main/java/com/tngtech/archunit/library/dependencies/SliceCycleArchCondition.java index 20d3b57ccc..e6d25fc820 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/dependencies/SliceCycleArchCondition.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/dependencies/SliceCycleArchCondition.java @@ -218,7 +218,7 @@ private String createDescription(Collection edgeDescriptions) { private String createDetails(Map> descriptionsToEdges) { List details = new ArrayList<>(); for (Map.Entry> edgeWithDescription : descriptionsToEdges.entrySet()) { - details.add(String.format("Dependencies of %s", edgeWithDescription.getKey())); + details.add("Dependencies of " + edgeWithDescription.getKey()); details.addAll(dependenciesDescription(edgeWithDescription.getValue())); } return Joiner.on(System.lineSeparator()).join(details); diff --git a/archunit/src/main/java/com/tngtech/archunit/library/freeze/ViolationStoreFactory.java b/archunit/src/main/java/com/tngtech/archunit/library/freeze/ViolationStoreFactory.java index 81e6418d42..68f8c07313 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/freeze/ViolationStoreFactory.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/freeze/ViolationStoreFactory.java @@ -183,7 +183,7 @@ public List getViolations(ArchRule rule) { private List readLines(String ruleDetailsFileName) { String violationsText = readStoreFile(ruleDetailsFileName); - List lines = Splitter.on(UNESCAPED_LINE_BREAK_PATTERN).splitToList(violationsText); + List lines = Splitter.on(UNESCAPED_LINE_BREAK_PATTERN).omitEmptyStrings().splitToList(violationsText); return unescape(lines); } diff --git a/archunit/src/test/java/com/tngtech/archunit/ArchConfigurationTest.java b/archunit/src/test/java/com/tngtech/archunit/ArchConfigurationTest.java index b95e565221..3ace2f8c96 100644 --- a/archunit/src/test/java/com/tngtech/archunit/ArchConfigurationTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/ArchConfigurationTest.java @@ -1,11 +1,5 @@ package com.tngtech.archunit; -import java.io.File; -import java.io.FileOutputStream; -import java.lang.reflect.Constructor; -import java.util.Arrays; -import java.util.Properties; - import com.tngtech.archunit.testutil.SystemPropertiesRule; import org.junit.After; import org.junit.Before; @@ -13,6 +7,12 @@ import org.junit.Test; import org.junit.rules.ExpectedException; +import java.io.File; +import java.io.FileOutputStream; +import java.lang.reflect.Constructor; +import java.util.Arrays; +import java.util.Properties; + import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.tngtech.archunit.testutil.Assertions.assertThat; @@ -231,9 +231,15 @@ public void allows_to_override_any_property_via_system_property() { assertThat(configuration.md5InClassSourcesEnabled()).as("MD5 sum in class sources enabled").isTrue(); assertThat(configuration.getProperty(customPropertyName)).as("custom property").isEqualTo("changed"); - assertThat(configuration.getSubProperties(subPropertyKeyOf(customPropertyName))).containsExactly( + assertThat(configuration.getSubProperties(subPropertyKeyOf(customPropertyName))).containsOnly( entry(subPropertyNameOf(customPropertyName), "changed"), entry(subPropertyNameOf(otherPropertyName), "other")); + + System.clearProperty("archunit." + ArchConfiguration.ENABLE_MD5_IN_CLASS_SOURCES); + System.clearProperty("archunit." + customPropertyName); + + assertThat(configuration.md5InClassSourcesEnabled()).as("MD5 sum in class sources enabled").isFalse(); + assertThat(configuration.getProperty(customPropertyName)).as("custom property").isEqualTo("original"); } @Test diff --git a/archunit/src/test/java/com/tngtech/archunit/base/OptionalTest.java b/archunit/src/test/java/com/tngtech/archunit/base/OptionalTest.java index f076f9132c..2a5ef2b376 100644 --- a/archunit/src/test/java/com/tngtech/archunit/base/OptionalTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/base/OptionalTest.java @@ -33,6 +33,25 @@ public void getOrThrow_works() { Optional.absent().getOrThrow(new IllegalStateException("Bummer")); } + @Test + public void getOrThrow_supplier_works() { + assertThat(Optional.of("test").getOrThrow(new Supplier() { + @Override + public IllegalStateException get() { + return new IllegalStateException("SupplierBummer"); + } + })).isEqualTo("test"); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("SupplierBummer"); + Optional.absent().getOrThrow(new Supplier() { + @Override + public IllegalStateException get() { + return new IllegalStateException("SupplierBummer"); + } + }); + } + @Test public void transform_works() { assertThat(Optional.of(5).transform(TO_STRING)).isEqualTo(Optional.of("5")); @@ -71,4 +90,4 @@ public String apply(Object input) { return "" + input; } }; -} \ No newline at end of file +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/PluginLoaderTest.java b/archunit/src/test/java/com/tngtech/archunit/core/PluginLoaderTest.java index 07f4c6c486..16851e3f1c 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/PluginLoaderTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/PluginLoaderTest.java @@ -1,41 +1,62 @@ package com.tngtech.archunit.core; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import com.tngtech.archunit.testutil.SystemPropertiesRule; import org.junit.Rule; import org.junit.Test; +import static com.tngtech.archunit.core.PluginLoader.JavaVersion.JAVA_14; import static com.tngtech.archunit.core.PluginLoader.JavaVersion.JAVA_9; import static org.assertj.core.api.Assertions.assertThat; public class PluginLoaderTest { + private static final Class pluginTypeBeforeJava9 = HashSet.class; + private static final Class pluginTypeBetweenJava9AndJava13 = ArrayList.class; + private static final Class pluginTypeAfterJava13 = HashMap.class; + @Rule public final SystemPropertiesRule systemPropertiesRule = new SystemPropertiesRule(); @Test public void loads_correct_plugin_for_version() { System.setProperty("java.version", "1.7.0_55"); - assertThat(loadsArrayListForJava9FallbackHashSet().load()).isInstanceOf(HashSet.class); + assertThat(createPluginLoader().load()).isInstanceOf(pluginTypeBeforeJava9); System.setProperty("java.version", "1.8.0_122"); - assertThat(loadsArrayListForJava9FallbackHashSet().load()).isInstanceOf(HashSet.class); + assertThat(createPluginLoader().load()).isInstanceOf(pluginTypeBeforeJava9); System.setProperty("java.version", "9"); - assertThat(loadsArrayListForJava9FallbackHashSet().load()).isInstanceOf(ArrayList.class); + assertThat(createPluginLoader().load()).isInstanceOf(pluginTypeBetweenJava9AndJava13); System.setProperty("java.version", "9.0.1"); - assertThat(loadsArrayListForJava9FallbackHashSet().load()).isInstanceOf(ArrayList.class); + assertThat(createPluginLoader().load()).isInstanceOf(pluginTypeBetweenJava9AndJava13); System.setProperty("java.version", "11-ea"); - assertThat(loadsArrayListForJava9FallbackHashSet().load()).isInstanceOf(ArrayList.class); + assertThat(createPluginLoader().load()).isInstanceOf(pluginTypeBetweenJava9AndJava13); + + System.setProperty("java.version", "13"); + assertThat(createPluginLoader().load()).isInstanceOf(pluginTypeBetweenJava9AndJava13); + + System.setProperty("java.version", "14"); + assertThat(createPluginLoader().load()).isInstanceOf(pluginTypeAfterJava13); } // PluginLoader memoizes the loaded plugin - private PluginLoader loadsArrayListForJava9FallbackHashSet() { + private PluginLoader createPluginLoader() { return PluginLoader.forType(Object.class) - .ifVersionGreaterOrEqualTo(JAVA_9).load(ArrayList.class.getName()) - .fallback(new HashSet<>()); + .ifVersionGreaterOrEqualTo(JAVA_9).load(pluginTypeBetweenJava9AndJava13.getName()) + .ifVersionGreaterOrEqualTo(JAVA_14).load(pluginTypeAfterJava13.getName()) + .fallback(newInstance(pluginTypeBeforeJava9)); + } + + private Object newInstance(Class clazz) { + try { + return clazz.getConstructor().newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } } -} \ No newline at end of file +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/AccessTargetTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/AccessTargetTest.java index 5eacb41545..8a70dfbd7c 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/AccessTargetTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/AccessTargetTest.java @@ -14,6 +14,7 @@ import static com.tngtech.archunit.core.domain.TestUtils.simulateCall; import static com.tngtech.archunit.core.domain.TestUtils.withinImportedClasses; import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; public class AccessTargetTest { @Test @@ -130,7 +131,7 @@ public void no_throws_clause_is_resolved() { assertThat(throwsClause).as("throws clause").isEmpty(); assertThat(throwsClause.getTypes()).isEmpty(); assertThat(throwsClause.getOwner()).isEqualTo(target); - assertThat(throwsClause.getDeclaringClass()).matches(Target.class); + assertThatType(throwsClause.getDeclaringClass()).matches(Target.class); } @Test @@ -197,7 +198,7 @@ private void assertDeclarations(CodeUnitCallTarget target, Class... exception ThrowsClause throwsClause = target.getThrowsClause(); assertThat(throwsClause.getTypes()).matches(exceptionTypes); for (ThrowsDeclaration throwsDeclaration : throwsClause) { - assertThat(throwsDeclaration.getDeclaringClass()).isEqualTo(target.getOwner()); + assertThatType(throwsDeclaration.getDeclaringClass()).isEqualTo(target.getOwner()); assertThat(throwsDeclaration.getOwner()).isEqualTo(target.getThrowsClause()); assertThat(throwsDeclaration.getLocation()).isEqualTo(target); } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationValueFormatterTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationPropertiesFormatterTest.java similarity index 57% rename from archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationValueFormatterTest.java rename to archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationPropertiesFormatterTest.java index 50b40838e7..69f42383d4 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationValueFormatterTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationPropertiesFormatterTest.java @@ -7,78 +7,78 @@ import static org.assertj.core.api.Assertions.assertThat; -public class AnnotationValueFormatterTest { +public class AnnotationPropertiesFormatterTest { @Test public void formats_arrays_with_square_brackets() { - AnnotationValueFormatter formatter = AnnotationValueFormatter.configure() + AnnotationPropertiesFormatter formatter = AnnotationPropertiesFormatter.configure() .formattingArraysWithSquareBrackets() .formattingTypesAsClassNames() .build(); - assertThat(formatter.apply(new String[]{"one", "two"})) + assertThat(formatter.formatValue(new String[]{"one", "two"})) .isEqualTo("[one, two]"); - assertThat(formatter.apply(new int[]{5, 9})) + assertThat(formatter.formatValue(new int[]{5, 9})) .isEqualTo("[5, 9]"); - assertThat(formatter.apply(new List[]{ImmutableList.of(1, 2), ImmutableList.of(3, 4)})) + assertThat(formatter.formatValue(new List[]{ImmutableList.of(1, 2), ImmutableList.of(3, 4)})) .isEqualTo("[[1, 2], [3, 4]]"); } @Test public void formats_arrays_with_curly_brackets() { - AnnotationValueFormatter formatter = AnnotationValueFormatter.configure() + AnnotationPropertiesFormatter formatter = AnnotationPropertiesFormatter.configure() .formattingArraysWithCurlyBrackets() .formattingTypesAsClassNames() .quotingStrings() .build(); - assertThat(formatter.apply(new String[]{"one", "two"})) + assertThat(formatter.formatValue(new String[]{"one", "two"})) .isEqualTo("{\"one\", \"two\"}"); - assertThat(formatter.apply(new int[]{5, 9})) + assertThat(formatter.formatValue(new int[]{5, 9})) .isEqualTo("{5, 9}"); - assertThat(formatter.apply(new List[]{ImmutableList.of(1, 2), ImmutableList.of(3, 4)})) + assertThat(formatter.formatValue(new List[]{ImmutableList.of(1, 2), ImmutableList.of(3, 4)})) .isEqualTo("{[1, 2], [3, 4]}"); } @Test public void formats_types_to_string() { - AnnotationValueFormatter formatter = AnnotationValueFormatter.configure() + AnnotationPropertiesFormatter formatter = AnnotationPropertiesFormatter.configure() .formattingArraysWithCurlyBrackets() .formattingTypesToString() .quotingStrings() .build(); - assertThat(formatter.apply(Object.class)) + assertThat(formatter.formatValue(Object.class)) .isEqualTo("class java.lang.Object"); } @Test public void formats_types_as_classNames() { - AnnotationValueFormatter formatter = AnnotationValueFormatter.configure() + AnnotationPropertiesFormatter formatter = AnnotationPropertiesFormatter.configure() .formattingArraysWithCurlyBrackets() .formattingTypesAsClassNames() .quotingStrings() .build(); - assertThat(formatter.apply(Object.class)) + assertThat(formatter.formatValue(Object.class)) .isEqualTo("java.lang.Object.class"); } @Test public void quotes_strings() { - AnnotationValueFormatter.Builder builder = AnnotationValueFormatter.configure() + AnnotationPropertiesFormatter.Builder builder = AnnotationPropertiesFormatter.configure() .formattingArraysWithCurlyBrackets() .formattingTypesAsClassNames(); - AnnotationValueFormatter formatter = builder.build(); - assertThat(formatter.apply("string")) + AnnotationPropertiesFormatter formatter = builder.build(); + assertThat(formatter.formatValue("string")) .isEqualTo("string"); formatter = builder.quotingStrings().build(); - assertThat(formatter.apply("string")) + assertThat(formatter.formatValue("string")) .isEqualTo("\"string\""); } -} \ No newline at end of file +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationProxyTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationProxyTest.java index 656e415491..4ba58114a7 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationProxyTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationProxyTest.java @@ -6,23 +6,20 @@ import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.google.common.collect.ImmutableMap; -import com.tngtech.archunit.base.Function; import com.tngtech.archunit.core.InitialConfiguration; -import com.tngtech.archunit.core.importer.ImportTestUtils; -import com.tngtech.archunit.core.importer.JavaAnnotationTestBuilder; +import com.tngtech.archunit.core.importer.ClassFileImporter; import org.assertj.core.api.Condition; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import static com.tngtech.archunit.core.domain.TestUtils.javaAnnotationFrom; -import static com.tngtech.archunit.core.domain.TestUtils.simpleImportedClasses; import static org.assertj.core.api.Assertions.assertThat; public class AnnotationProxyTest { @@ -31,14 +28,14 @@ public class AnnotationProxyTest { @Test public void annotation_type_is_returned() { - TestAnnotation annotation = getProxyFor(TestAnnotation.class); + TestAnnotation annotation = importAnnotation(ClassWithTestAnnotation.class, TestAnnotation.class); assertThat(annotation.annotationType()).isEqualTo(TestAnnotation.class); } @Test public void primitive_is_returned() { - TestAnnotation annotation = getProxyFor(TestAnnotation.class); + TestAnnotation annotation = importAnnotation(ClassWithTestAnnotation.class, TestAnnotation.class); assertThat(annotation.primitive()) .as(annotation.annotationType().getSimpleName() + ".primitive()") @@ -47,7 +44,7 @@ public void primitive_is_returned() { @Test public void primitive_default_is_returned() { - TestAnnotation annotation = getProxyFor(TestAnnotation.class); + TestAnnotation annotation = importAnnotation(ClassWithTestAnnotation.class, TestAnnotation.class); assertThat(annotation.primitiveWithDefault()) .as(annotation.annotationType().getSimpleName() + ".primitiveWithDefault()") @@ -56,7 +53,7 @@ public void primitive_default_is_returned() { @Test public void primitives_are_returned() { - TestAnnotation annotation = getProxyFor(TestAnnotation.class); + TestAnnotation annotation = importAnnotation(ClassWithTestAnnotation.class, TestAnnotation.class); assertThat(annotation.primitives()) .as(annotation.annotationType().getSimpleName() + ".primitives()") @@ -65,7 +62,7 @@ public void primitives_are_returned() { @Test public void primitives_defaults_are_returned() { - TestAnnotation annotation = getProxyFor(TestAnnotation.class); + TestAnnotation annotation = importAnnotation(ClassWithTestAnnotation.class, TestAnnotation.class); assertThat(annotation.primitivesWithDefault()) .as(annotation.annotationType().getSimpleName() + ".primitivesWithDefault()") @@ -74,7 +71,7 @@ public void primitives_defaults_are_returned() { @Test public void string_is_returned() { - TestAnnotation annotation = getProxyFor(TestAnnotation.class); + TestAnnotation annotation = importAnnotation(ClassWithTestAnnotation.class, TestAnnotation.class); assertThat(annotation.string()) .as(annotation.annotationType().getSimpleName() + ".string()") @@ -83,7 +80,7 @@ public void string_is_returned() { @Test public void string_default_is_returned() { - TestAnnotation annotation = getProxyFor(TestAnnotation.class); + TestAnnotation annotation = importAnnotation(ClassWithTestAnnotation.class, TestAnnotation.class); assertThat(annotation.stringWithDefault()) .as(annotation.annotationType().getSimpleName() + ".stringWithDefault()") @@ -92,7 +89,7 @@ public void string_default_is_returned() { @Test public void strings_are_returned() { - TestAnnotation annotation = getProxyFor(TestAnnotation.class); + TestAnnotation annotation = importAnnotation(ClassWithTestAnnotation.class, TestAnnotation.class); assertThat(annotation.strings()) .as(annotation.annotationType().getSimpleName() + ".strings()") @@ -101,7 +98,7 @@ public void strings_are_returned() { @Test public void strings_default_are_returned() { - TestAnnotation annotation = getProxyFor(TestAnnotation.class); + TestAnnotation annotation = importAnnotation(ClassWithTestAnnotation.class, TestAnnotation.class); assertThat(annotation.stringsWithDefault()) .as(annotation.annotationType().getSimpleName() + ".stringsWithDefault()") @@ -110,7 +107,7 @@ public void strings_default_are_returned() { @Test public void type_is_returned() { - TestAnnotation annotation = getProxyFor(TestAnnotation.class); + TestAnnotation annotation = importAnnotation(ClassWithTestAnnotation.class, TestAnnotation.class); assertThat(annotation.type()) .as(annotation.annotationType().getSimpleName() + ".type()") @@ -119,7 +116,7 @@ public void type_is_returned() { @Test public void type_default_is_returned() { - TestAnnotation annotation = getProxyFor(TestAnnotation.class); + TestAnnotation annotation = importAnnotation(ClassWithTestAnnotation.class, TestAnnotation.class); assertThat(annotation.typeWithDefault()) .as(annotation.annotationType().getSimpleName() + ".typeWithDefault()") @@ -128,7 +125,7 @@ public void type_default_is_returned() { @Test public void types_are_returned() { - TestAnnotation annotation = getProxyFor(TestAnnotation.class); + TestAnnotation annotation = importAnnotation(ClassWithTestAnnotation.class, TestAnnotation.class); assertThat(annotation.types()) .as(annotation.annotationType().getSimpleName() + ".types()") @@ -137,7 +134,7 @@ public void types_are_returned() { @Test public void types_default_are_returned() { - TestAnnotation annotation = getProxyFor(TestAnnotation.class); + TestAnnotation annotation = importAnnotation(ClassWithTestAnnotation.class, TestAnnotation.class); assertThat(annotation.typesWithDefault()) .as(annotation.annotationType().getSimpleName() + ".typesWithDefault()") @@ -146,7 +143,7 @@ public void types_default_are_returned() { @Test public void enumConstant_is_returned() { - TestAnnotation annotation = getProxyFor(TestAnnotation.class); + TestAnnotation annotation = importAnnotation(ClassWithTestAnnotation.class, TestAnnotation.class); assertThat(annotation.enumConstant()) .as(annotation.annotationType().getSimpleName() + ".enumConstant()") @@ -155,7 +152,7 @@ public void enumConstant_is_returned() { @Test public void enumConstant_default_is_returned() { - TestAnnotation annotation = getProxyFor(TestAnnotation.class); + TestAnnotation annotation = importAnnotation(ClassWithTestAnnotation.class, TestAnnotation.class); assertThat(annotation.enumConstantWithDefault()) .as(annotation.annotationType().getSimpleName() + ".enumConstantWithDefault()") @@ -164,7 +161,7 @@ public void enumConstant_default_is_returned() { @Test public void enumConstants_are_returned() { - TestAnnotation annotation = getProxyFor(TestAnnotation.class); + TestAnnotation annotation = importAnnotation(ClassWithTestAnnotation.class, TestAnnotation.class); assertThat(annotation.enumConstants()) .as(annotation.annotationType().getSimpleName() + ".enumConstants()") @@ -173,7 +170,7 @@ public void enumConstants_are_returned() { @Test public void enumConstants_default_are_returned() { - TestAnnotation annotation = getProxyFor(TestAnnotation.class); + TestAnnotation annotation = importAnnotation(ClassWithTestAnnotation.class, TestAnnotation.class); assertThat(annotation.enumConstantsWithDefault()) .as(annotation.annotationType().getSimpleName() + ".enumConstantsWithDefault()") @@ -182,7 +179,7 @@ public void enumConstants_default_are_returned() { @Test public void subAnnotation_is_returned() { - TestAnnotation annotation = getProxyFor(TestAnnotation.class); + TestAnnotation annotation = importAnnotation(ClassWithTestAnnotation.class, TestAnnotation.class); assertThat(annotation.subAnnotation().value()) .as(annotation.annotationType().getSimpleName() + ".subAnnotation().value()") @@ -191,7 +188,7 @@ public void subAnnotation_is_returned() { @Test public void subAnnotation_default_is_returned() { - TestAnnotation annotation = getProxyFor(TestAnnotation.class); + TestAnnotation annotation = importAnnotation(ClassWithTestAnnotation.class, TestAnnotation.class); assertThat(annotation.subAnnotationWithDefault().value()) .as(annotation.annotationType().getSimpleName() + ".subAnnotationWithDefault().value()") @@ -200,7 +197,7 @@ public void subAnnotation_default_is_returned() { @Test public void subAnnotations_are_returned() { - TestAnnotation annotation = getProxyFor(TestAnnotation.class); + TestAnnotation annotation = importAnnotation(ClassWithTestAnnotation.class, TestAnnotation.class); assertThat(valuesOf(annotation.subAnnotations())) .as(annotation.annotationType().getSimpleName() + ".subAnnotations()*.value()") @@ -209,7 +206,7 @@ public void subAnnotations_are_returned() { @Test public void subAnnotations_default_are_returned() { - TestAnnotation annotation = getProxyFor(TestAnnotation.class); + TestAnnotation annotation = importAnnotation(ClassWithTestAnnotation.class, TestAnnotation.class); assertThat(valuesOf(annotation.subAnnotationsWithDefault())) .as(annotation.annotationType().getSimpleName() + ".subAnnotationsWithDefault()*.value()") @@ -219,7 +216,7 @@ public void subAnnotations_default_are_returned() { @Test // NOTE: For now we'll just implement reference equality and hashcode of the proxy object public void equals_hashcode_and_toString() { - TestAnnotation annotation = getProxyFor(TestAnnotation.class); + TestAnnotation annotation = importAnnotation(ClassWithTestAnnotation.class, TestAnnotation.class); assertThat(annotation).isEqualTo(annotation); assertThat(annotation.hashCode()).isEqualTo(annotation.hashCode()); @@ -228,7 +225,8 @@ public void equals_hashcode_and_toString() { @Test public void wrong_annotation_type_is_rejected() { - JavaAnnotation mismatch = javaAnnotationFrom(TestAnnotation.class.getAnnotation(Retention.class), getClass()); + JavaAnnotation mismatch = new ClassFileImporter().importClasses(TestAnnotation.class, Retention.class) + .get(TestAnnotation.class).getAnnotationOfType(Retention.class.getName()); thrown.expect(IllegalArgumentException.class); thrown.expectMessage(Retention.class.getSimpleName()); @@ -239,69 +237,63 @@ public void wrong_annotation_type_is_rejected() { @Test public void array_is_converted_to_the_correct_type() { - ImportTestUtils.ImportedTestClasses importedClasses = simpleImportedClasses(); - JavaAnnotation annotation = new JavaAnnotationTestBuilder() - .withType(JavaType.From.name(TestAnnotation.class.getName())) - .addProperty("types", new Object[0]) - .addProperty("enumConstants", new Object[0]) - .addProperty("subAnnotations", new Object[0]) - .build(importedClasses.get(getClass().getName()), importedClasses); - - TestAnnotation reflected = annotation.as(TestAnnotation.class); + TestAnnotation reflected = importAnnotation(ClassWithTestAnnotationWithEmptyArrays.class, TestAnnotation.class); + assertThat(reflected.types()).isEmpty(); assertThat(reflected.enumConstants()).isEmpty(); assertThat(reflected.subAnnotations()).isEmpty(); } private ImmutableMap propertiesOf(Class type) { - Function formatter = getAnnotationValueFormatterForCurrentPlatform(); + AnnotationPropertiesFormatter formatter = getAnnotationPropertiesFormatterForCurrentPlatform(); ImmutableMap result = ImmutableMap.builder() .put("primitive", "77") .put("primitiveWithDefault", "1") - .put("primitives", formatter.apply(new int[]{77, 88})) - .put("primitivesWithDefault", formatter.apply(new int[]{1, 2})) - .put("string", formatter.apply("foo")) - .put("stringWithDefault", formatter.apply("something")) - .put("strings", formatter.apply(new String[]{"one", "two"})) - .put("stringsWithDefault", formatter.apply(new String[]{"something", "more"})) - .put("type", formatter.apply(String.class)) - .put("typeWithDefault", formatter.apply(Serializable.class)) - .put("types", formatter.apply(new Class[]{Map.class, List.class})) - .put("typesWithDefault", formatter.apply(new Class[]{Serializable.class, String.class})) + .put("primitives", formatter.formatValue(new int[]{77, 88})) + .put("primitivesWithDefault", formatter.formatValue(new int[]{1, 2})) + .put("string", formatter.formatValue("foo")) + .put("stringWithDefault", formatter.formatValue("something")) + .put("strings", formatter.formatValue(new String[]{"one", "two"})) + .put("stringsWithDefault", formatter.formatValue(new String[]{"something", "more"})) + .put("type", formatter.formatValue(String.class)) + .put("typeWithDefault", formatter.formatValue(Serializable.class)) + .put("types", formatter.formatValue(new Class[]{Map.class, List.class})) + .put("typesWithDefault", formatter.formatValue(new Class[]{Serializable.class, String.class})) .put("enumConstant", String.valueOf(TestEnum.SECOND)) .put("enumConstantWithDefault", String.valueOf(TestEnum.FIRST)) - .put("enumConstants", formatter.apply(new TestEnum[]{TestEnum.SECOND, TestEnum.THIRD})) - .put("enumConstantsWithDefault", formatter.apply(new TestEnum[]{TestEnum.FIRST, TestEnum.SECOND})) + .put("enumConstants", formatter.formatValue(new TestEnum[]{TestEnum.SECOND, TestEnum.THIRD})) + .put("enumConstantsWithDefault", formatter.formatValue(new TestEnum[]{TestEnum.FIRST, TestEnum.SECOND})) .put("subAnnotation", formatSubAnnotation(formatter, "custom")) .put("subAnnotationWithDefault", formatSubAnnotation(formatter, "default")) .put("subAnnotations", - formatter.apply(new Object[]{ + formatter.formatValue(new Object[]{ subAnnotationFormatter(formatter, "customOne"), subAnnotationFormatter(formatter, "customTwo")})) .put("subAnnotationsWithDefault", - formatter.apply(new Object[]{ + formatter.formatValue(new Object[]{ subAnnotationFormatter(formatter, "defaultOne"), subAnnotationFormatter(formatter, "defaultTwo")})) .build(); - ensureInSync(Irrelevant.class.getAnnotation(type), result); + ensureInSync(ClassWithTestAnnotation.class.getAnnotation(type), result); return result; } - private Function getAnnotationValueFormatterForCurrentPlatform() { + private AnnotationPropertiesFormatter getAnnotationPropertiesFormatterForCurrentPlatform() { DomainPlugin domainPlugin = DomainPlugin.Loader.loadForCurrentPlatform(); - InitialConfiguration> valueFormatter = new InitialConfiguration<>(); - domainPlugin.plugInAnnotationValueFormatter(valueFormatter); - return valueFormatter.get(); + InitialConfiguration formatter = new InitialConfiguration<>(); + domainPlugin.plugInAnnotationPropertiesFormatter(formatter); + return formatter.get(); } - private String formatSubAnnotation(Function formatter, String value) { - return "@com.tngtech.archunit.core.domain.AnnotationProxyTest$SubAnnotation(value=" + formatter.apply(value) + ")"; + private String formatSubAnnotation(AnnotationPropertiesFormatter formatter, String value) { + Map properties = Collections.singletonMap("value", (Object) value); + return "@com.tngtech.archunit.core.domain.AnnotationProxyTest$SubAnnotation(" + formatter.formatProperties(properties) + ")"; } // NOTE: We do not want this value to be treated as a string by the formatter, and e.g. quoted -> Object - private Object subAnnotationFormatter(final Function formatter, final String value) { + private Object subAnnotationFormatter(final AnnotationPropertiesFormatter formatter, final String value) { return new Object() { @Override public String toString() { @@ -332,9 +324,9 @@ public String toString() { Class typeWithDefault() default Serializable.class; - Class[] types(); + Class[] types(); - Class[] typesWithDefault() default {Serializable.class, String.class}; + Class[] typesWithDefault() default {Serializable.class, String.class}; TestEnum enumConstant(); @@ -372,7 +364,21 @@ private enum TestEnum { enumConstants = {TestEnum.SECOND, TestEnum.THIRD}, subAnnotation = @SubAnnotation("custom"), subAnnotations = {@SubAnnotation("customOne"), @SubAnnotation("customTwo")}) - private static class Irrelevant { + private static class ClassWithTestAnnotation { + } + + @TestAnnotation( + primitive = 0, + primitives = {}, + string = "", + strings = {}, + type = TestAnnotation.class, + types = {}, + enumConstant = TestEnum.SECOND, + enumConstants = {}, + subAnnotation = @SubAnnotation(""), + subAnnotations = {}) + private static class ClassWithTestAnnotationWithEmptyArrays { } private void ensureInSync(TestAnnotation annotation, Map result) { @@ -421,8 +427,7 @@ private boolean mismatch(String expectedPart) { }; } - private A getProxyFor(Class annotationType) { - JavaAnnotation toProxy = javaAnnotationFrom(Irrelevant.class.getAnnotation(annotationType), Irrelevant.class); - return AnnotationProxy.of(annotationType, toProxy); + private A importAnnotation(Class ownerType, Class annotationType) { + return new ClassFileImporter().importClasses(ownerType, annotationType).get(ownerType).getAnnotationOfType(annotationType); } } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/DependencyTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/DependencyTest.java index f2e6baf9c4..112b1c29c5 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/DependencyTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/DependencyTest.java @@ -3,10 +3,17 @@ import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Set; import com.google.common.base.MoreObjects; import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.core.domain.testobjects.ClassWithDependencyOnInstanceofCheck; +import com.tngtech.archunit.core.domain.testobjects.ClassWithDependencyOnInstanceofCheck.InstanceOfCheckTarget; +import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.testutil.Assertions; +import com.tngtech.archunit.testutil.assertion.DependenciesAssertion; import com.tngtech.archunit.testutil.assertion.DependencyAssertion; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; @@ -24,20 +31,102 @@ import static com.tngtech.archunit.core.domain.TestUtils.importClassesWithContext; import static com.tngtech.archunit.core.domain.TestUtils.simulateCall; import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatDependencies; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.assertion.DependenciesAssertion.from; +import static com.tngtech.java.junit.dataprovider.DataProviders.$; +import static com.tngtech.java.junit.dataprovider.DataProviders.$$; import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach; @RunWith(DataProviderRunner.class) public class DependencyTest { + + @DataProvider + public static Object[][] field_array_types() throws NoSuchFieldException { + @SuppressWarnings("unused") + class ClassWithArrayDependencies { + private String[] oneDimArray; + private String[][] multiDimArray; + } + return testForEach( + ClassWithArrayDependencies.class.getDeclaredField("oneDimArray"), + ClassWithArrayDependencies.class.getDeclaredField("multiDimArray")); + } + + @Test + @UseDataProvider("field_array_types") + public void Dependencies_from_field_with_component_type(Field reflectionArrayField) { + Class reflectionDeclaringClass = reflectionArrayField.getDeclaringClass(); + JavaField field = new ClassFileImporter().importClasses(reflectionDeclaringClass).get(reflectionDeclaringClass).getField(reflectionArrayField.getName()); + + Set dependencies = Dependency.tryCreateFromField(field); + + DependenciesAssertion.ExpectedDependencies expectedDependencies = from(reflectionDeclaringClass).to(reflectionArrayField.getType()) + .withDescriptionContaining("Field <%s> has type <%s>", field.getFullName(), reflectionArrayField.getType().getName()) + .inLocation(DependencyTest.class, 0); + Class expectedComponentType = reflectionArrayField.getType().getComponentType(); + while (expectedComponentType != null) { + expectedDependencies.from(reflectionDeclaringClass).to(expectedComponentType) + .withDescriptionContaining("Field <%s> depends on component type <%s>", field.getFullName(), expectedComponentType.getName()) + .inLocation(DependencyTest.class, 0); + expectedComponentType = expectedComponentType.getComponentType(); + } + + assertThatDependencies(dependencies).containOnly(expectedDependencies); + } + @Test public void Dependency_from_access() { JavaMethodCall call = simulateCall().from(getClass(), "toString").to(Object.class, "toString"); - Dependency dependency = Dependency.tryCreateFromAccess(call).get(); - assertThat(dependency.getTargetClass()).as("target class").isEqualTo(call.getTargetOwner()); + Dependency dependency = getOnlyElement(Dependency.tryCreateFromAccess(call)); + assertThatType(dependency.getTargetClass()).as("target class").isEqualTo(call.getTargetOwner()); assertThat(dependency.getDescription()) .as("description").isEqualTo(call.getDescription()); } + @DataProvider + public static Object[][] method_calls_to_array_types() throws NoSuchMethodException { + @SuppressWarnings("unused") + class ClassWithArrayDependencies { + private void oneDimArray() { + new String[0].clone(); + } + + private void multiDimArray() { + new String[0][0].clone(); + } + } + return $$( + $(ClassWithArrayDependencies.class.getDeclaredMethod("oneDimArray"), String[].class, 93), + $(ClassWithArrayDependencies.class.getDeclaredMethod("multiDimArray"), String[][].class, 97) + ); + } + + @Test + @UseDataProvider("method_calls_to_array_types") + public void Dependency_from_access_with_component_type(Method reflectionMethodWithArrayMethodCall, Class arrayType, int expectedLineNumber) { + Class reflectionDeclaringClass = reflectionMethodWithArrayMethodCall.getDeclaringClass(); + JavaMethod method = new ClassFileImporter().importClasses(reflectionDeclaringClass) + .get(reflectionDeclaringClass).getMethod(reflectionMethodWithArrayMethodCall.getName()); + JavaMethodCall call = getOnlyElement(method.getMethodCallsFromSelf()); + + Set dependencies = Dependency.tryCreateFromAccess(call); + + DependenciesAssertion.ExpectedDependencies expectedDependencies = from(reflectionDeclaringClass).to(arrayType) + .withDescriptionContaining("Method <%s> calls method <%s>", method.getFullName(), arrayType.getName() + ".clone()") + .inLocation(DependencyTest.class, expectedLineNumber); + Class expectedComponentType = arrayType.getComponentType(); + while (expectedComponentType != null) { + expectedDependencies.from(reflectionDeclaringClass).to(expectedComponentType) + .withDescriptionContaining("Method <%s> depends on component type <%s>", method.getFullName(), expectedComponentType.getName()) + .inLocation(DependencyTest.class, expectedLineNumber); + expectedComponentType = expectedComponentType.getComponentType(); + } + + assertThatDependencies(dependencies).containOnly(expectedDependencies); + } + @Test public void Dependency_from_origin_and_target() { JavaClass origin = importClassWithContext(getClass()); @@ -63,14 +152,38 @@ public void Dependency_from_throws_declaration() { .get(ClassWithDependencyOnThrowable.class).getMethod("method"); ThrowsDeclaration throwsDeclaration = getOnlyElement(origin.getThrowsClause()); - Dependency dependency = Dependency.tryCreateFromThrowsDeclaration(throwsDeclaration).get(); + Dependency dependency = getOnlyElement(Dependency.tryCreateFromThrowsDeclaration(throwsDeclaration)); - assertThat(dependency.getOriginClass()).matches(ClassWithDependencyOnThrowable.class); - assertThat(dependency.getTargetClass()).matches(IOException.class); + assertThatType(dependency.getOriginClass()).matches(ClassWithDependencyOnThrowable.class); + assertThatType(dependency.getTargetClass()).matches(IOException.class); assertThat(dependency.getDescription()).as("description") .contains("Method <" + origin.getFullName() + "> throws type <" + IOException.class.getName() + ">"); } + @DataProvider + public static Object[][] with_instanceof_check_members() { + JavaClass javaClass = importClassesWithContext(ClassWithDependencyOnInstanceofCheck.class, InstanceOfCheckTarget.class) + .get(ClassWithDependencyOnInstanceofCheck.class); + + return $$( + $(javaClass.getStaticInitializer().get(), 6), + $(javaClass.getConstructor(Object.class), 9), + $(javaClass.getMethod("method", Object.class), 13)); + } + + @Test + @UseDataProvider("with_instanceof_check_members") + public void Dependency_from_instanceof_check_in_code_unit(JavaCodeUnit memberWithInstanceofCheck, int expectedLineNumber) { + InstanceofCheck instanceofCheck = getOnlyElement(memberWithInstanceofCheck.getInstanceofChecks()); + + Dependency dependency = getOnlyElement(Dependency.tryCreateFromInstanceofCheck(instanceofCheck)); + + Assertions.assertThatDependency(dependency) + .matches(ClassWithDependencyOnInstanceofCheck.class, InstanceOfCheckTarget.class) + .hasDescription(memberWithInstanceofCheck.getFullName(), "checks instanceof", InstanceOfCheckTarget.class.getName()) + .inLocation(ClassWithDependencyOnInstanceofCheck.class, expectedLineNumber); + } + @DataProvider public static Object[][] annotated_classes() { JavaClasses classes = importClassesWithContext( @@ -87,9 +200,9 @@ public void Dependency_from_annotation_on_class(JavaClass annotatedClass) { JavaAnnotation annotation = annotatedClass.getAnnotations().iterator().next(); Class annotationClass = annotation.getRawType().reflect(); - Dependency dependency = Dependency.tryCreateFromAnnotation(annotation).get(); - assertThat(dependency.getOriginClass()).isEqualTo(annotatedClass); - assertThat(dependency.getTargetClass()).matches(annotationClass); + Dependency dependency = getOnlyElement(Dependency.tryCreateFromAnnotation(annotation)); + assertThatType(dependency.getOriginClass()).isEqualTo(annotatedClass); + assertThatType(dependency.getTargetClass()).matches(annotationClass); assertThat(dependency.getDescription()).as("description") .contains("Class <" + annotatedClass.getName() + "> is annotated with <" + annotationClass.getName() + ">"); } @@ -111,9 +224,9 @@ public void Dependency_from_annotation_on_member(JavaMember annotatedMember) { JavaAnnotation annotation = annotatedMember.getAnnotations().iterator().next(); Class annotationClass = annotation.getRawType().reflect(); - Dependency dependency = Dependency.tryCreateFromAnnotation(annotation).get(); - assertThat(dependency.getOriginClass()).matches(ClassWithAnnotatedMembers.class); - assertThat(dependency.getTargetClass()).matches(annotationClass); + Dependency dependency = getOnlyElement(Dependency.tryCreateFromAnnotation(annotation)); + assertThatType(dependency.getOriginClass()).matches(ClassWithAnnotatedMembers.class); + assertThatType(dependency.getTargetClass()).matches(annotationClass); assertThat(dependency.getDescription()).as("description") .contains(annotatedMember.getDescription() + " is annotated with <" + annotationClass.getName() + ">"); } @@ -124,9 +237,9 @@ public void Dependency_from_class_annotation_member(JavaClass annotatedClass) { JavaAnnotation annotation = annotatedClass.getAnnotationOfType(SomeAnnotation.class.getName()); JavaClass memberType = ((JavaClass) annotation.get("value").get()); - Dependency dependency = Dependency.tryCreateFromAnnotationMember(annotation, memberType).get(); - assertThat(dependency.getOriginClass()).isEqualTo(annotatedClass); - assertThat(dependency.getTargetClass()).isEqualTo(memberType); + Dependency dependency = getOnlyElement(Dependency.tryCreateFromAnnotationMember(annotation, memberType)); + assertThatType(dependency.getOriginClass()).isEqualTo(annotatedClass); + assertThatType(dependency.getTargetClass()).isEqualTo(memberType); assertThat(dependency.getDescription()).as("description") .contains("Class <" + annotatedClass.getName() + "> has annotation member of type <" + memberType.getName() + ">"); } @@ -137,13 +250,31 @@ public void Dependency_from_member_annotation_member(JavaMember annotatedMember) JavaAnnotation annotation = annotatedMember.getAnnotationOfType(SomeAnnotation.class.getName()); JavaClass memberType = ((JavaClass) annotation.get("value").get()); - Dependency dependency = Dependency.tryCreateFromAnnotationMember(annotation, memberType).get(); - assertThat(dependency.getOriginClass()).isEqualTo(annotatedMember.getOwner()); - assertThat(dependency.getTargetClass()).isEqualTo(memberType); + Dependency dependency = getOnlyElement(Dependency.tryCreateFromAnnotationMember(annotation, memberType)); + assertThatType(dependency.getOriginClass()).isEqualTo(annotatedMember.getOwner()); + assertThatType(dependency.getTargetClass()).isEqualTo(memberType); assertThat(dependency.getDescription()).as("description") .contains(annotatedMember.getDescription() + " has annotation member of type <" + memberType.getName() + ">"); } + @Test + public void Dependency_from_type_parameter() { + @SuppressWarnings("unused") + class ClassWithTypeParameters { + } + + JavaClass javaClass = importClassesWithContext(ClassWithTypeParameters.class, String.class).get(ClassWithTypeParameters.class); + JavaTypeVariable typeParameter = javaClass.getTypeParameters().get(0); + + Dependency dependency = getOnlyElement(Dependency.tryCreateFromTypeParameter(typeParameter, typeParameter.getUpperBounds().get(0).toErasure())); + + assertThatType(dependency.getOriginClass()).matches(ClassWithTypeParameters.class); + assertThatType(dependency.getTargetClass()).matches(String.class); + assertThat(dependency.getDescription()).as("description").contains(String.format( + "Class <%s> has type parameter '%s' depending on <%s> in (%s.java:0)", + ClassWithTypeParameters.class.getName(), typeParameter.getName(), String.class.getName(), getClass().getSimpleName())); + } + @Test public void origin_predicates_match() { assertThatDependency(Origin.class, Target.class) @@ -198,14 +329,14 @@ public void dependency_predicates_descriptions() { @Test public void functions() { - assertThat(GET_ORIGIN_CLASS.apply(createDependency(Origin.class, Target.class))).matches(Origin.class); - assertThat(GET_TARGET_CLASS.apply(createDependency(Origin.class, Target.class))).matches(Target.class); + assertThatType(GET_ORIGIN_CLASS.apply(createDependency(Origin.class, Target.class))).matches(Origin.class); + assertThatType(GET_TARGET_CLASS.apply(createDependency(Origin.class, Target.class))).matches(Target.class); } private Dependency createDependency(JavaClass origin, JavaClass target) { Dependency dependency = Dependency.fromInheritance(origin, target); - assertThat(dependency.getOriginClass()).as("origin class").isEqualTo(origin); - assertThat(dependency.getTargetClass()).as("target class").isEqualTo(target); + assertThatType(dependency.getOriginClass()).as("origin class").isEqualTo(origin); + assertThatType(dependency.getTargetClass()).as("target class").isEqualTo(target); return dependency; } @@ -257,10 +388,12 @@ private static class SomeMemberType { } @SomeAnnotation(SomeMemberType.class) - private static class ClassWithDependencyOnAnnotation { } + private interface InterfaceWithDependencyOnAnnotation { + } @SomeAnnotation(SomeMemberType.class) - private interface InterfaceWithDependencyOnAnnotation { } + private static class ClassWithDependencyOnAnnotation { + } @SuppressWarnings("unused") private static class ClassWithAnnotatedMembers { diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassDescriptorTest.java similarity index 58% rename from archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeTest.java rename to archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassDescriptorTest.java index db3621b14c..a1d28bec90 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassDescriptorTest.java @@ -17,14 +17,14 @@ import static com.tngtech.archunit.testutil.Assertions.assertThat; @RunWith(DataProviderRunner.class) -public class JavaTypeTest { +public class JavaClassDescriptorTest { @Rule public ExpectedException thrown = ExpectedException.none(); @Test @UseDataProvider("primitives") public void primitive_types_by_name_and_descriptor(String name, Class expected) { - JavaType primitiveType = JavaType.From.name(name); + JavaClassDescriptor primitiveType = JavaClassDescriptor.From.name(name); assertThat(primitiveType.isPrimitive()).isTrue(); assertThat(primitiveType.isArray()).isFalse(); assertThat(primitiveType.tryGetComponentType()).isAbsent(); @@ -35,17 +35,17 @@ public void primitive_types_by_name_and_descriptor(String name, Class expecte @Test @UseDataProvider("arrays") public void array_types_by_name_and_canonical_name(String name, Class expected) { - JavaType arrayType = JavaType.From.name(name); + JavaClassDescriptor arrayType = JavaClassDescriptor.From.name(name); assertThat(arrayType.isPrimitive()).isFalse(); assertThat(arrayType.isArray()).isTrue(); - assertThat(arrayType.tryGetComponentType()).contains(JavaType.From.name(expected.getComponentType().getName())); + assertThat(arrayType.tryGetComponentType()).contains(JavaClassDescriptor.From.name(expected.getComponentType().getName())); assertThat(arrayType).isEquivalentTo(expected); } @Test public void object_name() { - JavaType objectType = JavaType.From.name(Object.class.getName()); + JavaClassDescriptor objectType = JavaClassDescriptor.From.name(Object.class.getName()); assertThat(objectType.isPrimitive()).isFalse(); assertThat(objectType.isArray()).isFalse(); assertThat(objectType.tryGetComponentType()).isAbsent(); @@ -56,24 +56,24 @@ public void object_name() { @Test @UseDataProvider(value = "primitives") public void resolves_primitive_type_names(String name, Class expected) { - assertThat(JavaType.From.name(name).resolveClass()).isEqualTo(expected); + assertThat(JavaClassDescriptor.From.name(name).resolveClass()).isEqualTo(expected); } @Test @UseDataProvider(value = "arrays") public void resolves_arrays_type_names(String name, Class expected) { - assertThat(JavaType.From.name(name).resolveClass()).isEqualTo(expected); + assertThat(JavaClassDescriptor.From.name(name).resolveClass()).isEqualTo(expected); } @Test public void resolves_standard_class_name() { - assertThat(JavaType.From.name(getClass().getName()).resolveClass()).isEqualTo(getClass()); + assertThat(JavaClassDescriptor.From.name(getClass().getName()).resolveClass()).isEqualTo(getClass()); } @Test public void resolving_throws_exception_if_type_doesnt_exist() { thrown.expect(ReflectionException.class); - JavaType.From.name("does.not.exist").resolveClass(); + JavaClassDescriptor.From.name("does.not.exist").resolveClass(); } @Test @@ -81,28 +81,28 @@ public void anonymous_type() { Serializable input = new Serializable() { }; - JavaType anonymousType = JavaType.From.name(input.getClass().getName()); + JavaClassDescriptor anonymousType = JavaClassDescriptor.From.name(input.getClass().getName()); - assertThat(anonymousType.getName()).isEqualTo(getClass().getName() + "$1"); - assertThat(anonymousType.getSimpleName()).isEmpty(); + assertThat(anonymousType.getFullyQualifiedClassName()).isEqualTo(getClass().getName() + "$1"); + assertThat(anonymousType.getSimpleClassName()).isEmpty(); assertThat(anonymousType.getPackageName()).isEqualTo(getClass().getPackage().getName()); } @Test public void special_chars_type() { - JavaType specialChars = JavaType.From.name("s_123_wéirdâ.Weird_αρετη_Type"); + JavaClassDescriptor specialChars = JavaClassDescriptor.From.name("s_123_wéirdâ.Weird_αρετη_Type"); - assertThat(specialChars.getName()).isEqualTo("s_123_wéirdâ.Weird_αρετη_Type"); - assertThat(specialChars.getSimpleName()).isEqualTo("Weird_αρετη_Type"); + assertThat(specialChars.getFullyQualifiedClassName()).isEqualTo("s_123_wéirdâ.Weird_αρετη_Type"); + assertThat(specialChars.getSimpleClassName()).isEqualTo("Weird_αρετη_Type"); assertThat(specialChars.getPackageName()).isEqualTo("s_123_wéirdâ"); } @Test public void default_package() { - JavaType specialChars = JavaType.From.name("DefaultPackage"); + JavaClassDescriptor specialChars = JavaClassDescriptor.From.name("DefaultPackage"); - assertThat(specialChars.getName()).isEqualTo("DefaultPackage"); - assertThat(specialChars.getSimpleName()).isEqualTo("DefaultPackage"); + assertThat(specialChars.getFullyQualifiedClassName()).isEqualTo("DefaultPackage"); + assertThat(specialChars.getSimpleClassName()).isEqualTo("DefaultPackage"); assertThat(specialChars.getPackageName()).isEmpty(); } @@ -121,6 +121,43 @@ public static List> primitives() { .build(); } + @Test + public void convert_object_descriptor_to_array_descriptor() { + JavaClassDescriptor arrayDescriptor = JavaClassDescriptor.From.name(Object.class.getName()).toArrayDescriptor(); + + assertThat(arrayDescriptor.getFullyQualifiedClassName()).isEqualTo(Object[].class.getName()); + assertThat(arrayDescriptor.getSimpleClassName()).isEqualTo(Object[].class.getSimpleName()); + assertThat(arrayDescriptor.getPackageName()).isEqualTo(Object.class.getPackage().getName()); + } + + @Test + public void convert_primitive_descriptor_to_array_descriptor() { + JavaClassDescriptor arrayDescriptor = JavaClassDescriptor.From.name(int.class.getName()).toArrayDescriptor(); + + assertThat(arrayDescriptor.getFullyQualifiedClassName()).isEqualTo(int[].class.getName()); + assertThat(arrayDescriptor.getSimpleClassName()).isEqualTo(int[].class.getSimpleName()); + assertThat(arrayDescriptor.getPackageName()).isEmpty(); + } + + @Test + public void convert_array_descriptor_to_2_dim_array_descriptor() { + JavaClassDescriptor arrayDescriptor = JavaClassDescriptor.From.name(Object[].class.getName()).toArrayDescriptor(); + + assertThat(arrayDescriptor.getFullyQualifiedClassName()).isEqualTo(Object[][].class.getName()); + assertThat(arrayDescriptor.getSimpleClassName()).isEqualTo(Object[][].class.getSimpleName()); + assertThat(arrayDescriptor.getPackageName()).isEqualTo(Object.class.getPackage().getName()); + } + + @Test + public void converts_descriptor_repeatedly_multi_dim_array_descriptor() { + JavaClassDescriptor arrayDescriptor = JavaClassDescriptor.From.name(Object.class.getName()) + .toArrayDescriptor().toArrayDescriptor().toArrayDescriptor(); + + assertThat(arrayDescriptor.getFullyQualifiedClassName()).isEqualTo(Object[][][].class.getName()); + assertThat(arrayDescriptor.getSimpleClassName()).isEqualTo(Object[][][].class.getSimpleName()); + assertThat(arrayDescriptor.getPackageName()).isEqualTo(Object.class.getPackage().getName()); + } + private static List> namesToPrimitive(Class primitiveType) { return ImmutableList.>of( ImmutableList.of(primitiveType.getName(), primitiveType), diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java index 5384dc0179..bfb0c160df 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java @@ -1,5 +1,7 @@ package com.tngtech.archunit.core.domain; +import java.io.BufferedInputStream; +import java.io.File; import java.io.Serializable; import java.lang.annotation.Retention; import java.util.AbstractList; @@ -7,6 +9,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import com.google.common.collect.FluentIterable; @@ -19,9 +22,10 @@ import com.tngtech.archunit.core.domain.testobjects.AExtendingSuperAImplementingInterfaceForA; import com.tngtech.archunit.core.domain.testobjects.AhavingMembersOfTypeB; import com.tngtech.archunit.core.domain.testobjects.AllPrimitiveDependencies; +import com.tngtech.archunit.core.domain.testobjects.ArrayComponentTypeDependencies; import com.tngtech.archunit.core.domain.testobjects.B; +import com.tngtech.archunit.core.domain.testobjects.ComponentTypeDependency; import com.tngtech.archunit.core.domain.testobjects.InterfaceForA; -import com.tngtech.archunit.core.domain.testobjects.IsArrayTestClass; import com.tngtech.archunit.core.domain.testobjects.SuperA; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.core.importer.testexamples.arrays.ClassAccessingOneDimensionalArray; @@ -32,6 +36,7 @@ import com.tngtech.java.junit.dataprovider.UseDataProvider; import org.assertj.core.api.AbstractBooleanAssert; import org.assertj.core.api.Condition; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.assertj.core.api.iterable.Extractor; import org.junit.Assert; import org.junit.Rule; @@ -70,12 +75,17 @@ import static com.tngtech.archunit.core.domain.TestUtils.simulateCall; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; import static com.tngtech.archunit.testutil.Assertions.assertThat; -import static com.tngtech.archunit.testutil.Assertions.assertThatClasses; +import static com.tngtech.archunit.testutil.Assertions.assertThatDependencies; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static com.tngtech.archunit.testutil.Conditions.codeUnitWithSignature; import static com.tngtech.archunit.testutil.Conditions.containing; import static com.tngtech.archunit.testutil.ReflectionTestUtils.getHierarchy; +import static com.tngtech.archunit.testutil.assertion.DependenciesAssertion.from; import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach; import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static java.util.regex.Pattern.quote; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -86,18 +96,43 @@ public class JavaClassTest { @Test public void finds_array_type() { + @SuppressWarnings("unused") + class IsArrayTestClass { + Object[] anArray() { + return null; + } + } + JavaMethod method = importClassWithContext(IsArrayTestClass.class).getMethod("anArray"); + JavaClass arrayType = method.getRawReturnType(); - assertThat(method.getRawReturnType().isArray()).isTrue(); - assertThat(method.getRawReturnType().tryGetComponentType().get()).matches(Object.class); + assertThat(arrayType.isArray()).isTrue(); + assertThatType(arrayType.tryGetComponentType().get()) + .isSameAs(arrayType.getComponentType()) + .isSameAs(arrayType.getBaseComponentType()) + .matches(Object.class); } @Test public void finds_non_array_type() { + @SuppressWarnings("unused") + class IsArrayTestClass { + Object notAnArray() { + return null; + } + } + JavaMethod method = importClassWithContext(IsArrayTestClass.class).getMethod("notAnArray"); + final JavaClass nonArrayType = method.getRawReturnType(); - assertThat(method.getRawReturnType().isArray()).isFalse(); - assertThat(method.getRawReturnType().tryGetComponentType()).isAbsent(); + assertThat(nonArrayType.isArray()).isFalse(); + assertThat(nonArrayType.tryGetComponentType()).isAbsent(); + assertThatThrownBy(new ThrowingCallable() { + public void call() { + nonArrayType.getComponentType(); + } + }).isInstanceOf(IllegalStateException.class); + assertThat(nonArrayType.getBaseComponentType()).isSameAs(nonArrayType); } @Test @@ -108,14 +143,25 @@ public void finds_multidimensional_array_type() { JavaClass twoDimArray = classes.get(ClassAccessingTwoDimensionalArray.class).getField("array").getRawType(); assertThat(oneDimArray.isArray()).isTrue(); - assertThat(oneDimArray.tryGetComponentType().get()).isEqualTo(type); + assertThatType(oneDimArray.tryGetComponentType().get()).isEqualTo(type); assertThat(twoDimArray.isArray()).isTrue(); - assertThat(twoDimArray.tryGetComponentType().get()).isEqualTo(oneDimArray); - assertThat(twoDimArray.tryGetComponentType().get().tryGetComponentType().get()).isEqualTo(type); + assertThatType(twoDimArray.tryGetComponentType().get()).isEqualTo(oneDimArray); + assertThatType(twoDimArray.tryGetComponentType().get().tryGetComponentType().get()).isEqualTo(type); + } + + @Test + public void erased_type_of_class_is_the_class_itself() { + class SimpleClass { + } + + JavaType type = new ClassFileImporter().importClass(SimpleClass.class); + + assertThat(type.toErasure()).isEqualTo(type); } @Test public void finds_component_type_chain_of_otherwise_unreferenced_component_type() { + @SuppressWarnings("unused") class OnlyReferencingMultiDimArray { OnlyReferencingMultiDimArray[][][] field; } @@ -131,7 +177,13 @@ class OnlyReferencingMultiDimArray { assertThat(oneDim.getName()).isEqualTo(OnlyReferencingMultiDimArray[].class.getName()); JavaClass original = oneDim.getComponentType(); - assertThat(original).isEqualTo(javaClass); + assertThatType(original).isEqualTo(javaClass); + + assertThat(arrayType.getBaseComponentType()) + .isSameAs(twoDim.getBaseComponentType()) + .isSameAs(oneDim.getBaseComponentType()) + .isSameAs(original.getBaseComponentType()) + .isSameAs(original); } @Test @@ -143,10 +195,10 @@ public void finds_fields_and_methods() { assertThat(javaClass.getMethods()).hasSize(2); for (JavaField field : javaClass.getFields()) { - assertThat(field.getOwner()).isSameAs(javaClass); + assertThatType(field.getOwner()).isSameAs(javaClass); } for (JavaCodeUnit method : javaClass.getCodeUnits()) { - assertThat(method.getOwner()).isSameAs(javaClass); + assertThatType(method.getOwner()).isSameAs(javaClass); } } @@ -160,6 +212,19 @@ public void finds_constructors() { assertThat(javaClass.getConstructors()).is(containing(codeUnitWithSignature(CONSTRUCTOR_NAME, int.class, Object[].class))); } + @Test + public void reports_non_existing_members_as_absent() { + JavaClass javaClass = importClassWithContext(ParentWithFieldAndMethod.class); + + assertThat(javaClass.tryGetField("notthere")).isAbsent(); + assertThat(javaClass.tryGetMethod("notthere")).isAbsent(); + assertThat(javaClass.tryGetMethod("notthere", Object.class)).isAbsent(); + assertThat(javaClass.tryGetMethod("notthere", Object.class.getName())).isAbsent(); + assertThat(javaClass.tryGetConstructor()).isAbsent(); + assertThat(javaClass.tryGetConstructor(String.class)).isAbsent(); + assertThat(javaClass.tryGetConstructor(String.class.getName())).isAbsent(); + } + @Test public void anonymous_class_has_package_of_declaring_class() { Serializable input = new Serializable() { @@ -272,6 +337,17 @@ public void isMetaAnnotatedWith_predicate() { .as("predicate matches").isFalse(); } + @Test + public void isMetaAnnotatedWith_correctly_resolves_cyclic_annotations() { + JavaClass javaClass = importClasses(ClassWithCyclicMetaAnnotation.class, + AnnotationWithCyclicAnnotation.class, MetaAnnotationWithCyclicAnnotation.class, + Retention.class).get(ClassWithCyclicMetaAnnotation.class); + + assertThat(javaClass.isMetaAnnotatedWith(Deprecated.class)).isFalse(); + assertThat(javaClass.isMetaAnnotatedWith(Retention.class)).isTrue(); + assertThat(javaClass.isMetaAnnotatedWith(MetaAnnotationWithCyclicAnnotation.class)).isTrue(); + } + @Test public void allAccesses_contains_accesses_from_superclass() { JavaClass javaClass = importClasses(ClassWithTwoFieldsAndTwoMethods.class, SuperClassWithFieldAndMethod.class, Parent.class) @@ -426,7 +502,15 @@ public void direct_dependencies_from_self_by_member_declarations() { .areAtLeast(2, parameterTypeDependency() .from(AhavingMembersOfTypeB.class) .to(B.class) - .inLineNumber(0)); + .inLineNumber(0)) + .areAtLeastOne(methodChecksInstanceOfDependency() + .from(AhavingMembersOfTypeB.class) + .to(B.class) + .inLineNumber(7)) + .areAtLeastOne(methodChecksInstanceOfDependency() + .from(AhavingMembersOfTypeB.class) + .to(B.class) + .inLineNumber(25)); } @Test @@ -458,8 +542,92 @@ public void direct_dependencies_from_self_by_annotation() { .areAtLeastOne(annotationMemberOfTypeDependency() .from(ClassWithAnnotationDependencies.class) .to(B.class) - .inLineNumber(0)) - ; + .inLineNumber(0)); + } + + @Test + public void finds_array_component_types_as_dependencies_from_self() { + JavaClass javaClass = importClassWithContext(ArrayComponentTypeDependencies.class); + + assertThatDependencies(javaClass.getDirectDependenciesFromSelf()) + .contain( + from(ArrayComponentTypeDependencies.class).to(ComponentTypeDependency.class) + .withDescriptionContaining("Field <%s.asField> depends on component type <%s>", + ArrayComponentTypeDependencies.class.getName(), ComponentTypeDependency.class.getName()) + .inLocation(ArrayComponentTypeDependencies.class, 0). + + from(ArrayComponentTypeDependencies.class).to(ComponentTypeDependency.class) + .withDescriptionContaining("Constructor <%s.(%s)> depends on component type <%s>", + ArrayComponentTypeDependencies.class.getName(), ComponentTypeDependency[].class.getName(), ComponentTypeDependency.class.getName()) + .inLocation(ArrayComponentTypeDependencies.class, 0). + + from(ArrayComponentTypeDependencies.class).to(ComponentTypeDependency.class) + .withDescriptionContaining("Method <%s.asMethodParameter(%s)> depends on component type <%s>", + ArrayComponentTypeDependencies.class.getName(), ComponentTypeDependency[].class.getName(), ComponentTypeDependency.class.getName()) + .inLocation(ArrayComponentTypeDependencies.class, 0). + + from(ArrayComponentTypeDependencies.class).to(ComponentTypeDependency.class) + .withDescriptionContaining("Method <%s.asReturnType()> depends on component type <%s>", + ArrayComponentTypeDependencies.class.getName(), ComponentTypeDependency.class.getName()) + .inLocation(ArrayComponentTypeDependencies.class, 0). + + from(ArrayComponentTypeDependencies.class).to(ComponentTypeDependency.class) + .withDescriptionContaining("Method <%s.asCallTarget()> depends on component type <%s>", + ArrayComponentTypeDependencies.class.getName(), ComponentTypeDependency.class.getName()) + .inLocation(ArrayComponentTypeDependencies.class, 18)); + } + + @Test + public void direct_dependencies_from_self_by_type_parameter() { + @SuppressWarnings("unused") + class ClassWithTypeParameters< + FIRST extends List & Serializable & Comparable, + SECOND extends Map< + Map.Entry>, + Map>>>>>>>, + SELF extends ClassWithTypeParameters> { + } + + JavaClass javaClass = importClasses(ClassWithTypeParameters.class).get(ClassWithTypeParameters.class); + + assertThatDependencies(javaClass.getDirectDependenciesFromSelf()) + .contain(from(ClassWithTypeParameters.class).to(List.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'FIRST' depending on") + + .from(ClassWithTypeParameters.class).to(Serializable.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'FIRST' depending on") + + .from(ClassWithTypeParameters.class).to(Comparable.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'FIRST' depending on") + + .from(ClassWithTypeParameters.class).to(Map.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'SECOND' depending on") + + .from(ClassWithTypeParameters.class).to(Map.Entry.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'SECOND' depending on") + + .from(ClassWithTypeParameters.class).to(String.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'SECOND' depending on") + + .from(ClassWithTypeParameters.class).to(BufferedInputStream[][].class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'SECOND' depending on") + + .from(ClassWithTypeParameters.class).to(Serializable.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'SECOND' depending on") + + .from(ClassWithTypeParameters.class).to(List.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'SECOND' depending on") + + .from(ClassWithTypeParameters.class).to(Set.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'SECOND' depending on") + + .from(ClassWithTypeParameters.class).to(Iterable.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'SECOND' depending on") + + .from(ClassWithTypeParameters.class).to(File.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'SECOND' depending on") + ); } @Test @@ -469,13 +637,16 @@ public void direct_dependencies_from_self_finds_correct_set_of_target_types() { Set targets = FluentIterable.from(javaClass.getDirectDependenciesFromSelf()) .transform(toGuava(GET_TARGET_CLASS)).toSet(); - assertThatClasses(targets).matchInAnyOrder( + assertThatTypes(targets).matchInAnyOrder( B.class, AhavingMembersOfTypeB.class, Object.class, String.class, List.class, Serializable.class, SomeSuperClass.class, WithType.class, WithNestedAnnotations.class, OnClass.class, OnMethod.class, OnConstructor.class, OnField.class, MetaAnnotated.class, WithEnum.class, WithPrimitive.class, + WithOtherEnum.class, WithOtherType.class, SomeEnumAsAnnotationParameter.class, SomeEnumAsAnnotationArrayParameter.class, - SomeEnumAsNestedAnnotationParameter.class, SomeEnumAsDefaultParameter.class); + SomeEnumAsNestedAnnotationParameter.class, SomeEnumAsDefaultParameter.class, + SomeOtherEnumAsAnnotationParameter.class, SomeOtherEnumAsDefaultParameter.class, + SomeOtherEnumAsAnnotationArrayParameter.class, SomeTypeAsDefaultParameter.class); } @Test @@ -534,7 +705,15 @@ public void direct_dependencies_to_self_by_member_declarations() { .areAtLeast(2, parameterTypeDependency() .from(AhavingMembersOfTypeB.class) .to(B.class) - .inLineNumber(0)); + .inLineNumber(0)) + .areAtLeastOne(methodChecksInstanceOfDependency() + .from(AhavingMembersOfTypeB.class) + .to(B.class) + .inLineNumber(7)) + .areAtLeastOne(methodChecksInstanceOfDependency() + .from(AhavingMembersOfTypeB.class) + .to(B.class) + .inLineNumber(25)); JavaClass exceptionClass = importClassesWithContext(AhavingMembersOfTypeB.class, B.BException.class) .get(B.BException.class); @@ -587,22 +766,84 @@ public void direct_dependencies_to_self_by_annotation() { .inLineNumber(0)); } + @Test + public void finds_array_component_types_as_dependencies_to_self() { + JavaClass javaClass = new ClassFileImporter().importClasses(ArrayComponentTypeDependencies.class, ComponentTypeDependency.class) + .get(ComponentTypeDependency.class); + + assertThatDependencies(javaClass.getDirectDependenciesToSelf()) + .contain( + from(ArrayComponentTypeDependencies.class).to(ComponentTypeDependency.class) + .withDescriptionContaining("Field <%s.asField> depends on component type <%s>", + ArrayComponentTypeDependencies.class.getName(), ComponentTypeDependency.class.getName()) + .inLocation(ArrayComponentTypeDependencies.class, 0). + + from(ArrayComponentTypeDependencies.class).to(ComponentTypeDependency.class) + .withDescriptionContaining("Constructor <%s.(%s)> depends on component type <%s>", + ArrayComponentTypeDependencies.class.getName(), ComponentTypeDependency[].class.getName(), ComponentTypeDependency.class.getName()) + .inLocation(ArrayComponentTypeDependencies.class, 0). + + from(ArrayComponentTypeDependencies.class).to(ComponentTypeDependency.class) + .withDescriptionContaining("Method <%s.asMethodParameter(%s)> depends on component type <%s>", + ArrayComponentTypeDependencies.class.getName(), ComponentTypeDependency[].class.getName(), ComponentTypeDependency.class.getName()) + .inLocation(ArrayComponentTypeDependencies.class, 0). + + from(ArrayComponentTypeDependencies.class).to(ComponentTypeDependency.class) + .withDescriptionContaining("Method <%s.asReturnType()> depends on component type <%s>", + ArrayComponentTypeDependencies.class.getName(), ComponentTypeDependency.class.getName()) + .inLocation(ArrayComponentTypeDependencies.class, 0). + + from(ArrayComponentTypeDependencies.class).to(ComponentTypeDependency.class) + .withDescriptionContaining("Method <%s.asCallTarget()> depends on component type <%s>", + ArrayComponentTypeDependencies.class.getName(), ComponentTypeDependency.class.getName()) + .inLocation(ArrayComponentTypeDependencies.class, 18)); + } + + @Test + public void direct_dependencies_to_self_by_type_parameter() { + class ClassOtherTypeSignaturesDependOn { + } + @SuppressWarnings("unused") + class FirstDependingOnOtherThroughTypeParameter { + } + @SuppressWarnings("unused") + class SecondDependingOnOtherThroughTypeParameter< + U extends Map>>, + V extends Map> { + } + + JavaClass someClass = importClasses(ClassOtherTypeSignaturesDependOn.class, FirstDependingOnOtherThroughTypeParameter.class, SecondDependingOnOtherThroughTypeParameter.class) + .get(ClassOtherTypeSignaturesDependOn.class); + + assertThatDependencies(someClass.getDirectDependenciesToSelf()) + .contain(from(FirstDependingOnOtherThroughTypeParameter.class).to(ClassOtherTypeSignaturesDependOn.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'T' depending on") + + .from(SecondDependingOnOtherThroughTypeParameter.class).to(ClassOtherTypeSignaturesDependOn.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'U' depending on") + + .from(SecondDependingOnOtherThroughTypeParameter.class).to(ClassOtherTypeSignaturesDependOn.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'V' depending on") + ); + } + @Test public void direct_dependencies_to_self_finds_correct_set_of_origin_types() { JavaClasses classes = importPackagesOf(getClass()); Set origins = getOriginsOfDependenciesTo(classes.get(WithType.class)); - assertThatClasses(origins).matchInAnyOrder(ClassWithAnnotationDependencies.class, ClassWithSelfReferences.class, OnMethodParam.class); + assertThatTypes(origins).matchInAnyOrder( + ClassWithAnnotationDependencies.class, ClassWithSelfReferences.class, WithNestedAnnotations.class, OnMethodParam.class); origins = getOriginsOfDependenciesTo(classes.get(B.class)); - assertThatClasses(origins).matchInAnyOrder( + assertThatTypes(origins).matchInAnyOrder( ClassWithAnnotationDependencies.class, OnMethodParam.class, AAccessingB.class, AhavingMembersOfTypeB.class); origins = getOriginsOfDependenciesTo(classes.get(SomeEnumAsNestedAnnotationParameter.class)); - assertThatClasses(origins).matchInAnyOrder( + assertThatTypes(origins).matchInAnyOrder( ClassWithAnnotationDependencies.class, WithEnum.class); } @@ -962,6 +1203,10 @@ private static DependencyConditionCreation methodThrowsDeclarationDependency() { return new DependencyConditionCreation("throws type"); } + private static DependencyConditionCreation methodChecksInstanceOfDependency() { + return new DependencyConditionCreation("checks instanceof"); + } + private static DependencyConditionCreation annotationTypeDependency() { return new DependencyConditionCreation("is annotated with"); } @@ -1016,7 +1261,7 @@ Step2 from(Class origin) { private class Step2 { private final Class origin; - private String originDescription; + private final String originDescription; Step2(Class origin) { this.origin = origin; @@ -1037,7 +1282,7 @@ private class Step3 { Step3(Class target) { this.target = target; - targetDescription = target.getSimpleName(); + targetDescription = target.getName(); } Step3(Class target, String targetName) { @@ -1053,7 +1298,7 @@ public boolean matches(Dependency value) { return value.getOriginClass().isEquivalentTo(origin) && value.getTargetClass().isEquivalentTo(target) && value.getDescription().matches(String.format(".*%s.*%s.*%s.*:%d.*", - origin.getSimpleName(), descriptionPart, targetDescription, lineNumber)); + quote(origin.getSimpleName()), quote(descriptionPart), quote(targetDescription), lineNumber)); } }; } @@ -1142,7 +1387,7 @@ public ToEvaluation from(Class fromType) { } private class Evaluation { - private List> assignableAssertion = new ArrayList<>(); + private final List> assignableAssertion = new ArrayList<>(); private final Set> additionalTypes = new HashSet<>(); @@ -1238,6 +1483,7 @@ protected String stringMethod() { } static class ClassWithInnerClass { + @SuppressWarnings("InnerClassMayBeStatic") class Inner { } } @@ -1324,6 +1570,7 @@ public void run() { }; } + @SuppressWarnings("InnerClassMayBeStatic") private class NamedInnerClass { private class NestedNamedInnerClass { } @@ -1340,13 +1587,15 @@ private static class SomeSuperClass { nested = { @WithType(type = B.class) }, + typeWithDefaults = @WithOtherType, withEnum = @WithEnum( someEnum = SomeEnumAsNestedAnnotationParameter.NESTED_ANNOTATION_PARAMETER, enumArray = {SomeEnumAsAnnotationArrayParameter.ANNOTATION_ARRAY_PARAMETER} ) ) @MetaAnnotated - public static class ClassWithAnnotationDependencies extends SomeSuperClass { + @SuppressWarnings("unused") + private static class ClassWithAnnotationDependencies extends SomeSuperClass { @OnField(SomeEnumAsAnnotationParameter.ANNOTATION_PARAMETER) Object field; @@ -1363,48 +1612,62 @@ void method(@OnMethodParam String param) { } } - @interface OnClass { + private @interface OnClass { } - @interface OnField { + private @interface OnField { SomeEnumAsAnnotationParameter value(); } - @interface OnConstructor { + private @interface OnConstructor { } - @interface OnMethod { + private @interface OnMethod { } @WithType(type = B.class) - @interface OnMethodParam { + private @interface OnMethodParam { } @Retention(RUNTIME) - @interface WithType { + private @interface WithType { Class type(); } + @SuppressWarnings("unused") @Retention(RUNTIME) - @interface WithNestedAnnotations { + private @interface WithOtherType { + Class typeWithDefault() default SomeTypeAsDefaultParameter.class; + } + + @SuppressWarnings("unused") + @Retention(RUNTIME) + private @interface WithNestedAnnotations { Class outerType(); WithType[] nested(); + WithOtherType typeWithDefaults(); + WithEnum withEnum(); + + WithOtherEnum annotationWithDefault() default @WithOtherEnum( + someEnum = SomeOtherEnumAsAnnotationParameter.OTHER_ANNOTATION_PARAMETER, + enumArray = {SomeOtherEnumAsAnnotationArrayParameter.OTHER_ANNOTATION_ARRAY_PARAMETER, SomeOtherEnumAsAnnotationArrayParameter.OTHER_ANNOTATION_ARRAY_PARAMETER}); } @Retention(RUNTIME) @MetaAnnotation - @interface MetaAnnotation { + private @interface MetaAnnotation { } @Retention(RUNTIME) @MetaAnnotation - @interface MetaAnnotated { + private @interface MetaAnnotated { } - @interface WithEnum { + @SuppressWarnings("unused") + private @interface WithEnum { SomeEnumAsDefaultParameter enumWithDefault() default SomeEnumAsDefaultParameter.DEFAULT_PARAMETER; SomeEnumAsNestedAnnotationParameter someEnum(); @@ -1412,8 +1675,17 @@ void method(@OnMethodParam String param) { SomeEnumAsAnnotationArrayParameter[] enumArray(); } + @SuppressWarnings("unused") + private @interface WithOtherEnum { + SomeOtherEnumAsDefaultParameter enumWithDefault() default SomeOtherEnumAsDefaultParameter.OTHER_DEFAULT_PARAMETER; + + SomeOtherEnumAsAnnotationParameter someEnum(); + + SomeOtherEnumAsAnnotationArrayParameter[] enumArray(); + } + @Retention(RUNTIME) - @interface WithPrimitive { + private @interface WithPrimitive { int someInt(); int[] someInts(); @@ -1423,29 +1695,59 @@ void method(@OnMethodParam String param) { String[] someStrings(); } - enum SomeEnumAsAnnotationParameter { + private enum SomeEnumAsAnnotationParameter { ANNOTATION_PARAMETER } - enum SomeEnumAsNestedAnnotationParameter { + private enum SomeOtherEnumAsAnnotationParameter { + OTHER_ANNOTATION_PARAMETER + } + + private enum SomeEnumAsNestedAnnotationParameter { NESTED_ANNOTATION_PARAMETER } - enum SomeEnumAsDefaultParameter { + private enum SomeEnumAsDefaultParameter { DEFAULT_PARAMETER } - enum SomeEnumAsAnnotationArrayParameter { + private enum SomeOtherEnumAsDefaultParameter { + OTHER_DEFAULT_PARAMETER + } + + private enum SomeEnumAsAnnotationArrayParameter { ANNOTATION_ARRAY_PARAMETER } + private enum SomeOtherEnumAsAnnotationArrayParameter { + OTHER_ANNOTATION_ARRAY_PARAMETER + } + + private static class SomeTypeAsDefaultParameter { + } + + @AnnotationWithCyclicAnnotation + @Retention(RUNTIME) + private @interface MetaAnnotationWithCyclicAnnotation { + } + + @AnnotationWithCyclicAnnotation + @MetaAnnotationWithCyclicAnnotation + private @interface AnnotationWithCyclicAnnotation { + } + + @AnnotationWithCyclicAnnotation + private static class ClassWithCyclicMetaAnnotation { + } + @SuppressWarnings("ALL") private static class ClassWithSelfReferences extends Exception { static { - ClassWithSelfReferences selfReference = new ClassWithSelfReferences(null, null); + ClassWithSelfReferences selfReference = new ClassWithSelfReferences(null, (ClassWithSelfReferences) null); } ClassWithSelfReferences fieldSelfReference; + ClassWithSelfReferences[] arrayFieldSelfReference; ClassWithSelfReferences() throws ClassWithSelfReferences { } @@ -1453,25 +1755,38 @@ private static class ClassWithSelfReferences extends Exception { ClassWithSelfReferences(Object any, ClassWithSelfReferences selfReference) { } + ClassWithSelfReferences(Object any, ClassWithSelfReferences[] selfReference) { + } + ClassWithSelfReferences methodReturnTypeSelfReference() { return null; } + ClassWithSelfReferences[] methodArrayReturnTypeSelfReference() { + return null; + } + void methodParameterSelfReference(Object any, ClassWithSelfReferences selfReference) { } + void methodArrayParameterSelfReference(Object any, ClassWithSelfReferences[] selfReference) { + } + void methodCallSelfReference() { ClassWithSelfReferences self = null; self.methodParameterSelfReference(null, null); + ClassWithSelfReferences[] arraySelf = null; + arraySelf.clone(); } void constructorCallSelfReference() { - new ClassWithSelfReferences(null, null); + new ClassWithSelfReferences(null, (ClassWithSelfReferences) null); } void fieldAccessSelfReference() { ClassWithSelfReferences self = null; self.fieldSelfReference = null; + self.arrayFieldSelfReference = null; } @WithType(type = ClassWithSelfReferences.class) diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTransitiveDependenciesTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTransitiveDependenciesTest.java new file mode 100644 index 0000000000..94b6a01913 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTransitiveDependenciesTest.java @@ -0,0 +1,146 @@ +package com.tngtech.archunit.core.domain; + +import com.tngtech.archunit.core.importer.ClassFileImporter; +import org.junit.Test; + +import static com.tngtech.archunit.testutil.Assertions.assertThatDependencies; + +public class JavaClassTransitiveDependenciesTest { + + @SuppressWarnings("unused") + static class AcyclicGraph { + static class A { + B b; + C[][] c; + } + + static class B { + Integer i; + } + + static class C { + D d; + } + + static class D { + String s; + } + } + + @Test + public void findsTransitiveDependenciesInAcyclicGraph() { + Class a = AcyclicGraph.A.class; + Class b = AcyclicGraph.B.class; + Class c = AcyclicGraph.C.class; + Class d = AcyclicGraph.D.class; + JavaClasses classes = new ClassFileImporter().importClasses(a, b, c, d); + Class cArray = AcyclicGraph.C[][].class; + + // @formatter:off + assertThatDependencies(classes.get(a).getTransitiveDependenciesFromSelf()) + .contain(a, Object.class) + .contain(a, b) + .contain(b, Object.class) + .contain(b, Integer.class) + .contain(a, cArray) + .contain(c, Object.class) + .contain(c, d) + .contain(d, Object.class) + .contain(d, String.class); + + assertThatDependencies(classes.get(b).getTransitiveDependenciesFromSelf()) + .contain(b, Object.class) + .contain(b, Integer.class); + + assertThatDependencies(classes.get(c).getTransitiveDependenciesFromSelf()) + .contain(c, Object.class) + .contain(c, d) + .contain(d, Object.class) + .contain(d, String.class); + // @formatter:on + } + + @SuppressWarnings("unused") + static class CyclicGraph { + static class A { + B b; + C[][] c; + D d; + } + + static class B { + Integer i; + } + + static class C { + A a; + } + + static class D { + E e; + } + + static class E { + A a; + String s; + } + } + + @Test + public void findsTransitiveDependenciesInCyclicGraph() { + Class a = CyclicGraph.A.class; + Class b = CyclicGraph.B.class; + Class c = CyclicGraph.C.class; + Class d = CyclicGraph.D.class; + Class e = CyclicGraph.E.class; + JavaClasses classes = new ClassFileImporter().importClasses(a, b, c, d, e); + Class cArray = CyclicGraph.C[][].class; + + // @formatter:off + assertThatDependencies(classes.get(a).getTransitiveDependenciesFromSelf()) + .contain(a, Object.class) + .contain(a, b) + .contain(b, Object.class) + .contain(b, Integer.class) + .contain(a, cArray) + .contain(c, Object.class) + .contain(c, a) + .contain(a, d) + .contain(d, Object.class) + .contain(d, e) + .contain(e, Object.class) + .contain(e, a) + .contain(e, String.class); + + assertThatDependencies(classes.get(c).getTransitiveDependenciesFromSelf()) + .contain(c, Object.class) + .contain(c, a) + .contain(a, Object.class) + .contain(a, b) + .contain(b, Object.class) + .contain(b, Integer.class) + .contain(a, cArray) + .contain(a, d) + .contain(d, Object.class) + .contain(d, e) + .contain(e, Object.class) + .contain(e, a) + .contain(e, String.class); + + assertThatDependencies(classes.get(d).getTransitiveDependenciesFromSelf()) + .contain(d, Object.class) + .contain(d, e) + .contain(e, Object.class) + .contain(e, a) + .contain(a, Object.class) + .contain(a, b) + .contain(b, Object.class) + .contain(b, Integer.class) + .contain(a, cArray) + .contain(c, Object.class) + .contain(c, a) + .contain(a, d) + .contain(e, String.class); + // @formatter:on + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassesTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassesTest.java index 29e1152b4c..77cae703b7 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassesTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassesTest.java @@ -12,7 +12,7 @@ import static com.tngtech.archunit.core.domain.TestUtils.importClassWithContext; import static com.tngtech.archunit.core.domain.TestUtils.importClassesWithContext; -import static com.tngtech.archunit.testutil.Assertions.assertThatClasses; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; @@ -37,7 +37,7 @@ public void restriction_on_classes_should_keep_the_original_package_tree() { JavaPackage javaPackage = restrictedClasses.getPackage(SomeClass.class.getPackage().getName()); - assertThatClasses(javaPackage.getClasses()).contain(SomeOtherClass.class); + assertThatTypes(javaPackage.getClasses()).contain(SomeOtherClass.class); } @Test @@ -136,4 +136,4 @@ private static class SomeClass { private static class SomeOtherClass { } -} \ No newline at end of file +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaPackageTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaPackageTest.java index 25028e314f..70dd924535 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaPackageTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaPackageTest.java @@ -35,9 +35,10 @@ import static com.tngtech.archunit.core.domain.JavaPackage.Functions.GET_RELATIVE_NAME; import static com.tngtech.archunit.core.domain.JavaPackage.Functions.GET_SUB_PACKAGES; import static com.tngtech.archunit.testutil.Assertions.assertThat; -import static com.tngtech.archunit.testutil.Assertions.assertThatClasses; import static com.tngtech.archunit.testutil.Assertions.assertThatDependencies; import static com.tngtech.archunit.testutil.Assertions.assertThatPackages; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static java.util.regex.Pattern.quote; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -105,7 +106,7 @@ public void creates_single_package() { assertThat(javaPackage.getName()).isEqualTo("java.lang"); assertThat(javaPackage.getDescription()).isEqualTo("Package "); assertThat(javaPackage.getRelativeName()).isEqualTo("lang"); - assertThatClasses(javaPackage.getClasses()).contain(Object.class, String.class); + assertThatTypes(javaPackage.getClasses()).contain(Object.class, String.class); } @Test @@ -200,7 +201,7 @@ public void iterates_all_classes() { JavaPackage javaLang = defaultPackage.getPackage("java.lang"); - assertThatClasses(javaLang.getAllClasses()).contain(Object.class, String.class, Annotation.class, Field.class); + assertThatTypes(javaLang.getAllClasses()).contain(Object.class, String.class, Annotation.class, Field.class); } @Test @@ -226,7 +227,7 @@ public void visit(JavaClass javaClass) { } }); - assertThatClasses(visitedClasses).contain(String.class, Serializable.class, Security.class); + assertThatTypes(visitedClasses).contain(String.class, Serializable.class, Security.class); for (JavaClass visitedClass : visitedClasses) { assertThat(visitedClass.getSimpleName()).startsWith("S"); } @@ -357,7 +358,7 @@ public void test_getAnnotations() { JavaPackage nonAnnotatedPackage = importPackage("packageexamples"); JavaAnnotation annotation = getOnlyElement(annotatedPackage.getAnnotations()); - assertThat(annotation.getRawType()).matches(PackageLevelAnnotation.class); + assertThatType(annotation.getRawType()).matches(PackageLevelAnnotation.class); assertThat(annotation.getOwner()).isEqualTo(annotatedPackage); assertThat(nonAnnotatedPackage.getAnnotations()).isEmpty(); @@ -392,7 +393,7 @@ public void test_getAnnotationOfType_typeName() { final JavaPackage annotatedPackage = importPackage("packageexamples.annotated"); final JavaPackage nonAnnotatedPackage = importPackage("packageexamples"); - assertThat(annotatedPackage.getAnnotationOfType(PackageLevelAnnotation.class.getName()) + assertThatType(annotatedPackage.getAnnotationOfType(PackageLevelAnnotation.class.getName()) .getRawType()).matches(PackageLevelAnnotation.class); assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { @@ -481,7 +482,7 @@ public void function_GET_CLASSES() { Iterable classes = GET_CLASSES.apply(defaultPackage.getPackage("java.lang")); - assertThatClasses(classes).contain(Object.class, String.class); + assertThatTypes(classes).contain(Object.class, String.class); for (JavaClass javaClass : classes) { assertThat(javaClass.getPackageName()).startsWith("java.lang"); } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java new file mode 100644 index 0000000000..1ce12894cc --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java @@ -0,0 +1,166 @@ +package com.tngtech.archunit.core.domain; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; + +import com.tngtech.archunit.core.importer.ClassFileImporter; +import org.junit.Test; + +import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; + +public class JavaTypeVariableTest { + + @Test + public void type_variable_name() { + @SuppressWarnings("unused") + class ClassWithUnboundTypeParameter { + } + + JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithUnboundTypeParameter.class).getTypeParameters().get(0); + + assertThat(type.getName()).isEqualTo("SOME_NAME"); + } + + @Test + public void type_variable_class_owner() { + @SuppressWarnings("unused") + class ClassWithUnboundTypeParameter { + } + + JavaClass javaClass = new ClassFileImporter().importClass(ClassWithUnboundTypeParameter.class); + JavaTypeVariable type = javaClass.getTypeParameters().get(0); + + assertThat(type.getOwner()).isSameAs(javaClass); + assertThat(type.getGenericDeclaration()).isSameAs(javaClass); + } + + @Test + public void type_variable_upper_bounds() { + @SuppressWarnings("unused") + class ClassWithUnboundTypeParameter & Serializable> { + } + + JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithUnboundTypeParameter.class).getTypeParameters().get(0); + + assertThatTypes(type.getBounds()).matchExactly(HashMap.class, Serializable.class); + assertThatTypes(type.getUpperBounds()).matchExactly(HashMap.class, Serializable.class); + } + + @Test + public void erased_unbound_type_variable_is_java_lang_Object() { + @SuppressWarnings("unused") + class ClassWithUnboundTypeParameter { + } + + JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithUnboundTypeParameter.class).getTypeParameters().get(0); + + assertThatType(type.toErasure()).matches(Object.class); + } + + @Test + public void erased_type_variable_bound_by_single_class_is_this_class() { + @SuppressWarnings("unused") + class ClassWithBoundTypeParameterWithSingleClassBound { + } + + JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithBoundTypeParameterWithSingleClassBound.class).getTypeParameters().get(0); + + assertThatType(type.toErasure()).matches(Serializable.class); + } + + @Test + public void erased_type_variable_bound_by_single_generic_class_is_the_erasure_of_this_class() { + @SuppressWarnings("unused") + class ClassWithBoundTypeParameterWithSingleGenericClassBound> { + } + + JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithBoundTypeParameterWithSingleGenericClassBound.class).getTypeParameters().get(0); + + assertThatType(type.toErasure()).matches(List.class); + } + + @Test + public void erased_type_variable_bound_by_multiple_generic_classes_and_interfaces_is_the_erasure_of_the_leftmost_bound() { + @SuppressWarnings("unused") + class ClassWithBoundTypeParameterWithMultipleGenericClassAndInterfaceBounds & Iterable & Serializable> { + } + + JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithBoundTypeParameterWithMultipleGenericClassAndInterfaceBounds.class).getTypeParameters().get(0); + + assertThatType(type.toErasure()).matches(HashMap.class); + } + + @Test + public void erased_type_variable_bound_by_concrete_array_type_is_array_type() { + @SuppressWarnings("unused") + class ClassWithBoundTypeParameterWithSingleGenericArrayBound, U extends List, V extends List[][][]>> { + } + + List> typeParameters = new ClassFileImporter().importClass(ClassWithBoundTypeParameterWithSingleGenericArrayBound.class).getTypeParameters(); + + assertThatType(getTypeArgumentOfFirstBound(typeParameters.get(0)).toErasure()).matches(Object[].class); + assertThatType(getTypeArgumentOfFirstBound(typeParameters.get(1)).toErasure()).matches(String[][].class); + assertThatType(getTypeArgumentOfFirstBound(typeParameters.get(2)).toErasure()).matches(List[][][].class); + } + + @Test + public void erased_type_variable_bound_by_generic_array_type_is_array_with_erasure_component_type() { + @SuppressWarnings("unused") + class ClassWithBoundTypeParameterWithGenericArrayBounds, T extends List, U extends List, V extends List> { + } + + List> typeParameters = new ClassFileImporter().importClass(ClassWithBoundTypeParameterWithGenericArrayBounds.class).getTypeParameters(); + + assertThatType(getTypeArgumentOfFirstBound(typeParameters.get(3)).toErasure()).matches(Object[].class); + assertThatType(getTypeArgumentOfFirstBound(typeParameters.get(4)).toErasure()).matches(String[][].class); + assertThatType(getTypeArgumentOfFirstBound(typeParameters.get(5)).toErasure()).matches(List[][][].class); + } + + @Test + public void toString_unbounded() { + @SuppressWarnings("unused") + class Unbounded { + } + + JavaTypeVariable typeVariable = new ClassFileImporter().importClass(Unbounded.class).getTypeParameters().get(0); + + assertThat(typeVariable.toString()) + .contains(JavaTypeVariable.class.getSimpleName()) + .contains("NAME") + .doesNotContain("extends"); + } + + @Test + public void toString_upper_bounded_by_single_bound() { + @SuppressWarnings("unused") + class BoundedBySingleBound { + } + + JavaTypeVariable typeVariable = new ClassFileImporter().importClass(BoundedBySingleBound.class).getTypeParameters().get(0); + + assertThat(typeVariable.toString()) + .contains(JavaTypeVariable.class.getSimpleName()) + .contains("NAME extends java.lang.String"); + } + + @Test + public void toString_upper_bounded_by_multiple_bounds() { + @SuppressWarnings("unused") + class BoundedByMultipleBounds { + } + + JavaTypeVariable typeVariable = new ClassFileImporter().importClass(BoundedByMultipleBounds.class).getTypeParameters().get(0); + + assertThat(typeVariable.toString()) + .contains(JavaTypeVariable.class.getSimpleName()) + .contains("NAME extends java.lang.String & java.io.Serializable"); + } + + private static JavaType getTypeArgumentOfFirstBound(JavaTypeVariable typeParameter) { + JavaParameterizedType firstBound = (JavaParameterizedType) typeParameter.getBounds().get(0); + return firstBound.getActualTypeArguments().get(0); + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaWildcardTypeTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaWildcardTypeTest.java new file mode 100644 index 0000000000..5203af7692 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaWildcardTypeTest.java @@ -0,0 +1,166 @@ +package com.tngtech.archunit.core.domain; + +import java.io.Serializable; +import java.util.List; + +import com.tngtech.archunit.core.importer.ClassFileImporter; +import org.junit.Test; + +import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; + +public class JavaWildcardTypeTest { + + @Test + public void wildcard_name() { + @SuppressWarnings("unused") + class ClassWithUnboundTypeParameter> { + } + + JavaWildcardType type = importWildcardTypeOf(ClassWithUnboundTypeParameter.class); + + assertThat(type.getName()).isEqualTo("?"); + } + + @Test + public void wildcard_upper_bounds() { + @SuppressWarnings("unused") + class ClassWithUnboundTypeParameter> { + } + + JavaWildcardType type = importWildcardTypeOf(ClassWithUnboundTypeParameter.class); + + assertThatTypes(type.getUpperBounds()).matchExactly(Serializable.class); + } + + @Test + public void wildcard_lower_bounds() { + @SuppressWarnings("unused") + class ClassWithUnboundTypeParameter> { + } + + JavaWildcardType type = importWildcardTypeOf(ClassWithUnboundTypeParameter.class); + + assertThatTypes(type.getLowerBounds()).matchExactly(Serializable.class); + } + + @Test + public void erased_unbound_wildcard_is_java_lang_Object() { + @SuppressWarnings("unused") + class ClassWithUnboundWildcard> { + } + + JavaWildcardType type = importWildcardTypeOf(ClassWithUnboundWildcard.class); + + assertThatType(type.toErasure()).matches(Object.class); + } + + @Test + public void erased_wildcard_bound_by_single_class_is_this_class() { + @SuppressWarnings("unused") + class ClassWithBoundTypeParameterWithSingleClassBound> { + } + + JavaWildcardType type = importWildcardTypeOf(ClassWithBoundTypeParameterWithSingleClassBound.class); + + assertThatType(type.toErasure()).matches(Serializable.class); + } + + @Test + public void erased_wildcard_bound_by_single_generic_class_is_the_erasure_of_this_class() { + @SuppressWarnings("unused") + class ClassWithBoundTypeParameterWithSingleGenericClassBound>> { + } + + JavaWildcardType type = importWildcardTypeOf(ClassWithBoundTypeParameterWithSingleGenericClassBound.class); + + assertThatType(type.toErasure()).matches(List.class); + } + + @Test + public void erased_wildcard_bound_by_concrete_array_type_is_array_type() { + @SuppressWarnings("unused") + class ClassWithBoundTypeParameterWithSingleGenericArrayBound, U extends List, V extends List[][][]>> { + } + + JavaWildcardType wildcard = importWildcardTypeOf(ClassWithBoundTypeParameterWithSingleGenericArrayBound.class, 0); + assertThatType(wildcard.toErasure()).matches(Object[].class); + + wildcard = importWildcardTypeOf(ClassWithBoundTypeParameterWithSingleGenericArrayBound.class, 1); + assertThatType(wildcard.toErasure()).matches(String[][].class); + + wildcard = importWildcardTypeOf(ClassWithBoundTypeParameterWithSingleGenericArrayBound.class, 2); + assertThatType(wildcard.toErasure()).matches(List[][][].class); + } + + @Test + public void erased_wildcard_bound_by_generic_array_type_is_array_with_erasure_component_type() { + @SuppressWarnings("unused") + class ClassWithBoundTypeParameterWithGenericArrayBounds, T extends List, U extends List, V extends List> { + } + + JavaWildcardType wildcard = importWildcardTypeOf(ClassWithBoundTypeParameterWithGenericArrayBounds.class, 3); + assertThatType(wildcard.toErasure()).matches(Object[].class); + + wildcard = importWildcardTypeOf(ClassWithBoundTypeParameterWithGenericArrayBounds.class, 4); + assertThatType(wildcard.toErasure()).matches(String[][].class); + + wildcard = importWildcardTypeOf(ClassWithBoundTypeParameterWithGenericArrayBounds.class, 5); + assertThatType(wildcard.toErasure()).matches(List[][][].class); + } + + @Test + public void toString_unbounded() { + @SuppressWarnings("unused") + class Unbounded> { + } + + JavaWildcardType wildcardType = importWildcardTypeOf(Unbounded.class); + + assertThat(wildcardType.toString()) + .contains(JavaWildcardType.class.getSimpleName()) + .contains("?") + .doesNotContain("extends") + .doesNotContain("super"); + } + + @Test + public void toString_upper_bounded() { + @SuppressWarnings("unused") + class UpperBounded> { + } + + JavaWildcardType wildcardType = importWildcardTypeOf(UpperBounded.class); + + assertThat(wildcardType.toString()) + .contains(JavaWildcardType.class.getSimpleName()) + .contains("? extends java.lang.String") + .doesNotContain("super"); + } + + @Test + public void toString_lower_bounded() { + @SuppressWarnings("unused") + class LowerBounded> { + } + + JavaWildcardType wildcardType = importWildcardTypeOf(LowerBounded.class); + + assertThat(wildcardType.toString()) + .contains(JavaWildcardType.class.getSimpleName()) + .contains("? super java.lang.String") + .doesNotContain("extends"); + } + + private JavaWildcardType importWildcardTypeOf(Class clazz) { + return importWildcardTypeOf(clazz, 0); + } + + private JavaWildcardType importWildcardTypeOf(Class clazz, int typeParameterIndex) { + JavaType listType = new ClassFileImporter().importClass(clazz).getTypeParameters().get(typeParameterIndex) + .getUpperBounds().get(0); + JavaType wildcardType = ((JavaParameterizedType) listType).getActualTypeArguments().get(0); + return (JavaWildcardType) wildcardType; + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/SourceTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/SourceTest.java index d7c1b52cbd..868a8722f5 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/SourceTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/SourceTest.java @@ -28,6 +28,7 @@ import static com.tngtech.archunit.testutil.Assertions.assertThat; import static com.tngtech.java.junit.dataprovider.DataProviders.$; import static com.tngtech.java.junit.dataprovider.DataProviders.$$; +import static java.nio.charset.StandardCharsets.UTF_8; @RunWith(DataProviderRunner.class) public class SourceTest { @@ -38,10 +39,10 @@ public void tearDown() { @Test public void source_file_name() throws URISyntaxException { - Source source = new Source(urlOf(Object.class).toURI(), Optional.of("SomeClass.java")); + Source source = new Source(urlOf(Object.class).toURI(), Optional.of("SomeClass.java"), false); assertThat(source.getFileName()).as("source file name").contains("SomeClass.java"); - source = new Source(urlOf(Object.class).toURI(), Optional.absent()); + source = new Source(urlOf(Object.class).toURI(), Optional.absent(), false); assertThat(source.getFileName()).as("source file name").isAbsent(); } @@ -96,7 +97,7 @@ public static Object[][] equalMd5Sums() { return $$( $(Md5sum.UNDETERMINED, Md5sum.UNDETERMINED), $(Md5sum.NOT_SUPPORTED, Md5sum.NOT_SUPPORTED), - $(Md5sum.of("anything".getBytes()), Md5sum.of("anything".getBytes()))); + $(md5sumOf("anything"), md5sumOf("anything"))); } @Test @@ -111,8 +112,8 @@ public static List> unequalMd5Sums() { return createUnequalTestCasesFor( Md5sum.UNDETERMINED, Md5sum.NOT_SUPPORTED, - Md5sum.of("anything".getBytes()), - Md5sum.of("totallyDifferent".getBytes())); + md5sumOf("anything"), + md5sumOf("totallyDifferent")); } private static List> createUnequalTestCasesFor(Md5sum... md5sums) { @@ -148,13 +149,12 @@ public void compensates_error_on_md5_calculation() throws Exception { } @Test - public void disables_md5_calculation_via_config() throws Exception { - ArchConfiguration.get().setMd5InClassSourcesEnabled(false); + public void disables_md5_calculation_via_parameter() throws Exception { + Source source = new Source(urlOf(getClass()).toURI(), Optional.of("any.java"), false); + assertThat(source.getMd5sum()).isEqualTo(Md5sum.DISABLED); - assertThat(Md5sum.of("any".getBytes())).isEqualTo(Md5sum.DISABLED); - - // NOTE: This tests that URIs are note resolved, which costs performance, if it would be resolved, we would get UNDETERMINED - assertThat(newSource(new URI("bummer")).getMd5sum()).isEqualTo(Md5sum.DISABLED); + source = new Source(urlOf(getClass()).toURI(), Optional.of("any.java"), true); + assertThat(source.getMd5sum().asBytes()).isEqualTo(expectedMd5BytesAt(source.getUri().toURL())); } private Source newSource(URL url) throws URISyntaxException { @@ -162,7 +162,11 @@ private Source newSource(URL url) throws URISyntaxException { } private Source newSource(URI uri) { - return new Source(uri, Optional.absent()); + return new Source(uri, Optional.absent(), true); + } + + private static Md5sum md5sumOf(String data) { + return TestUtils.md5sumOf(data.getBytes(UTF_8)); } private static byte[] expectedMd5BytesAt(URL url) throws IOException, NoSuchAlgorithmException { @@ -186,4 +190,4 @@ public static URL urlOf(Class clazz) { return checkNotNull(clazz.getResource("/" + clazz.getName().replace('.', '/') + ".class"), "Can't determine url of %s", clazz.getName()); } -} \ No newline at end of file +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/TestUtils.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/TestUtils.java index a3d2caf133..a4dda5e075 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/TestUtils.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/TestUtils.java @@ -1,6 +1,7 @@ package com.tngtech.archunit.core.domain; -import java.lang.annotation.Annotation; +import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -8,9 +9,10 @@ import java.util.Set; import com.google.common.base.Suppliers; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.common.io.Files; import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.domain.AccessTarget.ConstructorCallTarget; import com.tngtech.archunit.core.domain.AccessTarget.FieldAccessTarget; import com.tngtech.archunit.core.domain.AccessTarget.MethodCallTarget; @@ -19,7 +21,6 @@ import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodCallBuilder; import com.tngtech.archunit.core.importer.ImportTestUtils; -import com.tngtech.archunit.core.importer.ImportTestUtils.ImportedTestClasses; import static com.google.common.collect.Iterables.getOnlyElement; import static com.tngtech.archunit.core.domain.Formatters.formatMethod; @@ -27,6 +28,7 @@ import static com.tngtech.archunit.core.importer.ImportTestUtils.newFieldAccess; import static com.tngtech.archunit.core.importer.ImportTestUtils.newMethodCall; import static com.tngtech.archunit.testutil.ReflectionTestUtils.getHierarchy; +import static org.assertj.core.util.Files.newTemporaryFile; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -43,7 +45,11 @@ public static JavaClassList javaClassList(Class... types) { @SafeVarargs public static ThrowsClause throwsClause(Class... types) { - List importedTypes = ImmutableList.copyOf(importClassesWithContext(types)); + JavaClasses classes = importClassesWithContext(types); + List importedTypes = new ArrayList<>(); + for (Class type : types) { + importedTypes.add(classes.get(type)); + } JavaMethod irrelevantOwner = importClassWithContext(Object.class).getMethod("toString"); return ThrowsClause.from(irrelevantOwner, importedTypes); } @@ -73,7 +79,13 @@ static ImportedContext withinImportedClasses(Class... contextClasses) { } public static Md5sum md5sumOf(byte[] bytes) { - return Md5sum.of(bytes); + File file = newTemporaryFile(); + try { + Files.write(bytes, file); + return new Source(file.toURI(), Optional.absent(), true).getMd5sum(); + } catch (IOException e) { + throw new RuntimeException(e); + } } public static JavaClass importClassWithContext(Class owner) { @@ -119,14 +131,6 @@ public static Class[] asClasses(List parameters) { return result.toArray(new Class[0]); } - static ImportedTestClasses simpleImportedClasses() { - return ImportTestUtils.simpleImportedClasses(); - } - - static JavaAnnotation javaAnnotationFrom(Annotation annotation, Class annotatedClass) { - return ImportTestUtils.javaAnnotationFrom(annotation, annotatedClass); - } - public static FieldAccessTarget targetFrom(JavaField javaField) { return ImportTestUtils.targetFrom(javaField); } @@ -136,7 +140,7 @@ public static ConstructorCallTarget targetFrom(JavaConstructor constructor) { } public static Dependency dependencyFrom(JavaAccess access) { - return Dependency.tryCreateFromAccess(access).get(); + return getOnlyElement(Dependency.tryCreateFromAccess(access)); } public static class AccessesSimulator { @@ -177,7 +181,7 @@ private JavaMethodCall to(MethodCallTarget methodCallTarget) { for (MethodCallTarget target : targets) { calls.add(newMethodCall(method, target, lineNumber)); } - when(context.getMethodCallsFor(method)).thenReturn(ImmutableSet.copyOf(calls)); + when(context.createMethodCallsFor(method)).thenReturn(ImmutableSet.copyOf(calls)); method.completeFrom(context); return getCallToTarget(methodCallTarget); } @@ -202,7 +206,7 @@ private JavaMethodCall getCallToTarget(MethodCallTarget callTarget) { public void to(JavaField target, AccessType accessType) { ImportContext context = mock(ImportContext.class); - when(context.getFieldAccessesFor(method)) + when(context.createFieldAccessesFor(method)) .thenReturn(ImmutableSet.of( newFieldAccess(method, target, lineNumber, accessType) )); diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/ThrowsClauseTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/ThrowsClauseTest.java index 3f128dd208..e96c9dc4ff 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/ThrowsClauseTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/ThrowsClauseTest.java @@ -9,7 +9,7 @@ import static com.tngtech.archunit.core.domain.JavaClass.Predicates.equivalentTo; import static com.tngtech.archunit.core.domain.TestUtils.importMethod; -import static com.tngtech.archunit.testutil.Assertions.assertThatClasses; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static org.assertj.core.api.Assertions.assertThat; public class ThrowsClauseTest { @@ -32,7 +32,7 @@ public void size() { public void getTypes() { JavaMethod method = importMethod(SomeClass.class, "method"); - assertThatClasses(method.getThrowsClause().getTypes()).matchInAnyOrder(IOException.class, SQLDataException.class); + assertThatTypes(method.getThrowsClause().getTypes()).matchInAnyOrder(IOException.class, SQLDataException.class); } private void assertAllTrue(Iterable> asserts) { @@ -59,4 +59,4 @@ private static class SomeClass { void method() throws IOException, SQLDataException { } } -} \ No newline at end of file +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/packageexamples/annotated/PackageLevelAnnotation.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/packageexamples/annotated/PackageLevelAnnotation.java index 314699e93a..aa43304c56 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/packageexamples/annotated/PackageLevelAnnotation.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/packageexamples/annotated/PackageLevelAnnotation.java @@ -1,11 +1,11 @@ -package com.tngtech.archunit.core.domain.packageexamples.annotated; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.PACKAGE) -public @interface PackageLevelAnnotation { -} +package com.tngtech.archunit.core.domain.packageexamples.annotated; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PACKAGE) +public @interface PackageLevelAnnotation { +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/packageexamples/annotated/package-info.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/packageexamples/annotated/package-info.java index 5132c8172a..6110533479 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/packageexamples/annotated/package-info.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/packageexamples/annotated/package-info.java @@ -1,2 +1,2 @@ -@PackageLevelAnnotation -package com.tngtech.archunit.core.domain.packageexamples.annotated; +@PackageLevelAnnotation +package com.tngtech.archunit.core.domain.packageexamples.annotated; diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasReturnTypeTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasReturnTypeTest.java index b20842d906..dc83ebf1e3 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasReturnTypeTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasReturnTypeTest.java @@ -5,8 +5,10 @@ import org.junit.Test; import static com.tngtech.archunit.core.domain.TestUtils.importClassWithContext; +import static com.tngtech.archunit.core.domain.properties.HasReturnType.Functions.GET_RAW_RETURN_TYPE; import static com.tngtech.archunit.core.domain.properties.HasReturnType.Predicates.rawReturnType; import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; public class HasReturnTypeTest { @Test @@ -44,7 +46,7 @@ public void predicate_on_return_type_by_Predicate() { @Test public void function_get_return_type() { JavaClass expectedType = importClassWithContext(String.class); - assertThat(HasReturnType.Functions.GET_RAW_RETURN_TYPE.apply(newHasReturnType(expectedType))) + assertThatType(GET_RAW_RETURN_TYPE.apply(newHasReturnType(expectedType))) .as("result of GET_RAW_RETURN_TYPE").isEqualTo(expectedType); } @@ -57,4 +59,4 @@ public JavaClass getRawReturnType() { } }; } -} \ No newline at end of file +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasThrowsClauseTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasThrowsClauseTest.java index 007748548c..06e94be661 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasThrowsClauseTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasThrowsClauseTest.java @@ -11,6 +11,7 @@ import org.junit.runner.RunWith; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.equivalentTo; +import static com.tngtech.archunit.core.domain.JavaClass.namesOf; import static com.tngtech.archunit.core.domain.TestUtils.throwsClause; import static com.tngtech.archunit.core.domain.properties.HasThrowsClause.Predicates.throwsClauseContainingType; import static com.tngtech.archunit.testutil.Assertions.assertThat; @@ -78,6 +79,11 @@ private final HasThrowsClause newHasThrowsClause(final Class getThrowsClause() { return throwsClause(throwsDeclarations); } + + @Override + public String toString() { + return HasThrowsClause.class.getSimpleName() + "{ throws " + namesOf(throwsDeclarations) + "}"; + } }; } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasTypeTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasTypeTest.java index 87cb0a2457..76501337b1 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasTypeTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasTypeTest.java @@ -2,6 +2,7 @@ import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; @@ -10,7 +11,9 @@ import static com.tngtech.archunit.core.domain.JavaClass.Predicates.equivalentTo; import static com.tngtech.archunit.core.domain.TestUtils.importClassWithContext; +import static com.tngtech.archunit.core.domain.properties.HasType.Functions.GET_RAW_TYPE; import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach; @RunWith(DataProviderRunner.class) @@ -44,16 +47,21 @@ public void predicate_description() { @Test public void function_getType() { - assertThat(HasType.Functions.GET_RAW_TYPE.apply(newHasType(String.class))).matches(String.class); + assertThatType(GET_RAW_TYPE.apply(newHasType(String.class))).matches(String.class); } private HasType newHasType(final Class owner) { return new HasType() { + @Override + public JavaType getType() { + return getRawType(); + } + @Override public JavaClass getRawType() { return importClassWithContext(owner); } }; } -} \ No newline at end of file +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/AhavingMembersOfTypeB.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/AhavingMembersOfTypeB.java index 96a3a3daaa..3afa6159b8 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/AhavingMembersOfTypeB.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/AhavingMembersOfTypeB.java @@ -4,6 +4,7 @@ @SuppressWarnings({"RedundantThrows", "unused"}) public class AhavingMembersOfTypeB { private B b; + private boolean staticInitializerInstanceofCheck = new Object() instanceof B; public AhavingMembersOfTypeB(B b) throws B.BException { this.b = b; @@ -19,4 +20,8 @@ void methodWithParameterTypeB(String some, B b) { void throwingBException() throws B.BException { } + + void checkingInstanceOfB() { + boolean check = new Object() instanceof B; + } } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/ArrayComponentTypeDependencies.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/ArrayComponentTypeDependencies.java new file mode 100644 index 0000000000..c142a1f0f0 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/ArrayComponentTypeDependencies.java @@ -0,0 +1,20 @@ +package com.tngtech.archunit.core.domain.testobjects; + +@SuppressWarnings("unused") +public class ArrayComponentTypeDependencies { + private ComponentTypeDependency[] asField; + + public ArrayComponentTypeDependencies(ComponentTypeDependency[] asConstructorParameter) { + } + + public void asMethodParameter(ComponentTypeDependency[] asMethodParameter) { + } + + public ComponentTypeDependency[] asReturnType() { + return null; + } + + private void asCallTarget() { + new ComponentTypeDependency[0].clone(); + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/ClassWithDependencyOnInstanceofCheck.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/ClassWithDependencyOnInstanceofCheck.java new file mode 100644 index 0000000000..b61e0f5923 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/ClassWithDependencyOnInstanceofCheck.java @@ -0,0 +1,18 @@ +package com.tngtech.archunit.core.domain.testobjects; + +@SuppressWarnings({"unused", "ConstantConditions"}) +public class ClassWithDependencyOnInstanceofCheck { + + private static final boolean check = new Object() instanceof InstanceOfCheckTarget; + + ClassWithDependencyOnInstanceofCheck(Object o) { + System.out.println(o instanceof InstanceOfCheckTarget); + } + + boolean method(Object o) { + return o instanceof InstanceOfCheckTarget; + } + + public static class InstanceOfCheckTarget { + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/ComponentTypeDependency.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/ComponentTypeDependency.java new file mode 100644 index 0000000000..a5512e2680 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/ComponentTypeDependency.java @@ -0,0 +1,4 @@ +package com.tngtech.archunit.core.domain.testobjects; + +public class ComponentTypeDependency { +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/IsArrayTestClass.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/IsArrayTestClass.java deleted file mode 100644 index d9eb04aa38..0000000000 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/IsArrayTestClass.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.tngtech.archunit.core.domain.testobjects; - -public class IsArrayTestClass { - - public Object[] anArray() { - return null; - } - - public Object notAnArray() { - return null; - } -} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAnnotationsTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAnnotationsTest.java new file mode 100644 index 0000000000..3e38e19336 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAnnotationsTest.java @@ -0,0 +1,658 @@ +package com.tngtech.archunit.core.importer; + +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.reflect.Array; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.ImmutableSet; +import com.tngtech.archunit.base.Optional; +import com.tngtech.archunit.core.domain.JavaAnnotation; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.domain.JavaConstructor; +import com.tngtech.archunit.core.domain.JavaEnumConstant; +import com.tngtech.archunit.core.domain.JavaField; +import com.tngtech.archunit.core.domain.JavaMethod; +import com.tngtech.archunit.core.domain.JavaModifier; +import com.tngtech.archunit.core.domain.properties.HasAnnotations; +import com.tngtech.archunit.core.importer.testexamples.SomeAnnotation; +import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.ClassAnnotationWithArrays; +import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.ClassWithAnnotationWithEmptyArrays; +import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.ClassWithComplexAnnotations; +import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.ClassWithOneAnnotation; +import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.ClassWithUnimportedAnnotation; +import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.SimpleAnnotation; +import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.TypeAnnotationWithEnumAndArrayValue; +import com.tngtech.archunit.core.importer.testexamples.annotationfieldimport.ClassWithAnnotatedFields; +import com.tngtech.archunit.core.importer.testexamples.annotationfieldimport.FieldAnnotationWithArrays; +import com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods; +import com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.MethodAnnotationWithArrays; +import com.tngtech.archunit.core.importer.testexamples.simpleimport.AnnotationParameter; +import com.tngtech.archunit.core.importer.testexamples.simpleimport.AnnotationToImport; +import com.tngtech.archunit.core.importer.testexamples.simpleimport.EnumToImport; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.tngtech.archunit.core.importer.testexamples.SomeEnum.OTHER_VALUE; +import static com.tngtech.archunit.core.importer.testexamples.SomeEnum.SOME_VALUE; +import static com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.enumAndArrayAnnotatedMethod; +import static com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.methodAnnotatedWithAnnotationFromParentPackage; +import static com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.methodAnnotatedWithEmptyArrays; +import static com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.stringAndIntAnnotatedMethod; +import static com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.stringAnnotatedMethod; +import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatAnnotation; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; +import static com.tngtech.archunit.testutil.assertion.JavaAnnotationAssertion.annotationProperty; +import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach; + +@RunWith(DataProviderRunner.class) +public class ClassFileImporterAnnotationsTest { + + @Test + public void imports_simple_annotation() { + JavaClass javaClass = new ClassFileImporter().importPackagesOf(AnnotationToImport.class).get(AnnotationToImport.class); + + assertThat(javaClass.getName()).as("full name").isEqualTo(AnnotationToImport.class.getName()); + assertThat(javaClass.getSimpleName()).as("simple name").isEqualTo(AnnotationToImport.class.getSimpleName()); + assertThat(javaClass.getPackageName()).as("package name").isEqualTo(AnnotationToImport.class.getPackage().getName()); + assertThat(javaClass.getModifiers()).as("modifiers").containsOnly(JavaModifier.PUBLIC, JavaModifier.ABSTRACT); + assertThat(javaClass.getSuperClass()).as("super class").isAbsent(); + assertThatTypes(javaClass.getInterfaces()).as("interfaces").matchInAnyOrder(Annotation.class); + assertThat(javaClass.isInterface()).as("is interface").isTrue(); + assertThat(javaClass.isEnum()).as("is enum").isFalse(); + assertThat(javaClass.isAnnotation()).as("is annotation").isTrue(); + + assertThat(getAnnotationDefaultValue(javaClass, "someStringMethod", String.class)).isEqualTo("DEFAULT"); + assertThatType(getAnnotationDefaultValue(javaClass, "someTypeMethod", JavaClass.class)).matches(List.class); + assertThat(getAnnotationDefaultValue(javaClass, "someEnumMethod", JavaEnumConstant.class)).isEquivalentTo(EnumToImport.SECOND); + assertThatType(getAnnotationDefaultValue(javaClass, "someAnnotationMethod", JavaAnnotation.class).getRawType()).matches(AnnotationParameter.class); + } + + @Test + public void imports_annotation_defaults() { + JavaClass annotationType = new ClassFileImporter().importPackagesOf(TypeAnnotationWithEnumAndArrayValue.class).get(TypeAnnotationWithEnumAndArrayValue.class); + + assertThat((JavaEnumConstant) annotationType.getMethod("valueWithDefault") + .getDefaultValue().get()) + .as("default of valueWithDefault()").isEquivalentTo(SOME_VALUE); + assertThat(((JavaEnumConstant[]) annotationType.getMethod("enumArrayWithDefault") + .getDefaultValue().get())) + .as("default of enumArrayWithDefault()").matches(OTHER_VALUE); + assertThat(((JavaAnnotation) annotationType.getMethod("subAnnotationWithDefault") + .getDefaultValue().get()).get("value").get()) + .as("default of subAnnotationWithDefault()").isEqualTo("default"); + assertThat(((JavaAnnotation[]) annotationType.getMethod("subAnnotationArrayWithDefault") + .getDefaultValue().get())[0].get("value").get()) + .as("default of subAnnotationArrayWithDefault()").isEqualTo("first"); + assertThatType((JavaClass) annotationType.getMethod("clazzWithDefault") + .getDefaultValue().get()) + .as("default of clazzWithDefault()").matches(String.class); + assertThat((JavaClass[]) annotationType.getMethod("classesWithDefault") + .getDefaultValue().get()) + .as("default of clazzWithDefault()").matchExactly(Serializable.class, List.class); + } + + @Test + public void imports_class_with_one_annotation_correctly() { + JavaClass clazz = new ClassFileImporter().importPackagesOf(ClassWithOneAnnotation.class) + .get(ClassWithOneAnnotation.class); + + JavaAnnotation annotation = clazz.getAnnotationOfType(SimpleAnnotation.class.getName()); + assertThatType(annotation.getRawType()).matches(SimpleAnnotation.class); + assertThatType(annotation.getOwner()).isEqualTo(clazz); + + JavaAnnotation annotationByName = clazz.getAnnotationOfType(SimpleAnnotation.class.getName()); + assertThat(annotationByName).isEqualTo(annotation); + + assertThat(annotation.get("value").get()).isEqualTo("test"); + + assertThatType(clazz).matches(ClassWithOneAnnotation.class); + } + + @Test + public void class_handles_optional_annotation_correctly() { + JavaClass clazz = new ClassFileImporter().importPackagesOf(ClassWithOneAnnotation.class) + .get(ClassWithOneAnnotation.class); + + assertThat(clazz.tryGetAnnotationOfType(SimpleAnnotation.class)).isPresent(); + assertThat(clazz.tryGetAnnotationOfType(Deprecated.class)).isAbsent(); + } + + @Test + public void imports_class_with_complex_annotations_correctly() { + JavaClass clazz = new ClassFileImporter().importPackagesOf(ClassWithComplexAnnotations.class) + .get(ClassWithComplexAnnotations.class); + + assertThat(clazz.getAnnotations()).as("annotations of " + clazz.getSimpleName()).hasSize(2); + + JavaAnnotation annotation = clazz.getAnnotationOfType(TypeAnnotationWithEnumAndArrayValue.class.getName()); + assertThat((JavaEnumConstant) annotation.get("value").get()).isEquivalentTo(OTHER_VALUE); + assertThat((JavaEnumConstant) annotation.get("valueWithDefault").get()).isEquivalentTo(SOME_VALUE); + assertThat(((JavaEnumConstant[]) annotation.get("enumArray").get())).matches(SOME_VALUE, OTHER_VALUE); + assertThat(((JavaEnumConstant[]) annotation.get("enumArrayWithDefault").get())).matches(OTHER_VALUE); + JavaAnnotation subAnnotation = (JavaAnnotation) annotation.get("subAnnotation").get(); + assertThat(subAnnotation.get("value").get()).isEqualTo("sub"); + assertThat(subAnnotation.getOwner()).isEqualTo(annotation); + assertThat(subAnnotation.getAnnotatedElement()).isEqualTo(clazz); + assertThat(((JavaAnnotation) annotation.get("subAnnotationWithDefault").get()).get("value").get()) + .isEqualTo("default"); + JavaAnnotation[] subAnnotationArray = (JavaAnnotation[]) annotation.get("subAnnotationArray").get(); + assertThat(subAnnotationArray[0].get("value").get()).isEqualTo("otherFirst"); + assertThat(subAnnotationArray[0].getOwner()).isEqualTo(annotation); + assertThat(subAnnotationArray[0].getAnnotatedElement()).isEqualTo(clazz); + assertThat(((JavaAnnotation[]) annotation.get("subAnnotationArrayWithDefault").get())[0].get("value").get()) + .isEqualTo("first"); + assertThatType((JavaClass) annotation.get("clazz").get()).matches(Serializable.class); + assertThatType((JavaClass) annotation.get("clazzWithDefault").get()).matches(String.class); + assertThat((JavaClass[]) annotation.get("classes").get()).matchExactly(Serializable.class, String.class); + assertThat((JavaClass[]) annotation.get("classesWithDefault").get()).matchExactly(Serializable.class, List.class); + + assertThatType(clazz).matches(ClassWithComplexAnnotations.class); + } + + @Test + public void imports_class_with_annotation_with_empty_array() { + JavaClass clazz = new ClassFileImporter().importPackagesOf(ClassWithAnnotationWithEmptyArrays.class) + .get(ClassWithAnnotationWithEmptyArrays.class); + + JavaAnnotation annotation = clazz.getAnnotationOfType(ClassAnnotationWithArrays.class.getName()); + + assertThat(Array.getLength(annotation.get("primitives").get())).isZero(); + assertThat(Array.getLength(annotation.get("objects").get())).isZero(); + assertThat(Array.getLength(annotation.get("enums").get())).isZero(); + assertThat(Array.getLength(annotation.get("classes").get())).isZero(); + assertThat(Array.getLength(annotation.get("annotations").get())).isZero(); + + ClassAnnotationWithArrays reflected = clazz.getAnnotationOfType(ClassAnnotationWithArrays.class); + Assertions.assertThat(reflected.primitives()).isEmpty(); + Assertions.assertThat(reflected.objects()).isEmpty(); + Assertions.assertThat(reflected.enums()).isEmpty(); + Assertions.assertThat(reflected.classes()).isEmpty(); + Assertions.assertThat(reflected.annotations()).isEmpty(); + } + + @Test + public void imports_class_annotated_with_unimported_annotation() { + JavaClass clazz = new ClassFileImporter().importPackagesOf(ClassWithUnimportedAnnotation.class) + .get(ClassWithUnimportedAnnotation.class); + + JavaAnnotation annotation = clazz.getAnnotationOfType(SomeAnnotation.class.getName()); + + assertThat(annotation.get("mandatory")).contains("mandatory"); + assertThat(annotation.get("optional")).contains("optional"); + assertThat((JavaEnumConstant) annotation.get("mandatoryEnum").get()).isEquivalentTo(SOME_VALUE); + assertThat((JavaEnumConstant) annotation.get("optionalEnum").get()).isEquivalentTo(OTHER_VALUE); + + SomeAnnotation reflected = clazz.getAnnotationOfType(SomeAnnotation.class); + Assertions.assertThat(reflected.mandatory()).isEqualTo("mandatory"); + Assertions.assertThat(reflected.optional()).isEqualTo("optional"); + Assertions.assertThat(reflected.mandatoryEnum()).isEqualTo(SOME_VALUE); + Assertions.assertThat(reflected.optionalEnum()).isEqualTo(OTHER_VALUE); + } + + @Test + public void imports_fields_with_one_annotation_correctly() throws Exception { + JavaField field = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedFields.class) + .get(ClassWithAnnotatedFields.class).getField("stringAnnotatedField"); + + JavaAnnotation annotation = field.getAnnotationOfType(ClassWithAnnotatedFields.FieldAnnotationWithStringValue.class.getName()); + assertThatType(annotation.getRawType()).matches(ClassWithAnnotatedFields.FieldAnnotationWithStringValue.class); + assertThat(annotation.get("value").get()).isEqualTo("something"); + assertThat(annotation.getOwner()).as("owning field").isEqualTo(field); + + assertThat(field).isEquivalentTo(field.getOwner().reflect().getDeclaredField("stringAnnotatedField")); + } + + @Test + public void fields_handle_optional_annotation_correctly() { + JavaField field = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedFields.class) + .get(ClassWithAnnotatedFields.class).getField("stringAnnotatedField"); + + assertThat(field.tryGetAnnotationOfType(ClassWithAnnotatedFields.FieldAnnotationWithStringValue.class)).isPresent(); + assertThat(field.tryGetAnnotationOfType(ClassWithAnnotatedFields.FieldAnnotationWithEnumClassAndArrayValue.class)).isAbsent(); + + Optional> optionalAnnotation = field.tryGetAnnotationOfType(ClassWithAnnotatedFields.FieldAnnotationWithStringValue.class.getName()); + assertThat(optionalAnnotation.get().getOwner()).as("owner of optional annotation").isEqualTo(field); + assertThat(field.tryGetAnnotationOfType(ClassWithAnnotatedFields.FieldAnnotationWithEnumClassAndArrayValue.class.getName())) + .as("optional annotation").isAbsent(); + } + + @Test + public void imports_fields_with_two_annotations_correctly() throws Exception { + JavaField field = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedFields.class) + .get(ClassWithAnnotatedFields.class).getField("stringAndIntAnnotatedField"); + + Set> annotations = field.getAnnotations(); + assertThat(annotations).hasSize(2); + assertThat(annotations).extractingResultOf("getOwner").containsOnly(field); + assertThat(annotations).extractingResultOf("getAnnotatedElement").containsOnly(field); + + JavaAnnotation annotationWithString = field.getAnnotationOfType(ClassWithAnnotatedFields.FieldAnnotationWithStringValue.class.getName()); + assertThat(annotationWithString.get("value").get()).isEqualTo("otherThing"); + + JavaAnnotation annotationWithInt = field.getAnnotationOfType(ClassWithAnnotatedFields.FieldAnnotationWithIntValue.class.getName()); + assertThat(annotationWithInt.get("intValue").get()).as("Annotation value with default").isEqualTo(0); + assertThat(annotationWithInt.get("otherValue").get()).isEqualTo("overridden"); + + assertThat(field).isEquivalentTo(field.getOwner().reflect().getDeclaredField("stringAndIntAnnotatedField")); + } + + @Test + public void imports_fields_with_complex_annotations_correctly() throws Exception { + JavaField field = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedFields.class) + .get(ClassWithAnnotatedFields.class).getField("enumAndArrayAnnotatedField"); + + JavaAnnotation annotation = field.getAnnotationOfType(ClassWithAnnotatedFields.FieldAnnotationWithEnumClassAndArrayValue.class.getName()); + assertThat((JavaEnumConstant) annotation.get("value").get()).isEquivalentTo(OTHER_VALUE); + assertThat((JavaEnumConstant) annotation.get("valueWithDefault").get()).isEquivalentTo(SOME_VALUE); + assertThat(((JavaEnumConstant[]) annotation.get("enumArray").get())).matches(SOME_VALUE, OTHER_VALUE); + assertThat(((JavaEnumConstant[]) annotation.get("enumArrayWithDefault").get())).matches(OTHER_VALUE); + JavaAnnotation subAnnotation = (JavaAnnotation) annotation.get("subAnnotation").get(); + assertThat(subAnnotation.get("value").get()).isEqualTo("changed"); + assertThat(subAnnotation.getOwner()).isEqualTo(annotation); + assertThat(subAnnotation.getAnnotatedElement()).isEqualTo(field); + assertThat(((JavaAnnotation) annotation.get("subAnnotationWithDefault").get()).get("value").get()) + .isEqualTo("default"); + JavaAnnotation[] subAnnotationArray = (JavaAnnotation[]) annotation.get("subAnnotationArray").get(); + assertThat(subAnnotationArray[0].get("value").get()).isEqualTo("another"); + assertThat(subAnnotationArray[0].getOwner()).isEqualTo(annotation); + assertThat(subAnnotation.getAnnotatedElement()).isEqualTo(field); + assertThat(((JavaAnnotation[]) annotation.get("subAnnotationArrayWithDefault").get())[0].get("value").get()) + .isEqualTo("first"); + assertThatType((JavaClass) annotation.get("clazz").get()).matches(Map.class); + assertThatType((JavaClass) annotation.get("clazzWithDefault").get()).matches(String.class); + assertThat((JavaClass[]) annotation.get("classes").get()).matchExactly(Object.class, Serializable.class); + assertThat((JavaClass[]) annotation.get("classesWithDefault").get()).matchExactly(Serializable.class, List.class); + + assertThat(field).isEquivalentTo(field.getOwner().reflect().getDeclaredField("enumAndArrayAnnotatedField")); + } + + @Test + public void imports_fields_with_annotation_with_empty_array() { + JavaClass clazz = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedFields.class).get(ClassWithAnnotatedFields.class); + + JavaAnnotation annotation = clazz.getField("fieldAnnotatedWithEmptyArrays") + .getAnnotationOfType(FieldAnnotationWithArrays.class.getName()); + + assertThat(Array.getLength(annotation.get("primitives").get())).isZero(); + assertThat(Array.getLength(annotation.get("objects").get())).isZero(); + assertThat(Array.getLength(annotation.get("enums").get())).isZero(); + assertThat(Array.getLength(annotation.get("classes").get())).isZero(); + assertThat(Array.getLength(annotation.get("annotations").get())).isZero(); + + FieldAnnotationWithArrays reflected = annotation.as(FieldAnnotationWithArrays.class); + assertThat(reflected.primitives()).isEmpty(); + assertThat(reflected.objects()).isEmpty(); + assertThat(reflected.enums()).isEmpty(); + assertThat(reflected.classes()).isEmpty(); + assertThat(reflected.annotations()).isEmpty(); + } + + @Test + public void imports_fields_annotated_with_unimported_annotation() { + JavaClass clazz = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedFields.class).get(ClassWithAnnotatedFields.class); + + JavaAnnotation annotation = clazz.getField("fieldAnnotatedWithAnnotationFromParentPackage") + .getAnnotationOfType(SomeAnnotation.class.getName()); + + assertThat(annotation.get("mandatory")).contains("mandatory"); + assertThat(annotation.get("optional")).contains("optional"); + + SomeAnnotation reflected = annotation.as(SomeAnnotation.class); + assertThat(reflected.mandatory()).isEqualTo("mandatory"); + assertThat(reflected.optional()).isEqualTo("optional"); + } + + @Test + public void imports_methods_with_one_annotation_correctly() throws Exception { + JavaMethod method = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedMethods.class) + .get(ClassWithAnnotatedMethods.class).getMethod(stringAnnotatedMethod); + + JavaAnnotation annotation = method.getAnnotationOfType(ClassWithAnnotatedMethods.MethodAnnotationWithStringValue.class.getName()); + assertThatType(annotation.getRawType()).matches(ClassWithAnnotatedMethods.MethodAnnotationWithStringValue.class); + assertThat(annotation.getOwner()).isEqualTo(method); + assertThat(annotation.get("value").get()).isEqualTo("something"); + + assertThat(method).isEquivalentTo(ClassWithAnnotatedMethods.class.getMethod(stringAnnotatedMethod)); + } + + @Test + public void methods_handle_optional_annotation_correctly() { + JavaMethod method = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedMethods.class) + .get(ClassWithAnnotatedMethods.class).getMethod(stringAnnotatedMethod); + + assertThat(method.tryGetAnnotationOfType(ClassWithAnnotatedMethods.MethodAnnotationWithStringValue.class)).isPresent(); + assertThat(method.tryGetAnnotationOfType(ClassWithAnnotatedMethods.MethodAnnotationWithEnumAndArrayValue.class)).isAbsent(); + + Optional> optionalAnnotation = method.tryGetAnnotationOfType(ClassWithAnnotatedMethods.MethodAnnotationWithStringValue.class.getName()); + assertThat(optionalAnnotation.get().getOwner()).as("owner of optional annotation").isEqualTo(method); + assertThat(method.tryGetAnnotationOfType(ClassWithAnnotatedMethods.MethodAnnotationWithEnumAndArrayValue.class.getName())).isAbsent(); + } + + @Test + public void imports_methods_with_two_annotations_correctly() throws Exception { + JavaMethod method = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedMethods.class) + .get(ClassWithAnnotatedMethods.class).getMethod(stringAndIntAnnotatedMethod); + + Set> annotations = method.getAnnotations(); + assertThat(annotations).hasSize(2); + assertThat(annotations).extractingResultOf("getOwner").containsOnly(method); + assertThat(annotations).extractingResultOf("getAnnotatedElement").containsOnly(method); + + JavaAnnotation annotationWithString = method.getAnnotationOfType(ClassWithAnnotatedMethods.MethodAnnotationWithStringValue.class.getName()); + assertThat(annotationWithString.get("value").get()).isEqualTo("otherThing"); + + JavaAnnotation annotationWithInt = method.getAnnotationOfType(ClassWithAnnotatedMethods.MethodAnnotationWithIntValue.class.getName()); + assertThat(annotationWithInt.get("otherValue").get()).isEqualTo("overridden"); + + assertThat(method).isEquivalentTo(ClassWithAnnotatedMethods.class.getMethod(stringAndIntAnnotatedMethod)); + } + + @Test + public void imports_methods_with_complex_annotations_correctly() throws Exception { + JavaMethod method = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedMethods.class) + .get(ClassWithAnnotatedMethods.class).getMethod(enumAndArrayAnnotatedMethod); + + JavaAnnotation annotation = method.getAnnotationOfType(ClassWithAnnotatedMethods.MethodAnnotationWithEnumAndArrayValue.class.getName()); + assertThat((JavaEnumConstant) annotation.get("value").get()).isEquivalentTo(OTHER_VALUE); + assertThat((JavaEnumConstant) annotation.get("valueWithDefault").get()).isEquivalentTo(SOME_VALUE); + assertThat(((JavaEnumConstant[]) annotation.get("enumArray").get())).matches(SOME_VALUE, OTHER_VALUE); + assertThat(((JavaEnumConstant[]) annotation.get("enumArrayWithDefault").get())).matches(OTHER_VALUE); + JavaAnnotation subAnnotation = (JavaAnnotation) annotation.get("subAnnotation").get(); + assertThat(subAnnotation.get("value").get()).isEqualTo("changed"); + assertThat(subAnnotation.getOwner()).isEqualTo(annotation); + assertThat(subAnnotation.getAnnotatedElement()).isEqualTo(method); + assertThat(((JavaAnnotation) annotation.get("subAnnotationWithDefault").get()).get("value").get()) + .isEqualTo("default"); + JavaAnnotation[] subAnnotationArray = (JavaAnnotation[]) annotation.get("subAnnotationArray").get(); + assertThat(subAnnotationArray[0].get("value").get()).isEqualTo("another"); + assertThat(subAnnotationArray[0].getOwner()).isEqualTo(annotation); + assertThat(subAnnotationArray[0].getAnnotatedElement()).isEqualTo(method); + assertThat(((JavaAnnotation[]) annotation.get("subAnnotationArrayWithDefault").get())[0].get("value").get()) + .isEqualTo("first"); + assertThatType((JavaClass) annotation.get("clazz").get()).matches(Map.class); + assertThatType((JavaClass) annotation.get("clazzWithDefault").get()).matches(String.class); + assertThat((JavaClass[]) annotation.get("classes").get()).matchExactly(Object.class, Serializable.class); + assertThat((JavaClass[]) annotation.get("classesWithDefault").get()).matchExactly(Serializable.class, List.class); + + assertThat(method).isEquivalentTo(ClassWithAnnotatedMethods.class.getMethod(enumAndArrayAnnotatedMethod)); + } + + @Test + public void imports_method_with_annotation_with_empty_array() { + JavaClass clazz = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedMethods.class).get(ClassWithAnnotatedMethods.class); + + JavaAnnotation annotation = clazz.getMethod(methodAnnotatedWithEmptyArrays) + .getAnnotationOfType(MethodAnnotationWithArrays.class.getName()); + + assertThat(Array.getLength(annotation.get("primitives").get())).isZero(); + assertThat(Array.getLength(annotation.get("objects").get())).isZero(); + assertThat(Array.getLength(annotation.get("enums").get())).isZero(); + assertThat(Array.getLength(annotation.get("classes").get())).isZero(); + assertThat(Array.getLength(annotation.get("annotations").get())).isZero(); + + MethodAnnotationWithArrays reflected = annotation.as(MethodAnnotationWithArrays.class); + Assertions.assertThat(reflected.primitives()).isEmpty(); + Assertions.assertThat(reflected.objects()).isEmpty(); + Assertions.assertThat(reflected.enums()).isEmpty(); + Assertions.assertThat(reflected.classes()).isEmpty(); + Assertions.assertThat(reflected.annotations()).isEmpty(); + } + + @Test + public void imports_methods_annotated_with_unimported_annotation() { + JavaClass clazz = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedMethods.class).get(ClassWithAnnotatedMethods.class); + + JavaAnnotation annotation = clazz.getMethod(methodAnnotatedWithAnnotationFromParentPackage) + .getAnnotationOfType(SomeAnnotation.class.getName()); + + assertThat(annotation.get("mandatory")).contains("mandatory"); + assertThat(annotation.get("optional")).contains("optional"); + + SomeAnnotation reflected = annotation.as(SomeAnnotation.class); + Assertions.assertThat(reflected.mandatory()).isEqualTo("mandatory"); + Assertions.assertThat(reflected.optional()).isEqualTo("optional"); + } + + @Test + public void imports_constructors_with_complex_annotations_correctly() throws Exception { + JavaConstructor constructor = new ClassFileImporter().importPackagesOf(ClassWithAnnotatedMethods.class) + .get(ClassWithAnnotatedMethods.class).getConstructor(); + + JavaAnnotation annotation = constructor.getAnnotationOfType(ClassWithAnnotatedMethods.MethodAnnotationWithEnumAndArrayValue.class.getName()); + assertThat((Object[]) annotation.get("classes").get()).extracting("name") + .containsExactly(Object.class.getName(), Serializable.class.getName()); + assertThat(annotation.getOwner()).isEqualTo(constructor); + JavaAnnotation subAnnotation = (JavaAnnotation) annotation.get("subAnnotation").get(); + assertThat(subAnnotation.get("value").get()).isEqualTo("changed"); + assertThat(subAnnotation.getOwner()).isEqualTo(annotation); + assertThat(subAnnotation.getAnnotatedElement()).isEqualTo(constructor); + JavaAnnotation[] subAnnotationArray = (JavaAnnotation[]) annotation.get("subAnnotationArray").get(); + assertThat(subAnnotationArray[0].get("value").get()).isEqualTo("another"); + assertThat(subAnnotationArray[0].getOwner()).isEqualTo(annotation); + assertThat(subAnnotationArray[0].getAnnotatedElement()).isEqualTo(constructor); + + assertThat(constructor).isEquivalentTo(ClassWithAnnotatedMethods.class.getConstructor()); + } + + @Test + public void classes_know_which_annotations_have_their_type() { + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithOneAnnotation.class, SimpleAnnotation.class); + + Set> annotations = classes.get(SimpleAnnotation.class).getAnnotationsWithTypeOfSelf(); + + assertThat(getOnlyElement(annotations).getOwner()).isEqualTo(classes.get(ClassWithOneAnnotation.class)); + } + + @Test + public void classes_know_which_annotation_members_have_their_type() { + @SuppressWarnings("unused") + @ParameterAnnotation(value = String.class) + class Dependent { + @ParameterAnnotation(value = String.class) + String field; + + @ParameterAnnotation(value = String.class) + Dependent() { + } + + @ParameterAnnotation(value = String.class) + void method() { + } + + @ParameterAnnotation(value = List.class) + void notToFind() { + } + } + + JavaClasses classes = new ClassFileImporter().importClasses(Dependent.class, ParameterAnnotation.class, String.class); + Set> annotations = classes.get(String.class).getAnnotationsWithParameterTypeOfSelf(); + + for (JavaAnnotation annotation : annotations) { + assertThatAnnotation(annotation).hasType(ParameterAnnotation.class); + } + + Set> expected = ImmutableSet.>of( + classes.get(Dependent.class).getAnnotationOfType(ParameterAnnotation.class.getName()), + classes.get(Dependent.class).getField("field").getAnnotationOfType(ParameterAnnotation.class.getName()), + classes.get(Dependent.class).getConstructor(getClass()).getAnnotationOfType(ParameterAnnotation.class.getName()), + classes.get(Dependent.class).getMethod("method").getAnnotationOfType(ParameterAnnotation.class.getName()) + ); + assertThat(annotations).as("annotations with parameter type " + String.class.getSimpleName()).containsOnlyElementsOf(expected); + } + + @Test + public void meta_annotation_types_are_transitively_imported() { + JavaClass javaClass = new ClassFileImporter().importClass(MetaAnnotatedClass.class); + JavaAnnotation someAnnotation = javaClass.getAnnotationOfType(MetaAnnotatedAnnotation.class.getName()); + JavaAnnotation someMetaAnnotation = someAnnotation.getRawType() + .getAnnotationOfType(SomeMetaAnnotation.class.getName()); + JavaAnnotation someMetaMetaAnnotation = someMetaAnnotation.getRawType() + .getAnnotationOfType(SomeMetaMetaAnnotation.class.getName()); + JavaAnnotation someMetaMetaMetaAnnotation = someMetaMetaAnnotation.getRawType() + .getAnnotationOfType(SomeMetaMetaMetaAnnotationWithParameters.class.getName()); + + assertThatType(someMetaMetaMetaAnnotation.getType()).matches(SomeMetaMetaMetaAnnotationWithParameters.class); + } + + @DataProvider + public static Object[][] elementsAnnotatedWithSomeAnnotation() { + return testForEach( + new ClassFileImporter().importClass(MetaAnnotatedClass.class), + new ClassFileImporter().importClass(ClassWithMetaAnnotatedField.class).getField("metaAnnotatedField"), + new ClassFileImporter().importClass(ClassWithMetaAnnotatedMethod.class).getMethod("metaAnnotatedMethod"), + new ClassFileImporter().importClass(ClassWithMetaAnnotatedConstructor.class).getConstructor() + ); + } + + @Test + @UseDataProvider("elementsAnnotatedWithSomeAnnotation") + public void parameters_of_meta_annotations_are_transitively_imported(HasAnnotations annotatedWithSomeAnnotation) { + JavaAnnotation someAnnotation = annotatedWithSomeAnnotation + .getAnnotationOfType(MetaAnnotatedAnnotation.class.getName()); + JavaAnnotation metaAnnotationWithParameters = someAnnotation.getRawType() + .getAnnotationOfType(MetaAnnotationWithParameters.class.getName()); + + assertThatAnnotation(metaAnnotationWithParameters) + .hasEnumProperty("someEnum", SomeEnum.CONSTANT) + .hasEnumProperty("someEnumDefault", SomeEnum.VARIABLE) + .hasAnnotationProperty("parameterAnnotation", + annotationProperty() + .withAnnotationType(ParameterAnnotation.class) + .withClassProperty("value", SomeAnnotationParameterType.class)) + .hasAnnotationProperty("parameterAnnotationDefault", + annotationProperty() + .withAnnotationType(ParameterAnnotation.class) + .withClassProperty("value", Integer.class)); + + JavaAnnotation metaMetaMetaAnnotation = someAnnotation + .getRawType().getAnnotationOfType(SomeMetaAnnotation.class.getName()) + .getRawType().getAnnotationOfType(SomeMetaMetaAnnotation.class.getName()) + .getRawType().getAnnotationOfType(SomeMetaMetaMetaAnnotationWithParameters.class.getName()); + + assertThatAnnotation(metaMetaMetaAnnotation) + .hasClassProperty("classParam", SomeMetaMetaMetaAnnotationClassParameter.class) + .hasClassProperty("classParamDefault", String.class) + .hasEnumProperty("enumParam", SomeMetaMetaMetaAnnotationEnumParameter.VALUE) + .hasEnumProperty("enumParamDefault", SomeMetaMetaMetaAnnotationEnumParameter.CONSTANT) + .hasAnnotationProperty("annotationParam", + annotationProperty() + .withAnnotationType(SomeMetaMetaMetaParameterAnnotation.class) + .withClassProperty("value", SomeMetaMetaMetaParameterAnnotationClassParameter.class)) + .hasAnnotationProperty("annotationParamDefault", + annotationProperty() + .withAnnotationType(SomeMetaMetaMetaParameterAnnotation.class)); + } + + @SuppressWarnings({"unchecked", "unused"}) + private static T getAnnotationDefaultValue(JavaClass javaClass, String methodName, Class valueType) { + return (T) javaClass.getMethod(methodName).getDefaultValue().get(); + } + + @SuppressWarnings("unused") + private @interface MetaAnnotationWithParameters { + SomeEnum someEnum(); + + SomeEnum someEnumDefault() default SomeEnum.VARIABLE; + + ParameterAnnotation parameterAnnotation(); + + ParameterAnnotation parameterAnnotationDefault() default @ParameterAnnotation(Integer.class); + } + + @SuppressWarnings("unused") + private @interface SomeMetaMetaMetaAnnotationWithParameters { + Class classParam(); + + Class classParamDefault() default String.class; + + SomeMetaMetaMetaAnnotationEnumParameter enumParam(); + + SomeMetaMetaMetaAnnotationEnumParameter enumParamDefault() default SomeMetaMetaMetaAnnotationEnumParameter.CONSTANT; + + SomeMetaMetaMetaParameterAnnotation annotationParam(); + + SomeMetaMetaMetaParameterAnnotation annotationParamDefault() default @SomeMetaMetaMetaParameterAnnotation(Boolean.class); + } + + @SomeMetaMetaMetaAnnotationWithParameters( + classParam = SomeMetaMetaMetaAnnotationClassParameter.class, + enumParam = SomeMetaMetaMetaAnnotationEnumParameter.VALUE, + annotationParam = @SomeMetaMetaMetaParameterAnnotation(SomeMetaMetaMetaParameterAnnotationClassParameter.class) + ) + private @interface SomeMetaMetaAnnotation { + } + + @SomeMetaMetaAnnotation + private @interface SomeMetaAnnotation { + } + + @MetaAnnotationWithParameters( + someEnum = SomeEnum.CONSTANT, + parameterAnnotation = @ParameterAnnotation(SomeAnnotationParameterType.class) + ) + @SomeMetaAnnotation + private @interface MetaAnnotatedAnnotation { + } + + private enum SomeEnum { + CONSTANT, + VARIABLE + } + + private @interface ParameterAnnotation { + Class value(); + } + + private static class SomeAnnotationParameterType { + } + + @MetaAnnotatedAnnotation + private static class MetaAnnotatedClass { + } + + @SuppressWarnings("unused") + private static class ClassWithMetaAnnotatedField { + @MetaAnnotatedAnnotation + int metaAnnotatedField; + } + + @SuppressWarnings("unused") + private static class ClassWithMetaAnnotatedMethod { + @MetaAnnotatedAnnotation + void metaAnnotatedMethod() { + } + } + + @SuppressWarnings("unused") + private static class ClassWithMetaAnnotatedConstructor { + @MetaAnnotatedAnnotation + ClassWithMetaAnnotatedConstructor() { + } + } + + private static class SomeMetaMetaMetaAnnotationClassParameter { + } + + private enum SomeMetaMetaMetaAnnotationEnumParameter { + VALUE, + CONSTANT + } + + private @interface SomeMetaMetaMetaParameterAnnotation { + Class value(); + } + + private static class SomeMetaMetaMetaParameterAnnotationClassParameter { + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java new file mode 100644 index 0000000000..7fd6dc6935 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java @@ -0,0 +1,620 @@ +package com.tngtech.archunit.core.importer; + +import java.io.Closeable; +import java.io.File; +import java.io.Serializable; +import java.lang.ref.Reference; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.tngtech.archunit.base.Function; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.testutil.ArchConfigurationRule; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion.ExpectedConcreteTypeVariable.typeVariable; +import static com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion.ExpectedConcreteTypeVariableArray.typeVariableArray; +import static com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion.ExpectedConcreteWildcardType.wildcardType; +import static com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion.parameterizedType; +import static com.tngtech.java.junit.dataprovider.DataProviders.$; +import static com.tngtech.java.junit.dataprovider.DataProviders.$$; + +@RunWith(DataProviderRunner.class) +public class ClassFileImporterGenericClassesTest { + + @Rule + public final ArchConfigurationRule configurationRule = new ArchConfigurationRule().resolveAdditionalDependenciesFromClassPath(false); + + @Test + public void imports_empty_list_of_type_parameters_for_non_generic_class() { + JavaClass javaClass = new ClassFileImporter().importClass(getClass()); + + assertThat(javaClass.getTypeParameters()).as("type parameters of non generic class").isEmpty(); + } + + @Test + public void imports_single_generic_type_parameter_of_class() { + @SuppressWarnings("unused") + class ClassWithSingleTypeParameterWithoutBound { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithSingleTypeParameterWithoutBound.class, Object.class); + + JavaClass javaClass = classes.get(ClassWithSingleTypeParameterWithoutBound.class); + + assertThatType(javaClass).hasOnlyTypeParameter("T").withBoundsMatching(Object.class); + } + + @Test + public void imports_multiple_generic_type_parameters_of_class() { + @SuppressWarnings("unused") + class ClassWithThreeTypeParametersWithoutBounds { + } + + JavaClass javaClass = new ClassFileImporter().importClass(ClassWithThreeTypeParametersWithoutBounds.class); + + assertThatType(javaClass).hasTypeParameters("A", "B", "C"); + } + + @Test + public void imports_simple_class_bound_of_type_variable() { + @SuppressWarnings("unused") + class ClassWithSingleTypeParameterWithSimpleClassBound { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithSingleTypeParameterWithSimpleClassBound.class, String.class); + + JavaClass javaClass = classes.get(ClassWithSingleTypeParameterWithSimpleClassBound.class); + + assertThatType(javaClass).hasOnlyTypeParameter("T").withBoundsMatching(String.class); + } + + @Test + public void imports_single_simple_class_bounds_of_multiple_type_variables() { + @SuppressWarnings("unused") + class ClassWithThreeTypeParametersWithSimpleClassBounds { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithThreeTypeParametersWithSimpleClassBounds.class, + String.class, System.class, File.class); + + JavaClass javaClass = classes.get(ClassWithThreeTypeParametersWithSimpleClassBounds.class); + + assertThatType(javaClass) + .hasTypeParameters("A", "B", "C") + .hasTypeParameter("A").withBoundsMatching(String.class) + .hasTypeParameter("B").withBoundsMatching(System.class) + .hasTypeParameter("C").withBoundsMatching(File.class); + } + + @Test + public void imports_simple_interface_bound_of_single_type_variable() { + @SuppressWarnings("unused") + class ClassWithSingleTypeParameterWithSimpleInterfaceBound { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithSingleTypeParameterWithSimpleInterfaceBound.class, Serializable.class); + + JavaClass javaClass = classes.get(ClassWithSingleTypeParameterWithSimpleInterfaceBound.class); + + assertThatType(javaClass).hasOnlyTypeParameter("T").withBoundsMatching(Serializable.class); + } + + @Test + public void imports_multiple_simple_bounds_of_single_type_variable() { + @SuppressWarnings("unused") + class ClassWithSingleTypeParameterWithMultipleSimpleClassAndInterfaceBounds { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithSingleTypeParameterWithMultipleSimpleClassAndInterfaceBounds.class, + String.class, Serializable.class, Runnable.class); + + JavaClass javaClass = classes.get(ClassWithSingleTypeParameterWithMultipleSimpleClassAndInterfaceBounds.class); + + assertThatType(javaClass).hasOnlyTypeParameter("T").withBoundsMatching(String.class, Serializable.class, Runnable.class); + } + + @Test + public void imports_multiple_simple_bounds_of_multiple_type_variables() { + @SuppressWarnings("unused") + class ClassWithThreeTypeParametersWithMultipleSimpleClassAndInterfaceBounds< + A extends String & Serializable, B extends System & Runnable, C extends File & Serializable & Closeable> { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithThreeTypeParametersWithMultipleSimpleClassAndInterfaceBounds.class, + String.class, Serializable.class, System.class, Runnable.class, File.class, Serializable.class, Closeable.class); + + JavaClass javaClass = classes.get(ClassWithThreeTypeParametersWithMultipleSimpleClassAndInterfaceBounds.class); + + assertThatType(javaClass) + .hasTypeParameters("A", "B", "C") + .hasTypeParameter("A").withBoundsMatching(String.class, Serializable.class) + .hasTypeParameter("B").withBoundsMatching(System.class, Runnable.class) + .hasTypeParameter("C").withBoundsMatching(File.class, Serializable.class, Closeable.class); + } + + @Test + public void imports_single_class_bound_with_single_type_parameter_assigned_to_concrete_class() { + @SuppressWarnings("unused") + class ClassWithSingleTypeParameterWithGenericClassBoundAssignedToConcreteClass> { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithSingleTypeParameterWithGenericClassBoundAssignedToConcreteClass.class, + ClassParameterWithSingleTypeParameter.class, String.class); + + JavaClass javaClass = classes.get(ClassWithSingleTypeParameterWithGenericClassBoundAssignedToConcreteClass.class); + + assertThatType(javaClass).hasOnlyTypeParameter("T") + .withBoundsMatching(parameterizedType(ClassParameterWithSingleTypeParameter.class).withTypeArguments(String.class)); + } + + @Test + public void imports_multiple_class_bounds_with_single_type_parameters_assigned_to_concrete_types() { + @SuppressWarnings("unused") + class ClassWithMultipleTypeParametersWithGenericClassOrInterfaceBoundsAssignedToConcreteTypes< + A extends ClassParameterWithSingleTypeParameter, + B extends InterfaceParameterWithSingleTypeParameter, + C extends InterfaceParameterWithSingleTypeParameter> { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithMultipleTypeParametersWithGenericClassOrInterfaceBoundsAssignedToConcreteTypes.class, + ClassParameterWithSingleTypeParameter.class, File.class, InterfaceParameterWithSingleTypeParameter.class, Serializable.class, String.class); + + JavaClass javaClass = classes.get(ClassWithMultipleTypeParametersWithGenericClassOrInterfaceBoundsAssignedToConcreteTypes.class); + + assertThatType(javaClass).hasTypeParameters("A", "B", "C") + .hasTypeParameter("A").withBoundsMatching(parameterizedType(ClassParameterWithSingleTypeParameter.class).withTypeArguments(File.class)) + .hasTypeParameter("B").withBoundsMatching(parameterizedType(InterfaceParameterWithSingleTypeParameter.class).withTypeArguments(Serializable.class)) + .hasTypeParameter("C").withBoundsMatching(parameterizedType(InterfaceParameterWithSingleTypeParameter.class).withTypeArguments(String.class)); + } + + @Test + public void imports_multiple_class_bounds_with_multiple_type_parameters_assigned_to_concrete_types() { + @SuppressWarnings("unused") + class ClassWithTwoTypeParametersWithMultipleGenericClassAndInterfaceBoundsAssignedToConcreteTypes< + A extends ClassParameterWithSingleTypeParameter & InterfaceParameterWithSingleTypeParameter, + B extends Map & Iterable & Function> { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithTwoTypeParametersWithMultipleGenericClassAndInterfaceBoundsAssignedToConcreteTypes.class, + ClassParameterWithSingleTypeParameter.class, InterfaceParameterWithSingleTypeParameter.class, + Map.class, Iterable.class, Function.class, String.class, Serializable.class, File.class, Integer.class, Long.class); + + JavaClass javaClass = classes.get(ClassWithTwoTypeParametersWithMultipleGenericClassAndInterfaceBoundsAssignedToConcreteTypes.class); + + assertThatType(javaClass).hasTypeParameters("A", "B") + .hasTypeParameter("A") + .withBoundsMatching( + parameterizedType(ClassParameterWithSingleTypeParameter.class).withTypeArguments(String.class), + parameterizedType(InterfaceParameterWithSingleTypeParameter.class).withTypeArguments(Serializable.class)) + .hasTypeParameter("B") + .withBoundsMatching( + parameterizedType(Map.class).withTypeArguments(String.class, Serializable.class), + parameterizedType(Iterable.class).withTypeArguments(File.class), + parameterizedType(Function.class).withTypeArguments(Integer.class, Long.class)); + } + + @Test + public void imports_single_type_bound_with_unbound_wildcard() { + @SuppressWarnings("unused") + class ClassWithSingleTypeParameterBoundByTypeWithUnboundWildcard> { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithSingleTypeParameterBoundByTypeWithUnboundWildcard.class, List.class, String.class); + + JavaClass javaClass = classes.get(ClassWithSingleTypeParameterBoundByTypeWithUnboundWildcard.class); + + assertThatType(javaClass) + .hasTypeParameter("T").withBoundsMatching(parameterizedType(List.class).withWildcardTypeParameter()); + } + + @DataProvider + public static Object[][] single_type_bound_with_upper_bound_wildcard() { + @SuppressWarnings("unused") + class ClassWithSingleTypeParameterBoundByTypeWithWildcardWithUpperClassBound> { + } + @SuppressWarnings("unused") + class ClassWithSingleTypeParameterBoundByTypeWithWildcardWithUpperInterfaceBound> { + } + + return $$( + $(ClassWithSingleTypeParameterBoundByTypeWithWildcardWithUpperClassBound.class, String.class), + $(ClassWithSingleTypeParameterBoundByTypeWithWildcardWithUpperInterfaceBound.class, Serializable.class) + ); + } + + @Test + @UseDataProvider("single_type_bound_with_upper_bound_wildcard") + public void imports_single_type_bound_with_upper_bound_wildcard(Class classWithWildcard, Class expectedUpperBound) { + JavaClasses classes = new ClassFileImporter().importClasses(classWithWildcard, List.class, expectedUpperBound); + + JavaClass javaClass = classes.get(classWithWildcard); + + assertThatType(javaClass) + .hasTypeParameter("T").withBoundsMatching(parameterizedType(List.class).withWildcardTypeParameterWithUpperBound(expectedUpperBound)); + } + + @DataProvider + public static Object[][] single_type_bound_with_lower_bound_wildcard() { + @SuppressWarnings("unused") + class ClassWithSingleTypeParameterBoundByTypeWithWildcardWithLowerClassBound> { + } + @SuppressWarnings("unused") + class ClassWithSingleTypeParameterBoundByTypeWithWildcardWithLowerInterfaceBound> { + } + + return $$( + $(ClassWithSingleTypeParameterBoundByTypeWithWildcardWithLowerClassBound.class, String.class), + $(ClassWithSingleTypeParameterBoundByTypeWithWildcardWithLowerInterfaceBound.class, Serializable.class) + ); + } + + @Test + @UseDataProvider("single_type_bound_with_lower_bound_wildcard") + public void imports_single_type_bound_with_lower_bound_wildcard(Class classWithWildcard, Class expectedLowerBound) { + JavaClasses classes = new ClassFileImporter().importClasses(classWithWildcard, List.class, expectedLowerBound); + + JavaClass javaClass = classes.get(classWithWildcard); + + assertThatType(javaClass) + .hasTypeParameter("T").withBoundsMatching(parameterizedType(List.class).withWildcardTypeParameterWithLowerBound(expectedLowerBound)); + } + + @Test + public void imports_multiple_type_bounds_with_multiple_wildcards_with_various_bounds() { + @SuppressWarnings("unused") + class ClassWithMultipleTypeParametersBoundByTypesWithDifferentBounds, B extends Reference & Map> { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithMultipleTypeParametersBoundByTypesWithDifferentBounds.class, + Map.class, Serializable.class, File.class, Reference.class, String.class); + + JavaClass javaClass = classes.get(ClassWithMultipleTypeParametersBoundByTypesWithDifferentBounds.class); + + assertThatType(javaClass).hasTypeParameters("A", "B") + .hasTypeParameter("A") + .withBoundsMatching( + parameterizedType(Map.class) + .withWildcardTypeParameters( + wildcardType().withUpperBound(Serializable.class), + wildcardType().withLowerBound(File.class) + )) + .hasTypeParameter("B") + .withBoundsMatching( + parameterizedType(Reference.class) + .withWildcardTypeParameters( + wildcardType().withLowerBound(String.class) + ), + parameterizedType(Map.class) + .withWildcardTypeParameters( + wildcardType(), + wildcardType() + )); + } + + @Test + public void imports_type_variable_bound_by_other_type_variable() { + @SuppressWarnings("unused") + class ClassWithTypeParameterWithTypeVariableBound { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithTypeParameterWithTypeVariableBound.class); + + JavaClass javaClass = classes.get(ClassWithTypeParameterWithTypeVariableBound.class); + + assertThatType(javaClass) + .hasTypeParameter("U").withBoundsMatching(typeVariable("T")); + } + + @Test + public void references_type_variable_bound() { + @SuppressWarnings("unused") + class ClassWithTypeParameterWithTypeVariableBound { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithTypeParameterWithTypeVariableBound.class, String.class); + + JavaClass javaClass = classes.get(ClassWithTypeParameterWithTypeVariableBound.class); + + assertThatType(javaClass).hasTypeParameters("U", "T", "V") + .hasTypeParameter("U").withBoundsMatching(typeVariable("T").withUpperBounds(String.class)) + .hasTypeParameter("V").withBoundsMatching(typeVariable("T").withUpperBounds(String.class)); + } + + @Test + public void references_type_variable_bound_for_inner_classes() { + @SuppressWarnings("unused") + class ClassWithTypeParameterWithInnerClassesWithTypeVariableBound { + @SuppressWarnings("InnerClassMayBeStatic") + class SomeInner { + class EvenMoreInnerDeclaringOwn { + class AndEvenMoreInner { + } + } + } + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithTypeParameterWithInnerClassesWithTypeVariableBound.class, + ClassWithTypeParameterWithInnerClassesWithTypeVariableBound.SomeInner.class, + ClassWithTypeParameterWithInnerClassesWithTypeVariableBound.SomeInner.EvenMoreInnerDeclaringOwn.class, + ClassWithTypeParameterWithInnerClassesWithTypeVariableBound.SomeInner.EvenMoreInnerDeclaringOwn.AndEvenMoreInner.class, + String.class); + + JavaClass javaClass = classes.get(ClassWithTypeParameterWithInnerClassesWithTypeVariableBound.SomeInner.EvenMoreInnerDeclaringOwn.AndEvenMoreInner.class); + + assertThatType(javaClass).hasTypeParameters("MOST_INNER1", "MOST_INNER2") + .hasTypeParameter("MOST_INNER1") + .withBoundsMatching( + typeVariable("T").withUpperBounds(String.class)) + .hasTypeParameter("MOST_INNER2") + .withBoundsMatching( + typeVariable("MORE_INNER2").withUpperBounds( + typeVariable("U").withUpperBounds( + typeVariable("T").withUpperBounds(String.class)))); + } + + @Test + public void creates_new_stub_type_variables_for_type_variables_of_enclosing_classes_that_are_out_of_context() { + @SuppressWarnings("unused") + class ClassWithTypeParameterWithInnerClassesWithTypeVariableBound { + @SuppressWarnings("InnerClassMayBeStatic") + class SomeInner { + class EvenMoreInnerDeclaringOwn { + class AndEvenMoreInner { + } + } + } + } + + JavaClasses classes = new ClassFileImporter().importClasses( + ClassWithTypeParameterWithInnerClassesWithTypeVariableBound.SomeInner.EvenMoreInnerDeclaringOwn.AndEvenMoreInner.class); + + JavaClass javaClass = classes.get(ClassWithTypeParameterWithInnerClassesWithTypeVariableBound.SomeInner.EvenMoreInnerDeclaringOwn.AndEvenMoreInner.class); + + assertThatType(javaClass).hasTypeParameters("MOST_INNER1", "MOST_INNER2") + .hasTypeParameter("MOST_INNER1") + .withBoundsMatching(typeVariable("T").withoutUpperBounds()) + .hasTypeParameter("MOST_INNER2") + .withBoundsMatching(typeVariable("MORE_INNER2").withoutUpperBounds()); + } + + @Test + public void imports_wild_cards_bound_by_type_variables() { + @SuppressWarnings("unused") + class ClassWithWildcardWithTypeVariableBounds, V extends List> { + class Inner> { + class MoreInner, MOST_INNER2 extends List> { + } + } + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithWildcardWithTypeVariableBounds.class, List.class, String.class); + + JavaClass javaClass = classes.get(ClassWithWildcardWithTypeVariableBounds.class); + + assertThatType(javaClass).hasTypeParameters("T", "U", "V") + .hasTypeParameter("U") + .withBoundsMatching( + parameterizedType(List.class).withWildcardTypeParameterWithUpperBound( + typeVariable("T").withUpperBounds(String.class))) + .hasTypeParameter("V") + .withBoundsMatching( + parameterizedType(List.class).withWildcardTypeParameterWithLowerBound( + typeVariable("T").withUpperBounds(String.class))); + } + + @Test + public void imports_wild_cards_bound_by_type_variables_of_enclosing_classes() { + @SuppressWarnings("unused") + class ClassWithWildcardWithTypeVariableBounds, V extends List> { + class Inner> { + class MoreInner, MOST_INNER2 extends List> { + } + } + } + + JavaClasses classes = new ClassFileImporter().importClasses( + ClassWithWildcardWithTypeVariableBounds.class, + ClassWithWildcardWithTypeVariableBounds.Inner.class, + ClassWithWildcardWithTypeVariableBounds.Inner.MoreInner.class, + List.class, String.class); + + JavaClass javaClass = classes.get(ClassWithWildcardWithTypeVariableBounds.Inner.MoreInner.class); + + assertThatType(javaClass).hasTypeParameters("MOST_INNER1", "MOST_INNER2") + .hasTypeParameter("MOST_INNER1") + .withBoundsMatching( + parameterizedType(List.class).withWildcardTypeParameterWithUpperBound( + typeVariable("T").withUpperBounds(String.class))) + .hasTypeParameter("MOST_INNER2") + .withBoundsMatching( + parameterizedType(List.class).withWildcardTypeParameterWithLowerBound( + typeVariable("V").withUpperBounds( + parameterizedType(List.class).withWildcardTypeParameterWithLowerBound( + typeVariable("T").withUpperBounds(String.class))))); + } + + @Test + public void creates_new_stub_type_variables_for_wildcards_bound_by_type_variables_of_enclosing_classes_that_are_out_of_context() { + @SuppressWarnings("unused") + class ClassWithWildcardWithTypeVariableBounds, V extends List> { + class Inner> { + class MoreInner, MOST_INNER2 extends List> { + } + } + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithWildcardWithTypeVariableBounds.Inner.MoreInner.class, + List.class, String.class); + + JavaClass javaClass = classes.get(ClassWithWildcardWithTypeVariableBounds.Inner.MoreInner.class); + + assertThatType(javaClass).hasTypeParameters("MOST_INNER1", "MOST_INNER2") + .hasTypeParameter("MOST_INNER1") + .withBoundsMatching( + parameterizedType(List.class).withWildcardTypeParameterWithUpperBound( + typeVariable("T").withoutUpperBounds())) + .hasTypeParameter("MOST_INNER2") + .withBoundsMatching( + parameterizedType(List.class).withWildcardTypeParameterWithLowerBound( + typeVariable("V").withoutUpperBounds())); + } + + @Test + public void imports_complex_type_with_multiple_nested_parameters_with_various_bounds_and_recursive_type_definitions() { + @SuppressWarnings("unused") + class ClassWithComplexTypeParameters< + A extends List & Serializable & Comparable, + B extends A, + C extends Map< + Map.Entry>, + Map>>>>>>>, + SELF extends ClassWithComplexTypeParameters, + D> { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithComplexTypeParameters.class, + List.class, Serializable.class, Comparable.class, Map.class, Map.Entry.class, String.class, Set.class, Iterable.class, Object.class); + + JavaClass javaClass = classes.get(ClassWithComplexTypeParameters.class); + + assertThatType(javaClass) + .hasTypeParameter("A") + .withBoundsMatching( + parameterizedType(List.class).withWildcardTypeParameter(), + parameterizedType(Serializable.class), + parameterizedType(Comparable.class).withTypeArguments(typeVariable("A"))) + .hasTypeParameter("B") + .withBoundsMatching(typeVariable("A")) + .hasTypeParameter("C") + .withBoundsMatching( + parameterizedType(Map.class).withTypeArguments( + parameterizedType(Map.Entry.class).withTypeArguments( + typeVariable("A"), + parameterizedType(Map.Entry.class).withTypeArguments( + parameterizedType(String.class), typeVariable("B"))), + parameterizedType(Map.class).withTypeArguments( + wildcardType().withUpperBound(String.class), + parameterizedType(Map.class).withTypeArguments( + wildcardType().withUpperBound(Serializable.class), + parameterizedType(List.class).withTypeArguments( + parameterizedType(List.class).withWildcardTypeParameterWithUpperBound( + parameterizedType(Set.class).withWildcardTypeParameterWithLowerBound( + parameterizedType(Iterable.class).withWildcardTypeParameterWithLowerBound( + parameterizedType(Map.class).withTypeArguments( + typeVariable("B"), wildcardType()))))) + ) + ) + )) + .hasTypeParameter("SELF") + .withBoundsMatching( + parameterizedType(ClassWithComplexTypeParameters.class).withTypeArguments( + typeVariable("A"), + typeVariable("B"), + typeVariable("C"), + typeVariable("SELF"), + typeVariable("D") + )) + .hasTypeParameter("D").withBoundsMatching(Object.class); + } + + @Test + public void imports_complex_type_with_multiple_nested_parameters_with_concrete_array_bounds() { + @SuppressWarnings("unused") + class ClassWithComplexTypeParametersWithConcreteArrayBounds< + A extends List, + B extends List, + C extends Map, Serializable[][]>> + > { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithComplexTypeParametersWithConcreteArrayBounds.class, + List.class, Serializable.class, Map.class, String.class); + + JavaClass javaClass = classes.get(ClassWithComplexTypeParametersWithConcreteArrayBounds.class); + + assertThatType(javaClass) + .hasTypeParameter("A") + .withBoundsMatching( + parameterizedType(List.class).withTypeArguments(Serializable[].class)) + .hasTypeParameter("B") + .withBoundsMatching( + parameterizedType(List.class).withWildcardTypeParameterWithUpperBound(Serializable[][].class)) + .hasTypeParameter("C") + .withBoundsMatching( + parameterizedType(Map.class).withTypeArguments( + wildcardType().withLowerBound(String[].class), + parameterizedType(Map.class).withTypeArguments( + parameterizedType(Map.class).withTypeArguments( + wildcardType().withLowerBound(String[][][].class), + wildcardType()), + parameterizedType(Serializable[][].class))) + ); + } + + @Test + public void imports_complex_type_with_multiple_nested_parameters_with_generic_array_bounds() { + @SuppressWarnings("unused") + class ClassWithComplexTypeParametersWithGenericArrayBounds< + X extends Serializable, + Y extends String, + A extends List, + B extends List, + C extends Map, X[][]>> + > { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithComplexTypeParametersWithGenericArrayBounds.class, + List.class, Serializable.class, Map.class, String.class); + + JavaClass javaClass = classes.get(ClassWithComplexTypeParametersWithGenericArrayBounds.class); + + assertThatType(javaClass) + .hasTypeParameter("A") + .withBoundsMatching( + parameterizedType(List.class).withTypeArguments( + typeVariableArray("X[]").withComponentType(typeVariable("X").withUpperBounds(Serializable.class)))) + .hasTypeParameter("B") + .withBoundsMatching( + parameterizedType(List.class).withWildcardTypeParameterWithUpperBound( + typeVariableArray("X[][]").withComponentType( + typeVariableArray("X[]").withComponentType( + typeVariable("X").withUpperBounds(Serializable.class))))) + .hasTypeParameter("C") + .withBoundsMatching( + parameterizedType(Map.class).withTypeArguments( + wildcardType().withLowerBound( + typeVariableArray("Y[]").withComponentType( + typeVariable("Y").withUpperBounds(String.class))), + parameterizedType(Map.class).withTypeArguments( + parameterizedType(Map.class).withTypeArguments( + wildcardType().withLowerBound( + typeVariableArray("Y[][][]").withComponentType( + typeVariableArray("Y[][]").withComponentType( + typeVariableArray("Y[]").withComponentType( + typeVariable("Y").withUpperBounds(String.class))))), + wildcardType()), + typeVariableArray("X[][]").withComponentType( + typeVariableArray("X[]").withComponentType( + typeVariable("X").withUpperBounds(Serializable.class))))) + ); + } + + @SuppressWarnings("unused") + public static class ClassParameterWithSingleTypeParameter { + } + + @SuppressWarnings("unused") + public interface InterfaceParameterWithSingleTypeParameter { + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterSlowTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterSlowTest.java index 1f265413af..01d12c9fa7 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterSlowTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterSlowTest.java @@ -4,12 +4,8 @@ import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Field; -import java.net.URL; -import java.net.URLClassLoader; import java.util.List; -import com.google.common.base.Joiner; -import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.tngtech.archunit.Slow; @@ -30,7 +26,8 @@ import static com.tngtech.archunit.core.importer.ImportOption.Predefined.DO_NOT_INCLUDE_TESTS; import static com.tngtech.archunit.core.importer.UrlSourceTest.JAVA_CLASS_PATH_PROP; import static com.tngtech.archunit.testutil.Assertions.assertThat; -import static com.tngtech.archunit.testutil.Assertions.assertThatClasses; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static java.util.jar.Attributes.Name.CLASS_PATH; @Category(Slow.class) @@ -48,62 +45,57 @@ public class ClassFileImporterSlowTest { public void imports_the_classpath() { JavaClasses classes = new ClassFileImporter().importClasspath(); - assertThatClasses(classes).contain(ClassFileImporter.class, getClass()); - assertThatClasses(classes).doNotContain(Rule.class); // Default does not import jars - assertThatClasses(classes).doNotContain(File.class); // Default does not import JDK classes + assertThatTypes(classes).contain(ClassFileImporter.class, getClass()); + assertThatTypes(classes).doNotContain(Rule.class); // Default does not import jars + assertThatTypes(classes).doNotContain(File.class); // Default does not import JDK classes - classes = new ClassFileImporter().importClasspath(new ImportOptions().with(new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.asURI().getScheme().equals("jrt") || location.contains("java.base"); - } - })); + classes = new ClassFileImporter().importClasspath(new ImportOptions().with(importJavaBaseOrRtAndJUnitJarAndFilesOnTheClasspath())); - assertThatClasses(classes).contain(ClassFileImporter.class, getClass(), Rule.class, File.class); + assertThatTypes(classes).contain(ClassFileImporter.class, getClass(), Rule.class, File.class); } @Test public void respects_ImportOptions_when_using_the_default_importClasspath_method() { JavaClasses classes = new ClassFileImporter().withImportOption(DO_NOT_INCLUDE_TESTS).importClasspath(); - assertThatClasses(classes).contain(ClassFileImporter.class); - assertThatClasses(classes).doNotContain(getClass(), Rule.class, String.class); + assertThatTypes(classes).contain(ClassFileImporter.class); + assertThatTypes(classes).doNotContain(getClass(), Rule.class, String.class); } @Test public void imports_packages() { JavaClasses classes = new ClassFileImporter().importPackages( getClass().getPackage().getName(), Rule.class.getPackage().getName()); - assertThatClasses(classes).contain(ImmutableSet.of(getClass(), Rule.class)); + assertThatTypes(classes).contain(ImmutableSet.of(getClass(), Rule.class)); classes = new ClassFileImporter().importPackages( ImmutableSet.of(getClass().getPackage().getName(), Rule.class.getPackage().getName())); - assertThatClasses(classes).contain(ImmutableSet.of(getClass(), Rule.class)); + assertThatTypes(classes).contain(ImmutableSet.of(getClass(), Rule.class)); } @Test public void imports_packages_of_classes() { JavaClasses classes = new ClassFileImporter().importPackagesOf(getClass(), Rule.class); - assertThatClasses(classes).contain(ImmutableSet.of(getClass(), Rule.class)); + assertThatTypes(classes).contain(ImmutableSet.of(getClass(), Rule.class)); classes = new ClassFileImporter().importPackagesOf(ImmutableSet.of(getClass(), Rule.class)); - assertThatClasses(classes).contain(ImmutableSet.of(getClass(), Rule.class)); + assertThatTypes(classes).contain(ImmutableSet.of(getClass(), Rule.class)); } @Test public void imports_jars() throws Exception { JavaClasses classes = new ClassFileImporter().importJar(jarFileOf(Rule.class)); - assertThatClasses(classes).contain(Rule.class); - assertThatClasses(classes).doNotContain(Object.class, ImmutableList.class); + assertThatTypes(classes).contain(Rule.class); + assertThatTypes(classes).doNotContain(Object.class, ImmutableList.class); classes = new ClassFileImporter().importJars(jarFileOf(Rule.class), jarFileOf(ImmutableList.class)); - assertThatClasses(classes).contain(Rule.class, ImmutableList.class); - assertThatClasses(classes).doNotContain(Object.class); + assertThatTypes(classes).contain(Rule.class, ImmutableList.class); + assertThatTypes(classes).doNotContain(Object.class); classes = new ClassFileImporter().importJars(ImmutableList.of( jarFileOf(Rule.class), jarFileOf(ImmutableList.class))); - assertThatClasses(classes).contain(Rule.class, ImmutableList.class); - assertThatClasses(classes).doNotContain(Object.class); + assertThatTypes(classes).contain(Rule.class, ImmutableList.class); + assertThatTypes(classes).doNotContain(Object.class); } @Test @@ -115,33 +107,30 @@ public void imports_duplicate_classes() throws IOException { JavaClasses classes = new ClassFileImporter().importPackages(getClass().getPackage().getName()); - assertThat(classes.get(JavaClass.class)).isNotNull(); + assertThatType(classes.get(JavaClass.class)).isNotNull(); } @Test public void imports_classes_from_classpath_specified_in_manifest_file() { - String manifestClasspath = - Joiner.on(" ").join(Splitter.on(File.pathSeparator).omitEmptyStrings().split(System.getProperty(JAVA_CLASS_PATH_PROP))); + TestClassFile testClassFile = new TestClassFile().create(); + String manifestClasspath = testClassFile.getClasspathRoot().getAbsolutePath(); String jarPath = new TestJarFile() .withManifestAttribute(CLASS_PATH, manifestClasspath) .create() .getName(); - System.clearProperty(JAVA_CLASS_PATH_PROP); - // Ensure we cannot load the class through the fallback via the Classloader - Thread.currentThread().setContextClassLoader(new URLClassLoader(new URL[0], null)); - verifyCantLoadWithCurrentClasspath(getClass()); + verifyCantLoadWithCurrentClasspath(testClassFile); System.setProperty(JAVA_CLASS_PATH_PROP, jarPath); - JavaClasses javaClasses = new ClassFileImporter().importPackages(getClass().getPackage().getName()); + JavaClasses javaClasses = new ClassFileImporter().importPackages(testClassFile.getPackageName()); - assertThatClasses(javaClasses).contain(getClass()); + assertThat(javaClasses).extracting("name").contains(testClassFile.getClassName()); } - private void verifyCantLoadWithCurrentClasspath(Class clazz) { + private void verifyCantLoadWithCurrentClasspath(TestClassFile testClassFile) { try { - new ClassFileImporter().importClass(clazz); - Assert.fail(String.format("Should not have been able to load class %s with the current classpath", clazz.getName())); + new ClassFileImporter().importPackages(testClassFile.getPackageName()).get(testClassFile.getClassName()); + Assert.fail(String.format("Should not have been able to load class %s with the current classpath", testClassFile.getClassName())); } catch (RuntimeException ignored) { } } @@ -156,20 +145,39 @@ public void creates_JavaPackages() { .as("Created default package contains 'java'").isTrue(); JavaPackage javaPackage = defaultPackage.getPackage("java.lang"); - assertThatClasses(javaPackage.getClasses()).contain(Object.class, String.class, Integer.class); - assertThatClasses(javaPackage.getAllClasses()).contain(Object.class, Annotation.class, Field.class); + assertThatTypes(javaPackage.getClasses()).contain(Object.class, String.class, Integer.class); + assertThatTypes(javaPackage.getAllClasses()).contain(Object.class, Annotation.class, Field.class); assertThat(javaClasses.containPackage("java.util")) .as("Classes contain package 'java.util'").isTrue(); - assertThatClasses(javaClasses.getPackage("java.util").getClasses()).contain(List.class); + assertThatTypes(javaClasses.getPackage("java.util").getClasses()).contain(List.class); } private JavaClasses importJavaBase() { return new ClassFileImporter().importClasspath(new ImportOptions().with(new ImportOption() { @Override public boolean includes(Location location) { - return location.asURI().getScheme().equals("jrt") && location.contains("java.base"); + return + // before Java 9 package like java.lang were in rt.jar + location.contains("rt.jar") || + // from Java 9 on those packages were in a JRT with name 'java.base' + (location.asURI().getScheme().equals("jrt") && location.contains("java.base")); } })); } + + private ImportOption importJavaBaseOrRtAndJUnitJarAndFilesOnTheClasspath() { + return new ImportOption() { + @Override + public boolean includes(Location location) { + if (!location.isArchive()) { + return true; + } + if (location.isJar() && (location.contains("junit") || location.contains("/rt.jar"))) { + return true; + } + return location.asURI().getScheme().equals("jrt") && location.contains("java.base"); + } + }; + } } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java index b2856d004f..75f59c74a0 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java @@ -5,7 +5,6 @@ import java.io.PrintStream; import java.io.Serializable; import java.lang.annotation.Annotation; -import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.JarURLConnection; @@ -21,7 +20,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.jar.JarFile; @@ -40,8 +38,8 @@ import com.tngtech.archunit.core.domain.AccessTarget.FieldAccessTarget; import com.tngtech.archunit.core.domain.AccessTarget.MethodCallTarget; import com.tngtech.archunit.core.domain.Dependency; +import com.tngtech.archunit.core.domain.InstanceofCheck; import com.tngtech.archunit.core.domain.JavaAccess; -import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClassList; import com.tngtech.archunit.core.domain.JavaClasses; @@ -65,26 +63,8 @@ import com.tngtech.archunit.core.importer.testexamples.FirstCheckedException; import com.tngtech.archunit.core.importer.testexamples.OtherClass; import com.tngtech.archunit.core.importer.testexamples.SecondCheckedException; -import com.tngtech.archunit.core.importer.testexamples.SomeAnnotation; import com.tngtech.archunit.core.importer.testexamples.SomeClass; import com.tngtech.archunit.core.importer.testexamples.SomeEnum; -import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.ClassAnnotationWithArrays; -import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.ClassWithAnnotationWithEmptyArrays; -import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.ClassWithComplexAnnotations; -import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.ClassWithOneAnnotation; -import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.ClassWithUnimportedAnnotation; -import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.SimpleAnnotation; -import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.TypeAnnotationWithEnumAndArrayValue; -import com.tngtech.archunit.core.importer.testexamples.annotationfieldimport.ClassWithAnnotatedFields; -import com.tngtech.archunit.core.importer.testexamples.annotationfieldimport.ClassWithAnnotatedFields.FieldAnnotationWithEnumClassAndArrayValue; -import com.tngtech.archunit.core.importer.testexamples.annotationfieldimport.ClassWithAnnotatedFields.FieldAnnotationWithIntValue; -import com.tngtech.archunit.core.importer.testexamples.annotationfieldimport.ClassWithAnnotatedFields.FieldAnnotationWithStringValue; -import com.tngtech.archunit.core.importer.testexamples.annotationfieldimport.FieldAnnotationWithArrays; -import com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods; -import com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.MethodAnnotationWithEnumAndArrayValue; -import com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.MethodAnnotationWithIntValue; -import com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.MethodAnnotationWithStringValue; -import com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.MethodAnnotationWithArrays; import com.tngtech.archunit.core.importer.testexamples.arrays.ClassAccessingOneDimensionalArray; import com.tngtech.archunit.core.importer.testexamples.arrays.ClassAccessingTwoDimensionalArray; import com.tngtech.archunit.core.importer.testexamples.arrays.ClassUsedInArray; @@ -116,12 +96,9 @@ import com.tngtech.archunit.core.importer.testexamples.constructorimport.ClassWithComplexConstructor; import com.tngtech.archunit.core.importer.testexamples.constructorimport.ClassWithSimpleConstructors; import com.tngtech.archunit.core.importer.testexamples.constructorimport.ClassWithThrowingConstructor; -import com.tngtech.archunit.core.importer.testexamples.dependents.ClassDependingOnParentThroughChild; import com.tngtech.archunit.core.importer.testexamples.dependents.ClassHoldingDependencies; import com.tngtech.archunit.core.importer.testexamples.dependents.FirstClassWithDependency; -import com.tngtech.archunit.core.importer.testexamples.dependents.ParentClassHoldingDependencies; import com.tngtech.archunit.core.importer.testexamples.dependents.SecondClassWithDependency; -import com.tngtech.archunit.core.importer.testexamples.dependents.SubClassHoldingDependencies; import com.tngtech.archunit.core.importer.testexamples.diamond.ClassCallingDiamond; import com.tngtech.archunit.core.importer.testexamples.diamond.ClassImplementingD; import com.tngtech.archunit.core.importer.testexamples.diamond.InterfaceB; @@ -150,6 +127,10 @@ import com.tngtech.archunit.core.importer.testexamples.hierarchicalmethodcall.SuperClassWithCalledMethod; import com.tngtech.archunit.core.importer.testexamples.innerclassimport.CalledClass; import com.tngtech.archunit.core.importer.testexamples.innerclassimport.ClassWithInnerClass; +import com.tngtech.archunit.core.importer.testexamples.instanceofcheck.ChecksInstanceofInConstructor; +import com.tngtech.archunit.core.importer.testexamples.instanceofcheck.ChecksInstanceofInMethod; +import com.tngtech.archunit.core.importer.testexamples.instanceofcheck.ChecksInstanceofInStaticInitializer; +import com.tngtech.archunit.core.importer.testexamples.instanceofcheck.InstanceofChecked; import com.tngtech.archunit.core.importer.testexamples.integration.ClassA; import com.tngtech.archunit.core.importer.testexamples.integration.ClassBDependingOnClassA; import com.tngtech.archunit.core.importer.testexamples.integration.ClassCDependingOnClassB_SuperClassOfX; @@ -165,6 +146,8 @@ import com.tngtech.archunit.core.importer.testexamples.pathone.Class12; import com.tngtech.archunit.core.importer.testexamples.pathtwo.Class21; import com.tngtech.archunit.core.importer.testexamples.pathtwo.Class22; +import com.tngtech.archunit.core.importer.testexamples.simpleimport.AnnotationParameter; +import com.tngtech.archunit.core.importer.testexamples.simpleimport.AnnotationToImport; import com.tngtech.archunit.core.importer.testexamples.simpleimport.ClassToImportOne; import com.tngtech.archunit.core.importer.testexamples.simpleimport.ClassToImportTwo; import com.tngtech.archunit.core.importer.testexamples.simpleimport.EnumToImport; @@ -172,6 +155,7 @@ import com.tngtech.archunit.core.importer.testexamples.simplenames.SimpleNameExamples; import com.tngtech.archunit.core.importer.testexamples.specialtargets.ClassCallingSpecialTarget; import com.tngtech.archunit.core.importer.testexamples.syntheticimport.ClassWithSynthetics; +import com.tngtech.archunit.testutil.ArchConfigurationRule; import com.tngtech.archunit.testutil.LogTestRule; import com.tngtech.archunit.testutil.OutsideOfClassPathRule; import com.tngtech.java.junit.dataprovider.DataProvider; @@ -180,7 +164,6 @@ import org.apache.logging.log4j.Level; import org.assertj.core.api.Condition; import org.assertj.core.util.Objects; -import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -213,21 +196,17 @@ import static com.tngtech.archunit.core.domain.TestUtils.asClasses; import static com.tngtech.archunit.core.domain.TestUtils.md5sumOf; import static com.tngtech.archunit.core.domain.TestUtils.targetFrom; -import static com.tngtech.archunit.core.importer.testexamples.SomeEnum.OTHER_VALUE; -import static com.tngtech.archunit.core.importer.testexamples.SomeEnum.SOME_VALUE; -import static com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.enumAndArrayAnnotatedMethod; -import static com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.methodAnnotatedWithAnnotationFromParentPackage; -import static com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.methodAnnotatedWithEmptyArrays; -import static com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.stringAndIntAnnotatedMethod; -import static com.tngtech.archunit.core.importer.testexamples.annotationmethodimport.ClassWithAnnotatedMethods.stringAnnotatedMethod; import static com.tngtech.archunit.testutil.Assertions.assertThat; import static com.tngtech.archunit.testutil.Assertions.assertThatAccess; import static com.tngtech.archunit.testutil.Assertions.assertThatCall; -import static com.tngtech.archunit.testutil.Assertions.assertThatClasses; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static com.tngtech.archunit.testutil.ReflectionTestUtils.constructor; import static com.tngtech.archunit.testutil.ReflectionTestUtils.field; import static com.tngtech.archunit.testutil.ReflectionTestUtils.method; import static com.tngtech.archunit.testutil.TestUtils.namesOf; +import static com.tngtech.java.junit.dataprovider.DataProviders.$; +import static com.tngtech.java.junit.dataprovider.DataProviders.$$; import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assume.assumeTrue; @@ -242,20 +221,22 @@ public class ClassFileImporterTest { public final LogTestRule logTest = new LogTestRule(); @Rule public final IndependentClasspathRule independentClasspathRule = new IndependentClasspathRule(); - - @After - public void tearDown() { - ArchConfiguration.get().reset(); - } + @Rule + public final ArchConfigurationRule archConfigurationRule = new ArchConfigurationRule(); @Test public void imports_simple_package() throws Exception { Set expectedClassNames = Sets.newHashSet( - ClassToImportOne.class.getName(), ClassToImportTwo.class.getName(), InterfaceToImport.class.getName(), EnumToImport.class.getName()); + ClassToImportOne.class.getName(), + ClassToImportTwo.class.getName(), + InterfaceToImport.class.getName(), + EnumToImport.class.getName(), + AnnotationToImport.class.getName(), + AnnotationParameter.class.getName()); Iterable classes = classesIn("testexamples/simpleimport"); - assertThat(namesOf(classes)).isEqualTo(expectedClassNames); + assertThat(namesOf(classes)).containsOnlyElementsOf(expectedClassNames); } @Test @@ -263,14 +244,16 @@ public void imports_simple_class_details() throws Exception { ImportedClasses classes = classesIn("testexamples/simpleimport"); JavaClass javaClass = classes.get(ClassToImportOne.class); + assertThat(javaClass.isFullyImported()).isTrue(); assertThat(javaClass.getName()).as("full name").isEqualTo(ClassToImportOne.class.getName()); assertThat(javaClass.getSimpleName()).as("simple name").isEqualTo(ClassToImportOne.class.getSimpleName()); assertThat(javaClass.getPackageName()).as("package name").isEqualTo(ClassToImportOne.class.getPackage().getName()); assertThat(javaClass.getModifiers()).as("modifiers").containsOnly(JavaModifier.PUBLIC); - assertThat(javaClass.getSuperClass().get()).as("super class").matches(Object.class); + assertThatType(javaClass.getSuperClass().get()).as("super class").matches(Object.class); assertThat(javaClass.getInterfaces()).as("interfaces").isEmpty(); assertThat(javaClass.isInterface()).as("is interface").isFalse(); assertThat(javaClass.isEnum()).as("is enum").isFalse(); + assertThat(javaClass.isAnnotation()).as("is annotation").isFalse(); assertThat(javaClass.getEnclosingClass()).as("enclosing class").isAbsent(); assertThat(javaClass.isTopLevelClass()).as("is top level class").isTrue(); assertThat(javaClass.isNestedClass()).as("is nested class").isFalse(); @@ -290,14 +273,15 @@ public void imports_simple_enum() throws Exception { assertThat(javaClass.getSimpleName()).as("simple name").isEqualTo(EnumToImport.class.getSimpleName()); assertThat(javaClass.getPackageName()).as("package name").isEqualTo(EnumToImport.class.getPackage().getName()); assertThat(javaClass.getModifiers()).as("modifiers").containsOnly(JavaModifier.PUBLIC, JavaModifier.FINAL); - assertThat(javaClass.getSuperClass().get()).as("super class").matches(Enum.class); + assertThatType(javaClass.getSuperClass().get()).as("super class").matches(Enum.class); assertThat(javaClass.getInterfaces()).as("interfaces").isEmpty(); - assertThatClasses(javaClass.getAllInterfaces()).matchInAnyOrder(Enum.class.getInterfaces()); + assertThatTypes(javaClass.getAllInterfaces()).matchInAnyOrder(Enum.class.getInterfaces()); assertThat(javaClass.isInterface()).as("is interface").isFalse(); assertThat(javaClass.isEnum()).as("is enum").isTrue(); + assertThat(javaClass.isAnnotation()).as("is annotation").isFalse(); JavaEnumConstant constant = javaClass.getEnumConstant(EnumToImport.FIRST.name()); - assertThat(constant.getDeclaringClass()).as("declaring class").isEqualTo(javaClass); + assertThatType(constant.getDeclaringClass()).as("declaring class").isEqualTo(javaClass); assertThat(constant.name()).isEqualTo(EnumToImport.FIRST.name()); assertThat(javaClass.getEnumConstants()).extractingResultOf("name").as("enum constant names") .containsOnly(EnumToImport.FIRST.name(), EnumToImport.SECOND.name()); @@ -314,7 +298,7 @@ public void imports_simple_static_nested_class(Class nestedStaticClass) throw ImportedClasses classes = classesIn("testexamples/innerclassimport"); JavaClass staticNestedClass = classes.get(nestedStaticClass); - assertThat(staticNestedClass).matches(nestedStaticClass); + assertThatType(staticNestedClass).matches(nestedStaticClass); assertThat(staticNestedClass.isTopLevelClass()).as("is top level class").isFalse(); assertThat(staticNestedClass.isNestedClass()).as("is nested class").isTrue(); assertThat(staticNestedClass.isMemberClass()).as("is member class").isTrue(); @@ -328,7 +312,7 @@ public void imports_simple_inner_class() throws Exception { ImportedClasses classes = classesIn("testexamples/innerclassimport"); JavaClass innerClass = classes.get(ClassWithInnerClass.Inner.class); - assertThat(innerClass).matches(ClassWithInnerClass.Inner.class); + assertThatType(innerClass).matches(ClassWithInnerClass.Inner.class); assertThat(innerClass.isTopLevelClass()).as("is top level class").isFalse(); assertThat(innerClass.isNestedClass()).as("is nested class").isTrue(); assertThat(innerClass.isMemberClass()).as("is member class").isTrue(); @@ -342,7 +326,7 @@ public void imports_simple_anonymous_class() throws Exception { ImportedClasses classes = classesIn("testexamples/innerclassimport"); JavaClass anonymousClass = classes.get(ClassWithInnerClass.class.getName() + "$1"); - assertThat(anonymousClass).matches(Class.forName(anonymousClass.getName())); + assertThatType(anonymousClass).matches(Class.forName(anonymousClass.getName())); assertThat(anonymousClass.isTopLevelClass()).as("is top level class").isFalse(); assertThat(anonymousClass.isNestedClass()).as("is nested class").isTrue(); assertThat(anonymousClass.isMemberClass()).as("is member class").isFalse(); @@ -356,7 +340,7 @@ public void imports_simple_local_class() throws Exception { ImportedClasses classes = classesIn("testexamples/innerclassimport"); JavaClass localClass = classes.get(ClassWithInnerClass.class.getName() + "$1LocalCaller"); - assertThat(localClass).matches(Class.forName(localClass.getName())); + assertThatType(localClass).matches(Class.forName(localClass.getName())); assertThat(localClass.isTopLevelClass()).as("is top level class").isFalse(); assertThat(localClass.isNestedClass()).as("is nested class").isTrue(); assertThat(localClass.isMemberClass()).as("is member class").isFalse(); @@ -398,7 +382,7 @@ public void imports_interfaces() throws Exception { public void imports_nested_classes() throws Exception { JavaClasses classes = classesIn("testexamples/nestedimport").classes; - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassWithNestedClass.class, ClassWithNestedClass.NestedClass.class, ClassWithNestedClass.StaticNestedClass.class, @@ -436,14 +420,14 @@ public void handles_synthetic_modifiers() throws Exception { public void imports_jdk_classes() { JavaClasses classes = new ClassFileImporter().importClasses(File.class); - assertThatClasses(classes).matchExactly(File.class); + assertThatTypes(classes).matchExactly(File.class); } @Test public void imports_jdk_packages() { JavaClasses classes = new ClassFileImporter().importPackagesOf(File.class); - assertThatClasses(classes).contain(File.class); + assertThatTypes(classes).contain(File.class); } @Test @@ -453,7 +437,7 @@ public void creates_JavaPackages_for_each_JavaClass() { JavaPackage javaPackage = classes.get(SomeClass.class).getPackage(); assertThat(javaPackage.containsClass(SomeEnum.class)).as("Package contains " + SomeEnum.class).isTrue(); - assertThatClasses(javaPackage.getParent().get().getClasses()).contain(getClass()); + assertThatTypes(javaPackage.getParent().get().getClasses()).contain(getClass()); } @DataProvider @@ -492,14 +476,14 @@ public void imports_fields() throws Exception { public void imports_primitive_fields() throws Exception { Set fields = classesIn("testexamples/primitivefieldimport").getFields(); - assertThat(findAnyByName(fields, "aBoolean").getRawType()).matches(boolean.class); - assertThat(findAnyByName(fields, "anInt").getRawType()).matches(int.class); - assertThat(findAnyByName(fields, "aByte").getRawType()).matches(byte.class); - assertThat(findAnyByName(fields, "aChar").getRawType()).matches(char.class); - assertThat(findAnyByName(fields, "aShort").getRawType()).matches(short.class); - assertThat(findAnyByName(fields, "aLong").getRawType()).matches(long.class); - assertThat(findAnyByName(fields, "aFloat").getRawType()).matches(float.class); - assertThat(findAnyByName(fields, "aDouble").getRawType()).matches(double.class); + assertThatType(findAnyByName(fields, "aBoolean").getRawType()).matches(boolean.class); + assertThatType(findAnyByName(fields, "anInt").getRawType()).matches(int.class); + assertThatType(findAnyByName(fields, "aByte").getRawType()).matches(byte.class); + assertThatType(findAnyByName(fields, "aChar").getRawType()).matches(char.class); + assertThatType(findAnyByName(fields, "aShort").getRawType()).matches(short.class); + assertThatType(findAnyByName(fields, "aLong").getRawType()).matches(long.class); + assertThatType(findAnyByName(fields, "aFloat").getRawType()).matches(float.class); + assertThatType(findAnyByName(fields, "aDouble").getRawType()).matches(double.class); } // NOTE: This provokes the scenario where the target type can't be determined uniquely due to a diamond @@ -511,13 +495,13 @@ public void imports_special_target_parameters() throws Exception { Set calls = classes.get(ClassCallingSpecialTarget.class).getMethodCallsFromSelf(); assertThat(targetParametersOf(calls, "primitiveArgs")).matches(byte.class, long.class); - assertThat(returnTypeOf(calls, "primitiveReturnType")).matches(byte.class); + assertThatType(returnTypeOf(calls, "primitiveReturnType")).matches(byte.class); assertThat(targetParametersOf(calls, "arrayArgs")).matches(byte[].class, Object[].class); - assertThat(returnTypeOf(calls, "primitiveArrayReturnType")).matches(short[].class); - assertThat(returnTypeOf(calls, "objectArrayReturnType")).matches(String[].class); + assertThatType(returnTypeOf(calls, "primitiveArrayReturnType")).matches(short[].class); + assertThatType(returnTypeOf(calls, "objectArrayReturnType")).matches(String[].class); assertThat(targetParametersOf(calls, "twoDimArrayArgs")).matches(float[][].class, Object[][].class); - assertThat(returnTypeOf(calls, "primitiveTwoDimArrayReturnType")).matches(double[][].class); - assertThat(returnTypeOf(calls, "objectTwoDimArrayReturnType")).matches(String[][].class); + assertThatType(returnTypeOf(calls, "primitiveTwoDimArrayReturnType")).matches(double[][].class); + assertThatType(returnTypeOf(calls, "objectTwoDimArrayReturnType")).matches(String[][].class); } @Test @@ -526,7 +510,7 @@ public void attaches_correct_owner_to_fields() throws Exception { for (JavaClass clazz : classes) { for (JavaField field : clazz.getFields()) { - assertThat(field.getOwner()).isSameAs(clazz); + assertThatType(field.getOwner()).isSameAs(clazz); } } } @@ -549,146 +533,6 @@ public void imports_fields_with_correct_modifiers() throws Exception { assertThat(findAnyByName(fields, "synchronizedField").getModifiers()).containsOnly(TRANSIENT); } - @Test - public void imports_annotation_defaults() throws Exception { - ImportedClasses classes = classesIn("testexamples/annotatedclassimport"); - - JavaClass annotationType = classes.get(TypeAnnotationWithEnumAndArrayValue.class); - assertThat((JavaEnumConstant) annotationType.getMethod("valueWithDefault") - .getDefaultValue().get()) - .as("default of valueWithDefault()").isEquivalentTo(SOME_VALUE); - assertThat(((JavaEnumConstant[]) annotationType.getMethod("enumArrayWithDefault") - .getDefaultValue().get())) - .as("default of enumArrayWithDefault()").matches(OTHER_VALUE); - assertThat(((JavaAnnotation) annotationType.getMethod("subAnnotationWithDefault") - .getDefaultValue().get()).get("value").get()) - .as("default of subAnnotationWithDefault()").isEqualTo("default"); - assertThat(((JavaAnnotation[]) annotationType.getMethod("subAnnotationArrayWithDefault") - .getDefaultValue().get())[0].get("value").get()) - .as("default of subAnnotationArrayWithDefault()").isEqualTo("first"); - assertThat((JavaClass) annotationType.getMethod("clazzWithDefault") - .getDefaultValue().get()) - .as("default of clazzWithDefault()").matches(String.class); - assertThat((JavaClass[]) annotationType.getMethod("classesWithDefault") - .getDefaultValue().get()) - .as("default of clazzWithDefault()").matchExactly(Serializable.class, List.class); - } - - @Test - public void imports_fields_with_one_annotation_correctly() throws Exception { - ImportedClasses classes = classesIn("testexamples/annotationfieldimport"); - - JavaField field = findAnyByName(classes.getFields(), "stringAnnotatedField"); - JavaAnnotation annotation = field.getAnnotationOfType(FieldAnnotationWithStringValue.class.getName()); - assertThat(annotation.getRawType()).isEqualTo(classes.get(FieldAnnotationWithStringValue.class)); - assertThat(annotation.get("value").get()).isEqualTo("something"); - assertThat(annotation.getOwner()).as("owning field").isEqualTo(field); - - assertThat(field).isEquivalentTo(field.getOwner().reflect().getDeclaredField("stringAnnotatedField")); - } - - @Test - public void fields_handle_optional_annotation_correctly() throws Exception { - Set fields = classesIn("testexamples/annotationfieldimport").getFields(); - - JavaField field = findAnyByName(fields, "stringAnnotatedField"); - assertThat(field.tryGetAnnotationOfType(FieldAnnotationWithStringValue.class)).isPresent(); - assertThat(field.tryGetAnnotationOfType(FieldAnnotationWithEnumClassAndArrayValue.class)).isAbsent(); - - Optional> optionalAnnotation = field.tryGetAnnotationOfType(FieldAnnotationWithStringValue.class.getName()); - assertThat(optionalAnnotation.get().getOwner()).as("owner of optional annotation").isEqualTo(field); - assertThat(field.tryGetAnnotationOfType(FieldAnnotationWithEnumClassAndArrayValue.class.getName())) - .as("optional annotation").isAbsent(); - } - - @Test - public void imports_fields_with_two_annotations_correctly() throws Exception { - Set fields = classesIn("testexamples/annotationfieldimport").getFields(); - - JavaField field = findAnyByName(fields, "stringAndIntAnnotatedField"); - Set> annotations = field.getAnnotations(); - assertThat(annotations).hasSize(2); - assertThat(annotations).extractingResultOf("getOwner").containsOnly(field); - assertThat(annotations).extractingResultOf("getAnnotatedElement").containsOnly(field); - - JavaAnnotation annotationWithString = field.getAnnotationOfType(FieldAnnotationWithStringValue.class.getName()); - assertThat(annotationWithString.get("value").get()).isEqualTo("otherThing"); - - JavaAnnotation annotationWithInt = field.getAnnotationOfType(FieldAnnotationWithIntValue.class.getName()); - assertThat(annotationWithInt.get("intValue").get()).as("Annotation value with default").isEqualTo(0); - assertThat(annotationWithInt.get("otherValue").get()).isEqualTo("overridden"); - - assertThat(field).isEquivalentTo(field.getOwner().reflect().getDeclaredField("stringAndIntAnnotatedField")); - } - - @Test - public void imports_fields_with_complex_annotations_correctly() throws Exception { - ImportedClasses classes = classesIn("testexamples/annotationfieldimport"); - - JavaField field = findAnyByName(classes.getFields(), "enumAndArrayAnnotatedField"); - - JavaAnnotation annotation = field.getAnnotationOfType(FieldAnnotationWithEnumClassAndArrayValue.class.getName()); - assertThat((JavaEnumConstant) annotation.get("value").get()).isEquivalentTo(OTHER_VALUE); - assertThat((JavaEnumConstant) annotation.get("valueWithDefault").get()).isEquivalentTo(SOME_VALUE); - assertThat(((JavaEnumConstant[]) annotation.get("enumArray").get())).matches(SOME_VALUE, OTHER_VALUE); - assertThat(((JavaEnumConstant[]) annotation.get("enumArrayWithDefault").get())).matches(OTHER_VALUE); - JavaAnnotation subAnnotation = (JavaAnnotation) annotation.get("subAnnotation").get(); - assertThat(subAnnotation.get("value").get()).isEqualTo("changed"); - assertThat(subAnnotation.getOwner()).isEqualTo(annotation); - assertThat(subAnnotation.getAnnotatedElement()).isEqualTo(field); - assertThat(((JavaAnnotation) annotation.get("subAnnotationWithDefault").get()).get("value").get()) - .isEqualTo("default"); - JavaAnnotation[] subAnnotationArray = (JavaAnnotation[]) annotation.get("subAnnotationArray").get(); - assertThat(subAnnotationArray[0].get("value").get()).isEqualTo("another"); - assertThat(subAnnotationArray[0].getOwner()).isEqualTo(annotation); - assertThat(subAnnotation.getAnnotatedElement()).isEqualTo(field); - assertThat(((JavaAnnotation[]) annotation.get("subAnnotationArrayWithDefault").get())[0].get("value").get()) - .isEqualTo("first"); - assertThat((JavaClass) annotation.get("clazz").get()).matches(Map.class); - assertThat((JavaClass) annotation.get("clazzWithDefault").get()).matches(String.class); - assertThat((JavaClass[]) annotation.get("classes").get()).matchExactly(Object.class, Serializable.class); - assertThat((JavaClass[]) annotation.get("classesWithDefault").get()).matchExactly(Serializable.class, List.class); - - assertThat(field).isEquivalentTo(field.getOwner().reflect().getDeclaredField("enumAndArrayAnnotatedField")); - } - - @Test - public void imports_fields_with_annotation_with_empty_array() throws Exception { - JavaClass clazz = classesIn("testexamples/annotationfieldimport").get(ClassWithAnnotatedFields.class); - - JavaAnnotation annotation = clazz.getField("fieldAnnotatedWithEmptyArrays") - .getAnnotationOfType(FieldAnnotationWithArrays.class.getName()); - - assertThat(Array.getLength(annotation.get("primitives").get())).isZero(); - assertThat(Array.getLength(annotation.get("objects").get())).isZero(); - assertThat(Array.getLength(annotation.get("enums").get())).isZero(); - assertThat(Array.getLength(annotation.get("classes").get())).isZero(); - assertThat(Array.getLength(annotation.get("annotations").get())).isZero(); - - FieldAnnotationWithArrays reflected = annotation.as(FieldAnnotationWithArrays.class); - assertThat(reflected.primitives()).isEmpty(); - assertThat(reflected.objects()).isEmpty(); - assertThat(reflected.enums()).isEmpty(); - assertThat(reflected.classes()).isEmpty(); - assertThat(reflected.annotations()).isEmpty(); - } - - @Test - public void imports_fields_annotated_with_unimported_annotation() throws Exception { - JavaClass clazz = classesIn("testexamples/annotationfieldimport").get(ClassWithAnnotatedFields.class); - - JavaAnnotation annotation = clazz.getField("fieldAnnotatedWithAnnotationFromParentPackage") - .getAnnotationOfType(SomeAnnotation.class.getName()); - - assertThat(annotation.get("mandatory")).contains("mandatory"); - // NOTE: If we haven't imported the annotation itself, the import can't determine default values - assertThat(annotation.get("optional")).isAbsent(); - - SomeAnnotation reflected = annotation.as(SomeAnnotation.class); - assertThat(reflected.mandatory()).isEqualTo("mandatory"); - assertThat(reflected.optional()).isEqualTo("optional"); - } - @Test public void imports_simple_methods_with_correct_parameters() throws Exception { Set methods = classesIn("testexamples/methodimport").getMethods(); @@ -722,11 +566,11 @@ public void imports_complex_method_with_correct_parameters() throws Exception { public void imports_methods_with_correct_return_types() throws Exception { Set methods = classesIn("testexamples/methodimport").getCodeUnits(); - assertThat(findAnyByName(methods, "createString").getRawReturnType()) + assertThatType(findAnyByName(methods, "createString").getRawReturnType()) .as("Return type of method 'createString'").matches(String.class); - assertThat(findAnyByName(methods, "consume").getRawReturnType()) + assertThatType(findAnyByName(methods, "consume").getRawReturnType()) .as("Return type of method 'consume'").matches(void.class); - assertThat(findAnyByName(methods, "createSerializable").getRawReturnType()) + assertThatType(findAnyByName(methods, "createSerializable").getRawReturnType()) .as("Return type of method 'createSerializable'").matches(Serializable.class); } @@ -770,229 +614,29 @@ public void imports_members_with_sourceCodeLocation() throws Exception { .hasToString("(" + sourceFileName + ":27)"); } - @Test - public void imports_methods_with_one_annotation_correctly() throws Exception { - JavaClass clazz = classesIn("testexamples/annotationmethodimport").get(ClassWithAnnotatedMethods.class); - - JavaMethod method = findAnyByName(clazz.getMethods(), stringAnnotatedMethod); - JavaAnnotation annotation = method.getAnnotationOfType(MethodAnnotationWithStringValue.class.getName()); - assertThat(annotation.getRawType()).matches(MethodAnnotationWithStringValue.class); - assertThat(annotation.getOwner()).isEqualTo(method); - assertThat(annotation.get("value").get()).isEqualTo("something"); - - assertThat(method).isEquivalentTo(ClassWithAnnotatedMethods.class.getMethod(stringAnnotatedMethod)); - } - - @Test - public void methods_handle_optional_annotation_correctly() throws Exception { - Set methods = classesIn("testexamples/annotationmethodimport").getMethods(); - - JavaMethod method = findAnyByName(methods, "stringAnnotatedMethod"); - assertThat(method.tryGetAnnotationOfType(MethodAnnotationWithStringValue.class)).isPresent(); - assertThat(method.tryGetAnnotationOfType(MethodAnnotationWithEnumAndArrayValue.class)).isAbsent(); - - Optional> optionalAnnotation = method.tryGetAnnotationOfType(MethodAnnotationWithStringValue.class.getName()); - assertThat(optionalAnnotation.get().getOwner()).as("owner of optional annotation").isEqualTo(method); - assertThat(method.tryGetAnnotationOfType(MethodAnnotationWithEnumAndArrayValue.class.getName())).isAbsent(); - } - - @Test - public void imports_methods_with_two_annotations_correctly() throws Exception { - JavaClass clazz = classesIn("testexamples/annotationmethodimport").get(ClassWithAnnotatedMethods.class); - - JavaMethod method = findAnyByName(clazz.getMethods(), stringAndIntAnnotatedMethod); - Set> annotations = method.getAnnotations(); - assertThat(annotations).hasSize(2); - assertThat(annotations).extractingResultOf("getOwner").containsOnly(method); - assertThat(annotations).extractingResultOf("getAnnotatedElement").containsOnly(method); - - JavaAnnotation annotationWithString = method.getAnnotationOfType(MethodAnnotationWithStringValue.class.getName()); - assertThat(annotationWithString.get("value").get()).isEqualTo("otherThing"); - - JavaAnnotation annotationWithInt = method.getAnnotationOfType(MethodAnnotationWithIntValue.class.getName()); - assertThat(annotationWithInt.get("otherValue").get()).isEqualTo("overridden"); - - assertThat(method).isEquivalentTo(ClassWithAnnotatedMethods.class.getMethod(stringAndIntAnnotatedMethod)); - } - - @Test - public void imports_methods_with_complex_annotations_correctly() throws Exception { - JavaClass clazz = classesIn("testexamples/annotationmethodimport").get(ClassWithAnnotatedMethods.class); - - JavaMethod method = findAnyByName(clazz.getMethods(), enumAndArrayAnnotatedMethod); - - JavaAnnotation annotation = method.getAnnotationOfType(MethodAnnotationWithEnumAndArrayValue.class.getName()); - assertThat((JavaEnumConstant) annotation.get("value").get()).isEquivalentTo(OTHER_VALUE); - assertThat((JavaEnumConstant) annotation.get("valueWithDefault").get()).isEquivalentTo(SOME_VALUE); - assertThat(((JavaEnumConstant[]) annotation.get("enumArray").get())).matches(SOME_VALUE, OTHER_VALUE); - assertThat(((JavaEnumConstant[]) annotation.get("enumArrayWithDefault").get())).matches(OTHER_VALUE); - JavaAnnotation subAnnotation = (JavaAnnotation) annotation.get("subAnnotation").get(); - assertThat(subAnnotation.get("value").get()).isEqualTo("changed"); - assertThat(subAnnotation.getOwner()).isEqualTo(annotation); - assertThat(subAnnotation.getAnnotatedElement()).isEqualTo(method); - assertThat(((JavaAnnotation) annotation.get("subAnnotationWithDefault").get()).get("value").get()) - .isEqualTo("default"); - JavaAnnotation[] subAnnotationArray = (JavaAnnotation[]) annotation.get("subAnnotationArray").get(); - assertThat(subAnnotationArray[0].get("value").get()).isEqualTo("another"); - assertThat(subAnnotationArray[0].getOwner()).isEqualTo(annotation); - assertThat(subAnnotationArray[0].getAnnotatedElement()).isEqualTo(method); - assertThat(((JavaAnnotation[]) annotation.get("subAnnotationArrayWithDefault").get())[0].get("value").get()) - .isEqualTo("first"); - assertThat((JavaClass) annotation.get("clazz").get()).matches(Map.class); - assertThat((JavaClass) annotation.get("clazzWithDefault").get()).matches(String.class); - assertThat((JavaClass[]) annotation.get("classes").get()).matchExactly(Object.class, Serializable.class); - assertThat((JavaClass[]) annotation.get("classesWithDefault").get()).matchExactly(Serializable.class, List.class); - - assertThat(method).isEquivalentTo(ClassWithAnnotatedMethods.class.getMethod(enumAndArrayAnnotatedMethod)); - } - - @Test - public void imports_method_with_annotation_with_empty_array() throws Exception { - JavaClass clazz = classesIn("testexamples/annotationmethodimport").get(ClassWithAnnotatedMethods.class); - - JavaAnnotation annotation = clazz.getMethod(methodAnnotatedWithEmptyArrays) - .getAnnotationOfType(MethodAnnotationWithArrays.class.getName()); - - assertThat(Array.getLength(annotation.get("primitives").get())).isZero(); - assertThat(Array.getLength(annotation.get("objects").get())).isZero(); - assertThat(Array.getLength(annotation.get("enums").get())).isZero(); - assertThat(Array.getLength(annotation.get("classes").get())).isZero(); - assertThat(Array.getLength(annotation.get("annotations").get())).isZero(); - - MethodAnnotationWithArrays reflected = annotation.as(MethodAnnotationWithArrays.class); - assertThat(reflected.primitives()).isEmpty(); - assertThat(reflected.objects()).isEmpty(); - assertThat(reflected.enums()).isEmpty(); - assertThat(reflected.classes()).isEmpty(); - assertThat(reflected.annotations()).isEmpty(); - } - - @Test - public void imports_methods_annotated_with_unimported_annotation() throws Exception { - JavaClass clazz = classesIn("testexamples/annotationmethodimport").get(ClassWithAnnotatedMethods.class); - - JavaAnnotation annotation = clazz.getMethod(methodAnnotatedWithAnnotationFromParentPackage) - .getAnnotationOfType(SomeAnnotation.class.getName()); - - assertThat(annotation.get("mandatory")).contains("mandatory"); - // NOTE: If we haven't imported the annotation itself, the import can't determine default values - assertThat(annotation.get("optional")).isAbsent(); - - SomeAnnotation reflected = annotation.as(SomeAnnotation.class); - assertThat(reflected.mandatory()).isEqualTo("mandatory"); - assertThat(reflected.optional()).isEqualTo("optional"); - } - - @Test - public void imports_class_with_one_annotation_correctly() throws Exception { - JavaClass clazz = classesIn("testexamples/annotatedclassimport").get(ClassWithOneAnnotation.class); - - JavaAnnotation annotation = clazz.getAnnotationOfType(SimpleAnnotation.class.getName()); - assertThat(annotation.getRawType()).matches(SimpleAnnotation.class); - assertThat(annotation.getOwner()).isEqualTo(clazz); - - JavaAnnotation annotationByName = clazz.getAnnotationOfType(SimpleAnnotation.class.getName()); - assertThat(annotationByName).isEqualTo(annotation); - - assertThat(annotation.get("value").get()).isEqualTo("test"); - - assertThat(clazz).matches(ClassWithOneAnnotation.class); - } - - @Test - public void class_handles_optional_annotation_correctly() throws Exception { - JavaClass clazz = classesIn("testexamples/annotatedclassimport").get(ClassWithOneAnnotation.class); - - assertThat(clazz.tryGetAnnotationOfType(SimpleAnnotation.class)).isPresent(); - assertThat(clazz.tryGetAnnotationOfType(Deprecated.class)).isAbsent(); - } - - @Test - public void imports_class_with_complex_annotations_correctly() throws Exception { - JavaClass clazz = classesIn("testexamples/annotatedclassimport").get(ClassWithComplexAnnotations.class); - - assertThat(clazz.getAnnotations()).as("annotations of " + clazz.getSimpleName()).hasSize(2); - - JavaAnnotation annotation = clazz.getAnnotationOfType(TypeAnnotationWithEnumAndArrayValue.class.getName()); - assertThat((JavaEnumConstant) annotation.get("value").get()).isEquivalentTo(OTHER_VALUE); - assertThat((JavaEnumConstant) annotation.get("valueWithDefault").get()).isEquivalentTo(SOME_VALUE); - assertThat(((JavaEnumConstant[]) annotation.get("enumArray").get())).matches(SOME_VALUE, OTHER_VALUE); - assertThat(((JavaEnumConstant[]) annotation.get("enumArrayWithDefault").get())).matches(OTHER_VALUE); - JavaAnnotation subAnnotation = (JavaAnnotation) annotation.get("subAnnotation").get(); - assertThat(subAnnotation.get("value").get()).isEqualTo("sub"); - assertThat(subAnnotation.getOwner()).isEqualTo(annotation); - assertThat(subAnnotation.getAnnotatedElement()).isEqualTo(clazz); - assertThat(((JavaAnnotation) annotation.get("subAnnotationWithDefault").get()).get("value").get()) - .isEqualTo("default"); - JavaAnnotation[] subAnnotationArray = (JavaAnnotation[]) annotation.get("subAnnotationArray").get(); - assertThat(subAnnotationArray[0].get("value").get()).isEqualTo("otherFirst"); - assertThat(subAnnotationArray[0].getOwner()).isEqualTo(annotation); - assertThat(subAnnotationArray[0].getAnnotatedElement()).isEqualTo(clazz); - assertThat(((JavaAnnotation[]) annotation.get("subAnnotationArrayWithDefault").get())[0].get("value").get()) - .isEqualTo("first"); - assertThat((JavaClass) annotation.get("clazz").get()).matches(Serializable.class); - assertThat((JavaClass) annotation.get("clazzWithDefault").get()).matches(String.class); - assertThat((JavaClass[]) annotation.get("classes").get()).matchExactly(Serializable.class, String.class); - assertThat((JavaClass[]) annotation.get("classesWithDefault").get()).matchExactly(Serializable.class, List.class); - - assertThat(clazz).matches(ClassWithComplexAnnotations.class); - } - - @Test - public void imports_class_with_annotation_with_empty_array() throws Exception { - JavaClass clazz = classesIn("testexamples/annotatedclassimport").get(ClassWithAnnotationWithEmptyArrays.class); - - JavaAnnotation annotation = clazz.getAnnotationOfType(ClassAnnotationWithArrays.class.getName()); - - assertThat(Array.getLength(annotation.get("primitives").get())).isZero(); - assertThat(Array.getLength(annotation.get("objects").get())).isZero(); - assertThat(Array.getLength(annotation.get("enums").get())).isZero(); - assertThat(Array.getLength(annotation.get("classes").get())).isZero(); - assertThat(Array.getLength(annotation.get("annotations").get())).isZero(); - - ClassAnnotationWithArrays reflected = clazz.getAnnotationOfType(ClassAnnotationWithArrays.class); - assertThat(reflected.primitives()).isEmpty(); - assertThat(reflected.objects()).isEmpty(); - assertThat(reflected.enums()).isEmpty(); - assertThat(reflected.classes()).isEmpty(); - assertThat(reflected.annotations()).isEmpty(); - } - - @Test - public void imports_class_annotated_with_unimported_annotation() throws Exception { - JavaClass clazz = classesIn("testexamples/annotatedclassimport").get(ClassWithUnimportedAnnotation.class); - - JavaAnnotation annotation = clazz.getAnnotationOfType(SomeAnnotation.class.getName()); - - assertThat(annotation.get("mandatory")).contains("mandatory"); - // NOTE: If we haven't imported the annotation itself, the import can't determine default values - assertThat(annotation.get("optional")).isAbsent(); - assertThat((JavaEnumConstant) annotation.get("mandatoryEnum").get()).isEquivalentTo(SOME_VALUE); - assertThat(annotation.get("optionalEnum")).isAbsent(); - - SomeAnnotation reflected = clazz.getAnnotationOfType(SomeAnnotation.class); - assertThat(reflected.mandatory()).isEqualTo("mandatory"); - assertThat(reflected.optional()).isEqualTo("optional"); - assertThat(reflected.mandatoryEnum()).isEqualTo(SOME_VALUE); - assertThat(reflected.optionalEnum()).isEqualTo(OTHER_VALUE); - } - @Test public void imports_simple_constructors_with_correct_parameters() throws Exception { JavaClass clazz = classesIn("testexamples/constructorimport").get(ClassWithSimpleConstructors.class); assertThat(clazz.getConstructors()).as("Constructors").hasSize(3); - assertThat(clazz.getConstructor()).isEquivalentTo(ClassWithSimpleConstructors.class.getDeclaredConstructor()); + + Constructor expectedConstructor = ClassWithSimpleConstructors.class.getDeclaredConstructor(); + assertThat(clazz.getConstructor()).isEquivalentTo(expectedConstructor); + assertThat(clazz.tryGetConstructor().get()).isEquivalentTo(expectedConstructor); Class[] parameterTypes = {Object.class}; - Constructor expectedConstructor = ClassWithSimpleConstructors.class.getDeclaredConstructor(parameterTypes); + expectedConstructor = ClassWithSimpleConstructors.class.getDeclaredConstructor(parameterTypes); assertThat(clazz.getConstructor(parameterTypes)).isEquivalentTo(expectedConstructor); assertThat(clazz.getConstructor(Objects.namesOf(parameterTypes))).isEquivalentTo(expectedConstructor); + assertThat(clazz.tryGetConstructor(parameterTypes).get()).isEquivalentTo(expectedConstructor); + assertThat(clazz.tryGetConstructor(Objects.namesOf(parameterTypes)).get()).isEquivalentTo(expectedConstructor); parameterTypes = new Class[]{int.class, int.class}; expectedConstructor = ClassWithSimpleConstructors.class.getDeclaredConstructor(parameterTypes); assertThat(clazz.getConstructor(parameterTypes)).isEquivalentTo(expectedConstructor); assertThat(clazz.getConstructor(Objects.namesOf(parameterTypes))).isEquivalentTo(expectedConstructor); + assertThat(clazz.tryGetConstructor(parameterTypes).get()).isEquivalentTo(expectedConstructor); + assertThat(clazz.tryGetConstructor(Objects.namesOf(parameterTypes)).get()).isEquivalentTo(expectedConstructor); } @Test @@ -1015,27 +659,6 @@ public void imports_constructor_with_correct_throws_declarations() throws Except assertThat(constructor.getExceptionTypes()).matches(FirstCheckedException.class, SecondCheckedException.class); } - @Test - public void imports_constructors_with_complex_annotations_correctly() throws Exception { - JavaConstructor constructor = classesIn("testexamples/annotationmethodimport").get(ClassWithAnnotatedMethods.class) - .getConstructor(); - - JavaAnnotation annotation = constructor.getAnnotationOfType(MethodAnnotationWithEnumAndArrayValue.class.getName()); - assertThat((Object[]) annotation.get("classes").get()).extracting("name") - .containsExactly(Object.class.getName(), Serializable.class.getName()); - assertThat(annotation.getOwner()).isEqualTo(constructor); - JavaAnnotation subAnnotation = (JavaAnnotation) annotation.get("subAnnotation").get(); - assertThat(subAnnotation.get("value").get()).isEqualTo("changed"); - assertThat(subAnnotation.getOwner()).isEqualTo(annotation); - assertThat(subAnnotation.getAnnotatedElement()).isEqualTo(constructor); - JavaAnnotation[] subAnnotationArray = (JavaAnnotation[]) annotation.get("subAnnotationArray").get(); - assertThat(subAnnotationArray[0].get("value").get()).isEqualTo("another"); - assertThat(subAnnotationArray[0].getOwner()).isEqualTo(annotation); - assertThat(subAnnotationArray[0].getAnnotatedElement()).isEqualTo(constructor); - - assertThat(constructor).isEquivalentTo(ClassWithAnnotatedMethods.class.getConstructor()); - } - @Test public void imports_interfaces_and_classes() throws Exception { ImportedClasses classes = classesIn("testexamples/classhierarchyimport"); @@ -1670,7 +1293,7 @@ public void imports_non_unique_targets_for_diamond_scenarios() throws Exception .inLineNumber(ClassCallingDiamond.callInterfaceLineNumber); // NOTE: There is no java.lang.reflect.Method InterfaceD.implementMe(), because the method is inherited assertThat(callToInterface.getTarget().getName()).isEqualTo(InterfaceD.implementMe); - assertThat(callToInterface.getTarget().getOwner()).isEqualTo(diamondPeakInterface); + assertThatType(callToInterface.getTarget().getOwner()).isEqualTo(diamondPeakInterface); assertThat(callToInterface.getTarget().getRawParameterTypes()).isEmpty(); assertThat(callToInterface.getTarget().resolve()).extracting("fullName") .containsOnly( @@ -1690,8 +1313,8 @@ public void imports_method_calls_that_return_Arrays() throws Exception { JavaClass classThatCallsMethodReturningArray = classesIn("testexamples/callimport").get(CallsMethodReturningArray.class); MethodCallTarget target = getOnlyElement(classThatCallsMethodReturningArray.getMethodCallsFromSelf()).getTarget(); - assertThat(target.getOwner()).matches(CallsMethodReturningArray.SomeEnum.class); - assertThat(target.getRawReturnType()).matches(CallsMethodReturningArray.SomeEnum[].class); + assertThatType(target.getOwner()).matches(CallsMethodReturningArray.SomeEnum.class); + assertThatType(target.getRawReturnType()).matches(CallsMethodReturningArray.SomeEnum[].class); } @Test @@ -1762,6 +1385,36 @@ public void classes_know_the_field_accesses_to_them() throws Exception { assertThat(accesses).as("Field Accesses to class").isEqualTo(expected); } + @Test + public void classes_know_shadowed_field_accesses_to_themselves() { + @SuppressWarnings("unused") + class Base { + String shadowed; + String nonShadowed; + } + class Child extends Base { + String shadowed; + } + @SuppressWarnings("unused") + class Accessor { + void access(Child child) { + consume(child.shadowed); + consume(child.nonShadowed); + } + + void consume(String string) { + } + } + JavaClasses classes = new ClassFileImporter().importClasses(Accessor.class, Base.class, Child.class); + + JavaFieldAccess access = getOnlyByCaller( + classes.get(Base.class).getFieldAccessesToSelf(), classes.get(Accessor.class).getMethod("access", Child.class)); + assertThatAccess(access).isFrom("access", Child.class).isTo("nonShadowed"); + access = getOnlyByCaller( + classes.get(Child.class).getFieldAccessesToSelf(), classes.get(Accessor.class).getMethod("access", Child.class)); + assertThatAccess(access).isFrom("access", Child.class).isTo("shadowed"); + } + @Test public void methods_know_callers() throws Exception { ImportedClasses classes = classesIn("testexamples/dependents"); @@ -1794,6 +1447,34 @@ public void classes_know_method_calls_to_themselves() throws Exception { assertThat(calls).as("Method calls to class").isEqualTo(expected); } + @Test + public void classes_know_overridden_method_calls_to_themselves() { + @SuppressWarnings("unused") + class Base { + void overridden() { + } + + void nonOverridden() { + } + } + class Child extends Base { + @Override + void overridden() { + } + } + @SuppressWarnings("unused") + class Caller { + void call(Child child) { + child.overridden(); + child.nonOverridden(); + } + } + JavaClasses classes = new ClassFileImporter().importClasses(Caller.class, Base.class, Child.class); + + assertThatCall(getOnlyElement(classes.get(Base.class).getMethodCallsToSelf())).isFrom("call", Child.class).isTo("nonOverridden"); + assertThatCall(getOnlyElement(classes.get(Child.class).getMethodCallsToSelf())).isFrom("call", Child.class).isTo("overridden"); + } + @Test public void constructors_know_callers() throws Exception { ImportedClasses classes = classesIn("testexamples/dependents"); @@ -1827,6 +1508,30 @@ public void classes_know_constructor_calls_to_themselves() throws Exception { assertThat(calls).as("Constructor calls to ClassWithDependents").isEqualTo(expected); } + @Test + public void classes_know_constructor_calls_to_themselves_for_subclass_default_constructors() { + // For constructors it's impossible to be accessed via a subclass, + // since the byte code always holds an explicitly declared constructor. + // Thus we do expect a call to the constructor of the subclass and one from subclass to super class + @SuppressWarnings("unused") + class Base { + Base() { + } + } + class Child extends Base { + } + @SuppressWarnings("unused") + class Caller { + void call() { + new Child(); + } + } + JavaClasses classes = new ClassFileImporter().importClasses(Caller.class, Base.class, Child.class); + + assertThatCall(getOnlyElement(classes.get(Base.class).getConstructorCallsToSelf())).isFrom(Child.class, CONSTRUCTOR_NAME, getClass()).isTo(Base.class); + assertThatCall(getOnlyElement(classes.get(Child.class).getConstructorCallsToSelf())).isFrom(Caller.class, "call").isTo(Child.class); + } + @Test public void classes_know_accesses_to_themselves() throws Exception { ImportedClasses classes = classesIn("testexamples/dependents"); @@ -1843,37 +1548,6 @@ public void classes_know_accesses_to_themselves() throws Exception { assertThat(accesses).as("Accesses to ClassWithDependents").isEqualTo(expected); } - @Test - public void inherited_field_accesses_and_method_calls_are_resolved() throws Exception { - ImportedClasses classes = classesIn("testexamples/dependents"); - JavaClass classHoldingDependencies = classes.get(ParentClassHoldingDependencies.class); - JavaClass subClassHoldingDependencies = classes.get(SubClassHoldingDependencies.class); - JavaClass dependentClass = classes.get(ClassDependingOnParentThroughChild.class); - - Set fieldAccessesToSelf = classHoldingDependencies.getFieldAccessesToSelf(); - Set expectedFieldAccesses = - getByTargetNot(dependentClass.getFieldAccessesFromSelf(), dependentClass); - assertThat(fieldAccessesToSelf).as("Field accesses to class").isEqualTo(expectedFieldAccesses); - - Set methodCalls = classHoldingDependencies.getMethodCallsToSelf(); - Set expectedMethodCalls = - getByTargetNot(dependentClass.getMethodCallsFromSelf(), dependentClass); - assertThat(methodCalls).as("Method calls to class").isEqualTo(expectedMethodCalls); - - // NOTE: For constructors it's impossible to be accessed via a subclass, - // since the byte code always holds an explicitly declared constructor - - Set constructorCalls = classHoldingDependencies.getConstructorCallsToSelf(); - Set expectedConstructorCalls = - getByTargetOwner(subClassHoldingDependencies.getConstructorCallsFromSelf(), classHoldingDependencies.getName()); - assertThat(constructorCalls).as("Constructor calls to class").isEqualTo(expectedConstructorCalls); - - constructorCalls = subClassHoldingDependencies.getConstructorCallsToSelf(); - expectedConstructorCalls = - getByTargetOwner(dependentClass.getConstructorCallsFromSelf(), subClassHoldingDependencies.getName()); - assertThat(constructorCalls).as("Constructor calls to class").isEqualTo(expectedConstructorCalls); - } - @Test public void classes_know_which_fields_have_their_type() { JavaClasses classes = new ClassFileImporter().importClasses(SomeClass.class, OtherClass.class, SomeEnum.class); @@ -1903,7 +1577,7 @@ public void classes_know_which_method_throws_clauses_contain_their_type() { JavaClasses classes = new ClassFileImporter().importClasses(ClassWithThrowingMethod.class, FirstCheckedException.class); Set> throwsDeclarations = classes.get(FirstCheckedException.class).getMethodThrowsDeclarationsWithTypeOfSelf(); - assertThat(getOnlyElement(throwsDeclarations).getDeclaringClass()).matches(ClassWithThrowingMethod.class); + assertThatType(getOnlyElement(throwsDeclarations).getDeclaringClass()).matches(ClassWithThrowingMethod.class); assertThat(classes.get(FirstCheckedException.class).getConstructorsWithParameterTypeOfSelf()).isEmpty(); } @@ -1922,17 +1596,19 @@ public void classes_know_which_constructor_throws_clauses_contain_their_type() { Set> throwsDeclarations = classes.get(FirstCheckedException.class).getConstructorsWithThrowsDeclarationTypeOfSelf(); - assertThat(getOnlyElement(throwsDeclarations).getDeclaringClass()).matches(ClassWithThrowingConstructor.class); + assertThatType(getOnlyElement(throwsDeclarations).getDeclaringClass()).matches(ClassWithThrowingConstructor.class); assertThat(classes.get(FirstCheckedException.class).getMethodThrowsDeclarationsWithTypeOfSelf()).isEmpty(); } @Test - public void classes_know_which_annotations_have_their_type() { - JavaClasses classes = new ClassFileImporter().importClasses(ClassWithOneAnnotation.class, SimpleAnnotation.class); - - Set> annotations = classes.get(SimpleAnnotation.class).getAnnotationsWithTypeOfSelf(); + public void classes_know_which_instanceof_checks_check_their_type() { + JavaClass clazz = new ClassFileImporter().importPackagesOf(InstanceofChecked.class).get(InstanceofChecked.class); - assertThat(getOnlyElement(annotations).getOwner()).isEqualTo(classes.get(ClassWithOneAnnotation.class)); + Set origins = new HashSet<>(); + for (InstanceofCheck instanceofCheck : clazz.getInstanceofChecksWithTypeOfSelf()) { + origins.add(instanceofCheck.getOwner().getOwner()); + } + assertThatTypes(origins).matchInAnyOrder(ChecksInstanceofInMethod.class, ChecksInstanceofInConstructor.class, ChecksInstanceofInStaticInitializer.class); } @Test @@ -1972,7 +1648,7 @@ public void imports_urls_of_folders() throws Exception { JavaClasses javaClasses = new ClassFileImporter().importUrl(testexamplesFolder.toURI().toURL()); - assertThatClasses(javaClasses).contain(SomeClass.class, OtherClass.class); + assertThatTypes(javaClasses).contain(SomeClass.class, OtherClass.class); } @Test @@ -2038,6 +1714,49 @@ public void resolve_missing_dependencies_from_classpath_can_be_toogled() throws assertThat(clazz.getSuperClass().get().getMethods()).isEmpty(); } + @DataProvider + public static Object[][] classes_not_fully_imported() { + class Element { + } + @SuppressWarnings("unused") + class DependsOnArray { + Element[] array; + } + ArchConfiguration.get().setResolveMissingDependenciesFromClassPath(true); + JavaClass resolvedFromClasspath = new ClassFileImporter().importClasses(DependsOnArray.class) + .get(DependsOnArray.class).getField("array").getRawType().getComponentType(); + + ArchConfiguration.get().setResolveMissingDependenciesFromClassPath(false); + JavaClass stub = new ClassFileImporter().importClasses(DependsOnArray.class) + .get(DependsOnArray.class).getField("array").getRawType().getComponentType(); + + return $$( + $("Resolved from classpath", resolvedFromClasspath), + $("Stub class", stub) + ); + } + + @Test + @UseDataProvider("classes_not_fully_imported") + public void classes_not_fully_imported_have_flag_fullyImported_false_and_empty_dependencies(@SuppressWarnings("unused") String description, JavaClass notFullyImported) { + assertThat(notFullyImported.isFullyImported()).isFalse(); + assertThat(notFullyImported.getDirectDependenciesFromSelf()).isEmpty(); + assertThat(notFullyImported.getDirectDependenciesToSelf()).isEmpty(); + assertThat(notFullyImported.getFieldAccessesToSelf()).isEmpty(); + assertThat(notFullyImported.getMethodCallsToSelf()).isEmpty(); + assertThat(notFullyImported.getConstructorCallsToSelf()).isEmpty(); + assertThat(notFullyImported.getAccessesToSelf()).isEmpty(); + assertThat(notFullyImported.getFieldsWithTypeOfSelf()).isEmpty(); + assertThat(notFullyImported.getMethodsWithParameterTypeOfSelf()).isEmpty(); + assertThat(notFullyImported.getMethodsWithReturnTypeOfSelf()).isEmpty(); + assertThat(notFullyImported.getMethodThrowsDeclarationsWithTypeOfSelf()).isEmpty(); + assertThat(notFullyImported.getConstructorsWithParameterTypeOfSelf()).isEmpty(); + assertThat(notFullyImported.getConstructorsWithThrowsDeclarationTypeOfSelf()).isEmpty(); + assertThat(notFullyImported.getAnnotationsWithTypeOfSelf()).isEmpty(); + assertThat(notFullyImported.getAnnotationsWithParameterTypeOfSelf()).isEmpty(); + assertThat(notFullyImported.getInstanceofChecksWithTypeOfSelf()).isEmpty(); + } + @Test public void import_is_resilient_against_broken_class_files() throws Exception { Class expectedClass = getClass(); @@ -2050,7 +1769,7 @@ public void import_is_resilient_against_broken_class_files() throws Exception { JavaClasses classes = new ClassFileImporter().importPath(folder.toPath()); - assertThatClasses(classes).matchExactly(expectedClass); + assertThatTypes(classes).matchExactly(expectedClass); logTest.assertLogMessage(Level.WARN, "Evil.class"); } @@ -2085,7 +1804,7 @@ public void class_has_source_of_import() throws Exception { public void imports_class_objects() { JavaClasses classes = new ClassFileImporter().importClasses(ClassToImportOne.class, ClassToImportTwo.class); - assertThatClasses(classes).matchInAnyOrder(ClassToImportOne.class, ClassToImportTwo.class); + assertThatTypes(classes).matchInAnyOrder(ClassToImportOne.class, ClassToImportTwo.class); } /** @@ -2120,28 +1839,28 @@ public void imports_paths() throws Exception { JavaClasses classes = new ClassFileImporter() .importPaths(ImmutableList.of(folderOne.toPath(), folderTwo.toPath())); - assertThatClasses(classes).matchInAnyOrder(Class11.class, Class12.class, Class21.class, Class22.class); + assertThatTypes(classes).matchInAnyOrder(Class11.class, Class12.class, Class21.class, Class22.class); classes = new ClassFileImporter().importPaths(folderOne.toPath(), folderTwo.toPath()); - assertThatClasses(classes).matchInAnyOrder(Class11.class, Class12.class, Class21.class, Class22.class); + assertThatTypes(classes).matchInAnyOrder(Class11.class, Class12.class, Class21.class, Class22.class); classes = new ClassFileImporter().importPaths(folderOne.getAbsolutePath(), folderTwo.getAbsolutePath()); - assertThatClasses(classes).matchInAnyOrder(Class11.class, Class12.class, Class21.class, Class22.class); + assertThatTypes(classes).matchInAnyOrder(Class11.class, Class12.class, Class21.class, Class22.class); classes = new ClassFileImporter().importPath(folderOne.toPath()); - assertThatClasses(classes).matchInAnyOrder(Class11.class, Class12.class); + assertThatTypes(classes).matchInAnyOrder(Class11.class, Class12.class); classes = new ClassFileImporter().importPath(folderOne.getAbsolutePath()); - assertThatClasses(classes).matchInAnyOrder(Class11.class, Class12.class); + assertThatTypes(classes).matchInAnyOrder(Class11.class, Class12.class); } @Test public void ImportOptions_are_respected() throws Exception { ClassFileImporter importer = new ClassFileImporter().withImportOption(importOnly(getClass(), Rule.class)); - assertThatClasses(importer.importPath(Paths.get(urlOf(getClass()).toURI()))).matchExactly(getClass()); - assertThatClasses(importer.importUrl(urlOf(getClass()))).matchExactly(getClass()); - assertThatClasses(importer.importJar(jarFileOf(Rule.class))).matchExactly(Rule.class); + assertThatTypes(importer.importPath(Paths.get(urlOf(getClass()).toURI()))).matchExactly(getClass()); + assertThatTypes(importer.importUrl(urlOf(getClass()))).matchExactly(getClass()); + assertThatTypes(importer.importJar(jarFileOf(Rule.class))).matchExactly(Rule.class); } @Test @@ -2263,10 +1982,6 @@ public boolean apply(JavaAccess input) { }); } - private > Set getByTargetNot(Set accesses, JavaClass target) { - return getBy(accesses, not(targetOwnerNameEquals(target.getName()))); - } - private > Set getByTargetOwner(Set calls, Class targetOwner) { return getByTargetOwner(calls, targetOwner.getName()); } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportOptionsTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportOptionsTest.java index aa6c1f26d3..fd5ed6ddbd 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportOptionsTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportOptionsTest.java @@ -15,7 +15,7 @@ import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; -import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.collect.Iterables.getLast; import static com.tngtech.archunit.core.importer.ImportOption.Predefined.DO_NOT_INCLUDE_ARCHIVES; import static com.tngtech.archunit.core.importer.ImportOption.Predefined.DO_NOT_INCLUDE_JARS; import static com.tngtech.archunit.core.importer.ImportOption.Predefined.DO_NOT_INCLUDE_TESTS; @@ -99,7 +99,7 @@ public void detects_Jars_correctly(ImportOption doNotIncludeJars) { .isFalse(); assertThat(doNotIncludeJars.includes(locationOf(Object.class))) .as("includes Jrt location") - .isTrue(); + .isEqualTo(!comesFromJarArchive(Object.class)); } @DataProvider @@ -122,6 +122,10 @@ public void detects_archives_correctly(ImportOption doNotIncludeArchives) { } private static Location locationOf(Class clazz) { - return getOnlyElement(Locations.ofClass(clazz)); + return getLast(Locations.ofClass(clazz)); + } + + private static boolean comesFromJarArchive(Class clazz) { + return LocationTest.urlOfClass(clazz).getProtocol().equals("jar"); } -} \ No newline at end of file +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java index a5ad7a5a7b..09a535b671 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java @@ -26,18 +26,19 @@ import com.tngtech.archunit.core.domain.ImportContext; import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.domain.JavaCodeUnit; import com.tngtech.archunit.core.domain.JavaConstructor; import com.tngtech.archunit.core.domain.JavaConstructorCall; import com.tngtech.archunit.core.domain.JavaEnumConstant; import com.tngtech.archunit.core.domain.JavaField; import com.tngtech.archunit.core.domain.JavaFieldAccess; +import com.tngtech.archunit.core.domain.JavaMember; import com.tngtech.archunit.core.domain.JavaMethod; import com.tngtech.archunit.core.domain.JavaMethodCall; import com.tngtech.archunit.core.domain.JavaModifier; import com.tngtech.archunit.core.domain.JavaStaticInitializer; -import com.tngtech.archunit.core.domain.JavaType; -import com.tngtech.archunit.core.domain.ThrowsDeclaration; +import com.tngtech.archunit.core.domain.JavaTypeVariable; import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodCallBuilder; import org.objectweb.asm.Type; @@ -46,57 +47,51 @@ public class ImportTestUtils { private static Set createConstructors(JavaClass owner, Class inputClass, ClassesByTypeName importedClasses) { - return finish(constructorBuildersFor(inputClass, importedClasses), owner, importedClasses); + return finish(constructorBuildersFor(inputClass), owner, importedClasses); } private static Set createMethods(JavaClass owner, Class inputClass, ClassesByTypeName importedClasses) { - return finish(methodBuildersFor(inputClass, importedClasses), owner, importedClasses); + return finish(methodBuildersFor(inputClass), owner, importedClasses); } private static Set createFields(JavaClass owner, Class inputClass, ClassesByTypeName importedClasses) { - return finish(fieldBuildersFor(inputClass, importedClasses), owner, importedClasses); + return finish(fieldBuildersFor(inputClass), owner, importedClasses); } - private static Set> fieldBuildersFor(Class inputClass, - ClassesByTypeName importedClasses) { + private static Set> fieldBuildersFor(Class inputClass) { final Set> fieldBuilders = new HashSet<>(); for (Field field : inputClass.getDeclaredFields()) { fieldBuilders.add(new DomainBuilders.JavaFieldBuilder() .withName(field.getName()) .withDescriptor(Type.getDescriptor(field.getType())) - .withAnnotations(javaAnnotationBuildersFrom(field.getAnnotations(), inputClass, importedClasses)) .withModifiers(JavaModifier.getModifiersForField(field.getModifiers())) - .withType(JavaType.From.name(field.getType().getName()))); + .withType(JavaClassDescriptor.From.name(field.getType().getName()))); } return fieldBuilders; } - private static Set> methodBuildersFor(Class inputClass, - ClassesByTypeName importedClasses) { + private static Set> methodBuildersFor(Class inputClass) { final Set> methodBuilders = new HashSet<>(); for (Method method : inputClass.getDeclaredMethods()) { methodBuilders.add(new DomainBuilders.JavaMethodBuilder() - .withReturnType(JavaType.From.name(method.getReturnType().getName())) + .withReturnType(JavaClassDescriptor.From.name(method.getReturnType().getName())) .withParameters(typesFrom(method.getParameterTypes())) .withName(method.getName()) .withDescriptor(Type.getMethodDescriptor(method)) - .withAnnotations(javaAnnotationBuildersFrom(method.getAnnotations(), inputClass, importedClasses)) .withModifiers(JavaModifier.getModifiersForMethod(method.getModifiers())) .withThrowsClause(typesFrom(method.getExceptionTypes()))); } return methodBuilders; } - private static Set> constructorBuildersFor(Class inputClass, - ClassesByTypeName importedClasses) { + private static Set> constructorBuildersFor(Class inputClass) { final Set> constructorBuilders = new HashSet<>(); for (Constructor constructor : inputClass.getDeclaredConstructors()) { constructorBuilders.add(new DomainBuilders.JavaConstructorBuilder() - .withReturnType(JavaType.From.name(void.class.getName())) + .withReturnType(JavaClassDescriptor.From.name(void.class.getName())) .withParameters(typesFrom(constructor.getParameterTypes())) .withName(CONSTRUCTOR_NAME) .withDescriptor(Type.getConstructorDescriptor(constructor)) - .withAnnotations(javaAnnotationBuildersFrom(constructor.getAnnotations(), inputClass, importedClasses)) .withModifiers(JavaModifier.getModifiersForMethod(constructor.getModifiers())) .withThrowsClause(typesFrom(constructor.getExceptionTypes()))); } @@ -112,41 +107,32 @@ private static Set finish(Set javaAnnotationBuildersFrom(Annotation[] reflectionAnnotations, - Class annotatedClass, ClassesByTypeName importedClasses) { - ImmutableSet.Builder result = ImmutableSet.builder(); - for (Annotation annotation : reflectionAnnotations) { - result.add(javaAnnotationBuilderFrom(annotation, annotatedClass, importedClasses)); - } - return result.build(); - } - private static JavaClass javaClassFor(Class owner) { return new DomainBuilders.JavaClassBuilder() - .withType(JavaType.From.name(owner.getName())) + .withDescriptor(JavaClassDescriptor.From.name(owner.getName())) .withInterface(owner.isInterface()) .withModifiers(JavaModifier.getModifiersForClass(owner.getModifiers())) .build(); } - private static Map mapOf(Annotation annotation, Class annotatedClass, ClassesByTypeName importedClasses) { + private static Map mapOf(Annotation annotation, Class annotatedClass, ImportContext importContext) { ImmutableMap.Builder result = ImmutableMap.builder(); for (Method method : annotation.annotationType().getDeclaredMethods()) { - result.put(method.getName(), get(annotation, annotatedClass, method.getName(), importedClasses)); + result.put(method.getName(), get(annotation, annotatedClass, method.getName(), importContext)); } return result.build(); } - private static Object get(Annotation annotation, Class owner, String methodName, ClassesByTypeName importedClasses) { + private static Object get(Annotation annotation, Class owner, String methodName, ImportContext importContext) { try { Method method = annotation.annotationType().getMethod(methodName); method.setAccessible(true); Object result = method.invoke(annotation); if (result instanceof Class) { - return importedClasses.get(((Class) result).getName()); + return importContext.resolveClass(((Class) result).getName()); } if (result instanceof Class[]) { - List classes = javaClassesFrom((Class[]) result, importedClasses); + List classes = javaClassesFrom((Class[]) result, importContext); return classes.toArray(new JavaClass[0]); } if (result instanceof Enum) { @@ -167,10 +153,10 @@ private static Object get(Annotation annotation, Class owner, String methodNa } } - private static List javaClassesFrom(Class[] classes, ClassesByTypeName importedClasses) { + private static List javaClassesFrom(Class[] classes, ImportContext importContext) { ImmutableList.Builder result = ImmutableList.builder(); for (Class c : classes) { - result.add(importedClasses.get(c.getName())); + result.add(importContext.resolveClass(c.getName())); } return result.build(); } @@ -184,13 +170,13 @@ private static JavaEnumConstant[] enumConstants(Enum[] enums) { } private static List> javaAnnotationsFrom(Annotation[] annotations, Class owner) { - return javaAnnotationsFrom(annotations, simpleImportedClasses(), owner); + return javaAnnotationsFrom(annotations, simulateImportContext(owner, simpleImportedClasses()), owner); } - private static List> javaAnnotationsFrom(Annotation[] annotations, ClassesByTypeName importedClasses, Class owner) { + private static List> javaAnnotationsFrom(Annotation[] annotations, ImportContext importContext, Class owner) { List> result = new ArrayList<>(); for (Annotation a : annotations) { - result.add(ImportTestUtils.javaAnnotationFrom(a, owner, importedClasses)); + result.add(ImportTestUtils.javaAnnotationFrom(a, owner, importContext)); } return result; } @@ -215,10 +201,10 @@ public static JavaFieldAccess newFieldAccess(JavaMethod origin, JavaField target .build(); } - private static List typesFrom(Class[] classes) { - List result = new ArrayList<>(); + private static List typesFrom(Class[] classes) { + List result = new ArrayList<>(); for (Class clazz : classes) { - result.add(JavaType.From.name(clazz.getName())); + result.add(JavaClassDescriptor.From.name(clazz.getName())); } return result; } @@ -259,17 +245,17 @@ public static MethodCallTarget targetFrom(JavaMethod target, Supplier javaAnnotationFrom(Annotation annotation, Class annotatedClass) { - return javaAnnotationFrom(annotation, annotatedClass, ImportTestUtils.simpleImportedClasses()); + return javaAnnotationFrom(annotation, annotatedClass, simulateImportContext(annotatedClass, ImportTestUtils.simpleImportedClasses())); } - private static JavaAnnotation javaAnnotationFrom(Annotation annotation, Class annotatedClass, ClassesByTypeName importedClasses) { - return javaAnnotationBuilderFrom(annotation, annotatedClass, importedClasses).build(importedClasses.get(annotatedClass.getName()), importedClasses); + private static JavaAnnotation javaAnnotationFrom(Annotation annotation, Class annotatedClass, ImportContext importContext) { + return javaAnnotationBuilderFrom(annotation, annotatedClass, importContext).build(importContext.resolveClass(annotatedClass.getName()), importContext); } private static DomainBuilders.JavaAnnotationBuilder javaAnnotationBuilderFrom(Annotation annotation, Class annotatedClass, - ClassesByTypeName importedClasses) { + ImportContext importedClasses) { DomainBuilders.JavaAnnotationBuilder builder = new DomainBuilders.JavaAnnotationBuilder() - .withType(JavaType.From.name(annotation.annotationType().getName())); + .withType(JavaClassDescriptor.From.name(annotation.annotationType().getName())); for (Map.Entry entry : mapOf(annotation, annotatedClass, importedClasses).entrySet()) { builder.addProperty(entry.getKey(), DomainBuilders.JavaAnnotationBuilder.ValueBuilder.ofFinished(entry.getValue())); } @@ -307,12 +293,12 @@ public Set createConstructors(JavaClass owner) { @Override public Map> createAnnotations(JavaClass owner) { - return annotationsFor(inputClass, importedClasses); + return annotationsFor(inputClass, simulateImportContext(inputClass, importedClasses)); } }; } - private static ImmutableMap> annotationsFor(Class inputClass, ImportedTestClasses importedClasses) { + private static ImmutableMap> annotationsFor(Class inputClass, ImportContext importedClasses) { return FluentIterable.from(javaAnnotationsFrom(inputClass.getAnnotations(), importedClasses, inputClass)) .uniqueIndex(new Function, String>() { @Override @@ -333,7 +319,7 @@ void register(JavaClass clazz) { public JavaClass get(String typeName) { return imported.containsKey(typeName) ? imported.get(typeName) : - importNew(JavaType.From.name(typeName).resolveClass()); + importNew(JavaClassDescriptor.From.name(typeName).resolveClass()); } private JavaClass importNew(Class owner) { @@ -355,6 +341,11 @@ public Set createInterfaces(JavaClass owner) { return Collections.emptySet(); } + @Override + public List> createTypeParameters(JavaClass owner) { + return Collections.emptyList(); + } + @Override public Set createFields(JavaClass owner) { return Collections.emptySet(); @@ -381,68 +372,38 @@ public Map> createAnnotations(JavaClass owner) } @Override - public Optional createEnclosingClass(JavaClass owner) { - return Optional.absent(); - } - - @Override - public Set getFieldAccessesFor(JavaCodeUnit codeUnit) { - return Collections.emptySet(); - } - - @Override - public Set getMethodCallsFor(JavaCodeUnit codeUnit) { - return Collections.emptySet(); - } - - @Override - public Set getConstructorCallsFor(JavaCodeUnit codeUnit) { - return Collections.emptySet(); - } - - @Override - public Set getFieldsOfType(JavaClass javaClass) { - return Collections.emptySet(); - } - - @Override - public Set getMethodsWithParameterOfType(JavaClass javaClass) { - return Collections.emptySet(); - } - - @Override - public Set getMethodsWithReturnType(JavaClass javaClass) { - return Collections.emptySet(); + public Map> createAnnotations(JavaMember owner) { + return Collections.emptyMap(); } @Override - public Set> getMethodThrowsDeclarationsOfType(JavaClass javaClass) { - return Collections.emptySet(); + public Optional createEnclosingClass(JavaClass owner) { + return Optional.absent(); } @Override - public Set getConstructorsWithParameterOfType(JavaClass javaClass) { + public Set createFieldAccessesFor(JavaCodeUnit codeUnit) { return Collections.emptySet(); } @Override - public Set> getConstructorThrowsDeclarationsOfType(JavaClass javaClass) { + public Set createMethodCallsFor(JavaCodeUnit codeUnit) { return Collections.emptySet(); } @Override - public Set> getAnnotationsOfType(JavaClass javaClass) { + public Set createConstructorCallsFor(JavaCodeUnit codeUnit) { return Collections.emptySet(); } @Override - public Set> getAnnotationsWithParameterOfType(JavaClass javaClass) { - return Collections.emptySet(); + public JavaClass resolveClass(String fullyQualifiedClassName) { + throw new UnsupportedOperationException("Override me where necessary"); } @Override - public JavaClass resolveClass(String fullyQualifiedClassName) { - throw new UnsupportedOperationException("Override me where necessary"); + public Optional getMethodReturnType(String declaringClassName, String methodName) { + return Optional.absent(); } } } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/JavaAnnotationTestBuilder.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/JavaAnnotationTestBuilder.java index ff7d104fb2..ee4896d1a4 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/JavaAnnotationTestBuilder.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/JavaAnnotationTestBuilder.java @@ -1,15 +1,16 @@ package com.tngtech.archunit.core.importer; +import com.tngtech.archunit.core.domain.ImportContext; import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; -import com.tngtech.archunit.core.domain.JavaType; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.importer.DomainBuilders.JavaAnnotationBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaAnnotationBuilder.ValueBuilder; public class JavaAnnotationTestBuilder { private final JavaAnnotationBuilder delegate = new JavaAnnotationBuilder(); - public JavaAnnotationTestBuilder withType(JavaType type) { + public JavaAnnotationTestBuilder withType(JavaClassDescriptor type) { delegate.withType(type); return this; } @@ -19,7 +20,7 @@ public JavaAnnotationTestBuilder addProperty(String key, Object value) { return this; } - public JavaAnnotation build(JavaClass owner, ClassesByTypeName byTypeName) { - return delegate.build(owner, byTypeName); + public JavaAnnotation build(JavaClass owner, ImportContext importContext) { + return delegate.build(owner, importContext); } } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/JavaClassDescriptorImporterTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/JavaClassDescriptorImporterTest.java new file mode 100644 index 0000000000..27bcd0e7e9 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/JavaClassDescriptorImporterTest.java @@ -0,0 +1,27 @@ +package com.tngtech.archunit.core.importer; + +import com.tngtech.archunit.core.domain.JavaClassDescriptor; +import org.junit.Test; +import org.objectweb.asm.Type; + +import static com.tngtech.archunit.testutil.Assertions.assertThat; + +public class JavaClassDescriptorImporterTest { + + @Test + public void asm_object_type() { + JavaClassDescriptor objectType = JavaClassDescriptorImporter.createFromAsmObjectTypeName("java/lang/Object"); + + assertThat(objectType).isEquivalentTo(Object.class); + } + + @Test + public void asm_Type() throws NoSuchMethodException { + Type toStringType = Type.getReturnType(Object.class.getDeclaredMethod("toString")); + + JavaClassDescriptor toStringDescriptor = JavaClassDescriptorImporter.importAsmType(toStringType); + + assertThat(toStringDescriptor.getFullyQualifiedClassName()).isEqualTo(String.class.getName()); + assertThat(toStringDescriptor.resolveClass()).isEqualTo(String.class); + } +} \ No newline at end of file diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/JavaTypeImporterTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/JavaTypeImporterTest.java deleted file mode 100644 index a6fd6555a1..0000000000 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/JavaTypeImporterTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.tngtech.archunit.core.importer; - -import com.tngtech.archunit.core.domain.JavaType; -import org.junit.Test; -import org.objectweb.asm.Type; - -import static com.tngtech.archunit.testutil.Assertions.assertThat; - -public class JavaTypeImporterTest { - - @Test - public void asm_object_type() { - JavaType objectType = JavaTypeImporter.createFromAsmObjectTypeName("java/lang/Object"); - - assertThat(objectType).isEquivalentTo(Object.class); - } - - @Test - public void asm_Type() throws NoSuchMethodException { - Type toStringType = Type.getReturnType(Object.class.getDeclaredMethod("toString")); - - JavaType toStringJavaType = JavaTypeImporter.importAsmType(toStringType); - - assertThat(toStringJavaType.getName()).isEqualTo(String.class.getName()); - assertThat(toStringJavaType.resolveClass()).isEqualTo(String.class); - } -} \ No newline at end of file diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/TestClassFile.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/TestClassFile.java new file mode 100644 index 0000000000..700f9e5747 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/TestClassFile.java @@ -0,0 +1,51 @@ +package com.tngtech.archunit.core.importer; + +import java.io.File; +import java.io.IOException; + +import com.google.common.io.Files; + +import static com.google.common.base.Preconditions.checkState; +import static com.tngtech.archunit.testutil.TestUtils.newTemporaryFolder; +import static java.nio.charset.StandardCharsets.UTF_8; +import static javax.tools.ToolProvider.getSystemJavaCompiler; + +public class TestClassFile { + private static final String PACKAGE_NAME = "com.dummy"; + private static final String SOURCE_FOLDER = PACKAGE_NAME.replace(".", File.separator) + File.separator; + private static final String CLASS_NAME = "Dummy"; + private final File classpathRoot = newTemporaryFolder(); + private final File sourceFile = new File(classpathRoot, SOURCE_FOLDER + CLASS_NAME + ".java"); + + public TestClassFile() { + } + + public TestClassFile create() { + try { + String sourceCode = String.format("package %s;public class %s {}", PACKAGE_NAME, CLASS_NAME); + + checkState(sourceFile.getParentFile().exists() || sourceFile.getParentFile().mkdirs(), + "Can't create directory %s", sourceFile.getParentFile().getAbsolutePath()); + Files.write(sourceCode, sourceFile, UTF_8); + + int result = getSystemJavaCompiler().run(null, null, null, sourceFile.getAbsolutePath()); + checkState(result == 0, "Compiler exit code should be 0, but it was " + result); + + return this; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public String getPackageName() { + return PACKAGE_NAME; + } + + public String getClassName() { + return PACKAGE_NAME + "." + CLASS_NAME; + } + + public File getClasspathRoot() { + return classpathRoot; + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/UrlSourceTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/UrlSourceTest.java index f0182bfb83..8be6c2ba1d 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/UrlSourceTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/UrlSourceTest.java @@ -80,6 +80,7 @@ public void ignores_invalid_paths_in_class_path_property() { String classPath = createClassPathProperty(valid.toString(), "/invalid/path/because/of/" + CHARACTER_THAT_IS_HOPEFULLY_ILLEGAL_ON_EVERY_PLATFORM + "/"); System.setProperty(JAVA_CLASS_PATH_PROP, classPath); + System.clearProperty(JAVA_BOOT_PATH_PROP); assertThat(UrlSource.From.classPathSystemProperties()).containsOnly(toUrl(valid)); } @@ -107,6 +108,20 @@ public void handles_paths_with_spaces() throws Exception { assertThat(urls).contains(toUrl(destination)); } + @Test + public void handles_jar_uri_with_spaces() throws Exception { + File folderWithSpaces = temporaryFolder.newFolder("folder with spaces"); + File folder = temporaryFolder.newFolder(); + + WrittenJarFile jarInFolderWithSpaces = writeJarWithManifestClasspathAttribute(folderWithSpaces, "folder-with-spaces"); + WrittenJarFile parentJar = writeJarWithManifestClasspathAttribute(folder, "parent", ManifestClasspathEntry.absoluteUrl(jarInFolderWithSpaces.path)); + + System.setProperty(JAVA_CLASS_PATH_PROP, parentJar.path.toString()); + UrlSource urls = UrlSource.From.classPathSystemProperties(); + + assertThat(urls).containsAll(concat(parentJar.getExpectedClasspathUrls(), jarInFolderWithSpaces.getExpectedClasspathUrls())); + } + @Test public void recursively_resolves_classpath_attributes_in_manifests() throws Exception { File folder = temporaryFolder.newFolder(); @@ -142,6 +157,7 @@ public void terminates_recursively_resolving_manifest_classpaths_if_manifests_ha .create(jarTwoPath); System.setProperty(JAVA_CLASS_PATH_PROP, jarOne.getName()); + System.clearProperty(JAVA_BOOT_PATH_PROP); UrlSource urls = UrlSource.From.classPathSystemProperties(); assertThat(urls).containsOnly(toUrl(Paths.get(jarOne.getName())), toUrl(Paths.get(jarTwo.getName()))); diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/dependents/ClassDependingOnParentThroughChild.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/dependents/ClassDependingOnParentThroughChild.java deleted file mode 100644 index 1a909df4a1..0000000000 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/dependents/ClassDependingOnParentThroughChild.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.tngtech.archunit.core.importer.testexamples.dependents; - -public class ClassDependingOnParentThroughChild { - SubClassHoldingDependencies classHoldingDependencies; - - void doSomething() { - classHoldingDependencies = new SubClassHoldingDependencies(); - classHoldingDependencies.parentField = new Object(); - classHoldingDependencies.parentMethod(); - } -} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/dependents/ParentClassHoldingDependencies.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/dependents/ParentClassHoldingDependencies.java deleted file mode 100644 index 0efb4828bd..0000000000 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/dependents/ParentClassHoldingDependencies.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.tngtech.archunit.core.importer.testexamples.dependents; - -public class ParentClassHoldingDependencies { - Object parentField; - - public ParentClassHoldingDependencies() { - } - - void parentMethod() { - } -} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/dependents/SubClassHoldingDependencies.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/dependents/SubClassHoldingDependencies.java deleted file mode 100644 index 32e5db4451..0000000000 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/dependents/SubClassHoldingDependencies.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.tngtech.archunit.core.importer.testexamples.dependents; - -public class SubClassHoldingDependencies extends ParentClassHoldingDependencies { -} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/ChecksInstanceofInConstructor.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/ChecksInstanceofInConstructor.java new file mode 100644 index 0000000000..9b47abb5f9 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/ChecksInstanceofInConstructor.java @@ -0,0 +1,9 @@ +package com.tngtech.archunit.core.importer.testexamples.instanceofcheck; + +@SuppressWarnings("StatementWithEmptyBody") +public class ChecksInstanceofInConstructor { + public ChecksInstanceofInConstructor(Object param) { + if (param instanceof InstanceofChecked) { + } + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/ChecksInstanceofInMethod.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/ChecksInstanceofInMethod.java new file mode 100644 index 0000000000..70b850e14a --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/ChecksInstanceofInMethod.java @@ -0,0 +1,8 @@ +package com.tngtech.archunit.core.importer.testexamples.instanceofcheck; + +@SuppressWarnings("unused") +public class ChecksInstanceofInMethod { + boolean method(Object param) { + return param instanceof InstanceofChecked; + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/ChecksInstanceofInStaticInitializer.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/ChecksInstanceofInStaticInitializer.java new file mode 100644 index 0000000000..ea9620d30b --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/ChecksInstanceofInStaticInitializer.java @@ -0,0 +1,8 @@ +package com.tngtech.archunit.core.importer.testexamples.instanceofcheck; + +@SuppressWarnings({"unused", "ConstantConditions"}) +public class ChecksInstanceofInStaticInitializer { + static { + boolean foo = ((Object) null) instanceof InstanceofChecked; + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/InstanceofChecked.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/InstanceofChecked.java new file mode 100644 index 0000000000..f8e9164a05 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/InstanceofChecked.java @@ -0,0 +1,4 @@ +package com.tngtech.archunit.core.importer.testexamples.instanceofcheck; + +public class InstanceofChecked { +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/simpleimport/AnnotationParameter.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/simpleimport/AnnotationParameter.java new file mode 100644 index 0000000000..4f018535f2 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/simpleimport/AnnotationParameter.java @@ -0,0 +1,4 @@ +package com.tngtech.archunit.core.importer.testexamples.simpleimport; + +public @interface AnnotationParameter { +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/simpleimport/AnnotationToImport.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/simpleimport/AnnotationToImport.java new file mode 100644 index 0000000000..1c7d5e0ad9 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/simpleimport/AnnotationToImport.java @@ -0,0 +1,14 @@ +package com.tngtech.archunit.core.importer.testexamples.simpleimport; + +import java.util.List; + +@SuppressWarnings("unused") +public @interface AnnotationToImport { + String someStringMethod() default "DEFAULT"; + + Class someTypeMethod() default List.class; + + EnumToImport someEnumMethod() default EnumToImport.SECOND; + + AnnotationParameter someAnnotationMethod() default @AnnotationParameter; +} diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/CompositeArchRuleTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/CompositeArchRuleTest.java index 6546b6a51c..849c08dad5 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/CompositeArchRuleTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/CompositeArchRuleTest.java @@ -1,5 +1,6 @@ package com.tngtech.archunit.lang; +import com.google.common.collect.ImmutableList; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.java.junit.dataprovider.DataProvider; @@ -8,6 +9,8 @@ import org.junit.Test; import org.junit.runner.RunWith; +import java.util.List; + import static com.tngtech.archunit.core.domain.TestUtils.importClasses; import static com.tngtech.archunit.lang.Priority.HIGH; import static com.tngtech.archunit.lang.Priority.MEDIUM; @@ -40,6 +43,16 @@ public void rules_are_ANDed(ArchRule first, ArchRule second, boolean expectedSat assertPriority(result.getFailureReport().toString(), MEDIUM); } + @Test + @UseDataProvider("rules_to_AND") + public void archRuleCollection(ArchRule first, ArchRule second, boolean expectedSatisfied) { + List ruleCollection = ImmutableList.of(first, second); + EvaluationResult result = CompositeArchRule.of(ruleCollection).evaluate(importClasses(getClass())); + + assertThat(result.hasViolation()).as("result has violation").isEqualTo(!expectedSatisfied); + assertPriority(result.getFailureReport().toString(), MEDIUM); + } + @Test public void description_is_modified_correctly() { ArchRule input = classes().should().bePublic(); diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ClassesShouldTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ClassesShouldTest.java index c2f79c600d..ed45347374 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ClassesShouldTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ClassesShouldTest.java @@ -248,6 +248,31 @@ public void haveSimpleNameStartingWith(ArchRule rule, String prefix) { .doesNotContain(SomeClass.class.getName()); } + @DataProvider + public static Object[][] haveSimpleNameNotStartingWith_rules() { + String simpleName = WrongNamedClass.class.getSimpleName(); + String prefix = simpleName.substring(0, simpleName.length() - 1); + return $$( + $(classes().should().haveSimpleNameNotStartingWith(prefix), prefix), + $(classes().should(ArchConditions.haveSimpleNameNotStartingWith(prefix)), prefix) + ); + } + + @Test + @UseDataProvider("haveSimpleNameNotStartingWith_rules") + public void haveSimpleNameNotStartingWith(ArchRule rule, String prefix) { + EvaluationResult result = rule.evaluate(importClasses( + SomeClass.class, WrongNamedClass.class)); + + assertThat(singleLineFailureReportOf(result)) + .contains(String.format("classes should have simple name not starting with '%s'", prefix)) + .containsPattern(String.format("simple name of %s starts with '%s' in %s", + quote(WrongNamedClass.class.getName()), + quote(prefix), + locationPattern(WrongNamedClass.class))) + .doesNotContain(SomeClass.class.getName()); + } + @DataProvider public static Object[][] haveSimpleNameContaining_rules() { String simpleName = SomeClass.class.getSimpleName(); @@ -298,31 +323,6 @@ public void haveSimpleNameNotContaining(ArchRule rule, String infix) { .doesNotContain(SomeClass.class.getName()); } - @DataProvider - public static Object[][] haveSimpleNameNotStartingWith_rules() { - String simpleName = WrongNamedClass.class.getSimpleName(); - String prefix = simpleName.substring(0, simpleName.length() - 1); - return $$( - $(classes().should().haveSimpleNameNotStartingWith(prefix), prefix), - $(classes().should(ArchConditions.haveSimpleNameNotStartingWith(prefix)), prefix) - ); - } - - @Test - @UseDataProvider("haveSimpleNameNotStartingWith_rules") - public void haveSimpleNameNotStartingWith(ArchRule rule, String prefix) { - EvaluationResult result = rule.evaluate(importClasses( - SomeClass.class, WrongNamedClass.class)); - - assertThat(singleLineFailureReportOf(result)) - .contains(String.format("classes should have simple name not starting with '%s'", prefix)) - .containsPattern(String.format("simple name of %s starts with '%s' in %s", - quote(WrongNamedClass.class.getName()), - quote(prefix), - locationPattern(WrongNamedClass.class))) - .doesNotContain(SomeClass.class.getName()); - } - @DataProvider public static Object[][] haveSimpleNameEndingWith_rules() { String simpleName = SomeClass.class.getSimpleName(); @@ -1722,7 +1722,7 @@ public void onlyCall_should_report_success_if_targets_are_non_resolvable(ArchRul } static String locationPattern(Class clazz) { - return String.format("\\(%s.java:0\\)", quote(clazz.getSimpleName())); + return String.format("\\(%s.java:\\d+\\)", quote(clazz.getSimpleName())); } static String singleLineFailureReportOf(EvaluationResult result) { diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenClassesThatTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenClassesThatTest.java index 482df81216..2467638585 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenClassesThatTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenClassesThatTest.java @@ -43,7 +43,8 @@ import static com.tngtech.archunit.lang.conditions.ArchPredicates.have; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; import static com.tngtech.archunit.testutil.Assertions.assertThat; -import static com.tngtech.archunit.testutil.Assertions.assertThatClasses; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; public class GivenClassesThatTest { @Rule @@ -54,7 +55,7 @@ public void haveFullyQualifiedName() { List classes = filterResultOf(classes().that().haveFullyQualifiedName(List.class.getName())) .on(List.class, String.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(List.class); + assertThatType(getOnlyElement(classes)).matches(List.class); } @Test @@ -62,7 +63,7 @@ public void doNotHaveFullyQualifiedName() { List classes = filterResultOf(classes().that().doNotHaveFullyQualifiedName(List.class.getName())) .on(List.class, String.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(String.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(String.class, Iterable.class); } @Test @@ -70,7 +71,7 @@ public void haveSimpleName() { List classes = filterResultOf(classes().that().haveSimpleName(List.class.getSimpleName())) .on(List.class, String.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(List.class); + assertThatType(getOnlyElement(classes)).matches(List.class); } @Test @@ -78,7 +79,7 @@ public void doNotHaveSimpleName() { List classes = filterResultOf(classes().that().doNotHaveSimpleName(List.class.getSimpleName())) .on(List.class, String.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(String.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(String.class, Iterable.class); } @Test @@ -86,7 +87,7 @@ public void haveNameMatching() { List classes = filterResultOf(classes().that().haveNameMatching(".*List")) .on(List.class, String.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(List.class); + assertThatType(getOnlyElement(classes)).matches(List.class); } @Test @@ -94,7 +95,7 @@ public void haveNameNotMatching() { List classes = filterResultOf(classes().that().haveNameNotMatching(".*List")) .on(List.class, String.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(String.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(String.class, Iterable.class); } @Test @@ -102,7 +103,7 @@ public void haveSimpleNameStartingWith() { List classes = filterResultOf(classes().that().haveSimpleNameStartingWith("String")) .on(AttributedString.class, String.class, StringBuilder.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(String.class, StringBuilder.class); + assertThatTypes(classes).matchInAnyOrder(String.class, StringBuilder.class); } @Test @@ -110,7 +111,7 @@ public void haveSimpleNameNotStartingWith() { List classes = filterResultOf(classes().that().haveSimpleNameNotStartingWith("String")) .on(AttributedString.class, String.class, StringBuilder.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(AttributedString.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(AttributedString.class, Iterable.class); } @Test @@ -118,7 +119,7 @@ public void haveSimpleNameContaining() { List classes = filterResultOf(classes().that().haveSimpleNameContaining("rin")) .on(List.class, String.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(String.class); + assertThatTypes(classes).matchInAnyOrder(String.class); } @Test @@ -126,7 +127,7 @@ public void haveSimpleNameNotContaining() { List classes = filterResultOf(classes().that().haveSimpleNameNotContaining("rin")) .on(List.class, String.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(List.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(List.class, Iterable.class); } @Test @@ -134,7 +135,7 @@ public void haveSimpleNameEndingWith() { List classes = filterResultOf(classes().that().haveSimpleNameEndingWith("String")) .on(String.class, AttributedString.class, StringBuilder.class); - assertThatClasses(classes).matchInAnyOrder(String.class, AttributedString.class); + assertThatTypes(classes).matchInAnyOrder(String.class, AttributedString.class); } @Test @@ -142,7 +143,7 @@ public void haveSimpleNameNotEndingWith() { List classes = filterResultOf(classes().that().haveSimpleNameNotEndingWith("String")) .on(String.class, AttributedString.class, StringBuilder.class); - assertThatClasses(classes).matchInAnyOrder(StringBuilder.class); + assertThatTypes(classes).matchInAnyOrder(StringBuilder.class); } @Test @@ -150,7 +151,7 @@ public void resideInAPackage() { List classes = filterResultOf(classes().that().resideInAPackage("..tngtech..")) .on(getClass(), String.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(getClass()); + assertThatTypes(classes).matchInAnyOrder(getClass()); } @Test @@ -158,7 +159,7 @@ public void resideOutsideOfPackage() { List classes = filterResultOf(classes().that().resideOutsideOfPackage("..tngtech..")) .on(getClass(), String.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(String.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(String.class, Iterable.class); } @Test @@ -166,7 +167,7 @@ public void resideInAnyPackage() { List classes = filterResultOf(classes().that().resideInAnyPackage("..tngtech..", "java.lang.reflect")) .on(getClass(), String.class, Constructor.class); - assertThatClasses(classes).matchInAnyOrder(getClass(), Constructor.class); + assertThatTypes(classes).matchInAnyOrder(getClass(), Constructor.class); } @Test @@ -175,7 +176,7 @@ public void resideOutsideOfPackages() { .resideOutsideOfPackages("..tngtech..", "java.lang.reflect") ).on(getClass(), String.class, Constructor.class); - assertThatClasses(classes).matchInAnyOrder(String.class); + assertThatTypes(classes).matchInAnyOrder(String.class); } @Test @@ -183,7 +184,7 @@ public void arePublic() { List classes = filterResultOf(classes().that().arePublic()) .on(getClass(), PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(getClass()); + assertThatTypes(classes).matchInAnyOrder(getClass()); } @Test @@ -191,7 +192,7 @@ public void areNotPublic() { List classes = filterResultOf(classes().that().areNotPublic()) .on(getClass(), PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); + assertThatTypes(classes).matchInAnyOrder(PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); } @Test @@ -199,7 +200,7 @@ public void areProtected() { List classes = filterResultOf(classes().that().areProtected()) .on(getClass(), PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ProtectedClass.class); + assertThatTypes(classes).matchInAnyOrder(ProtectedClass.class); } @Test @@ -207,7 +208,7 @@ public void areNotProtected() { List classes = filterResultOf(classes().that().areNotProtected()) .on(getClass(), PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(getClass(), PrivateClass.class, PackagePrivateClass.class); + assertThatTypes(classes).matchInAnyOrder(getClass(), PrivateClass.class, PackagePrivateClass.class); } @Test @@ -215,7 +216,7 @@ public void arePackagePrivate() { List classes = filterResultOf(classes().that().arePackagePrivate()) .on(getClass(), PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(PackagePrivateClass.class); + assertThatTypes(classes).matchInAnyOrder(PackagePrivateClass.class); } @Test @@ -223,7 +224,7 @@ public void areNotPackagePrivate() { List classes = filterResultOf(classes().that().areNotPackagePrivate()) .on(getClass(), PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(getClass(), PrivateClass.class, ProtectedClass.class); + assertThatTypes(classes).matchInAnyOrder(getClass(), PrivateClass.class, ProtectedClass.class); } @Test @@ -231,7 +232,7 @@ public void arePrivate() { List classes = filterResultOf(classes().that().arePrivate()) .on(getClass(), PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(PrivateClass.class); + assertThatTypes(classes).matchInAnyOrder(PrivateClass.class); } @Test @@ -239,7 +240,7 @@ public void areNotPrivate() { List classes = filterResultOf(classes().that().areNotPrivate()) .on(getClass(), PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(getClass(), PackagePrivateClass.class, ProtectedClass.class); + assertThatTypes(classes).matchInAnyOrder(getClass(), PackagePrivateClass.class, ProtectedClass.class); } @Test @@ -247,7 +248,7 @@ public void haveModifiers() { List classes = filterResultOf(classes().that().haveModifier(PRIVATE)) .on(getClass(), PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(PrivateClass.class); + assertThatTypes(classes).matchInAnyOrder(PrivateClass.class); } @Test @@ -255,7 +256,7 @@ public void doNotHaveModifiers() { List classes = filterResultOf(classes().that().doNotHaveModifier(PRIVATE)) .on(getClass(), PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(getClass(), PackagePrivateClass.class, ProtectedClass.class); + assertThatTypes(classes).matchInAnyOrder(getClass(), PackagePrivateClass.class, ProtectedClass.class); } @Test @@ -263,7 +264,7 @@ public void areAnnotatedWith_type() { List classes = filterResultOf(classes().that().areAnnotatedWith(SomeAnnotation.class)) .on(AnnotatedClass.class, SimpleClass.class); - assertThat(getOnlyElement(classes)).matches(AnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(AnnotatedClass.class); } /** @@ -284,7 +285,7 @@ public void areNotAnnotatedWith_type() { List classes = filterResultOf(classes().that().areNotAnnotatedWith(SomeAnnotation.class)) .on(AnnotatedClass.class, SimpleClass.class); - assertThat(getOnlyElement(classes)).matches(SimpleClass.class); + assertThatType(getOnlyElement(classes)).matches(SimpleClass.class); } /** @@ -305,7 +306,7 @@ public void areAnnotatedWith_typeName() { List classes = filterResultOf(classes().that().areAnnotatedWith(SomeAnnotation.class.getName())) .on(AnnotatedClass.class, SimpleClass.class); - assertThat(getOnlyElement(classes)).matches(AnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(AnnotatedClass.class); } @Test @@ -313,7 +314,7 @@ public void areNotAnnotatedWith_typeName() { List classes = filterResultOf(classes().that().areNotAnnotatedWith(SomeAnnotation.class.getName())) .on(AnnotatedClass.class, SimpleClass.class); - assertThat(getOnlyElement(classes)).matches(SimpleClass.class); + assertThatType(getOnlyElement(classes)).matches(SimpleClass.class); } @Test @@ -322,7 +323,7 @@ public void areAnnotatedWith_predicate() { List classes = filterResultOf(classes().that().areAnnotatedWith(hasNamePredicate)) .on(AnnotatedClass.class, SimpleClass.class); - assertThat(getOnlyElement(classes)).matches(AnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(AnnotatedClass.class); } @Test @@ -331,7 +332,7 @@ public void areNotAnnotatedWith_predicate() { List classes = filterResultOf(classes().that().areNotAnnotatedWith(hasNamePredicate)) .on(AnnotatedClass.class, SimpleClass.class); - assertThat(getOnlyElement(classes)).matches(SimpleClass.class); + assertThatType(getOnlyElement(classes)).matches(SimpleClass.class); } @Test @@ -339,7 +340,7 @@ public void areMetaAnnotatedWith_type() { List classes = filterResultOf(classes().that().areMetaAnnotatedWith(SomeAnnotation.class)) .on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); - assertThat(getOnlyElement(classes)).matches(MetaAnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(MetaAnnotatedClass.class); } @Test @@ -347,7 +348,7 @@ public void areNotMetaAnnotatedWith_type() { List classes = filterResultOf(classes().that().areNotMetaAnnotatedWith(SomeAnnotation.class)) .on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); + assertThatTypes(classes).matchInAnyOrder(AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); } @Test @@ -355,7 +356,7 @@ public void areMetaAnnotatedWith_typeName() { List classes = filterResultOf(classes().that().areMetaAnnotatedWith(SomeAnnotation.class.getName())) .on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); - assertThat(getOnlyElement(classes)).matches(MetaAnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(MetaAnnotatedClass.class); } @Test @@ -363,7 +364,7 @@ public void areNotMetaAnnotatedWith_typeName() { List classes = filterResultOf(classes().that().areNotMetaAnnotatedWith(SomeAnnotation.class.getName())) .on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); + assertThatTypes(classes).matchInAnyOrder(AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); } @Test @@ -372,7 +373,7 @@ public void areMetaAnnotatedWith_predicate() { List classes = filterResultOf(classes().that().areMetaAnnotatedWith(hasNamePredicate)) .on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); - assertThat(getOnlyElement(classes)).matches(MetaAnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(MetaAnnotatedClass.class); } @Test @@ -381,7 +382,7 @@ public void areNotMetaAnnotatedWith_predicate() { List classes = filterResultOf(classes().that().areNotMetaAnnotatedWith(hasNamePredicate)) .on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); + assertThatTypes(classes).matchInAnyOrder(AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); } @Test @@ -389,7 +390,7 @@ public void implement_type() { List classes = filterResultOf(classes().that().implement(Collection.class)) .on(ArrayList.class, List.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(ArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ArrayList.class); classes = filterResultOf(classes().that().implement(Set.class)) .on(ArrayList.class, List.class, Iterable.class); @@ -410,7 +411,7 @@ public void doNotImplement_type() { List classes = filterResultOf(classes().that().doNotImplement(Collection.class)) .on(ArrayList.class, List.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(List.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(List.class, Iterable.class); } @Test @@ -426,7 +427,7 @@ public void implement_typeName() { List classes = filterResultOf(classes().that().implement(Collection.class.getName())) .on(ArrayList.class, List.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(ArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ArrayList.class); classes = filterResultOf(classes().that().implement(AbstractList.class.getName())) .on(ArrayList.class, List.class, Iterable.class); @@ -439,7 +440,7 @@ public void doNotImplement_typeName() { List classes = filterResultOf(classes().that().doNotImplement(Collection.class.getName())) .on(ArrayList.class, List.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(List.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(List.class, Iterable.class); } @Test @@ -447,7 +448,7 @@ public void implement_predicate() { List classes = filterResultOf(classes().that().implement(classWithNameOf(Collection.class))) .on(ArrayList.class, List.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(ArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ArrayList.class); classes = filterResultOf(classes().that().implement(classWithNameOf(AbstractList.class))) .on(ArrayList.class, List.class, Iterable.class); @@ -460,7 +461,7 @@ public void doNotImplement_predicate() { List classes = filterResultOf(classes().that().doNotImplement(classWithNameOf(Collection.class))) .on(ArrayList.class, List.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(List.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(List.class, Iterable.class); } @Test @@ -468,12 +469,12 @@ public void areAssignableTo_type() { List classes = filterResultOf(classes().that().areAssignableTo(Collection.class)) .on(List.class, String.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(List.class); + assertThatType(getOnlyElement(classes)).matches(List.class); classes = filterResultOf(classes().that().areAssignableTo(AbstractList.class)) .on(ArrayList.class, List.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(ArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ArrayList.class); } @Test @@ -481,7 +482,7 @@ public void areNotAssignableTo_type() { List classes = filterResultOf(classes().that().areNotAssignableTo(Collection.class)) .on(List.class, String.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(String.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(String.class, Iterable.class); } @Test @@ -489,12 +490,12 @@ public void areAssignableTo_typeName() { List classes = filterResultOf(classes().that().areAssignableTo(Collection.class.getName())) .on(List.class, String.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(List.class); + assertThatType(getOnlyElement(classes)).matches(List.class); classes = filterResultOf(classes().that().areAssignableTo(AbstractList.class.getName())) .on(ArrayList.class, List.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(ArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ArrayList.class); } @Test @@ -502,7 +503,7 @@ public void areNotAssignableTo_typeName() { List classes = filterResultOf(classes().that().areNotAssignableTo(Collection.class.getName())) .on(List.class, String.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(String.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(String.class, Iterable.class); } @Test @@ -510,12 +511,12 @@ public void areAssignableTo_predicate() { List classes = filterResultOf(classes().that().areAssignableTo(classWithNameOf(Collection.class))) .on(List.class, String.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(List.class); + assertThatType(getOnlyElement(classes)).matches(List.class); classes = filterResultOf(classes().that().areAssignableTo(classWithNameOf(AbstractList.class))) .on(ArrayList.class, List.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(ArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ArrayList.class); } @Test @@ -523,7 +524,7 @@ public void areNotAssignableTo_predicate() { List classes = filterResultOf(classes().that().areNotAssignableTo(classWithNameOf(Collection.class))) .on(List.class, String.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(String.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(String.class, Iterable.class); } @Test @@ -531,7 +532,7 @@ public void areAssignableFrom_type() { List classes = filterResultOf(classes().that().areAssignableFrom(Collection.class)) .on(List.class, String.class, Collection.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(Collection.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(Collection.class, Iterable.class); } @Test @@ -539,7 +540,7 @@ public void areNotAssignableFrom_type() { List classes = filterResultOf(classes().that().areNotAssignableFrom(Collection.class)) .on(List.class, String.class, Collection.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(List.class, String.class); + assertThatTypes(classes).matchInAnyOrder(List.class, String.class); } @Test @@ -547,7 +548,7 @@ public void areAssignableFrom_typeName() { List classes = filterResultOf(classes().that().areAssignableFrom(Collection.class.getName())) .on(List.class, String.class, Collection.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(Collection.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(Collection.class, Iterable.class); } @Test @@ -555,7 +556,7 @@ public void areNotAssignableFrom_typeName() { List classes = filterResultOf(classes().that().areNotAssignableFrom(Collection.class.getName())) .on(List.class, String.class, Collection.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(List.class, String.class); + assertThatTypes(classes).matchInAnyOrder(List.class, String.class); } @Test @@ -563,7 +564,7 @@ public void areAssignableFrom_predicate() { List classes = filterResultOf(classes().that().areAssignableFrom(classWithNameOf(Collection.class))) .on(List.class, String.class, Collection.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(Collection.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(Collection.class, Iterable.class); } @Test @@ -571,7 +572,7 @@ public void areNotAssignableFrom_predicate() { List classes = filterResultOf(classes().that().areNotAssignableFrom(classWithNameOf(Collection.class))) .on(List.class, String.class, Collection.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(List.class, String.class); + assertThatTypes(classes).matchInAnyOrder(List.class, String.class); } @Test @@ -579,7 +580,7 @@ public void areInterfaces_predicate() { List classes = filterResultOf(classes().that().areInterfaces()) .on(List.class, String.class, Collection.class, Integer.class); - assertThatClasses(classes).matchInAnyOrder(List.class, Collection.class); + assertThatTypes(classes).matchInAnyOrder(List.class, Collection.class); } @Test @@ -587,7 +588,7 @@ public void areNotInterfaces_predicate() { List classes = filterResultOf(classes().that().areNotInterfaces()) .on(List.class, String.class, Collection.class, Integer.class); - assertThatClasses(classes).matchInAnyOrder(String.class, Integer.class); + assertThatTypes(classes).matchInAnyOrder(String.class, Integer.class); } @Test @@ -595,7 +596,7 @@ public void areEnums_predicate() { List classes = filterResultOf(classes().that().areEnums()) .on(StandardCopyOption.class, StandardOpenOption.class, Collection.class, Integer.class); - assertThatClasses(classes).matchInAnyOrder(StandardCopyOption.class, StandardOpenOption.class); + assertThatTypes(classes).matchInAnyOrder(StandardCopyOption.class, StandardOpenOption.class); } @Test @@ -603,7 +604,23 @@ public void areNotEnums_predicate() { List classes = filterResultOf(classes().that().areNotEnums()) .on(StandardCopyOption.class, StandardOpenOption.class, Collection.class, Integer.class); - assertThatClasses(classes).matchInAnyOrder(Collection.class, Integer.class); + assertThatTypes(classes).matchInAnyOrder(Collection.class, Integer.class); + } + + @Test + public void areAnnotations_predicate() { + List classes = filterResultOf(classes().that().areAnnotations()) + .on(Deprecated.class, Collection.class, SafeVarargs.class, Integer.class); + + assertThatTypes(classes).matchInAnyOrder(Deprecated.class, SafeVarargs.class); + } + + @Test + public void areNotAnnotations_predicate() { + List classes = filterResultOf(classes().that().areNotAnnotations()) + .on(Deprecated.class, Collection.class, SafeVarargs.class, Integer.class); + + assertThatTypes(classes).matchInAnyOrder(Collection.class, Integer.class); } @Test @@ -613,7 +630,7 @@ public void areTopLevelClasses_predicate() { NestedClassWithSomeMoreClasses.StaticNestedClass.class, NestedClassWithSomeMoreClasses.InnerMemberClass.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes).matchInAnyOrder(List.class, Map.class); + assertThatTypes(classes).matchInAnyOrder(List.class, Map.class); } @Test @@ -623,7 +640,7 @@ public void areNotTopLevelClasses_predicate() { NestedClassWithSomeMoreClasses.StaticNestedClass.class, NestedClassWithSomeMoreClasses.InnerMemberClass.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes) + assertThatTypes(classes) .matchInAnyOrder(Map.Entry.class, NestedClassWithSomeMoreClasses.class, NestedClassWithSomeMoreClasses.StaticNestedClass.class, NestedClassWithSomeMoreClasses.InnerMemberClass.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); @@ -636,7 +653,7 @@ public void areNestedClasses_predicate() { NestedClassWithSomeMoreClasses.StaticNestedClass.class, NestedClassWithSomeMoreClasses.InnerMemberClass.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes) + assertThatTypes(classes) .matchInAnyOrder(Map.Entry.class, NestedClassWithSomeMoreClasses.class, NestedClassWithSomeMoreClasses.StaticNestedClass.class, NestedClassWithSomeMoreClasses.InnerMemberClass.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); @@ -649,7 +666,7 @@ public void areNotNestedClasses_predicate() { NestedClassWithSomeMoreClasses.StaticNestedClass.class, NestedClassWithSomeMoreClasses.InnerMemberClass.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes).matchInAnyOrder(List.class, Map.class); + assertThatTypes(classes).matchInAnyOrder(List.class, Map.class); } @Test @@ -659,7 +676,7 @@ public void areMemberClasses_predicate() { NestedClassWithSomeMoreClasses.StaticNestedClass.class, NestedClassWithSomeMoreClasses.InnerMemberClass.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes) + assertThatTypes(classes) .matchInAnyOrder(Map.Entry.class, NestedClassWithSomeMoreClasses.class, NestedClassWithSomeMoreClasses.StaticNestedClass.class, NestedClassWithSomeMoreClasses.InnerMemberClass.class); } @@ -671,7 +688,7 @@ public void areNotMemberClasses_predicate() { NestedClassWithSomeMoreClasses.StaticNestedClass.class, NestedClassWithSomeMoreClasses.InnerMemberClass.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes).matchInAnyOrder(List.class, Map.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), + assertThatTypes(classes).matchInAnyOrder(List.class, Map.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); } @@ -682,7 +699,7 @@ public void areInnerClasses_predicate() { NestedClassWithSomeMoreClasses.StaticNestedClass.class, NestedClassWithSomeMoreClasses.InnerMemberClass.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes) + assertThatTypes(classes) .matchInAnyOrder(NestedClassWithSomeMoreClasses.InnerMemberClass.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); } @@ -694,7 +711,7 @@ public void areNotInnerClasses_predicate() { NestedClassWithSomeMoreClasses.StaticNestedClass.class, NestedClassWithSomeMoreClasses.InnerMemberClass.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes).matchInAnyOrder(List.class, Map.class, Map.Entry.class, NestedClassWithSomeMoreClasses.class, + assertThatTypes(classes).matchInAnyOrder(List.class, Map.class, Map.Entry.class, NestedClassWithSomeMoreClasses.class, NestedClassWithSomeMoreClasses.StaticNestedClass.class); } @@ -703,7 +720,7 @@ public void areAnonymousClasses_predicate() { List classes = filterResultOf(classes().that().areAnonymousClasses()) .on(Map.class, Map.Entry.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes).matchInAnyOrder(NestedClassWithSomeMoreClasses.getAnonymousClass()); + assertThatTypes(classes).matchInAnyOrder(NestedClassWithSomeMoreClasses.getAnonymousClass()); } @Test @@ -711,7 +728,7 @@ public void areNotAnonymousClasses_predicate() { List classes = filterResultOf(classes().that().areNotAnonymousClasses()) .on(Map.class, Map.Entry.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes).matchInAnyOrder(Map.class, Map.Entry.class, NestedClassWithSomeMoreClasses.getLocalClass()); + assertThatTypes(classes).matchInAnyOrder(Map.class, Map.Entry.class, NestedClassWithSomeMoreClasses.getLocalClass()); } @Test @@ -719,7 +736,7 @@ public void areLocalClasses_predicate() { List classes = filterResultOf(classes().that().areLocalClasses()) .on(Map.class, Map.Entry.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes).matchInAnyOrder(NestedClassWithSomeMoreClasses.getLocalClass()); + assertThatTypes(classes).matchInAnyOrder(NestedClassWithSomeMoreClasses.getLocalClass()); } @Test @@ -727,7 +744,7 @@ public void areNotLocalClasses_predicate() { List classes = filterResultOf(classes().that().areNotLocalClasses()) .on(Map.class, Map.Entry.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes).matchInAnyOrder(Map.class, Map.Entry.class, NestedClassWithSomeMoreClasses.getAnonymousClass()); + assertThatTypes(classes).matchInAnyOrder(Map.class, Map.Entry.class, NestedClassWithSomeMoreClasses.getAnonymousClass()); } @Test @@ -736,11 +753,21 @@ public void belongToAnyOf() { .on(ClassWithInnerClasses.class, ClassWithInnerClasses.InnerClass.class, ClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class, List.class, String.class, Iterable.class, StringBuilder.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassWithInnerClasses.class, ClassWithInnerClasses.InnerClass.class, ClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class, String.class); } + @Test + public void doNotBelongToAnyOf() { + List classes = filterResultOf(classes().that().doNotBelongToAnyOf(ClassWithInnerClasses.class, String.class)) + .on(ClassWithInnerClasses.class, ClassWithInnerClasses.InnerClass.class, ClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class, + List.class, String.class, Iterable.class, StringBuilder.class); + + assertThatTypes(classes).matchInAnyOrder( + List.class, Iterable.class, StringBuilder.class); + } + @Test public void and_conjunction() { List classes = filterResultOf( @@ -749,7 +776,7 @@ public void and_conjunction() { .and().haveNameMatching(".*\\..*n.*")) .on(List.class, String.class, Collection.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(String.class); + assertThatTypes(classes).matchInAnyOrder(String.class); } @Test @@ -760,7 +787,7 @@ public void or_conjunction() { .or().haveSimpleName(Collection.class.getSimpleName())) .on(List.class, String.class, Collection.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(List.class, String.class, Collection.class); + assertThatTypes(classes).matchInAnyOrder(List.class, String.class, Collection.class); } /** @@ -786,7 +813,7 @@ public void conjunctions_aggregate_in_sequence_without_special_precedence() { .or().haveSimpleName(Iterable.class.getSimpleName())) .on(List.class, String.class, Collection.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(Collection.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(Collection.class, Iterable.class); } private DescribedPredicate classWithNameOf(Class type) { diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersDeclaredInClassesThatTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersDeclaredInClassesThatTest.java index e2fadacf96..f47a76f8bb 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersDeclaredInClassesThatTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersDeclaredInClassesThatTest.java @@ -603,6 +603,22 @@ public void areNotEnums_predicate() { assertThatMembers(members).matchInAnyOrderMembersOf(Collection.class, Integer.class); } + @Test + public void areAnnotations_predicate() { + List members = filterResultOf(members().that().areDeclaredInClassesThat().areAnnotations()) + .on(Deprecated.class, Collection.class, SafeVarargs.class, Integer.class); + + assertThatMembers(members).matchInAnyOrderMembersOf(Deprecated.class, SafeVarargs.class); + } + + @Test + public void areNotAnnotations_predicate() { + List members = filterResultOf(members().that().areDeclaredInClassesThat().areNotAnnotations()) + .on(Deprecated.class, Collection.class, SafeVarargs.class, Integer.class); + + assertThatMembers(members).matchInAnyOrderMembersOf(Collection.class, Integer.class); + } + @Test public void areTopLevelClasses_predicate() { List members = filterResultOf(members().that().areDeclaredInClassesThat().areTopLevelClasses()) @@ -746,6 +762,19 @@ public void belongToAnyOf() { ); } + @Test + public void doNotBelongToAnyOf() { + List members = + filterResultOf(members().that().areDeclaredInClassesThat().doNotBelongToAnyOf(ClassWithInnerClasses.class, String.class)) + .on(ClassWithInnerClasses.class, ClassWithInnerClasses.InnerClass.class, + ClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class, + List.class, String.class, Iterable.class, StringBuilder.class); + + assertThatMembers(members).matchInAnyOrderMembersOf( + List.class, Iterable.class, StringBuilder.class + ); + } + @Test public void and_conjunction() { List members = filterResultOf( diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersTest.java index e83e127d72..d7408a9b81 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersTest.java @@ -214,6 +214,39 @@ public static Object[][] restricted_property_rule_starts() { allConstructorsExcept(CONSTRUCTOR_ONE_ARG)), $(described(fields().that().haveFullNameNotMatching(quote(classNameDot) + ".*A.*")), allFieldsExcept(FIELD_A)), + $(described(members().that().haveNameStartingWith("fie")), ALL_FIELD_DESCRIPTIONS), + $(described(codeUnits().that().haveNameStartingWith("me")), ALL_METHOD_DESCRIPTIONS), + $(described(methods().that().haveNameStartingWith("m")), ALL_METHOD_DESCRIPTIONS), + $(described(constructors().that().haveNameStartingWith("<")), ALL_CONSTRUCTOR_DESCRIPTIONS), + $(described(fields().that().haveNameStartingWith("f")), ALL_FIELD_DESCRIPTIONS), + $(described(members().that().haveNameNotStartingWith("fie")), ALL_CODE_UNIT_DESCRIPTIONS), + $(described(codeUnits().that().haveNameNotStartingWith("me")), ALL_CONSTRUCTOR_DESCRIPTIONS), + $(described(methods().that().haveNameNotStartingWith("m")), emptySet()), + $(described(constructors().that().haveNameNotStartingWith("<")), emptySet()), + $(described(fields().that().haveNameNotStartingWith("f")), emptySet()), + + $(described(members().that().haveNameContaining("et")), ALL_METHOD_DESCRIPTIONS), + $(described(codeUnits().that().haveNameContaining("et")), ALL_METHOD_DESCRIPTIONS), + $(described(methods().that().haveNameContaining("dA")), ImmutableSet.of(METHOD_A)), + $(described(constructors().that().haveNameContaining("init")), ALL_CONSTRUCTOR_DESCRIPTIONS), + $(described(fields().that().haveNameContaining("dA")), ImmutableSet.of(FIELD_A)), + $(described(members().that().haveNameNotContaining("et")), union(ALL_FIELD_DESCRIPTIONS, ALL_CONSTRUCTOR_DESCRIPTIONS)), + $(described(codeUnits().that().haveNameNotContaining("et")), ALL_CONSTRUCTOR_DESCRIPTIONS), + $(described(methods().that().haveNameNotContaining("dA")), allMethodsExcept(METHOD_A)), + $(described(constructors().that().haveNameNotContaining("init")), emptySet()), + $(described(fields().that().haveNameNotContaining("dA")), allFieldsExcept(FIELD_A)), + + $(described(members().that().haveNameEndingWith("D")), ImmutableSet.of(FIELD_D, METHOD_D)), + $(described(codeUnits().that().haveNameEndingWith("A")), ImmutableSet.of(METHOD_A)), + $(described(methods().that().haveNameEndingWith("C")), ImmutableSet.of(METHOD_C)), + $(described(constructors().that().haveNameEndingWith("it>")), ALL_CONSTRUCTOR_DESCRIPTIONS), + $(described(fields().that().haveNameEndingWith("B")), ImmutableSet.of(FIELD_B)), + $(described(members().that().haveNameNotEndingWith("D")), allMembersExcept(FIELD_D, METHOD_D)), + $(described(codeUnits().that().haveNameNotEndingWith("A")), allCodeUnitsExcept(METHOD_A)), + $(described(methods().that().haveNameNotEndingWith("C")), allMethodsExcept(METHOD_C)), + $(described(constructors().that().haveNameNotEndingWith("it>")), emptySet()), + $(described(fields().that().haveNameNotEndingWith("B")), allFieldsExcept(FIELD_B)), + $(described(members().that().arePublic()), ImmutableSet.of( FIELD_PUBLIC, METHOD_PUBLIC, CONSTRUCTOR_PUBLIC)), $(described(fields().that().arePublic()), ImmutableSet.of(FIELD_C)), 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 index 3adbb41295..b7d4d50c72 100644 --- 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 @@ -2,7 +2,6 @@ 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; diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/MembersShouldTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/MembersShouldTest.java index 3725b15664..7683a63384 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/MembersShouldTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/MembersShouldTest.java @@ -9,13 +9,16 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.tngtech.archunit.base.Function; +import com.tngtech.archunit.lang.ArchRule; import com.tngtech.archunit.lang.EvaluationResult; +import com.tngtech.archunit.lang.conditions.ArchConditions; import com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.A; import com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.B; import com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.C; import com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.ClassWithVariousMembers; import com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.MetaAnnotation; import com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.OtherClassWithMembers; +import com.tngtech.archunit.lang.syntax.elements.testclasses.SimpleFieldAndMethod; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; @@ -35,6 +38,8 @@ import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.members; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods; +import static com.tngtech.archunit.lang.syntax.elements.ClassesShouldTest.locationPattern; +import static com.tngtech.archunit.lang.syntax.elements.ClassesShouldTest.singleLineFailureReportOf; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.ALL_CODE_UNIT_DESCRIPTIONS; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.ALL_CONSTRUCTOR_DESCRIPTIONS; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.ALL_FIELD_DESCRIPTIONS; @@ -53,6 +58,7 @@ import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.CONSTRUCTOR_PUBLIC; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.FIELD_A; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.FIELD_ANNOTATED_WITH_A; +import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.FIELD_B; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.FIELD_C; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.FIELD_D; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.FIELD_PACKAGE_PRIVATE; @@ -61,6 +67,9 @@ import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.FIELD_PUBLIC; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_A; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_ANNOTATED_WITH_A; +import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_B; +import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_C; +import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_D; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_PACKAGE_PRIVATE; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_PRIVATE; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_PROTECTED; @@ -160,6 +169,39 @@ public static Object[][] restricted_property_rule_ends() { $(codeUnits().should().haveFullNameNotMatching(quote(classNameDot) + ".*init.*"), ALL_CONSTRUCTOR_DESCRIPTIONS), $(constructors().should().haveFullNameNotMatching(quote(classNameDot) + ".*init.*String\\)"), ImmutableSet.of(CONSTRUCTOR_ONE_ARG)), + $(members().should().haveNameStartingWith("fi"), ALL_CODE_UNIT_DESCRIPTIONS), + $(fields().should().haveNameStartingWith("m"), ALL_FIELD_DESCRIPTIONS), + $(codeUnits().should().haveNameStartingWith(""), ALL_METHOD_DESCRIPTIONS), + $(methods().should().haveNameEndingWith("dC"), allMethodsExcept(METHOD_C)), + $(constructors().should().haveNameEndingWith(""), ALL_CONSTRUCTOR_DESCRIPTIONS), + $(methods().should().haveNameNotEndingWith("dC"), ImmutableSet.of(METHOD_C)), + $(constructors().should().haveNameNotEndingWith(" conjunction, Set< assertThat(actualMembers).containsOnlyElementsOf(expectedMessages); } + @DataProvider + public static Object[][] haveNameStartingWith_rules() { + return $$( + $(members().should().haveNameStartingWith("field"), "field", "violated"), + $(members().should(ArchConditions.haveNameStartingWith("field")), "field", "violated"), + $(fields().should().haveNameStartingWith("field"), "field", "violated"), + $(fields().should(ArchConditions.haveNameStartingWith("field")), "field", "violated"), + $(codeUnits().should().haveNameStartingWith("method"), "method", "violated"), + $(codeUnits().should(ArchConditions.haveNameStartingWith("method")), "method", "violated"), + $(methods().should().haveNameStartingWith("method"), "method", "violated"), + $(methods().should(ArchConditions.haveNameStartingWith("method")), "method", "violated"), + $(constructors().should().haveNameStartingWith("constructor"), "constructor", ""), + $(constructors().should(ArchConditions.haveNameStartingWith("constructor")), "constructor", "") + ); + } + + @Test + @UseDataProvider("haveNameStartingWith_rules") + public void haveNameStartingWith(ArchRule rule, String prefix, String violatingMember) { + EvaluationResult result = rule.evaluate(importClasses(SimpleFieldAndMethod.class)); + + assertThat(singleLineFailureReportOf(result)) + .containsPattern(String.format(".*%s.* name does not start with '%s' in %s", + quote(violatingMember), + quote(prefix), + locationPattern(SimpleFieldAndMethod.class))); + } + + @DataProvider + public static Object[][] haveNameNotStartingWith_rules() { + return $$( + $(members().should().haveNameNotStartingWith("violated"), "violated"), + $(members().should(ArchConditions.haveNameNotStartingWith("violated")), "violated"), + $(fields().should().haveNameNotStartingWith("violated"), "violated"), + $(fields().should(ArchConditions.haveNameNotStartingWith("violated")), "violated"), + $(codeUnits().should().haveNameNotStartingWith("violated"), "violated"), + $(codeUnits().should(ArchConditions.haveNameNotStartingWith("violated")), "violated"), + $(methods().should().haveNameNotStartingWith("violated"), "violated"), + $(methods().should(ArchConditions.haveNameNotStartingWith("violated")), "violated"), + $(constructors().should().haveNameNotStartingWith(""), ""), + $(constructors().should(ArchConditions.haveNameNotStartingWith("")), "") + ); + } + + @Test + @UseDataProvider("haveNameNotStartingWith_rules") + public void haveNameNotStartingWith(ArchRule rule, String prefix) { + EvaluationResult result = rule.evaluate(importClasses(SimpleFieldAndMethod.class)); + + assertThat(singleLineFailureReportOf(result)) + .containsPattern(String.format(".*%s.* name starts with '%s' in %s", + quote(prefix), + quote(prefix), + locationPattern(SimpleFieldAndMethod.class))); + } + + @DataProvider + public static Object[][] haveNameContaining_rules() { + return $$( + $(members().should().haveNameContaining("field"), "field", "violated"), + $(members().should(ArchConditions.haveNameContaining("field")), "field", "violated"), + $(fields().should().haveNameContaining("field"), "field", "violated"), + $(fields().should(ArchConditions.haveNameContaining("field")), "field", "violated"), + $(codeUnits().should().haveNameContaining("method"), "method", "violated"), + $(codeUnits().should(ArchConditions.haveNameContaining("method")), "method", "violated"), + $(methods().should().haveNameContaining("method"), "method", "violated"), + $(methods().should(ArchConditions.haveNameContaining("method")), "method", "violated"), + $(constructors().should().haveNameContaining("constructor"), "constructor", ""), + $(constructors().should(ArchConditions.haveNameContaining("constructor")), "constructor", "") + ); + } + + @Test + @UseDataProvider("haveNameContaining_rules") + public void haveNameContaining(ArchRule rule, String infix, String violatingMember) { + EvaluationResult result = rule.evaluate(importClasses(SimpleFieldAndMethod.class)); + + assertThat(singleLineFailureReportOf(result)) + .containsPattern(String.format(".*%s.* name does not contain '%s' in %s", + quote(violatingMember), + quote(infix), + locationPattern(SimpleFieldAndMethod.class))); + } + + @DataProvider + public static Object[][] haveNameNotContaining_rules() { + return $$( + $(members().should().haveNameNotContaining("violated"), "violated"), + $(members().should(ArchConditions.haveNameNotContaining("violated")), "violated"), + $(fields().should().haveNameNotContaining("violated"), "violated"), + $(fields().should(ArchConditions.haveNameNotContaining("violated")), "violated"), + $(codeUnits().should().haveNameNotContaining("violated"), "violated"), + $(codeUnits().should(ArchConditions.haveNameNotContaining("violated")), "violated"), + $(methods().should().haveNameNotContaining("violated"), "violated"), + $(methods().should(ArchConditions.haveNameNotContaining("violated")), "violated"), + $(constructors().should().haveNameNotContaining(""), ""), + $(constructors().should(ArchConditions.haveNameNotContaining("")), "") + ); + } + + @Test + @UseDataProvider("haveNameNotContaining_rules") + public void haveNameNotContaining(ArchRule rule, String infix) { + EvaluationResult result = rule.evaluate(importClasses(SimpleFieldAndMethod.class)); + + assertThat(singleLineFailureReportOf(result)) + .containsPattern(String.format(".*%s.* name contains '%s' in %s", + quote(infix), + quote(infix), + locationPattern(SimpleFieldAndMethod.class))); + } + + @DataProvider + public static Object[][] haveNameEndingWith_rules() { + return $$( + $(members().should().haveNameEndingWith("field"), "field", "violated"), + $(members().should(ArchConditions.haveNameEndingWith("field")), "field", "violated"), + $(fields().should().haveNameEndingWith("field"), "field", "violated"), + $(fields().should(ArchConditions.haveNameEndingWith("field")), "field", "violated"), + $(codeUnits().should().haveNameEndingWith("method"), "method", "violated"), + $(codeUnits().should(ArchConditions.haveNameEndingWith("method")), "method", "violated"), + $(methods().should().haveNameEndingWith("method"), "method", "violated"), + $(methods().should(ArchConditions.haveNameEndingWith("method")), "method", "violated"), + $(constructors().should().haveNameEndingWith("constructor"), "constructor", ""), + $(constructors().should(ArchConditions.haveNameEndingWith("constructor")), "constructor", "") + ); + } + + @Test + @UseDataProvider("haveNameEndingWith_rules") + public void haveNameEndingWith(ArchRule rule, String suffix, String violatingMember) { + EvaluationResult result = rule.evaluate(importClasses(SimpleFieldAndMethod.class)); + + assertThat(singleLineFailureReportOf(result)) + .containsPattern(String.format(".*%s.* name does not end with '%s' in %s", + quote(violatingMember), + quote(suffix), + locationPattern(SimpleFieldAndMethod.class))); + } + + @DataProvider + public static Object[][] haveNameNotEndingWith_rules() { + return $$( + $(members().should().haveNameNotEndingWith("violated"), "violated"), + $(members().should(ArchConditions.haveNameNotEndingWith("violated")), "violated"), + $(fields().should().haveNameNotEndingWith("violated"), "violated"), + $(fields().should(ArchConditions.haveNameNotEndingWith("violated")), "violated"), + $(codeUnits().should().haveNameNotEndingWith("violated"), "violated"), + $(codeUnits().should(ArchConditions.haveNameNotEndingWith("violated")), "violated"), + $(methods().should().haveNameNotEndingWith("violated"), "violated"), + $(methods().should(ArchConditions.haveNameNotEndingWith("violated")), "violated"), + $(constructors().should().haveNameNotEndingWith(""), ""), + $(constructors().should(ArchConditions.haveNameNotEndingWith("")), "") + ); + } + + @Test + @UseDataProvider("haveNameNotEndingWith_rules") + public void haveNameNotEndingWith(ArchRule rule, String suffix) { + EvaluationResult result = rule.evaluate(importClasses(SimpleFieldAndMethod.class)); + + assertThat(singleLineFailureReportOf(result)) + .containsPattern(String.format(".*%s.* name ends with '%s' in %s", + quote(suffix), + quote(suffix), + locationPattern(SimpleFieldAndMethod.class))); + } + private Set parseMembers(List details) { return parseMembers(ImmutableList.of(ClassWithVariousMembers.class, OtherClassWithMembers.class), details); } diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ShouldClassesThatTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ShouldClassesThatTest.java index 51bd90dd3e..72d80b69c8 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ShouldClassesThatTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ShouldClassesThatTest.java @@ -37,7 +37,8 @@ import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; import static com.tngtech.archunit.lang.syntax.elements.ClassesShouldEvaluator.filterClassesAppearingInFailureReport; import static com.tngtech.archunit.testutil.Assertions.assertThat; -import static com.tngtech.archunit.testutil.Assertions.assertThatClasses; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach; @RunWith(DataProviderRunner.class) @@ -67,7 +68,7 @@ public void haveFullyQualifiedName(ClassesThat noClass noClassesShouldThatRuleStart.haveFullyQualifiedName(List.class.getName())) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -77,7 +78,7 @@ public void doNotHaveFullyQualifiedName(ClassesThat no noClassesShouldThatRuleStart.doNotHaveFullyQualifiedName(List.class.getName())) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -87,7 +88,7 @@ public void haveSimpleName(ClassesThat noClassesShould noClassesShouldThatRuleStart.haveSimpleName(List.class.getSimpleName())) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -97,7 +98,7 @@ public void doNotHaveSimpleName(ClassesThat noClassesS noClassesShouldThatRuleStart.doNotHaveSimpleName(List.class.getSimpleName())) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -107,7 +108,7 @@ public void haveNameMatching(ClassesThat noClassesShou noClassesShouldThatRuleStart.haveNameMatching(".*\\.List")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -117,7 +118,7 @@ public void haveNameNotMatching(ClassesThat noClassesS noClassesShouldThatRuleStart.haveNameNotMatching(".*\\.List")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -127,7 +128,7 @@ public void haveSimpleNameStartingWith(ClassesThat noC noClassesShouldThatRuleStart.haveSimpleNameStartingWith("Lis")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -137,7 +138,7 @@ public void haveSimpleNameNotStartingWith(ClassesThat noClassesShouldThatRuleStart.haveSimpleNameNotStartingWith("Lis")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -147,7 +148,7 @@ public void haveSimpleNameContaining(ClassesThat noCla noClassesShouldThatRuleStart.haveSimpleNameContaining("is")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -157,7 +158,7 @@ public void haveSimpleNameNotContaining(ClassesThat no noClassesShouldThatRuleStart.haveSimpleNameNotContaining("is")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -167,7 +168,7 @@ public void haveSimpleNameEndingWith(ClassesThat noCla noClassesShouldThatRuleStart.haveSimpleNameEndingWith("ist")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -177,7 +178,7 @@ public void haveSimpleNameNotEndingWith(ClassesThat no noClassesShouldThatRuleStart.haveSimpleNameNotEndingWith("ist")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -187,7 +188,7 @@ public void resideInAPackage(ClassesThat noClassesShou noClassesShouldThatRuleStart.resideInAPackage("..tngtech..")) .on(ClassAccessingPublicClass.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPublicClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPublicClass.class); } @Test @@ -197,7 +198,7 @@ public void resideOutsideOfPackage(ClassesThat noClass noClassesShouldThatRuleStart.resideOutsideOfPackage("..tngtech..")) .on(ClassAccessingPublicClass.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -207,7 +208,7 @@ public void resideInAnyPackage(ClassesThat noClassesSh noClassesShouldThatRuleStart.resideInAnyPackage("..tngtech..", "java.lang.reflect")) .on(ClassAccessingPublicClass.class, ClassAccessingString.class, ClassAccessingConstructor.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPublicClass.class, ClassAccessingConstructor.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPublicClass.class, ClassAccessingConstructor.class); } @Test @@ -217,7 +218,7 @@ public void resideOutsideOfPackages(ClassesThat noClas noClassesShouldThatRuleStart.resideOutsideOfPackages("..tngtech..", "java.lang.reflect") ).on(ClassAccessingPublicClass.class, ClassAccessingString.class, ClassAccessingConstructor.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class); } @Test @@ -227,7 +228,7 @@ public void arePublic(ClassesThat noClassesShouldThatR .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPublicClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPublicClass.class); } @Test @@ -237,7 +238,7 @@ public void areNotPublic(ClassesThat noClassesShouldTh .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); } @@ -248,7 +249,7 @@ public void areProtected(ClassesThat noClassesShouldTh .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingProtectedClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingProtectedClass.class); } @Test @@ -258,7 +259,7 @@ public void areNotProtected(ClassesThat noClassesShoul .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class); } @@ -269,7 +270,7 @@ public void arePackagePrivate(ClassesThat noClassesSho .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPackagePrivateClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPackagePrivateClass.class); } @Test @@ -279,7 +280,7 @@ public void areNotPackagePrivate(ClassesThat noClasses .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingProtectedClass.class); } @@ -290,7 +291,7 @@ public void arePrivate(ClassesThat noClassesShouldThat .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPrivateClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPrivateClass.class); } @Test @@ -300,7 +301,7 @@ public void areNotPrivate(ClassesThat noClassesShouldT .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessingPublicClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); } @@ -311,7 +312,7 @@ public void haveModifier(ClassesThat noClassesShouldTh .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPrivateClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPrivateClass.class); } @Test @@ -321,7 +322,7 @@ public void doNotHaveModifier(ClassesThat noClassesSho .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessingPublicClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); } @@ -332,7 +333,7 @@ public void areAnnotatedWith_type(ClassesThat noClasse noClassesShouldThatRuleStart.areAnnotatedWith(SomeAnnotation.class)) .on(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingAnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingAnnotatedClass.class); } @Test @@ -342,7 +343,7 @@ public void areNotAnnotatedWith_type(ClassesThat noCla noClassesShouldThatRuleStart.areNotAnnotatedWith(SomeAnnotation.class)) .on(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingSimpleClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingSimpleClass.class); } @Test @@ -352,7 +353,7 @@ public void areAnnotatedWith_typeName(ClassesThat noCl noClassesShouldThatRuleStart.areAnnotatedWith(SomeAnnotation.class.getName())) .on(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingAnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingAnnotatedClass.class); } @Test @@ -362,7 +363,7 @@ public void areNotAnnotatedWith_typeName(ClassesThat n noClassesShouldThatRuleStart.areNotAnnotatedWith(SomeAnnotation.class.getName())) .on(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingSimpleClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingSimpleClass.class); } @Test @@ -373,7 +374,7 @@ public void areAnnotatedWith_predicate(ClassesThat noC noClassesShouldThatRuleStart.areAnnotatedWith(hasNamePredicate)) .on(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingAnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingAnnotatedClass.class); } @Test @@ -384,7 +385,7 @@ public void areNotAnnotatedWith_predicate(ClassesThat noClassesShouldThatRuleStart.areNotAnnotatedWith(hasNamePredicate)) .on(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingSimpleClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingSimpleClass.class); } @Test @@ -395,7 +396,7 @@ public void areMetaAnnotatedWith_type(ClassesThat noCl .on(ClassAccessingMetaAnnotatedClass.class, ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingMetaAnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingMetaAnnotatedClass.class); } @Test @@ -405,7 +406,7 @@ public void areNotMetaAnnotatedWith_type_access() { .on(ClassAccessingMetaAnnotatedClass.class, ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); } @Test @@ -415,7 +416,7 @@ public void areNotMetaAnnotatedWith_type_dependency() { .on(ClassAccessingMetaAnnotatedClass.class, ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); } @Test @@ -426,7 +427,7 @@ public void areMetaAnnotatedWith_typeName(ClassesThat .on(ClassAccessingMetaAnnotatedClass.class, ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingMetaAnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingMetaAnnotatedClass.class); } @Test @@ -436,7 +437,7 @@ public void areNotMetaAnnotatedWith_typeName_access() { .on(ClassAccessingMetaAnnotatedClass.class, ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); } @Test @@ -446,7 +447,7 @@ public void areNotMetaAnnotatedWith_typeName_dependency() { .on(ClassAccessingMetaAnnotatedClass.class, ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); } @Test @@ -458,7 +459,7 @@ public void areMetaAnnotatedWith_predicate(ClassesThat .on(ClassAccessingMetaAnnotatedClass.class, ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingMetaAnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingMetaAnnotatedClass.class); } @Test @@ -469,7 +470,7 @@ public void areNotMetaAnnotatedWith_predicate_access() { .on(ClassAccessingMetaAnnotatedClass.class, ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); } @Test @@ -480,7 +481,7 @@ public void areNotMetaAnnotatedWith_predicate_dependency() { .on(ClassAccessingMetaAnnotatedClass.class, ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); } @Test @@ -490,7 +491,7 @@ public void implement_type(ClassesThat noClassesShould noClassesShouldThatRuleStart.implement(Collection.class)) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); } @Test @@ -500,7 +501,7 @@ public void doNotImplement_type(ClassesThat noClassesS noClassesShouldThatRuleStart.doNotImplement(Collection.class)) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); } @Test @@ -510,7 +511,7 @@ public void implement_typeName(ClassesThat noClassesSh noClassesShouldThatRuleStart.implement(Collection.class.getName())) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); } @Test @@ -520,7 +521,7 @@ public void doNotImplement_typeName(ClassesThat noClas noClassesShouldThatRuleStart.doNotImplement(Collection.class.getName())) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); } @Test @@ -530,7 +531,7 @@ public void implement_predicate(ClassesThat noClassesS noClassesShouldThatRuleStart.implement(classWithNameOf(Collection.class))) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); } @Test @@ -540,7 +541,7 @@ public void doNotImplement_predicate(ClassesThat noCla noClassesShouldThatRuleStart.doNotImplement(classWithNameOf(Collection.class))) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); } @Test @@ -550,7 +551,7 @@ public void areAssignableTo_type(ClassesThat noClasses noClassesShouldThatRuleStart.areAssignableTo(Collection.class)) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -560,7 +561,7 @@ public void areNotAssignableTo_type(ClassesThat noClas noClassesShouldThatRuleStart.areNotAssignableTo(Collection.class)) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -570,7 +571,7 @@ public void areAssignableTo_typeName(ClassesThat noCla noClassesShouldThatRuleStart.areAssignableTo(Collection.class.getName())) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -580,7 +581,7 @@ public void areNotAssignableTo_typeName(ClassesThat no noClassesShouldThatRuleStart.areNotAssignableTo(Collection.class.getName())) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -590,7 +591,7 @@ public void areAssignableTo_predicate(ClassesThat noCl noClassesShouldThatRuleStart.areAssignableTo(classWithNameOf(Collection.class))) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -600,7 +601,7 @@ public void areNotAssignableTo_predicate(ClassesThat n noClassesShouldThatRuleStart.areNotAssignableTo(classWithNameOf(Collection.class))) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -611,7 +612,7 @@ public void areAssignableFrom_type(ClassesThat noClass .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingCollection.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingCollection.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingCollection.class, ClassAccessingIterable.class); } @Test @@ -622,7 +623,7 @@ public void areNotAssignableFrom_type(ClassesThat noCl .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingCollection.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingString.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingString.class); } @Test @@ -633,7 +634,7 @@ public void areAssignableFrom_typeName(ClassesThat noC .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingCollection.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingCollection.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingCollection.class, ClassAccessingIterable.class); } @Test @@ -644,7 +645,7 @@ public void areNotAssignableFrom_typeName(ClassesThat .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingCollection.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingString.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingString.class); } @Test @@ -655,7 +656,7 @@ public void areAssignableFrom_predicate(ClassesThat no .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingCollection.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingCollection.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingCollection.class, ClassAccessingIterable.class); } @Test @@ -666,7 +667,7 @@ public void areNotAssignableFrom_predicate(ClassesThat .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingCollection.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingString.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingString.class); } @Test @@ -677,7 +678,7 @@ public void areInterfaces_predicate(ClassesThat noClas .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingCollection.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingCollection.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingCollection.class); } @Test @@ -688,7 +689,7 @@ public void areNotInterfaces_predicate(ClassesThat noC .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingCollection.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingSimpleClass.class); } @Test @@ -698,7 +699,7 @@ public void areEnums_predicate(ClassesThat noClassesSh noClassesShouldThatRuleStart.areEnums()) .on(ClassAccessingEnum.class, ClassAccessingString.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingEnum.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingEnum.class); } @Test @@ -708,7 +709,27 @@ public void areNotEnums_predicate(ClassesThat noClasse noClassesShouldThatRuleStart.areNotEnums()) .on(ClassAccessingEnum.class, ClassAccessingString.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class); + } + + @Test + @UseDataProvider("no_classes_should_that_rule_starts") + public void areAnnotations_predicate(ClassesThat noClassesShouldThatRuleStart) { + Set classes = filterClassesAppearingInFailureReport( + noClassesShouldThatRuleStart.areAnnotations()) + .on(ClassAccessingAnnotation.class, ClassAccessingString.class); + + assertThatTypes(classes).matchInAnyOrder(ClassAccessingAnnotation.class); + } + + @Test + @UseDataProvider("no_classes_should_that_rule_starts") + public void areNotAnnotations_predicate(ClassesThat noClassesShouldThatRuleStart) { + Set classes = filterClassesAppearingInFailureReport( + noClassesShouldThatRuleStart.areNotAnnotations()) + .on(ClassAccessingAnnotation.class, ClassAccessingString.class); + + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class); } @Test @@ -718,7 +739,7 @@ public void areTopLevelClasses_predicate(ClassesThat n noClassesShouldThatRuleStart.areTopLevelClasses()) .on(ClassAccessingTopLevelClass.class, ClassAccessingStaticNestedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingTopLevelClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingTopLevelClass.class); } @Test @@ -728,7 +749,7 @@ public void areNotTopLevelClasses_predicate(ClassesThat noC noClassesShouldThatRuleStart.areNestedClasses()) .on(ClassAccessingStaticNestedClass.class, ClassAccessingTopLevelClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingStaticNestedClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingStaticNestedClass.class); } @Test @@ -748,7 +769,7 @@ public void areNotNestedClasses_predicate(ClassesThat noClassesShouldThatRuleStart.areNotNestedClasses()) .on(ClassAccessingStaticNestedClass.class, ClassAccessingTopLevelClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingTopLevelClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingTopLevelClass.class); } @Test @@ -758,7 +779,7 @@ public void areMemberClasses_predicate(ClassesThat noC noClassesShouldThatRuleStart.areMemberClasses()) .on(ClassAccessingStaticNestedClass.class, ClassAccessingTopLevelClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingStaticNestedClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingStaticNestedClass.class); } @Test @@ -768,7 +789,7 @@ public void areNotMemberClasses_predicate(ClassesThat noClassesShouldThatRuleStart.areNotMemberClasses()) .on(ClassAccessingStaticNestedClass.class, ClassAccessingTopLevelClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingTopLevelClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingTopLevelClass.class); } @Test @@ -778,7 +799,7 @@ public void areInnerClasses_predicate(ClassesThat noCl noClassesShouldThatRuleStart.areInnerClasses()) .on(ClassAccessingInnerMemberClass.class, ClassAccessingTopLevelClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingInnerMemberClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingInnerMemberClass.class); } @Test @@ -788,7 +809,7 @@ public void areNotInnerClasses_predicate(ClassesThat n noClassesShouldThatRuleStart.areNotInnerClasses()) .on(ClassAccessingInnerMemberClass.class, ClassAccessingTopLevelClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingTopLevelClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingTopLevelClass.class); } @Test @@ -798,7 +819,7 @@ public void areAnonymousClasses_predicate(ClassesThat noClassesShouldThatRuleStart.areAnonymousClasses()) .on(ClassAccessingAnonymousClass.class, ClassAccessingTopLevelClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingAnonymousClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingAnonymousClass.class); } @Test @@ -808,7 +829,7 @@ public void areNotAnonymousClasses_predicate(ClassesThat noCl noClassesShouldThatRuleStart.areLocalClasses()) .on(ClassAccessingLocalClass.class, ClassAccessingTopLevelClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingLocalClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingLocalClass.class); } @Test @@ -828,7 +849,7 @@ public void areNotLocalClasses_predicate(ClassesThat n noClassesShouldThatRuleStart.areNotLocalClasses()) .on(ClassAccessingLocalClass.class, ClassAccessingTopLevelClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingTopLevelClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingTopLevelClass.class); } @Test @@ -839,11 +860,22 @@ public void belongToAnyOf(ClassesThat noClassesShouldT .on(ClassAccessingNestedInnerClass.class, ClassWithInnerClasses.class, ClassWithInnerClasses.InnerClass.class, ClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingNestedInnerClass.class, + assertThatTypes(classes).matchInAnyOrder(ClassAccessingNestedInnerClass.class, ClassWithInnerClasses.class, ClassWithInnerClasses.InnerClass.class, ClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class, ClassAccessingString.class); } + @Test + @UseDataProvider("no_classes_should_that_rule_starts") + public void doNotBelongToAnyOf(ClassesThat noClassesShouldThatRuleStart) { + Set classes = filterClassesAppearingInFailureReport( + noClassesShouldThatRuleStart.doNotBelongToAnyOf(ClassWithInnerClasses.class, String.class)) + .on(ClassAccessingNestedInnerClass.class, ClassWithInnerClasses.class, ClassWithInnerClasses.InnerClass.class, + ClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class, ClassAccessingString.class, ClassAccessingIterable.class); + + assertThatTypes(classes).matchInAnyOrder(ClassAccessingIterable.class); + } + @Test @UseDataProvider("classes_should_only_that_rule_starts") public void only_haveFullyQualifiedName(ClassesThat classesShouldOnlyThatRuleStart) { @@ -851,7 +883,7 @@ public void only_haveFullyQualifiedName(ClassesThat cl classesShouldOnlyThatRuleStart.haveFullyQualifiedName(List.class.getName())) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -861,7 +893,7 @@ public void only_doNotHaveFullyQualifiedName(ClassesThat classesSho classesShouldOnlyThatRuleStart.haveSimpleName(List.class.getSimpleName())) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -881,7 +913,7 @@ public void only_doNotHaveSimpleName(ClassesThat class classesShouldOnlyThatRuleStart.doNotHaveSimpleName(List.class.getSimpleName())) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -891,7 +923,7 @@ public void only_haveNameMatching(ClassesThat classesS classesShouldOnlyThatRuleStart.haveNameMatching(".*\\.List")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -901,7 +933,7 @@ public void only_haveNameNotMatching(ClassesThat class classesShouldOnlyThatRuleStart.haveNameNotMatching(".*\\.List")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -911,7 +943,7 @@ public void only_haveSimpleNameStartingWith(ClassesThat classesShouldOnlyThatRuleStart.haveSimpleNameContaining("is")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -941,7 +973,7 @@ public void only_haveSimpleNameNotContaining(ClassesThat classesShouldOnlyThatRuleStart.haveSimpleNameEndingWith("ist")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -961,7 +993,7 @@ public void only_haveSimpleNameNotEndingWith(ClassesThat classesS classesShouldOnlyThatRuleStart.resideInAPackage("..tngtech..")) .on(ClassAccessingPublicClass.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -981,7 +1013,7 @@ public void only_resideOutsideOfPackage(ClassesThat cl classesShouldOnlyThatRuleStart.resideOutsideOfPackage("..tngtech..")) .on(ClassAccessingPublicClass.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPublicClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPublicClass.class); } @Test @@ -991,7 +1023,7 @@ public void only_resideInAnyPackage(ClassesThat classe classesShouldOnlyThatRuleStart.resideInAnyPackage("..tngtech..", "java.lang.reflect")) .on(ClassAccessingPublicClass.class, ClassAccessingString.class, ClassAccessingConstructor.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class); } @Test @@ -1001,7 +1033,7 @@ public void only_resideOutsideOfPackages(ClassesThat c classesShouldOnlyThatRuleStart.resideOutsideOfPackages("..tngtech..", "java.lang.reflect") ).on(ClassAccessingPublicClass.class, ClassAccessingString.class, ClassAccessingConstructor.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPublicClass.class, ClassAccessingConstructor.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPublicClass.class, ClassAccessingConstructor.class); } @Test @@ -1011,7 +1043,7 @@ public void only_arePublic(ClassesThat classesShouldOn .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); } @@ -1022,7 +1054,7 @@ public void only_areNotPublic(ClassesThat classesShoul .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPublicClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPublicClass.class); } @Test @@ -1032,7 +1064,7 @@ public void only_areProtected(ClassesThat classesShoul .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class); } @@ -1043,7 +1075,7 @@ public void only_areNotProtected(ClassesThat classesSh .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingProtectedClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingProtectedClass.class); } @Test @@ -1053,7 +1085,7 @@ public void only_arePackagePrivate(ClassesThat classes .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingProtectedClass.class); } @@ -1064,7 +1096,7 @@ public void only_areNotPackagePrivate(ClassesThat clas .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPackagePrivateClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPackagePrivateClass.class); } @Test @@ -1074,7 +1106,7 @@ public void only_arePrivate(ClassesThat classesShouldO .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessingPublicClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); } @@ -1085,7 +1117,7 @@ public void only_areNotPrivate(ClassesThat classesShou .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPrivateClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPrivateClass.class); } @Test @@ -1095,7 +1127,7 @@ public void only_haveModifier(ClassesThat classesShoul .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessingPublicClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); } @@ -1106,7 +1138,7 @@ public void only_doNotHaveModifier(ClassesThat classes .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPrivateClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPrivateClass.class); } @Test @@ -1116,7 +1148,7 @@ public void only_areAnnotatedWith_type(ClassesThat cla classesShouldOnlyThatRuleStart.areAnnotatedWith(SomeAnnotation.class)) .on(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingSimpleClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingSimpleClass.class); } @Test @@ -1126,7 +1158,7 @@ public void only_areNotAnnotatedWith_type(ClassesThat classesShouldOnlyThatRuleStart.areNotAnnotatedWith(SomeAnnotation.class)) .on(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingAnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingAnnotatedClass.class); } @Test @@ -1136,7 +1168,7 @@ public void only_areAnnotatedWith_typeName(ClassesThat classesShouldOnlyThatRuleStart.areAnnotatedWith(SomeAnnotation.class.getName())) .on(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingSimpleClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingSimpleClass.class); } @Test @@ -1146,7 +1178,7 @@ public void only_areNotAnnotatedWith_typeName(ClassesThat classesSho classesShouldOnlyThatRuleStart.implement(Collection.class)) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); } @Test @@ -1284,7 +1316,7 @@ public void only_doNotImplement_type(ClassesThat class classesShouldOnlyThatRuleStart.doNotImplement(Collection.class)) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); } @Test @@ -1294,7 +1326,7 @@ public void only_implement_typeName(ClassesThat classe classesShouldOnlyThatRuleStart.implement(Collection.class.getName())) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); } @Test @@ -1304,7 +1336,7 @@ public void only_doNotImplement_typeName(ClassesThat c classesShouldOnlyThatRuleStart.doNotImplement(Collection.class.getName())) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); } @Test @@ -1314,7 +1346,7 @@ public void only_implement_predicate(ClassesThat class classesShouldOnlyThatRuleStart.implement(classWithNameOf(Collection.class))) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); } @Test @@ -1324,7 +1356,7 @@ public void only_doNotImplement_predicate(ClassesThat classesShouldOnlyThatRuleStart.doNotImplement(classWithNameOf(Collection.class))) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); } @Test @@ -1334,7 +1366,7 @@ public void only_areAssignableTo_type(ClassesThat clas classesShouldOnlyThatRuleStart.areAssignableTo(Collection.class)) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -1344,7 +1376,7 @@ public void only_areNotAssignableTo_type(ClassesThat c classesShouldOnlyThatRuleStart.areNotAssignableTo(Collection.class)) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -1354,7 +1386,7 @@ public void only_areAssignableTo_typeName(ClassesThat classesShouldOnlyThatRuleStart.areAssignableTo(Collection.class.getName())) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -1364,7 +1396,7 @@ public void only_areNotAssignableTo_typeName(ClassesThat classesShouldOnlyThatRuleStart.areAssignableTo(classWithNameOf(Collection.class))) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -1384,7 +1416,7 @@ public void only_areNotAssignableTo_predicate(ClassesThat cl .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingCollection.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingString.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingString.class); } @Test @@ -1406,7 +1438,7 @@ public void only_areNotAssignableFrom_type(ClassesThat .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingCollection.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingCollection.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingCollection.class, ClassAccessingIterable.class); } @Test @@ -1417,7 +1449,7 @@ public void only_areAssignableFrom_typeName(ClassesThat c .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingCollection.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingSimpleClass.class); } @Test @@ -1472,7 +1504,7 @@ public void only_areNotInterfaces_predicate(ClassesThat apply(ArchRule rule) { Set classes = filterClassesInFailureReport.apply( noClasses().should().dependOnClassesThat(are(not(assignableFrom(classWithNameOf(Collection.class)))))); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassHavingFieldOfTypeList.class, ClassHavingMethodParameterOfTypeString.class, ClassHavingReturnTypeArrayList.class, ClassImplementingSerializable.class); @@ -1551,7 +1583,7 @@ public Set apply(ArchRule rule) { Set classes = filterClassesInFailureReport.apply( classes().should().onlyDependOnClassesThat(are(not(assignableFrom(classWithNameOf(Collection.class)))))); - assertThatClasses(classes).matchInAnyOrder(ClassHavingConstructorParameterOfTypeCollection.class); + assertThatTypes(classes).matchInAnyOrder(ClassHavingConstructorParameterOfTypeCollection.class); classes = filterClassesInFailureReport.apply( classes().should().onlyAccessClassesThat(are(not(assignableFrom(classWithNameOf(Collection.class)))))); @@ -1564,7 +1596,7 @@ private static DescribedPredicate classWithNameOf(Class type) { } private static class ClassAccessingList { - @SuppressWarnings("unused") + @SuppressWarnings({"unused", "ResultOfMethodCallIgnored"}) void call(List list) { list.size(); } @@ -1576,7 +1608,7 @@ private static class ClassHavingFieldOfTypeList { } private static class ClassAccessingArrayList { - @SuppressWarnings("unused") + @SuppressWarnings({"unused", "ResultOfMethodCallIgnored"}) void call(ArrayList list) { list.size(); } @@ -1603,7 +1635,7 @@ void call(String string) { } private static class ClassAccessingCollection { - @SuppressWarnings("unused") + @SuppressWarnings({"unused", "ResultOfMethodCallIgnored"}) void call(Collection collection) { collection.size(); } @@ -1741,6 +1773,15 @@ void access() { } } + private static class ClassAccessingAnnotation { + Deprecated deprecated; + + @SuppressWarnings({"unused"}) + void access() { + deprecated.annotationType(); + } + } + private static class ClassAccessingTopLevelClass { @SuppressWarnings({"ResultOfMethodCallIgnored", "unused"}) void access() { diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ShouldOnlyByClassesThatTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ShouldOnlyByClassesThatTest.java index ce8ad78af7..40d9c6d476 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ShouldOnlyByClassesThatTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ShouldOnlyByClassesThatTest.java @@ -35,7 +35,7 @@ import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; import static com.tngtech.archunit.lang.syntax.elements.ClassesShouldEvaluator.filterClassesAppearingInFailureReport; import static com.tngtech.archunit.lang.syntax.elements.ClassesShouldEvaluator.filterViolationCausesInFailureReport; -import static com.tngtech.archunit.testutil.Assertions.assertThatClasses; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach; import static org.assertj.core.api.Assertions.assertThat; @@ -62,7 +62,7 @@ public void haveFullyQualifiedName(ClassesThat classes ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); } @@ -76,7 +76,7 @@ public void doNotHaveFullyQualifiedName(ClassesThat cl ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessedByFoo.class, Foo.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessedByFoo.class, Foo.class); } @Test @@ -88,7 +88,7 @@ public void haveSimpleName(ClassesThat classesShouldOn ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); } @@ -102,7 +102,7 @@ public void doNotHaveSimpleName(ClassesThat classesSho ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessedByFoo.class, Foo.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessedByFoo.class, Foo.class); } @Test @@ -114,7 +114,7 @@ public void haveNameMatching(ClassesThat classesShould ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); } @@ -128,7 +128,7 @@ public void haveNameNotMatching(ClassesThat classesSho ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessedByFoo.class, Foo.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessedByFoo.class, Foo.class); } @Test @@ -140,7 +140,7 @@ public void haveSimpleNameStartingWith(ClassesThat cla ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); } @@ -154,7 +154,7 @@ public void haveSimpleNameNotStartingWith(ClassesThat ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByFoo.class, Foo.class); } @@ -167,7 +167,7 @@ public void haveSimpleNameContaining(ClassesThat class ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); } @@ -181,7 +181,7 @@ public void haveSimpleNameNotContaining(ClassesThat cl ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByFoo.class, Foo.class); } @@ -194,7 +194,7 @@ public void haveSimpleNameEndingWith(ClassesThat class ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); } @@ -208,7 +208,7 @@ public void haveSimpleNameNotEndingWith(ClassesThat cl ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByFoo.class, Foo.class); } @@ -219,7 +219,7 @@ public void resideInAPackage(ClassesThat classesShould classesShouldOnlyBeBy.resideInAPackage("..access..")) .on(ClassAccessingOtherClass.class, ClassAlsoAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAlsoAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAlsoAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); } @Test @@ -229,7 +229,7 @@ public void resideOutsideOfPackage(ClassesThat classes classesShouldOnlyBeBy.resideOutsideOfPackage("..access..")) .on(ClassAccessingOtherClass.class, ClassAlsoAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); } @Test @@ -240,7 +240,7 @@ public void resideInAnyPackage(ClassesThat classesShou .on(ClassAccessingOtherClass.class, ClassAlsoAccessingOtherClass.class, YetAnotherClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(YetAnotherClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); + assertThatTypes(classes).matchInAnyOrder(YetAnotherClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); } @Test @@ -251,7 +251,7 @@ public void resideOutsideOfPackages(ClassesThat classe ).on(ClassAccessingOtherClass.class, ClassAlsoAccessingOtherClass.class, YetAnotherClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessingOtherClass.class, ClassAlsoAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); } @@ -264,7 +264,7 @@ public void arePublic(ClassesThat classesShouldOnlyBeB PublicClass.class, PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByPrivateClass.class, ClassAccessedByPackagePrivateClass.class, ClassAccessedByProtectedClass.class, PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); @@ -279,7 +279,7 @@ public void areNotPublic(ClassesThat classesShouldOnly PublicClass.class, PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByPublicClass.class, PublicClass.class); } @@ -292,7 +292,7 @@ public void areProtected(ClassesThat classesShouldOnly PublicClass.class, PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByPublicClass.class, ClassAccessedByPrivateClass.class, ClassAccessedByPackagePrivateClass.class, PublicClass.class, PrivateClass.class, PackagePrivateClass.class); @@ -307,7 +307,7 @@ public void areNotProtected(ClassesThat classesShouldO PublicClass.class, PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByProtectedClass.class, ProtectedClass.class); } @@ -320,7 +320,7 @@ public void arePackagePrivate(ClassesThat classesShoul PublicClass.class, PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByPublicClass.class, ClassAccessedByPrivateClass.class, ClassAccessedByProtectedClass.class, PublicClass.class, PrivateClass.class, ProtectedClass.class); @@ -335,7 +335,7 @@ public void areNotPackagePrivate(ClassesThat classesSh PublicClass.class, PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByPackagePrivateClass.class, PackagePrivateClass.class); } @@ -348,7 +348,7 @@ public void arePrivate(ClassesThat classesShouldOnlyBe PublicClass.class, PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByPublicClass.class, ClassAccessedByPackagePrivateClass.class, ClassAccessedByProtectedClass.class, PublicClass.class, PackagePrivateClass.class, ProtectedClass.class); @@ -363,7 +363,7 @@ public void areNotPrivate(ClassesThat classesShouldOnl PublicClass.class, PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByPrivateClass.class, PrivateClass.class); } @@ -376,7 +376,7 @@ public void haveModifier(ClassesThat classesShouldOnly PublicClass.class, PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByPublicClass.class, ClassAccessedByPackagePrivateClass.class, ClassAccessedByProtectedClass.class, PublicClass.class, PackagePrivateClass.class, ProtectedClass.class); @@ -391,7 +391,7 @@ public void doNotHaveModifier(ClassesThat classesShoul PublicClass.class, PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByPrivateClass.class, PrivateClass.class); } @@ -403,7 +403,7 @@ public void areAnnotatedWith_type(ClassesThat classesS .on(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -414,7 +414,7 @@ public void areNotAnnotatedWith_type(ClassesThat class .on(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class); } @Test @@ -425,7 +425,7 @@ public void areAnnotatedWith_typeName(ClassesThat clas .on(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -436,7 +436,7 @@ public void areNotAnnotatedWith_typeName(ClassesThat c .on(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class); } @Test @@ -448,7 +448,7 @@ public void areAnnotatedWith_predicate(ClassesThat cla .on(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -460,7 +460,7 @@ public void areNotAnnotatedWith_predicate(ClassesThat .on(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class); } @Test @@ -473,7 +473,7 @@ public void areMetaAnnotatedWith_type(ClassesThat clas SimpleClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, ClassAccessingSimpleClass.class); } @@ -486,7 +486,7 @@ public void areNotMetaAnnotatedWith_type_access() { SimpleClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class); } @Test @@ -498,7 +498,7 @@ public void areNotMetaAnnotatedWith_type_dependency() { SimpleClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class, MetaAnnotatedAnnotation.class); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class, MetaAnnotatedAnnotation.class); } @Test @@ -511,7 +511,7 @@ public void areMetaAnnotatedWith_typeName(ClassesThat SimpleClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, ClassAccessingSimpleClass.class); } @@ -524,7 +524,7 @@ public void areNotMetaAnnotatedWith_typeName_access() { SimpleClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class); } @Test @@ -536,7 +536,7 @@ public void areNotMetaAnnotatedWith_typeName_dependency() { SimpleClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class, MetaAnnotatedAnnotation.class); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class, MetaAnnotatedAnnotation.class); } @Test @@ -550,7 +550,7 @@ public void areMetaAnnotatedWith_predicate(ClassesThat SimpleClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, ClassAccessingSimpleClass.class); } @@ -564,7 +564,7 @@ public void areNotMetaAnnotatedWith_predicate_access() { SimpleClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class); } @Test @@ -577,7 +577,7 @@ public void areNotMetaAnnotatedWith_predicate_dependency() { SimpleClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class, MetaAnnotatedAnnotation.class); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class, MetaAnnotatedAnnotation.class); } @Test @@ -588,7 +588,7 @@ public void implement_type(ClassesThat classesShouldOn .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -599,7 +599,7 @@ public void doNotImplement_type(ClassesThat classesSho .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); + assertThatTypes(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); } @Test @@ -610,7 +610,7 @@ public void implement_typeName(ClassesThat classesShou .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -621,7 +621,7 @@ public void doNotImplement_typeName(ClassesThat classe .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); + assertThatTypes(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); } @Test @@ -632,7 +632,7 @@ public void implement_predicate(ClassesThat classesSho .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -643,7 +643,7 @@ public void doNotImplement_predicate(ClassesThat class .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); + assertThatTypes(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); } @Test @@ -654,7 +654,7 @@ public void areAssignableTo_type(ClassesThat classesSh .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -665,7 +665,7 @@ public void areNotAssignableTo_type(ClassesThat classe .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); + assertThatTypes(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); } @Test @@ -676,7 +676,7 @@ public void areAssignableTo_typeName(ClassesThat class .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -687,7 +687,7 @@ public void areNotAssignableTo_typeName(ClassesThat cl .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); + assertThatTypes(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); } @Test @@ -698,7 +698,7 @@ public void areAssignableTo_predicate(ClassesThat clas .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -709,7 +709,7 @@ public void areNotAssignableTo_predicate(ClassesThat c .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); + assertThatTypes(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); } @Test @@ -720,7 +720,7 @@ public void areAssignableFrom_type(ClassesThat classes .on(ClassExtendingClass.class, ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -731,7 +731,7 @@ public void areNotAssignableFrom_type(ClassesThat clas .on(ClassExtendingClass.class, ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassExtendingClass.class, + assertThatTypes(classes).matchInAnyOrder(ClassExtendingClass.class, ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); } @@ -743,7 +743,7 @@ public void areAssignableFrom_typeName(ClassesThat cla .on(ClassExtendingClass.class, ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -754,7 +754,7 @@ public void areNotAssignableFrom_typeName(ClassesThat .on(ClassExtendingClass.class, ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassExtendingClass.class, + assertThatTypes(classes).matchInAnyOrder(ClassExtendingClass.class, ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); } @@ -766,7 +766,7 @@ public void areAssignableFrom_predicate(ClassesThat cl .on(ClassExtendingClass.class, ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -777,7 +777,7 @@ public void areNotAssignableFrom_predicate(ClassesThat .on(ClassExtendingClass.class, ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassExtendingClass.class, + assertThatTypes(classes).matchInAnyOrder(ClassExtendingClass.class, ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); } @@ -788,7 +788,7 @@ public void areInterfaces_predicate(ClassesThat classe classesShouldOnlyBeBy.areInterfaces()) .on(ClassAccessingSimpleClass.class, SimpleClass.class, ClassBeingAccessedByInterface.class, InterfaceAccessingAClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingSimpleClass.class, SimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingSimpleClass.class, SimpleClass.class); } @Test @@ -798,7 +798,7 @@ public void areNotInterfaces_predicate(ClassesThat cla classesShouldOnlyBeBy.areNotInterfaces()) .on(ClassAccessingSimpleClass.class, SimpleClass.class, ClassBeingAccessedByInterface.class, InterfaceAccessingAClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByInterface.class, InterfaceAccessingAClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByInterface.class, InterfaceAccessingAClass.class); } @Test @@ -808,7 +808,7 @@ public void areEnums_predicate(ClassesThat classesShou classesShouldOnlyBeBy.areEnums()) .on(ClassAccessingSimpleClass.class, SimpleClass.class, ClassBeingAccessedByEnum.class, EnumAccessingAClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingSimpleClass.class, SimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingSimpleClass.class, SimpleClass.class); } @Test @@ -818,7 +818,27 @@ public void areNotEnums_predicate(ClassesThat classesS classesShouldOnlyBeBy.areNotEnums()) .on(ClassAccessingSimpleClass.class, SimpleClass.class, ClassBeingAccessedByEnum.class, EnumAccessingAClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByEnum.class, EnumAccessingAClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByEnum.class, EnumAccessingAClass.class); + } + + @Test + @UseDataProvider("should_only_be_by_rule_starts") + public void areAnnotations_predicate(ClassesThat classesShouldOnlyBeBy) { + Set classes = filterClassesAppearingInFailureReport( + classesShouldOnlyBeBy.areAnnotations()) + .on(ClassAccessingSimpleClass.class, SimpleClass.class, ClassBeingAccessedByAnnotation.class, AnnotationAccessingAClass.class); + + assertThatTypes(classes).matchInAnyOrder(ClassAccessingSimpleClass.class, SimpleClass.class); + } + + @Test + @UseDataProvider("should_only_be_by_rule_starts") + public void areNotAnnotations_predicate(ClassesThat classesShouldOnlyBeBy) { + Set classes = filterClassesAppearingInFailureReport( + classesShouldOnlyBeBy.areNotAnnotations()) + .on(ClassAccessingSimpleClass.class, SimpleClass.class, ClassBeingAccessedByAnnotation.class, AnnotationAccessingAClass.class); + + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByAnnotation.class, AnnotationAccessingAClass.class); } @Test @@ -829,7 +849,7 @@ public void areTopLevelClasses_predicate(ClassesThat c .on(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class, ClassAccessingStaticNestedClass.class, StaticNestedClassBeingAccessed.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingStaticNestedClass.class, StaticNestedClassBeingAccessed.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingStaticNestedClass.class, StaticNestedClassBeingAccessed.class); } @Test @@ -840,7 +860,7 @@ public void areNotTopLevelClasses_predicate(ClassesThat cla .on(ClassAccessingStaticNestedClass.class, StaticNestedClassBeingAccessed.class, ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); } @Test @@ -862,7 +882,7 @@ public void areNotNestedClasses_predicate(ClassesThat .on(ClassAccessingStaticNestedClass.class, StaticNestedClassBeingAccessed.class, ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingStaticNestedClass.class, StaticNestedClassBeingAccessed.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingStaticNestedClass.class, StaticNestedClassBeingAccessed.class); } @Test @@ -873,7 +893,7 @@ public void areMemberClasses_predicate(ClassesThat cla .on(ClassAccessingStaticNestedClass.class, StaticNestedClassBeingAccessed.class, ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); } @Test @@ -884,7 +904,7 @@ public void areNotMemberClasses_predicate(ClassesThat .on(ClassAccessingStaticNestedClass.class, StaticNestedClassBeingAccessed.class, ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingStaticNestedClass.class, StaticNestedClassBeingAccessed.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingStaticNestedClass.class, StaticNestedClassBeingAccessed.class); } @Test @@ -895,7 +915,7 @@ public void areInnerClasses_predicate(ClassesThat clas .on(ClassAccessingInnerMemberClass.class, InnerMemberClassBeingAccessed.class, ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); } @Test @@ -906,7 +926,7 @@ public void areNotInnerClasses_predicate(ClassesThat c .on(ClassAccessingInnerMemberClass.class, InnerMemberClassBeingAccessed.class, ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingInnerMemberClass.class, InnerMemberClassBeingAccessed.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingInnerMemberClass.class, InnerMemberClassBeingAccessed.class); } @Test @@ -917,7 +937,7 @@ public void areAnonymousClasses_predicate(ClassesThat .on(ClassAccessingAnonymousClass.class, anonymousClassBeingAccessed.getClass(), ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); } @Test @@ -928,7 +948,7 @@ public void areNotAnonymousClasses_predicate(ClassesThat clas .on(ClassBeingAccessedByLocalClass.class, ClassBeingAccessedByLocalClass.getLocalClass(), ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); } @Test @@ -950,7 +970,7 @@ public void areNotLocalClasses_predicate(ClassesThat c .on(ClassBeingAccessedByLocalClass.class, ClassBeingAccessedByLocalClass.getLocalClass(), ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByLocalClass.class, ClassBeingAccessedByLocalClass.getLocalClass()); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByLocalClass.class, ClassBeingAccessedByLocalClass.getLocalClass()); } @Test @@ -964,7 +984,21 @@ public void belongToAnyOf(ClassesThat classesShouldOnl AnotherClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class, ClassBeingAccessedByInnerClass.class); - assertThatClasses(classes).matchInAnyOrder(AnotherClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class); + assertThatTypes(classes).matchInAnyOrder(AnotherClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class); + } + + @Test + @UseDataProvider("should_only_be_by_rule_starts") + public void doNotBelongToAnyOf(ClassesThat classesShouldOnlyBeBy) { + Set classes = filterViolationCausesInFailureReport( + classesShouldOnlyBeBy.doNotBelongToAnyOf(ClassWithInnerClasses.class)) + .on(ClassWithInnerClasses.class, ClassWithInnerClasses.InnerClass.class, + ClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class, + AnotherClassWithInnerClasses.class, AnotherClassWithInnerClasses.InnerClass.class, + AnotherClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class, + ClassBeingAccessedByInnerClass.class); + + assertThatTypes(classes).matchInAnyOrder(ClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class); } @DataProvider @@ -981,7 +1015,7 @@ public void classesThat_predicate(ArchRule rule) { .on(ClassExtendingClass.class, ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassExtendingClass.class, + assertThatTypes(classes).matchInAnyOrder(ClassExtendingClass.class, ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); } @@ -1003,7 +1037,7 @@ public Set apply(ArchRule rule) { Set classes = filterViolationOriginsInFailureReport.apply( classes().should().onlyHaveDependentClassesThat(have(not(nameMatching(".*DependingVia.*"))))); - assertThatClasses(classes).matchInAnyOrder(ClassDependingViaFieldType.class, + assertThatTypes(classes).matchInAnyOrder(ClassDependingViaFieldType.class, ClassDependingViaMethodParameter.class, ClassDependingViaImplementing.class); classes = filterViolationOriginsInFailureReport.apply( @@ -1163,7 +1197,7 @@ private static class ClassBeingAccessedByClassImplementingSomeInterface { @SuppressWarnings("unused") private static class ClassAccessingItself { - private String field; + private final String field; ClassAccessingItself(String field) { this.field = field; @@ -1243,6 +1277,14 @@ static void access() { } } + private static class ClassBeingAccessedByAnnotation { + } + + @SuppressWarnings({"unused"}) + private @interface AnnotationAccessingAClass { + ClassBeingAccessedByAnnotation access = new ClassBeingAccessedByAnnotation(); + } + private static class ClassAccessingStaticNestedClass { @SuppressWarnings("unused") void access() { @@ -1271,7 +1313,7 @@ void access() { } } - private static Runnable anonymousClassBeingAccessed = new Runnable() { + private static final Runnable anonymousClassBeingAccessed = new Runnable() { @Override public void run() { new ClassAccessingAnonymousClass(); diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/testclasses/SimpleFieldAndMethod.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/testclasses/SimpleFieldAndMethod.java new file mode 100644 index 0000000000..a9f63dcc8c --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/testclasses/SimpleFieldAndMethod.java @@ -0,0 +1,8 @@ +package com.tngtech.archunit.lang.syntax.elements.testclasses; + +public class SimpleFieldAndMethod { + Object violated; + + void violated() { + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/library/dependencies/EdgeTest.java b/archunit/src/test/java/com/tngtech/archunit/library/dependencies/EdgeTest.java index 6777e7dc6c..61fb24ccdf 100644 --- a/archunit/src/test/java/com/tngtech/archunit/library/dependencies/EdgeTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/library/dependencies/EdgeTest.java @@ -1,7 +1,5 @@ package com.tngtech.archunit.library.dependencies; -import java.util.Collections; - import org.junit.Test; import static java.util.Collections.emptySet; diff --git a/archunit/src/test/java/com/tngtech/archunit/library/dependencies/SliceRulePerformanceTest.java b/archunit/src/test/java/com/tngtech/archunit/library/dependencies/SliceRulePerformanceTest.java index 954ff5f7b8..71c999ddcb 100644 --- a/archunit/src/test/java/com/tngtech/archunit/library/dependencies/SliceRulePerformanceTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/library/dependencies/SliceRulePerformanceTest.java @@ -1,9 +1,12 @@ package com.tngtech.archunit.library.dependencies; +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; import com.tngtech.archunit.ArchConfiguration; import com.tngtech.archunit.Slow; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; +import com.tngtech.archunit.lang.FailureReport; import com.tngtech.archunit.library.dependencies.testexamples.completedependencygraph.ninenodes.CompleteNineNodesGraphRoot; import com.tngtech.archunit.testutil.ArchConfigurationRule; import org.junit.Rule; @@ -11,7 +14,6 @@ import org.junit.experimental.categories.Category; import static com.tngtech.archunit.library.dependencies.CycleConfiguration.MAX_NUMBER_OF_CYCLES_TO_DETECT_PROPERTY_NAME; -import static com.tngtech.archunit.library.dependencies.SliceRuleTest.countCyclesInMessage; import static com.tngtech.archunit.library.dependencies.SliceRuleTest.getNumberOfCyclesInCompleteGraph; import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; import static org.assertj.core.api.Assertions.assertThat; @@ -37,9 +39,14 @@ public void searching_for_cycles_should_terminate_reasonably_fast_for_complete_g int expectedNumberOfCycles = getNumberOfCyclesInCompleteGraph(numberOfClassesFormingCompleteGraph); ArchConfiguration.get().setProperty(MAX_NUMBER_OF_CYCLES_TO_DETECT_PROPERTY_NAME, String.valueOf(2 * expectedNumberOfCycles)); - String violations = cycleFree.evaluate(classesFormingCompleteDependencyGraph).getFailureReport().toString(); + FailureReport failureReport = cycleFree.evaluate(classesFormingCompleteDependencyGraph).getFailureReport(); - int numberOfDetectedCycles = countCyclesInMessage(violations); + int numberOfDetectedCycles = FluentIterable.from(failureReport.getDetails()).filter(new Predicate() { + @Override + public boolean apply(String input) { + return input.contains("Cycle detected: "); + } + }).size(); assertThat(numberOfDetectedCycles).as("number of cycles detected").isEqualTo(expectedNumberOfCycles); } } diff --git a/archunit/src/test/java/com/tngtech/archunit/library/dependencies/SlicesTest.java b/archunit/src/test/java/com/tngtech/archunit/library/dependencies/SlicesTest.java index f2a5b464a7..e0017b7905 100644 --- a/archunit/src/test/java/com/tngtech/archunit/library/dependencies/SlicesTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/library/dependencies/SlicesTest.java @@ -23,7 +23,7 @@ import static com.tngtech.archunit.core.domain.TestUtils.dependencyFrom; import static com.tngtech.archunit.core.domain.TestUtils.importClassesWithContext; import static com.tngtech.archunit.core.domain.TestUtils.simulateCall; -import static com.tngtech.archunit.testutil.Assertions.assertThatClasses; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static org.assertj.core.api.Assertions.assertThat; public class SlicesTest { @@ -100,8 +100,8 @@ public void slices_from_identifier() { assertThat(slices.getDescription()).isEqualTo("slices assigned from some description"); assertThat(slices).extractingResultOf("getDescription").containsOnly("Any Lang - $2", "Any Adjusted - Util"); assertThat(slices).hasSize(2); - assertThatClasses(getSliceOf(Object.class, slices)).contain(Number.class); - assertThatClasses(getSliceOf(List.class, slices)).contain(Collection.class); + assertThatTypes(getSliceOf(Object.class, slices)).contain(Number.class); + assertThatTypes(getSliceOf(List.class, slices)).contain(Collection.class); Assertions.assertThat(tryGetSliceOf(File.class, slices)) .as("Slice of class java.io.File (which should be missing from the assignment)") .isAbsent(); @@ -141,4 +141,4 @@ public SliceIdentifier getIdentifierOf(JavaClass javaClass) { } }; } -} \ No newline at end of file +} diff --git a/archunit/src/test/java/com/tngtech/archunit/library/freeze/TextFileBasedViolationStoreTest.java b/archunit/src/test/java/com/tngtech/archunit/library/freeze/TextFileBasedViolationStoreTest.java index 4c4c41463a..7720a3de1c 100644 --- a/archunit/src/test/java/com/tngtech/archunit/library/freeze/TextFileBasedViolationStoreTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/library/freeze/TextFileBasedViolationStoreTest.java @@ -79,6 +79,15 @@ public void reads_violations_of_single_rule_from_configured_folder() { assertThat(storedViolations).containsOnly("first violation", "second violation"); } + @Test + public void reads_empty_list_of_violations() { + store.save(defaultRule(), ImmutableList.of()); + + List storedViolations = store.getViolations(defaultRule()); + + assertThat(storedViolations).isEmpty(); + } + @Test public void stores_violations_of_multiple_rules() { ArchRule firstRule = rule("first rule"); diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java b/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java index d7f5904fc0..a27b573443 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java @@ -1,19 +1,13 @@ package com.tngtech.archunit.testutil; -import java.util.Arrays; import java.util.Collection; -import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableSet; +import com.google.common.base.Joiner; import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.domain.AccessTarget; @@ -22,7 +16,9 @@ import com.tngtech.archunit.core.domain.AccessTarget.MethodCallTarget; import com.tngtech.archunit.core.domain.Dependency; import com.tngtech.archunit.core.domain.JavaAccess; +import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.domain.JavaClassList; import com.tngtech.archunit.core.domain.JavaCodeUnit; import com.tngtech.archunit.core.domain.JavaConstructor; @@ -33,9 +29,9 @@ import com.tngtech.archunit.core.domain.JavaMember; import com.tngtech.archunit.core.domain.JavaMethod; import com.tngtech.archunit.core.domain.JavaMethodCall; -import com.tngtech.archunit.core.domain.JavaModifier; import com.tngtech.archunit.core.domain.JavaPackage; import com.tngtech.archunit.core.domain.JavaType; +import com.tngtech.archunit.core.domain.JavaTypeVariable; import com.tngtech.archunit.core.domain.ThrowsClause; import com.tngtech.archunit.core.domain.ThrowsDeclaration; import com.tngtech.archunit.lang.ArchCondition; @@ -47,6 +43,8 @@ import com.tngtech.archunit.testutil.assertion.DependenciesAssertion; import com.tngtech.archunit.testutil.assertion.DependencyAssertion; import com.tngtech.archunit.testutil.assertion.DescribedPredicateAssertion; +import com.tngtech.archunit.testutil.assertion.JavaAnnotationAssertion; +import com.tngtech.archunit.testutil.assertion.JavaClassDescriptorAssertion; import com.tngtech.archunit.testutil.assertion.JavaCodeUnitAssertion; import com.tngtech.archunit.testutil.assertion.JavaConstructorAssertion; import com.tngtech.archunit.testutil.assertion.JavaFieldAssertion; @@ -56,21 +54,22 @@ import com.tngtech.archunit.testutil.assertion.JavaMethodAssertion; import com.tngtech.archunit.testutil.assertion.JavaMethodsAssertion; import com.tngtech.archunit.testutil.assertion.JavaPackagesAssertion; +import com.tngtech.archunit.testutil.assertion.JavaTypeAssertion; +import com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion; +import com.tngtech.archunit.testutil.assertion.JavaTypesAssertion; import org.assertj.core.api.AbstractIterableAssert; import org.assertj.core.api.AbstractListAssert; import org.assertj.core.api.AbstractObjectAssert; import org.assertj.core.api.Condition; import org.assertj.core.api.ObjectAssert; import org.assertj.core.api.ObjectAssertFactory; -import org.objectweb.asm.Type; +import static com.google.common.base.Strings.emptyToNull; import static com.tngtech.archunit.core.domain.Formatters.formatMethodSimple; import static com.tngtech.archunit.core.domain.JavaClass.namesOf; import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME; import static com.tngtech.archunit.core.domain.TestUtils.resolvedTargetFrom; import static com.tngtech.archunit.core.domain.TestUtils.targetFrom; -import static com.tngtech.archunit.testutil.assertion.JavaAnnotationAssertion.propertiesOf; -import static com.tngtech.archunit.testutil.assertion.JavaPackagesAssertion.sortByName; public class Assertions extends org.assertj.core.api.Assertions { public static ArchConditionAssertion assertThat(ArchCondition archCondition) { @@ -93,12 +92,16 @@ public static DescribedPredicateAssertion assertThat(DescribedPredicate(predicate); } - public static JavaClassAssertion assertThat(JavaClass javaClass) { - return new JavaClassAssertion(javaClass); + public static JavaTypeAssertion assertThatType(JavaType javaType) { + return new JavaTypeAssertion(javaType); + } + + public static JavaTypeVariableAssertion assertThatTypeVariable(JavaTypeVariable typeVariable) { + return new JavaTypeVariableAssertion(typeVariable); } - public static JavaClassesAssertion assertThatClasses(Iterable javaClasses) { - return new JavaClassesAssertion(javaClasses); + public static JavaTypesAssertion assertThatTypes(Iterable javaTypes) { + return new JavaTypesAssertion(javaTypes); } public static JavaPackagesAssertion assertThatPackages(Iterable javaPackages) { @@ -133,8 +136,8 @@ public static JavaFieldsAssertion assertThatFields(Iterable fields) { return new JavaFieldsAssertion(fields); } - public static JavaClassesAssertion assertThat(JavaClass[] javaClasses) { - return new JavaClassesAssertion(javaClasses); + public static JavaTypesAssertion assertThat(JavaType[] javaTypes) { + return new JavaTypesAssertion(javaTypes); } public static JavaClassListAssertion assertThat(JavaClassList javaClasses) { @@ -157,8 +160,8 @@ public static JavaEnumConstantsAssertion assertThat(JavaEnumConstant[] enumConst return new JavaEnumConstantsAssertion(enumConstants); } - public static JavaTypeAssertion assertThat(JavaType javaType) { - return new JavaTypeAssertion(javaType); + public static JavaClassDescriptorAssertion assertThat(JavaClassDescriptor javaClassDescriptor) { + return new JavaClassDescriptorAssertion(javaClassDescriptor); } public static ThrowsDeclarationAssertion assertThat(ThrowsDeclaration throwsDeclaration) { @@ -169,6 +172,10 @@ public static ThrowsClauseAssertion assertThat(ThrowsClause throwsClause) { return new ThrowsClauseAssertion(throwsClause); } + public static JavaAnnotationAssertion assertThatAnnotation(JavaAnnotation annotation) { + return new JavaAnnotationAssertion(annotation); + } + @SuppressWarnings("unchecked") // covariant public static AccessesAssertion assertThatAccesses(Collection> accesses) { return new AccessesAssertion((Collection>) accesses); @@ -194,7 +201,7 @@ public Step2 from(Class originClass, String codeUnitName) { return new Step2(originClass, codeUnitName); } - public class Step2 { + public static class Step2 { private final Class originClass; private final String originCodeUnitName; @@ -248,104 +255,6 @@ public static ConstructorCallAssertion assertThatCall(JavaConstructorCall call) return new ConstructorCallAssertion(call); } - public static class JavaClassesAssertion extends AbstractObjectAssert { - private JavaClassesAssertion(JavaClass[] actual) { - super(actual, JavaClassesAssertion.class); - } - - private JavaClassesAssertion(Iterable actual) { - super(sort(actual), JavaClassesAssertion.class); - } - - private static JavaClass[] sort(Iterable actual) { - JavaClass[] result = Iterables.toArray(actual, JavaClass.class); - sortByName(result); - return result; - } - - public void matchInAnyOrder(Iterable> classes) { - Class[] sorted = Iterables.toArray(classes, Class.class); - Arrays.sort(sorted, new Comparator>() { - @Override - public int compare(Class o1, Class o2) { - return o1.getName().compareTo(o2.getName()); - } - }); - matchExactly(sorted); - } - - public void matchInAnyOrder(Class... classes) { - matchInAnyOrder(ImmutableSet.copyOf(classes)); - } - - public void matchExactly(Class... classes) { - assertThat(TestUtils.namesOf(actual)).as("classes").containsExactlyElementsOf(namesOf(classes)); - for (int i = 0; i < actual.length; i++) { - assertThat(actual[i]).as("Element %d", i).matches(classes[i]); - } - } - - public JavaClassesAssertion contain(Class... classes) { - contain(ImmutableSet.copyOf(classes)); - return this; - } - - public void doNotContain(Class... classes) { - assertThat(actualNames()).doesNotContainAnyElementsOf(JavaClass.namesOf(classes)); - } - - public void contain(Iterable> classes) { - List expectedNames = JavaClass.namesOf(Lists.newArrayList(classes)); - assertThat(actualNames()).as("actual classes").containsAll(expectedNames); - } - - private Set actualNames() { - Set actualNames = new HashSet<>(); - for (JavaClass javaClass : actual) { - actualNames.add(javaClass.getName()); - } - return actualNames; - } - } - - public static class JavaClassAssertion extends AbstractObjectAssert { - private static final Pattern ARRAY_PATTERN = Pattern.compile("(\\[+)(.*)"); - - private JavaClassAssertion(JavaClass javaClass) { - super(javaClass, JavaClassAssertion.class); - } - - public void matches(Class clazz) { - assertThat(actual.getName()).as("Name of " + actual) - .isEqualTo(clazz.getName()); - assertThat(actual.getSimpleName()).as("Simple name of " + actual) - .isEqualTo(ensureArrayName(clazz.getSimpleName())); - assertThat(actual.getPackage().getName()).as("Package of " + actual) - .isEqualTo(getExpectedPackageName(clazz)); - assertThat(actual.getPackageName()).as("Package name of " + actual) - .isEqualTo(getExpectedPackageName(clazz)); - assertThat(actual.getModifiers()).as("Modifiers of " + actual) - .isEqualTo(JavaModifier.getModifiersForClass(clazz.getModifiers())); - assertThat(actual.isArray()).as(actual + " is array").isEqualTo(clazz.isArray()); - assertThat(propertiesOf(actual.getAnnotations())).as("Annotations of " + actual) - .isEqualTo(propertiesOf(clazz.getAnnotations())); - - if (clazz.isArray()) { - new JavaClassAssertion(actual.getComponentType()).matches(clazz.getComponentType()); - } - } - - private String ensureArrayName(String name) { - String suffix = ""; - Matcher matcher = ARRAY_PATTERN.matcher(name); - if (matcher.matches()) { - name = Type.getType(matcher.group(2)).getClassName(); - suffix = Strings.repeat("[]", matcher.group(1).length()); - } - return name + suffix; - } - } - public static class JavaClassListAssertion extends AbstractListAssert, JavaClass, ObjectAssert> { private JavaClassListAssertion(JavaClassList javaClasses) { @@ -355,7 +264,7 @@ private JavaClassListAssertion(JavaClassList javaClasses) { public void matches(Class... classes) { assertThat(actual).as("JavaClasses").hasSize(classes.length); for (int i = 0; i < actual.size(); i++) { - assertThat(actual.get(i)).as("Element %d", i).matches(classes[i]); + assertThatType(actual.get(i)).as("Element %d", i).matches(classes[i]); } } @@ -371,9 +280,17 @@ private JavaEnumConstantAssertion(JavaEnumConstant enumConstant) { } public void isEquivalentTo(Enum enumConstant) { - assertThat(actual).as(JavaEnumConstant.class.getSimpleName()).isNotNull(); - assertThat(actual.getDeclaringClass().getName()).isEqualTo(enumConstant.getDeclaringClass().getName()); - assertThat(actual.name()).isEqualTo(enumConstant.name()); + assertThat(actual).as(describePartialAssertion()).isNotNull(); + assertThat(actual.getDeclaringClass().getName()).as(describePartialAssertion("type")).isEqualTo(enumConstant.getDeclaringClass().getName()); + assertThat(actual.name()).as(describePartialAssertion("name")).isEqualTo(enumConstant.name()); + } + + private String describePartialAssertion() { + return describePartialAssertion(""); + } + + private String describePartialAssertion(String partialAssertionDescription) { + return Joiner.on(": ").skipNulls().join(emptyToNull(descriptionText()), emptyToNull(partialAssertionDescription)); } } @@ -396,7 +313,7 @@ private ThrowsDeclarationAssertion(ThrowsDeclaration throwsDeclaration) { } public void matches(Class clazz) { - assertThat(actual.getRawType()).as("Type of " + actual) + assertThatType(actual.getRawType()).as("Type of " + actual) .matches(clazz); } } @@ -462,8 +379,13 @@ public SELF isFrom(String name, Class... parameterTypes) { return isFrom(access.getOrigin().getOwner().getCodeUnitWithParameterTypes(name, parameterTypes)); } + public SELF isFrom(Class originClass, String name, Class... parameterTypes) { + assertThatType(access.getOriginOwner()).matches(originClass); + return isFrom(name, parameterTypes); + } + public SELF isFrom(JavaCodeUnit codeUnit) { - assertThat(access.getOrigin()).as("Origin of field access").isEqualTo(codeUnit); + assertThat(access.getOrigin()).as("Origin of access").isEqualTo(codeUnit); return newAssertion(access); } @@ -496,8 +418,13 @@ protected AccessToFieldAssertion newAssertion(JavaFieldAccess access) { return new AccessToFieldAssertion(access); } - public AccessToFieldAssertion isTo(String name) { - return isTo(access.getTarget().getOwner().getField(name)); + public AccessToFieldAssertion isTo(final String name) { + return isTo(new Condition("field with name '" + name + "'") { + @Override + public boolean matches(FieldAccessTarget fieldAccessTarget) { + return fieldAccessTarget.getName().equals(name); + } + }); } public AccessToFieldAssertion isTo(JavaField field) { @@ -519,6 +446,16 @@ public MethodCallAssertion isTo(JavaMethod target) { return isTo(resolvedTargetFrom(target)); } + public MethodCallAssertion isTo(final String methodName, final Class... parameterTypes) { + return isTo(new Condition("method " + methodName + "(" + namesOf(parameterTypes) + ")") { + @Override + public boolean matches(MethodCallTarget methodCallTarget) { + return methodCallTarget.getName().equals(methodName) + && TestUtils.namesOf(methodCallTarget.getRawParameterTypes()).equals(namesOf(parameterTypes)); + } + }); + } + @Override protected MethodCallAssertion newAssertion(JavaMethodCall call) { return new MethodCallAssertion(call); @@ -534,29 +471,19 @@ public ConstructorCallAssertion isTo(JavaConstructor target) { return isTo(targetFrom(target)); } + public ConstructorCallAssertion isTo(final Class constructorOwner) { + return isTo(new Condition() { + @Override + public boolean matches(ConstructorCallTarget constructorCallTarget) { + return constructorCallTarget.getOwner().isEquivalentTo(constructorOwner); + } + }); + } + @Override protected ConstructorCallAssertion newAssertion(JavaConstructorCall call) { return new ConstructorCallAssertion(call); } } - public static class JavaTypeAssertion extends AbstractObjectAssert { - private JavaTypeAssertion(JavaType actual) { - super(actual, JavaTypeAssertion.class); - } - - public void isEquivalentTo(Class clazz) { - assertThat(actual.getName()).as("name").isEqualTo(clazz.getName()); - assertThat(actual.getSimpleName()).as("simple name").isEqualTo(clazz.getSimpleName()); - String expectedPackageName = getExpectedPackageName(clazz); - assertThat(actual.getPackageName()).as("package").isEqualTo(expectedPackageName); - } - } - - private static String getExpectedPackageName(Class clazz) { - if (!clazz.isArray()) { - return clazz.getPackage() != null ? clazz.getPackage().getName() : ""; - } - return getExpectedPackageName(clazz.getComponentType()); - } } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/TestUtils.java b/archunit/src/test/java/com/tngtech/archunit/testutil/TestUtils.java index 5bcabd9d79..e056b4663e 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/TestUtils.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/TestUtils.java @@ -2,12 +2,14 @@ import java.io.File; import java.lang.reflect.Method; -import java.util.LinkedHashSet; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; import java.util.Properties; import java.util.Random; -import java.util.Set; -import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableList; import com.tngtech.archunit.core.domain.properties.HasName; import org.assertj.core.util.Files; @@ -56,15 +58,24 @@ public static Properties properties(String... keyValues) { return result; } - public static Set namesOf(HasName[] thingsWithNames) { - return namesOf(ImmutableSet.copyOf(thingsWithNames)); + public static List namesOf(HasName[] thingsWithNames) { + return namesOf(ImmutableList.copyOf(thingsWithNames)); } - public static Set namesOf(Iterable thingsWithNames) { - Set result = new LinkedHashSet<>(); + public static List namesOf(Iterable thingsWithNames) { + List result = new ArrayList<>(); for (HasName hasName : thingsWithNames) { result.add(hasName.getName()); } return result; } + + public static void sortByName(T[] result) { + Arrays.sort(result, new Comparator() { + @Override + public int compare(HasName o1, HasName o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + } } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/DependenciesAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/DependenciesAssertion.java index 91504a16cf..629e972830 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/DependenciesAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/DependenciesAssertion.java @@ -3,17 +3,24 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.regex.Pattern; +import com.google.common.base.Joiner; +import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.collect.FluentIterable; -import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import com.tngtech.archunit.core.domain.Dependency; import org.assertj.core.api.AbstractIterableAssert; +import static com.google.common.base.Predicates.not; +import static com.google.common.collect.Iterables.getLast; +import static java.lang.System.lineSeparator; +import static java.util.regex.Pattern.quote; import static org.assertj.core.api.Assertions.assertThat; public class DependenciesAssertion extends AbstractIterableAssert< - DependenciesAssertion, Iterable, Dependency, DependencyAssertion> { + DependenciesAssertion, Iterable, Dependency, DependencyAssertion> { public DependenciesAssertion(Iterable dependencies) { super(dependencies, DependenciesAssertion.class); @@ -53,20 +60,40 @@ public DependenciesAssertion doesNotContain(Class expectedOrigin, Class ex return this; } + public DependenciesAssertion contain(final ExpectedDependencies expectedDependencies) { + matchExpectedDependencies(expectedDependencies) + .assertNoMissingDependencies(); + return this; + } + public DependenciesAssertion containOnly(final ExpectedDependencies expectedDependencies) { - FluentIterable rest = FluentIterable.from(actual); - for (final List> expectedDependency : expectedDependencies) { - rest = rest.filter(new Predicate() { - @Override - public boolean apply(Dependency input) { - return !matches(input, expectedDependency.get(0), expectedDependency.get(1)); - } - }); - } - assertThat(rest.toSet()).as("unexpected elements").isEmpty(); + ExpectedDependenciesMatchResult result = matchExpectedDependencies(expectedDependencies); + result.assertNoMissingDependencies(); + result.assertAllDependenciesMatched(); return this; } + private ExpectedDependenciesMatchResult matchExpectedDependencies(ExpectedDependencies expectedDependencies) { + FluentIterable rest = FluentIterable.from(actual); + List missingDependencies = new ArrayList<>(); + for (final ExpectedDependency expectedDependency : expectedDependencies) { + if (!rest.anyMatch(matches(expectedDependency))) { + missingDependencies.add(expectedDependency); + } + rest = rest.filter(not(matches(expectedDependency))); + } + return new ExpectedDependenciesMatchResult(missingDependencies, rest.toList()); + } + + private Predicate matches(final ExpectedDependency expectedDependency) { + return new Predicate() { + @Override + public boolean apply(Dependency input) { + return expectedDependency.matches(input); + } + }; + } + public DependenciesAssertion containOnly(Class expectedOrigin, Class expectedTarget) { for (Dependency dependency : actual) { toAssert(dependency, dependency.getDescription()).matches(expectedOrigin, expectedTarget); @@ -96,14 +123,14 @@ public ExpectedDependencies to(Class target) { } } - public static class ExpectedDependencies implements Iterable>> { - List>> expectedDependencies = new ArrayList<>(); + public static class ExpectedDependencies implements Iterable { + List expectedDependencies = new ArrayList<>(); private ExpectedDependencies() { } @Override - public Iterator>> iterator() { + public Iterator iterator() { return expectedDependencies.iterator(); } @@ -112,8 +139,74 @@ public ExpectedDependenciesCreator from(Class origin) { } ExpectedDependencies add(Class origin, Class target) { - expectedDependencies.add(ImmutableList.of(origin, target)); + expectedDependencies.add(new ExpectedDependency(origin, target)); return this; } + + public ExpectedDependencies withDescriptionContaining(String descriptionTemplate, Object... args) { + getLast(expectedDependencies).descriptionContaining(descriptionTemplate, args); + return this; + } + + public ExpectedDependencies inLocation(Class location, int lineNumber) { + getLast(expectedDependencies).location(location, lineNumber); + return this; + } + } + + private static class ExpectedDependency { + private final Class origin; + private final Class target; + private Optional descriptionPattern = Optional.absent(); + private Optional locationPart = Optional.absent(); + + ExpectedDependency(Class origin, Class target) { + this.origin = origin; + this.target = target; + } + + boolean matches(Dependency dependency) { + return dependency.getOriginClass().isEquivalentTo(origin) + && dependency.getTargetClass().isEquivalentTo(target) + && (!descriptionPattern.isPresent() || descriptionPattern.get().matcher(dependency.getDescription()).matches()) + && (!locationPart.isPresent() || dependency.getDescription().endsWith(locationPart.get())); + } + + public void descriptionContaining(String descriptionTemplate, Object[] args) { + String descriptionPart = String.format(descriptionTemplate, args); + descriptionPattern = Optional.of(Pattern.compile(".*" + quote(descriptionPart) + ".*")); + } + + public void location(Class location, int lineNumber) { + locationPart = Optional.of(String.format("in (%s.java:%d)", location.getSimpleName(), lineNumber)); + } + + @Override + public String toString() { + String dependency = origin.getName() + " -> " + target.getName(); + String location = locationPart.isPresent() ? " " + locationPart.get() : ""; + String description = descriptionPattern.isPresent() ? " with description matching " + descriptionPattern.get() : ""; + return dependency + location + description; + } + } + + private static class ExpectedDependenciesMatchResult { + private final Iterable missingDependencies; + private final Iterable unexpectedDependencies; + + private ExpectedDependenciesMatchResult(Iterable missingDependencies, Iterable unexpectedDependencies) { + this.missingDependencies = missingDependencies; + this.unexpectedDependencies = unexpectedDependencies; + } + + void assertNoMissingDependencies() { + if (!Iterables.isEmpty(missingDependencies)) { + throw new AssertionError("Could not find expected dependencies:" + lineSeparator() + Joiner.on(lineSeparator()).join(missingDependencies)); + } + } + + public void assertAllDependenciesMatched() { + assertThat(unexpectedDependencies).as("unexpected dependencies").isEmpty(); + } } } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/DependencyAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/DependencyAssertion.java index 6bbd836117..beb8101de5 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/DependencyAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/DependencyAssertion.java @@ -11,6 +11,7 @@ import static com.tngtech.archunit.core.domain.Dependency.Predicates.dependency; import static com.tngtech.archunit.core.domain.Dependency.Predicates.dependencyOrigin; import static com.tngtech.archunit.core.domain.Dependency.Predicates.dependencyTarget; +import static java.util.regex.Pattern.quote; import static org.assertj.core.api.Assertions.assertThat; public class DependencyAssertion extends AbstractObjectAssert { @@ -95,4 +96,18 @@ private List> dependencyMatchesTarget(Class targetCl assertThat(dependencyTarget(HasName.Predicates.name(targetClass.getName())).apply(actual)) .as("Dependency target matches '%s.class'", targetClass.getSimpleName())); } + + public LocationAssertion hasDescription(String originDescription, String dependencyDescription, String targetDescription) { + String expectedOriginDependsOnTargetDescription = quote(String.format("<%s> %s <%s>", originDescription, dependencyDescription, targetDescription)); + String descriptionPattern = String.format(".+ %s in \\([^ ]+:\\d+\\)", expectedOriginDependsOnTargetDescription); + assertThat(actual.getDescription()).matches(descriptionPattern); + return new LocationAssertion(); + } + + public class LocationAssertion { + public DependencyAssertion inLocation(Class expectedLocationSource, int expectedLineNumber) { + assertThat(actual.getDescription()).endsWith(String.format("in (%s.java:%d)", expectedLocationSource.getSimpleName(), expectedLineNumber)); + return DependencyAssertion.this; + } + } } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaAnnotationAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaAnnotationAssertion.java index 50175d4e24..66c6d1c694 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaAnnotationAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaAnnotationAssertion.java @@ -1,6 +1,7 @@ package com.tngtech.archunit.testutil.assertion; import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; @@ -10,22 +11,82 @@ import java.util.Objects; import java.util.Set; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; +import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.domain.JavaAnnotation; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaEnumConstant; +import org.assertj.core.api.AbstractObjectAssert; import static com.google.common.base.Preconditions.checkArgument; +import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatAnnotation; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; import static com.tngtech.archunit.testutil.TestUtils.invoke; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +public class JavaAnnotationAssertion extends AbstractObjectAssert> { + public JavaAnnotationAssertion(JavaAnnotation actual) { + super(actual, JavaAnnotationAssertion.class); + } + + public JavaAnnotationAssertion hasType(Class annotationType) { + assertThatType(actual.getRawType()) + .as("annotation type of " + descriptionText()) + .matches(annotationType); + return this; + } + + public JavaAnnotationAssertion hasClassProperty(String propertyName, Class expectedClass) { + JavaClass actualClassValue = getPropertyOfType(propertyName, JavaClass.class); + assertThatType(actualClassValue).as("Class @%s.%s()" + context(), actual.getRawType().getSimpleName(), propertyName).matches(expectedClass); + return this; + } + + public JavaAnnotationAssertion hasEnumProperty(String propertyName, Enum expectedEnumConstant) { + JavaEnumConstant actualEnumConstant = getPropertyOfType(propertyName, JavaEnumConstant.class); + assertThat(actualEnumConstant) + .as("%s @%s.%s()" + context(), actualEnumConstant.getDeclaringClass().getSimpleName(), actual.getRawType().getSimpleName(), propertyName) + .isEquivalentTo(expectedEnumConstant); + return this; + } + + public JavaAnnotationAssertion hasAnnotationProperty(String propertyName, AnnotationPropertyAssertion propertyAssertion) { + JavaAnnotation actualAnnotationProperty = getPropertyOfType(propertyName, JavaAnnotation.class); + propertyAssertion.check(actual, propertyName, actualAnnotationProperty); + return this; + } + + @SuppressWarnings("unchecked") + private T getPropertyOfType(String propertyName, Class propertyType) { + Optional property = actual.get(propertyName); + assertThat(property).as("property '%s'", propertyName).isPresent(); + assertThat(property.get()).as("property '%s'", propertyName).isInstanceOf(propertyType); + return (T) property.get(); + } + + private String context() { + return Strings.isNullOrEmpty(descriptionText()) ? "" : " of " + descriptionText(); + } -public class JavaAnnotationAssertion { @SuppressWarnings("rawtypes") - public static Set> propertiesOf(Set> annotations) { + public static Set> runtimePropertiesOf(Set> annotations) { List converted = new ArrayList<>(); for (JavaAnnotation annotation : annotations) { - converted.add(annotation.as((Class) annotation.getRawType().reflect())); + Annotation reflectionAnnotation = annotation.as((Class) annotation.getRawType().reflect()); + if (isRetentionRuntime(reflectionAnnotation)) { + converted.add(reflectionAnnotation); + } } return propertiesOf(converted.toArray(new Annotation[0])); } + private static boolean isRetentionRuntime(Annotation annotation) { + return annotation.annotationType().isAnnotationPresent(Retention.class) + && annotation.annotationType().getAnnotation(Retention.class).value() == RUNTIME; + } + public static Set> propertiesOf(Annotation[] annotations) { Set> result = new HashSet<>(); for (Annotation annotation : annotations) { @@ -80,6 +141,10 @@ private static List listFrom(Object primitiveArray) { return result.build(); } + public static AnnotationPropertyAssertion annotationProperty() { + return new AnnotationPropertyAssertion(); + } + private static class SimpleTypeReference { private final String typeName; @@ -158,4 +223,40 @@ static List allOf(Enum[] values) { return result.build(); } } + + public static class AnnotationPropertyAssertion { + private final List> executeAssertions = new ArrayList<>(); + + public AnnotationPropertyAssertion withAnnotationType(final Class annotationType) { + executeAssertions.add(new Consumer() { + @Override + public void accept(JavaAnnotationAssertion assertion) { + assertion.hasType(annotationType); + } + }); + return this; + } + + public AnnotationPropertyAssertion withClassProperty(final String propertyName, final Class propertyValue) { + executeAssertions.add(new Consumer() { + @Override + public void accept(JavaAnnotationAssertion assertion) { + assertion.hasClassProperty(propertyName, propertyValue); + } + }); + return this; + } + + void check(JavaAnnotation owner, String propertyName, JavaAnnotation actualAnnotationProperty) { + JavaAnnotationAssertion assertion = assertThatAnnotation(actualAnnotationProperty) + .as("%s @%s.%s()", actualAnnotationProperty.getRawType().getSimpleName(), owner.getRawType().getSimpleName(), propertyName); + for (Consumer executeAssertion : executeAssertions) { + executeAssertion.accept(assertion); + } + } + + private interface Consumer { + void accept(T value); + } + } } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaClassDescriptorAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaClassDescriptorAssertion.java new file mode 100644 index 0000000000..705454b11b --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaClassDescriptorAssertion.java @@ -0,0 +1,20 @@ +package com.tngtech.archunit.testutil.assertion; + +import com.tngtech.archunit.core.domain.JavaClassDescriptor; +import org.assertj.core.api.AbstractObjectAssert; + +import static com.tngtech.archunit.testutil.assertion.JavaTypeAssertion.getExpectedPackageName; +import static org.assertj.core.api.Assertions.assertThat; + +public class JavaClassDescriptorAssertion extends AbstractObjectAssert { + public JavaClassDescriptorAssertion(JavaClassDescriptor actual) { + super(actual, JavaClassDescriptorAssertion.class); + } + + public void isEquivalentTo(Class clazz) { + assertThat(actual.getFullyQualifiedClassName()).as("name").isEqualTo(clazz.getName()); + assertThat(actual.getSimpleClassName()).as("simple name").isEqualTo(clazz.getSimpleName()); + String expectedPackageName = getExpectedPackageName(clazz); + assertThat(actual.getPackageName()).as("package").isEqualTo(expectedPackageName); + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaCodeUnitAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaCodeUnitAssertion.java index 1bf4943487..5497552980 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaCodeUnitAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaCodeUnitAssertion.java @@ -6,6 +6,7 @@ import com.tngtech.archunit.core.domain.JavaCodeUnit; import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; public class JavaCodeUnitAssertion> extends JavaMemberAssertion { @@ -17,12 +18,12 @@ public JavaCodeUnitAssertion(T javaMember, Class selfType) { public void isEquivalentTo(Method method) { super.isEquivalentTo(method); assertThat(actual.getRawParameterTypes()).matches(method.getParameterTypes()); - assertThat(actual.getRawReturnType()).matches(method.getReturnType()); + assertThatType(actual.getRawReturnType()).matches(method.getReturnType()); } public void isEquivalentTo(Constructor constructor) { super.isEquivalentTo(constructor); assertThat(actual.getRawParameterTypes()).matches(constructor.getParameterTypes()); - assertThat(actual.getRawReturnType()).matches(void.class); + assertThatType(actual.getRawReturnType()).matches(void.class); } } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaConstructorAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaConstructorAssertion.java index 4710c1115e..221d0db073 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaConstructorAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaConstructorAssertion.java @@ -7,6 +7,7 @@ import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME; import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; import static com.tngtech.archunit.testutil.assertion.JavaMemberAssertion.getExpectedNameOf; public class JavaConstructorAssertion extends AbstractObjectAssert { @@ -19,6 +20,6 @@ public void isEquivalentTo(Constructor constructor) { assertThat(actual.getName()).isEqualTo(CONSTRUCTOR_NAME); assertThat(actual.getFullName()).isEqualTo(getExpectedNameOf(constructor, CONSTRUCTOR_NAME)); assertThat(actual.getRawParameterTypes()).matches(constructor.getParameterTypes()); - assertThat(actual.getRawReturnType()).matches(void.class); + assertThatType(actual.getRawReturnType()).matches(void.class); } } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaFieldAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaFieldAssertion.java index 53df47aaa3..c82d45f264 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaFieldAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaFieldAssertion.java @@ -4,7 +4,7 @@ import com.tngtech.archunit.core.domain.JavaField; -import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; public class JavaFieldAssertion extends JavaMemberAssertion { public JavaFieldAssertion(JavaField javaField) { @@ -13,6 +13,6 @@ public JavaFieldAssertion(JavaField javaField) { public void isEquivalentTo(Field field) { super.isEquivalentTo(field); - assertThat(actual.getRawType()).matches(field.getType()); + assertThatType(actual.getRawType()).matches(field.getType()); } } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaMembersAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaMembersAssertion.java index 5374a2d082..ba8f233234 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaMembersAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaMembersAssertion.java @@ -30,6 +30,7 @@ import static com.tngtech.archunit.core.domain.JavaModifier.VOLATILE; import static com.tngtech.archunit.testutil.Assertions.assertThat; import static com.tngtech.archunit.testutil.assertion.JavaAnnotationAssertion.propertiesOf; +import static com.tngtech.archunit.testutil.assertion.JavaAnnotationAssertion.runtimePropertiesOf; import static com.tngtech.archunit.testutil.assertion.JavaMemberAssertion.getExpectedFullNameOf; public class JavaMembersAssertion extends AbstractObjectAssert> { @@ -89,7 +90,7 @@ private void matchCasted(Set members) { static void assertEquivalent(JavaMember javaMember, T member) { assertThat(javaMember.getOwner().reflect()).isEqualTo(member.getDeclaringClass()); assertModifiersMatch(javaMember, member); - assertThat(propertiesOf(javaMember.getAnnotations())) + assertThat(runtimePropertiesOf(javaMember.getAnnotations())) .isEqualTo(propertiesOf(member.getAnnotations())); } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaMethodAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaMethodAssertion.java index 53fdedb7b1..12f7990938 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaMethodAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaMethodAssertion.java @@ -6,8 +6,9 @@ import org.assertj.core.api.AbstractObjectAssert; import static com.tngtech.archunit.testutil.Assertions.assertThat; -import static com.tngtech.archunit.testutil.assertion.JavaMembersAssertion.assertEquivalent; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; import static com.tngtech.archunit.testutil.assertion.JavaMemberAssertion.getExpectedNameOf; +import static com.tngtech.archunit.testutil.assertion.JavaMembersAssertion.assertEquivalent; public class JavaMethodAssertion extends AbstractObjectAssert { public JavaMethodAssertion(JavaMethod javaMethod) { @@ -19,12 +20,12 @@ public JavaMethodAssertion isEquivalentTo(Method method) { assertThat(actual.getFullName()).isEqualTo(getExpectedNameOf(method, method.getName())); assertThat(actual.getName()).isEqualTo(method.getName()); assertThat(actual.getRawParameterTypes()).matches(method.getParameterTypes()); - assertThat(actual.getRawReturnType()).matches(method.getReturnType()); + assertThatType(actual.getRawReturnType()).matches(method.getReturnType()); return this; } public JavaMethodAssertion isEquivalentTo(Class owner, String methodName, Class... parameterTypes) { - assertThat(actual.getOwner()).matches(owner); + assertThatType(actual.getOwner()).matches(owner); assertThat(actual.getName()).isEqualTo(methodName); assertThat(actual.getRawParameterTypes()).matches(parameterTypes); return this; diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaPackagesAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaPackagesAssertion.java index a9de2f0968..a3ea7903a1 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaPackagesAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaPackagesAssertion.java @@ -1,7 +1,5 @@ package com.tngtech.archunit.testutil.assertion; -import java.util.Arrays; -import java.util.Comparator; import java.util.HashSet; import java.util.Set; @@ -9,7 +7,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.tngtech.archunit.core.domain.JavaPackage; -import com.tngtech.archunit.core.domain.properties.HasName; +import com.tngtech.archunit.testutil.TestUtils; import org.assertj.core.api.AbstractObjectAssert; import static com.tngtech.archunit.base.Guava.toGuava; @@ -24,7 +22,7 @@ public JavaPackagesAssertion(Iterable javaPackages) { private static JavaPackage[] sort(Iterable actual) { JavaPackage[] result = Iterables.toArray(actual, JavaPackage.class); - sortByName(result); + TestUtils.sortByName(result); return result; } @@ -60,13 +58,4 @@ private ImmutableSet getActualNames() { private ImmutableSet getActualRelativeNames() { return FluentIterable.from(actual).transform(toGuava(GET_RELATIVE_NAME)).toSet(); } - - public static void sortByName(T[] result) { - Arrays.sort(result, new Comparator() { - @Override - public int compare(HasName o1, HasName o2) { - return o1.getName().compareTo(o2.getName()); - } - }); - } } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeAssertion.java new file mode 100644 index 0000000000..2bd4a81ec7 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeAssertion.java @@ -0,0 +1,127 @@ +package com.tngtech.archunit.testutil.assertion; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.google.common.base.Optional; +import com.google.common.base.Strings; +import com.google.common.collect.FluentIterable; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaModifier; +import com.tngtech.archunit.core.domain.JavaType; +import com.tngtech.archunit.core.domain.JavaTypeVariable; +import com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion.ExpectedConcreteType; +import org.assertj.core.api.AbstractObjectAssert; +import org.objectweb.asm.Type; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Strings.isNullOrEmpty; +import static com.tngtech.archunit.base.Guava.toGuava; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypeVariable; +import static com.tngtech.archunit.testutil.TestUtils.namesOf; +import static com.tngtech.archunit.testutil.assertion.JavaAnnotationAssertion.propertiesOf; +import static com.tngtech.archunit.testutil.assertion.JavaAnnotationAssertion.runtimePropertiesOf; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.guava.api.Assertions.assertThat; + +public class JavaTypeAssertion extends AbstractObjectAssert { + private static final Pattern ARRAY_PATTERN = Pattern.compile("(\\[+)(.*)"); + + public JavaTypeAssertion(JavaType javaType) { + super(javaType, JavaTypeAssertion.class); + } + + public void matches(java.lang.reflect.Type type) { + checkArgument(type instanceof Class, "Only %s implemented so far, please extend", Class.class.getName()); + matches((Class) type); + } + + public void matches(Class clazz) { + JavaClass javaClass = actualClass(); + + assertThat(javaClass.getName()).as(describeAssertion("Name of " + javaClass)) + .isEqualTo(clazz.getName()); + assertThat(javaClass.getSimpleName()).as(describeAssertion("Simple name of " + javaClass)) + .isEqualTo(ensureArrayName(clazz.getSimpleName())); + assertThat(javaClass.getPackage().getName()).as(describeAssertion("Package of " + javaClass)) + .isEqualTo(getExpectedPackageName(clazz)); + assertThat(javaClass.getPackageName()).as(describeAssertion("Package name of " + javaClass)) + .isEqualTo(getExpectedPackageName(clazz)); + assertThat(javaClass.getModifiers()).as(describeAssertion("Modifiers of " + javaClass)) + .isEqualTo(JavaModifier.getModifiersForClass(clazz.getModifiers())); + assertThat(javaClass.isArray()).as(describeAssertion(javaClass + " is array")).isEqualTo(clazz.isArray()); + assertThat(runtimePropertiesOf(javaClass.getAnnotations())).as(describeAssertion("Annotations of " + javaClass)) + .isEqualTo(propertiesOf(clazz.getAnnotations())); + + if (clazz.isArray()) { + new JavaTypeAssertion(javaClass.getComponentType()) + .as(describeAssertion(String.format("Component type of %s: ", javaClass.getSimpleName()))) + .matches(clazz.getComponentType()); + } + } + + private String describeAssertion(String partialAssertionDescription) { + return isNullOrEmpty(descriptionText()) + ? partialAssertionDescription + : descriptionText() + ": " + partialAssertionDescription; + } + + public JavaTypeAssertion hasTypeParameters(String... names) { + assertThat(namesOf(actualClass().getTypeParameters())).as("names of type parameters").containsExactly(names); + return this; + } + + @SuppressWarnings("OptionalGetWithoutIsPresent") // checked via AssertJ + public JavaTypeVariableOfClassAssertion hasTypeParameter(String name) { + List> typeVariables = actualClass().getTypeParameters(); + + Optional> variable = FluentIterable.from(typeVariables).firstMatch(toGuava(name(name))); + assertThat(variable).as("Type variable with name '%s'", name).isPresent(); + + return new JavaTypeVariableOfClassAssertion(variable.get()); + } + + public JavaTypeVariableOfClassAssertion hasOnlyTypeParameter(String name) { + assertThat(actualClass().getTypeParameters()).as("Type parameters").hasSize(1); + return hasTypeParameter(name); + } + + private JavaClass actualClass() { + return actual instanceof JavaClass ? (JavaClass) actual : actual.toErasure(); + } + + private String ensureArrayName(String name) { + String suffix = ""; + Matcher matcher = ARRAY_PATTERN.matcher(name); + if (matcher.matches()) { + name = Type.getType(matcher.group(2)).getClassName(); + suffix = Strings.repeat("[]", matcher.group(1).length()); + } + return name + suffix; + } + + public static String getExpectedPackageName(Class clazz) { + if (!clazz.isArray()) { + return clazz.getPackage() != null ? clazz.getPackage().getName() : ""; + } + return getExpectedPackageName(clazz.getComponentType()); + } + + public class JavaTypeVariableOfClassAssertion extends AbstractObjectAssert> { + private JavaTypeVariableOfClassAssertion(JavaTypeVariable actual) { + super(actual, JavaTypeVariableOfClassAssertion.class); + } + + public JavaTypeAssertion withBoundsMatching(Class... bounds) { + assertThatTypeVariable(actual).hasBoundsMatching(bounds); + return JavaTypeAssertion.this; + } + + public JavaTypeAssertion withBoundsMatching(ExpectedConcreteType... bounds) { + assertThatTypeVariable(actual).hasBoundsMatching(bounds); + return JavaTypeAssertion.this; + } + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java new file mode 100644 index 0000000000..820227e0c4 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java @@ -0,0 +1,335 @@ +package com.tngtech.archunit.testutil.assertion; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +import com.google.common.base.Joiner; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.tngtech.archunit.core.domain.JavaGenericArrayType; +import com.tngtech.archunit.core.domain.JavaParameterizedType; +import com.tngtech.archunit.core.domain.JavaType; +import com.tngtech.archunit.core.domain.JavaTypeVariable; +import com.tngtech.archunit.core.domain.JavaWildcardType; +import org.assertj.core.api.AbstractObjectAssert; +import org.junit.Assert; + +import static com.google.common.collect.Iterables.cycle; +import static com.google.common.collect.Iterables.limit; +import static com.tngtech.archunit.core.domain.Formatters.ensureSimpleName; +import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion.ExpectedConcreteWildcardType.wildcardType; +import static java.util.Collections.emptyList; + +public class JavaTypeVariableAssertion extends AbstractObjectAssert> { + public JavaTypeVariableAssertion(JavaTypeVariable actual) { + super(actual, JavaTypeVariableAssertion.class); + } + + public void hasBoundsMatching(Class... bounds) { + hasBoundsMatching(ExpectedConcreteParameterizedType.wrap(bounds)); + } + + public void hasBoundsMatching(ExpectedConcreteType... bounds) { + DescriptionContext context = new DescriptionContext(actual.getName()).step("bounds").describeUpperBounds(); + assertConcreteTypesMatch(context, actual.getBounds(), ImmutableList.copyOf(bounds)); + } + + private static void assertConcreteTypesMatch(DescriptionContext context, List actual, List expected) { + assertThat(actual).as(context.describeElements(actual.size()).toString()).hasSize(expected.size()); + for (int i = 0; i < actual.size(); i++) { + DescriptionContext elementContext = context.describeElement(i, actual.size()); + expected.get(i).assertMatchWith(actual.get(i), elementContext); + } + } + + public static ExpectedConcreteParameterizedType parameterizedType(Class expectedType) { + return new ExpectedConcreteParameterizedType(expectedType); + } + + public interface ExpectedConcreteType { + void assertMatchWith(JavaType actual, DescriptionContext context); + } + + public static class ExpectedConcreteParameterizedType implements ExpectedConcreteType { + private Type type; + private final List typeParameters = new ArrayList<>(); + + private ExpectedConcreteParameterizedType(Type type) { + this.type = type; + } + + public ExpectedConcreteType withTypeArguments(Type... type) { + return withTypeArguments(ExpectedConcreteParameterizedType.wrap(type)); + } + + public ExpectedConcreteType withTypeArguments(ExpectedConcreteType... type) { + typeParameters.addAll(ImmutableList.copyOf(type)); + return this; + } + + public ExpectedConcreteType withWildcardTypeParameter() { + return withTypeArguments(new ExpectedConcreteWildcardType()); + } + + public ExpectedConcreteType withWildcardTypeParameterWithUpperBound(Class bound) { + return withWildcardTypeParameterWithUpperBound(new ExpectedConcreteParameterizedType(bound)); + } + + public ExpectedConcreteType withWildcardTypeParameterWithUpperBound(ExpectedConcreteType bound) { + return withTypeArguments(wildcardType().withUpperBound(bound)); + } + + public ExpectedConcreteType withWildcardTypeParameterWithLowerBound(Class bound) { + return withWildcardTypeParameterWithLowerBound(new ExpectedConcreteParameterizedType(bound)); + } + + public ExpectedConcreteType withWildcardTypeParameterWithLowerBound(ExpectedConcreteType bound) { + return withTypeArguments(wildcardType().withLowerBound(bound)); + } + + public ExpectedConcreteType withWildcardTypeParameters(ExpectedConcreteWildcardType... wildcardTypes) { + return withTypeArguments(wildcardTypes); + } + + @Override + public void assertMatchWith(JavaType actual, DescriptionContext context) { + DescriptionContext newContext = context.describe(ensureSimpleName(actual.getName())); + assertThatType(actual).as(newContext.toString()).matches(type); + assertTypeParametersMatch(actual, newContext); + } + + private void assertTypeParametersMatch(JavaType actual, DescriptionContext context) { + DescriptionContext parameterContext = context.step("type parameters").describeTypeParameters(); + if (!typeParameters.isEmpty() && !(actual instanceof JavaParameterizedType)) { + Assert.fail(String.format("%s: Not parameterized, but expected to have type parameters %s", parameterContext, typeParameters)); + } + List actualTypeParameters = ((JavaParameterizedType) actual).getActualTypeArguments(); + assertConcreteTypesMatch(parameterContext, actualTypeParameters, typeParameters); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{" + type + formatTypeParameters() + '}'; + } + + private String formatTypeParameters() { + return !typeParameters.isEmpty() ? "<" + Joiner.on(", ").join(typeParameters) + ">" : ""; + } + + static ExpectedConcreteType[] wrap(Type... types) { + ExpectedConcreteType[] result = new ExpectedConcreteType[types.length]; + for (int i = 0; i < types.length; i++) { + result[i] = new ExpectedConcreteParameterizedType(types[i]); + } + return result; + } + } + + public static class ExpectedConcreteWildcardType implements ExpectedConcreteType { + private final List upperBounds = new ArrayList<>(); + private final List lowerBounds = new ArrayList<>(); + + private ExpectedConcreteWildcardType() { + } + + @Override + public void assertMatchWith(JavaType actual, DescriptionContext context) { + context = context.describe(actual.getName()); + + assertThat(actual).as(context.toString()).isInstanceOf(JavaWildcardType.class); + JavaWildcardType wildcardType = (JavaWildcardType) actual; + assertThat(wildcardType.getName()).as(context.toString()).isEqualTo("?"); + + assertUpperBoundsMatch(wildcardType, context); + assertLowerBoundMatch(wildcardType, context); + } + + private void assertUpperBoundsMatch(JavaWildcardType actual, DescriptionContext context) { + context = context.step("upper bounds"); + assertThat(actual.getUpperBounds()).as(context.toString()).hasSameSizeAs(upperBounds); + context = context.describeUpperBounds(); + assertBoundsMatch(actual.getUpperBounds(), upperBounds, context); + } + + private void assertLowerBoundMatch(JavaWildcardType actual, DescriptionContext context) { + context = context.step("lower bounds"); + assertThat(actual.getLowerBounds()).as(context.toString()).hasSameSizeAs(lowerBounds); + context = context.describeLowerBounds(); + assertBoundsMatch(actual.getLowerBounds(), lowerBounds, context); + } + + private void assertBoundsMatch(List actualBounds, List expectedBounds, DescriptionContext context) { + for (int i = 0; i < expectedBounds.size(); i++) { + expectedBounds.get(i).assertMatchWith(actualBounds.get(i), context); + } + } + + public ExpectedConcreteWildcardType withUpperBound(Class bound) { + return withUpperBound(new ExpectedConcreteParameterizedType(bound)); + } + + public ExpectedConcreteWildcardType withUpperBound(ExpectedConcreteType bound) { + upperBounds.add(bound); + return this; + } + + public ExpectedConcreteWildcardType withLowerBound(Class bound) { + return withLowerBound(new ExpectedConcreteParameterizedType(bound)); + } + + public ExpectedConcreteWildcardType withLowerBound(ExpectedConcreteType bound) { + lowerBounds.add(bound); + return this; + } + + public static ExpectedConcreteWildcardType wildcardType() { + return new ExpectedConcreteWildcardType(); + } + } + + public static class ExpectedConcreteTypeVariable implements ExpectedConcreteType { + private final String name; + private List upperBounds; + + private ExpectedConcreteTypeVariable(String name) { + this.name = name; + } + + public ExpectedConcreteTypeVariable withUpperBounds(Class... bounds) { + return withUpperBounds(ExpectedConcreteParameterizedType.wrap(bounds)); + } + + public ExpectedConcreteTypeVariable withUpperBounds(ExpectedConcreteType... bounds) { + upperBounds = ImmutableList.copyOf(bounds); + return this; + } + + public ExpectedConcreteTypeVariable withoutUpperBounds() { + upperBounds = emptyList(); + return this; + } + + @Override + public void assertMatchWith(JavaType actual, DescriptionContext context) { + assertThat(actual).as(context.step("JavaType").toString()).isInstanceOf(JavaTypeVariable.class); + JavaTypeVariable actualTypeVariable = (JavaTypeVariable) actual; + assertThat(actualTypeVariable.getName()).as(context.step("type variable name").toString()).isEqualTo(name); + + if (upperBounds != null) { + DescriptionContext newContext = context.describe(actual.getName()).step("bounds").metaInfo().describeUpperBounds(); + assertConcreteTypesMatch(newContext, actualTypeVariable.getUpperBounds(), upperBounds); + } + } + + public static ExpectedConcreteTypeVariable typeVariable(String name) { + return new ExpectedConcreteTypeVariable(name); + } + } + + public static class ExpectedConcreteTypeVariableArray implements ExpectedConcreteType { + private final String name; + private ExpectedConcreteType componentType; + + private ExpectedConcreteTypeVariableArray(String name) { + this.name = name; + } + + public ExpectedConcreteTypeVariableArray withComponentType(ExpectedConcreteType componentType) { + this.componentType = componentType; + return this; + } + + @Override + public void assertMatchWith(JavaType actual, DescriptionContext context) { + assertThat(actual).as(context.step("JavaType").toString()).isInstanceOf(JavaGenericArrayType.class); + JavaGenericArrayType actualArrayType = (JavaGenericArrayType) actual; + assertThat(actualArrayType.getName()).as(context.step("type variable name").toString()).isEqualTo(name); + + if (componentType != null) { + DescriptionContext newContext = context.describe(actual.getName()).step("component type").metaInfo(); + componentType.assertMatchWith(actualArrayType.getComponentType(), newContext); + } + } + + public static ExpectedConcreteTypeVariableArray typeVariableArray(String typeVariableArrayString) { + return new ExpectedConcreteTypeVariableArray(typeVariableArrayString); + } + } + + private static class DescriptionContext { + private static final String MARKER = "##MARKER##"; + private static final String PLACEHOLDER = "_"; + + private final String context; + private final String description; + private final String currentElement; + private final String joinString; + + DescriptionContext(String context) { + this(context + MARKER, "assertion", "", ", "); + } + + private DescriptionContext(String context, String description, String currentElement, String joinString) { + this.context = context; + this.description = description; + this.currentElement = currentElement; + this.joinString = joinString; + } + + public DescriptionContext describe(String part) { + return new DescriptionContext(context.replace(MARKER, part + MARKER), description, part, joinString); + } + + public DescriptionContext describeUpperBounds() { + String newContext = context.replace(MARKER, " extends " + MARKER); + return new DescriptionContext(newContext, description, currentElement, " & "); + } + + public DescriptionContext describeLowerBounds() { + String newContext = context.replace(MARKER, " super " + MARKER); + return new DescriptionContext(newContext, description, currentElement, " & "); + } + + public DescriptionContext describeElements(int number) { + String elementsPlaceHolder = number > 0 ? joinedPlaceHolders(number) : "[]"; + return new DescriptionContext(context.replace(MARKER, elementsPlaceHolder), description, currentElement, joinString); + } + + public DescriptionContext describeElement(int index, int totalSize) { + int maxIndex = totalSize - 1; + String prefix = index > 0 ? joinedPlaceHolders(index) + joinString : ""; + String suffix = index < maxIndex ? joinString + joinedPlaceHolders(maxIndex - index) : ""; + String newContext = context.replace(MARKER, prefix + MARKER + suffix); + String newCurrentElement = this.currentElement + "[" + index + "]"; + return new DescriptionContext(newContext, description, newCurrentElement, joinString); + } + + private String joinedPlaceHolders(int number) { + return FluentIterable.from(limit(cycle(PLACEHOLDER), number)).join(Joiner.on(joinString)); + } + + public DescriptionContext step(String description) { + return new DescriptionContext(context, description, currentElement, joinString); + } + + public DescriptionContext metaInfo() { + String newContext = context.replace(MARKER, "{" + MARKER + "}"); + return new DescriptionContext(newContext, description, currentElement, joinString); + } + + public DescriptionContext describeTypeParameters() { + String newContext = context.replace(MARKER, "<" + MARKER + ">"); + String newJoinString = ", "; + return new DescriptionContext(newContext, description, currentElement, newJoinString); + } + + @Override + public String toString() { + String currentElementInfix = currentElement.isEmpty() ? "" : "[" + currentElement + "]"; + return "\"" + description + "\"" + currentElementInfix + " -> " + context.replace(MARKER, ""); + } + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypesAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypesAssertion.java new file mode 100644 index 0000000000..c0c108eb83 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypesAssertion.java @@ -0,0 +1,89 @@ +package com.tngtech.archunit.testutil.assertion; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaType; +import com.tngtech.archunit.testutil.TestUtils; +import org.assertj.core.api.AbstractObjectAssert; + +import static com.google.common.collect.Iterables.toArray; +import static com.tngtech.archunit.core.domain.JavaClass.namesOf; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.TestUtils.sortByName; +import static java.util.Arrays.sort; +import static org.assertj.core.api.Assertions.assertThat; + +public class JavaTypesAssertion extends AbstractObjectAssert { + public JavaTypesAssertion(JavaType[] actual) { + super(actual, JavaTypesAssertion.class); + } + + public JavaTypesAssertion(Iterable actual) { + super(toArray(actual, JavaType.class), JavaTypesAssertion.class); + } + + public void matchInAnyOrder(Iterable> classes) { + assertThat(TestUtils.namesOf(actual)).as("classes").containsOnlyElementsOf(namesOf(classes)); + + JavaType[] actualSorted = sortedJavaTypes(actual); + Class[] expectedSorted = sortedClasses(classes); + for (int i = 0; i < actualSorted.length; i++) { + assertThatType(actualSorted[i]).as("Element %d", i).matches(expectedSorted[i]); + } + } + + public void matchInAnyOrder(Class... classes) { + matchInAnyOrder(ImmutableSet.copyOf(classes)); + } + + public void matchExactly(Class... classes) { + assertThat(TestUtils.namesOf(actual)).as("classes").containsExactlyElementsOf(namesOf(classes)); + matchInAnyOrder(classes); + } + + public JavaTypesAssertion contain(Class... classes) { + contain(ImmutableSet.copyOf(classes)); + return this; + } + + public void doNotContain(Class... classes) { + assertThat(actualNames()).doesNotContainAnyElementsOf(JavaClass.namesOf(classes)); + } + + public void contain(Iterable> classes) { + List expectedNames = JavaClass.namesOf(Lists.newArrayList(classes)); + assertThat(actualNames()).as("actual classes").containsAll(expectedNames); + } + + private Set actualNames() { + Set actualNames = new HashSet<>(); + for (JavaType javaClass : actual) { + actualNames.add(javaClass.getName()); + } + return actualNames; + } + + private JavaType[] sortedJavaTypes(JavaType[] javaTypes) { + JavaType[] result = Arrays.copyOf(javaTypes, javaTypes.length); + sortByName(result); + return result; + } + + private Class[] sortedClasses(Iterable> classes) { + Class[] sorted = toArray(classes, Class.class); + sort(sorted, new Comparator>() { + @Override + public int compare(Class o1, Class o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + return sorted; + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodCallChain.java b/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodCallChain.java index 5a288da40a..1976530c6a 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodCallChain.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodCallChain.java @@ -18,7 +18,7 @@ class MethodCallChain { MethodCallChain(MethodChoiceStrategy methodChoiceStrategy, TypedValue typedValue) { this.methodChoiceStrategy = checkNotNull(methodChoiceStrategy); currentValue = checkNotNull(typedValue); - nextMethodCandidate = methodChoiceStrategy.choose(typedValue.getType()); + nextMethodCandidate = methodChoiceStrategy.choose(typedValue.getType(), false); } TypedValue getCurrentValue() { @@ -33,11 +33,11 @@ boolean hasAnotherMethodCandidate() { return nextMethodCandidate.isPresent(); } - void invokeNextMethodCandidate(Parameters parameters) { + void invokeNextMethodCandidate(Parameters parameters, boolean tryToTerminate) { PropagatedType nextType = currentValue.resolveType(nextMethodCandidate.get().getGenericReturnType()); Object nextValue = invoke(nextMethodCandidate.get(), currentValue.getValue(), parameters.getValues()); currentValue = validate(new TypedValue(nextType, nextValue)); - nextMethodCandidate = methodChoiceStrategy.choose(currentValue.getType()); + nextMethodCandidate = methodChoiceStrategy.choose(currentValue.getType(), tryToTerminate); } private TypedValue validate(TypedValue value) { diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodCallChainTest.java b/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodCallChainTest.java index f3a63bac56..f60e22f7df 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodCallChainTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodCallChainTest.java @@ -115,7 +115,7 @@ private MethodCallChain createCallChainStart(Class startInterface, T star } private void invokeNext(MethodCallChain callChain) { - callChain.invokeNextMethodCandidate(new Parameters(Collections.emptyList())); + callChain.invokeNextMethodCandidate(new Parameters(Collections.emptyList()), false); } private static class CallChainTestCase { diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodChoiceStrategy.java b/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodChoiceStrategy.java index 6e7cee59a3..655258ca47 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodChoiceStrategy.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodChoiceStrategy.java @@ -48,11 +48,24 @@ public boolean apply(Method input) { }; } - Optional choose(PropagatedType type) { + Optional choose(PropagatedType type, boolean tryToTerminate) { List methods = getPossibleMethodCandidates(type.getRawType()); - return !methods.isEmpty() - ? Optional.of(methods.get(random.nextInt(methods.size()))) - : Optional.absent(); + if (methods.isEmpty()) { + return Optional.absent(); + } + + return tryToTerminate + ? findMethodWithReturnType(methods, ArchRule.class).or(Optional.of(methods.iterator().next())) + : Optional.of(methods.get(random.nextInt(methods.size()))); + } + + private Optional findMethodWithReturnType(List methods, Class returnType) { + for (Method method : methods) { + if (returnType.isAssignableFrom(method.getReturnType())) { + return Optional.of(method); + } + } + return Optional.absent(); } private List getPossibleMethodCandidates(Class clazz) { diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/RandomSyntaxTestBase.java b/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/RandomSyntaxTestBase.java index ba0e658848..8cd348bc72 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/RandomSyntaxTestBase.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/RandomSyntaxTestBase.java @@ -217,10 +217,12 @@ LastStep continueSteps(int currentStepCount, int maxSteps) { throw new IllegalStateException("Creating rule was not finished within " + maxSteps + " steps"); } - methodCallChain.invokeNextMethodCandidate(parameters); + int stepsLeft = maxSteps - currentStepCount; + boolean lowNumberOfStepsLeft = stepsLeft <= LOW_NUMBER_OF_LEFT_STEPS; + methodCallChain.invokeNextMethodCandidate(parameters, lowNumberOfStepsLeft); boolean shouldContinue = methodCallChain.hasAnotherMethodCandidate() - && shouldContinue(methodCallChain.getCurrentValue(), maxSteps - currentStepCount); + && shouldContinue(methodCallChain.getCurrentValue(), lowNumberOfStepsLeft); Step nextStep = shouldContinue ? new PartialStep(expectedDescription, methodCallChain) : new LastStep(expectedDescription, methodCallChain); @@ -229,11 +231,11 @@ LastStep continueSteps(int currentStepCount, int maxSteps) { return nextStep.continueSteps(currentStepCount + 1, maxSteps); } - private boolean shouldContinue(TypedValue nextValue, int stepsLeft) { + private boolean shouldContinue(TypedValue nextValue, boolean lowNumberOfStepsLeft) { if (!ArchRule.class.isAssignableFrom(nextValue.getRawType())) { return true; } - return random.nextBoolean() && stepsLeft > LOW_NUMBER_OF_LEFT_STEPS; + return random.nextBoolean() && !lowNumberOfStepsLeft; } public String getDescription() { @@ -471,7 +473,7 @@ private boolean firstParameterTypeMatches(List> parameterTypes) { } } - private abstract class CallCodeUnitParametersProvider extends SpecificParametersProvider { + private abstract static class CallCodeUnitParametersProvider extends SpecificParametersProvider { private final CanHandlePredicate predicate; CallCodeUnitParametersProvider(CanHandlePredicate predicate) { diff --git a/build-steps/build-steps.gradle b/build-steps/build-steps.gradle index 32487608fd..5af27d7796 100644 --- a/build-steps/build-steps.gradle +++ b/build-steps/build-steps.gradle @@ -1,11 +1,12 @@ def utilsPath = { "build-steps/${it}" } +apply from: utilsPath('testing/testing.gradle') apply from: utilsPath('archiving/archiving.gradle') -apply from: utilsPath('codequality/spotbugs.gradle') +apply from: utilsPath('codequality/codequality.gradle') apply from: utilsPath('release/publish.gradle') apply from: utilsPath('license/license.gradle') apply from: utilsPath('maven-integration-test/maven-integration-test.gradle') apply from: utilsPath('ci/ci-config.gradle') apply from: utilsPath('release/check-uploaded-artifacts.gradle') apply from: utilsPath('release/create-release-news.gradle') -apply from: utilsPath('release/release.gradle') \ No newline at end of file +apply from: utilsPath('release/release.gradle') diff --git a/build-steps/codequality/spotbugs.gradle b/build-steps/codequality/codequality.gradle similarity index 71% rename from build-steps/codequality/spotbugs.gradle rename to build-steps/codequality/codequality.gradle index e90b078877..7fb7964ac4 100644 --- a/build-steps/codequality/spotbugs.gradle +++ b/build-steps/codequality/codequality.gradle @@ -18,3 +18,13 @@ productionProjects*.with { spotbugsTest.enabled = false } + +productionProjects*.with { + apply plugin: 'com.diffplug.spotless' + + spotless { + java { + removeUnusedImports() + } + } +} diff --git a/build-steps/maven-integration-test/maven-integration-test.gradle b/build-steps/maven-integration-test/maven-integration-test.gradle index 01c3e8e2cb..e664e75d7f 100644 --- a/build-steps/maven-integration-test/maven-integration-test.gradle +++ b/build-steps/maven-integration-test/maven-integration-test.gradle @@ -190,23 +190,9 @@ def addMavenTest = { config -> } } -def javaConfigs = [ - [suffix: "java7", javaVersion: JavaVersion.VERSION_1_7, jdkProp: "java7Home"], - [suffix: "java8", javaVersion: JavaVersion.VERSION_1_8, jdkProp: "java8Home"], - [suffix: "java9", javaVersion: JavaVersion.VERSION_1_9, jdkProp: "java9Home"], - [suffix: "java10", javaVersion: JavaVersion.VERSION_1_10, jdkProp: "java10Home"], - [suffix: "java11", javaVersion: JavaVersion.VERSION_11, jdkProp: "java11Home"], - [suffix: "java12", javaVersion: JavaVersion.VERSION_12, jdkProp: "java12Home"], - [suffix: "java13", javaVersion: JavaVersion.VERSION_13, jdkProp: "java13Home"], - [suffix: "java14", javaVersion: JavaVersion.VERSION_14, jdkProp: "java14Home"] -] - -javaConfigs = javaConfigs.findAll { project.hasProperty(it.jdkProp) } - .collect { config -> config + [jdkPath: project[config.jdkProp]] } - -javaConfigs = javaConfigs ?: [[suffix: 'java7', javaVersion: JavaVersion.VERSION_1_7]] +def integrationTestJdks = testJdks() ?: [[suffix: 'java7', javaVersion: JavaVersion.VERSION_1_7]] -javaConfigs = javaConfigs.collect { config -> +integrationTestJdks = integrationTestJdks.collect { config -> // JUnit 5 needs at least Java 8 def testSupportTypes = config.javaVersion > JavaVersion.VERSION_1_7 ? ['plain', 'junit4', 'junit5'] : @@ -216,17 +202,17 @@ javaConfigs = javaConfigs.collect { config -> } }.flatten() -javaConfigs.findAll { it.suffix.endsWith('plain') }*.with { +integrationTestJdks.findAll { it.suffix.endsWith('plain') }*.with { surefireExampleConfiguration = 'com.tngtech.archunit.exampletest.Example' archunitTestArtifact = 'archunit' } -javaConfigs.findAll { it.suffix.endsWith('junit4') }*.with { +integrationTestJdks.findAll { it.suffix.endsWith('junit4') }*.with { surefireExampleConfiguration = 'com.tngtech.archunit.exampletest.junit4.Example' archunitTestArtifact = 'archunit-junit4' } -javaConfigs.findAll { it.suffix.endsWith('junit5') }*.with { +integrationTestJdks.findAll { it.suffix.endsWith('junit5') }*.with { surefireExampleConfiguration = 'example' archunitTestArtifact = 'archunit-junit5' additionalDependencies = """ @@ -238,11 +224,11 @@ javaConfigs.findAll { it.suffix.endsWith('junit5') }*.with { """ } -javaConfigs.each { config -> +integrationTestJdks.each { config -> project.with(addMavenTest(config)) } -def suffixes = javaConfigs*.suffix.sort() +def suffixes = integrationTestJdks*.suffix.sort() [suffixes, suffixes.tail()].transpose().each { twoConsecutiveSuffixes -> tasks["prepareMavenTest${twoConsecutiveSuffixes[1]}"].mustRunAfter(tasks["cleanUpMavenTest${twoConsecutiveSuffixes[0]}"]) } diff --git a/build-steps/testing/testing.gradle b/build-steps/testing/testing.gradle new file mode 100644 index 0000000000..52484a4ae2 --- /dev/null +++ b/build-steps/testing/testing.gradle @@ -0,0 +1,171 @@ +def unquote = { string -> string.replaceAll(/^"(.*)"$/, '$1')} +def testJdksDefinition = [ + [suffix: "Jre7", javaVersion: JavaVersion.VERSION_1_7, jdkProp: "java7Home"], + [suffix: "Jre8", javaVersion: JavaVersion.VERSION_1_8, jdkProp: "java8Home"], + [suffix: "Jre9", javaVersion: JavaVersion.VERSION_1_9, jdkProp: "java9Home"], + [suffix: "Jre10", javaVersion: JavaVersion.VERSION_1_10, jdkProp: "java10Home"], + [suffix: "Jre11", javaVersion: JavaVersion.VERSION_11, jdkProp: "java11Home"], + [suffix: "Jre12", javaVersion: JavaVersion.VERSION_12, jdkProp: "java12Home"], + [suffix: "Jre13", javaVersion: JavaVersion.VERSION_13, jdkProp: "java13Home"], + [suffix: "Jre14", javaVersion: JavaVersion.VERSION_14, jdkProp: "java14Home"] +] + .findAll { project.hasProperty(it.jdkProp) } + .collect { config -> config + [jdkPath: unquote(project[config.jdkProp])] } + +ext { + testJdks = { + testJdksDefinition.collect { [:] + it } + } + + addTestJarTo = { proj -> + proj.with { + configurations { + tests.extendsFrom testRuntime + } + + task testJar(type: Jar) { + archiveClassifier = 'tests' + from sourceSets.test.output + } + + artifacts { + tests testJar + } + } + } + + configureSlowTestsFor = { proj -> + proj.afterEvaluate { projAfterEvaluate -> + projAfterEvaluate.tasks.withType(Test) { + if (!project.hasProperty('allTests')) { + useJUnit { + excludeCategories 'com.tngtech.archunit.Slow' + } + } + } + } + } + + addMultiJdkTestsFor = { Project proj, Test testTask -> + if (!project.hasProperty('multiJdkTest')) { + return + } + + assert testTask.name == 'test' || testTask.name ==~ /jdk\d+Test/: "Task must either be named 'test' or match 'jdk{x}Test'" + testTask.enabled = false + + def taskMinimumJdkVersion = testTask.name.replaceAll(/jdk(\d+)Test/, '$1').with { it ==~ /\d+/ ? JavaVersion.toVersion(it) : proj.targetCompatibility } + + def findJavaExecutable = { jdkPath -> + def javaExecutableUnix = new File("${jdkPath}", 'bin/java') + def javaExecutableWindows = new File("${javaExecutableUnix.absolutePath}.exe") + def result = javaExecutableUnix.exists() ? javaExecutableUnix : javaExecutableWindows + assert result.exists(): "Could not find path of Java executable for JDK path ${jdkPath}. Tried ${javaExecutableUnix} and ${javaExecutableWindows}." + result + } + + testJdks().each { jdk -> + def additionalTestTask = proj.tasks.create(name: "${testTask.name}${jdk.suffix}", type: Test) { + executable = findJavaExecutable(jdk.jdkPath) + + testClassesDirs = testTask.testClassesDirs + classpath = testTask.classpath + } + additionalTestTask.enabled = jdk.javaVersion >= taskMinimumJdkVersion + + testTask.dependsOn(additionalTestTask) + } + } +} + +configure(subprojects.findAll { it.name != 'docs' }) { + afterEvaluate { project -> + project.tasks.withType(Test) { + maxHeapSize = "2G" + + testLogging { + events "failed" + exceptionFormat "full" + } + + ignoreFailures = project.hasProperty('ignoreTestFailures') + } + } +} + +// Add some better test failure reporting on the console + +List testReports = [] +configure(subprojects.findAll { it.name != 'docs' }) { + TestFailureReport testReport = new TestFailureReport(project: name) + testReports << testReport + + afterEvaluate { project -> + project.tasks.withType(Test) { + afterTest { TestDescriptor descriptor, TestResult result -> + if (result.resultType == TestResult.ResultType.FAILURE) { + testReport.addFailure(new TestFailure(descriptor, result.exception)) + } + } + } + } +} + +gradle.buildFinished { + testReports.each { it.printFailures() } +} + +class TestFailureReport { + String project + private Map reports = [:].withDefault { testClassName -> new TestClassFailureReport(className: testClassName) } + + void addFailure(TestFailure testFailure) { + reports[testFailure.className].testFailures << testFailure + } + + void printFailures() { + def failureReports = reports.values().findAll { !it.testFailures.empty } + if (failureReports.empty) { + return + } + + String redX = "\033[1;31m\u2718 \033[0m" + String heading = "Failed tests for ${project}" + int headingBoxWidth = heading.length() + 4 + println """ + ${'-' * headingBoxWidth} + | ${heading} | + ${'-' * headingBoxWidth} + """.stripIndent() + failureReports.each { TestClassFailureReport failureReport -> + println "- ${failureReport.className} --> ${failureReport.location}" + failureReport.testFailures.each { TestFailure failure -> + println " ${redX}.${failure.testDescription} --> ${failure.location}" + } + } + println "${'-' * headingBoxWidth}" + } +} + +class TestClassFailureReport { + String className + List testFailures = [] + + String getLocation() { + return "(${className.replaceAll(/.*\./, '')}.java:0)" + } +} + +class TestFailure { + String className + String testDescription + String location = 'Unknown' + + TestFailure(TestDescriptor descriptor, Throwable failure) { + className = descriptor.className + testDescription = descriptor.displayName + failure?.stackTrace?.find { it.className == className }?.each { + location = "(${it.fileName}:${it.lineNumber})" + } + } +} diff --git a/build.gradle b/build.gradle index cdcda0e8c9..2230386219 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,8 @@ plugins { - id 'com.github.johnrengelman.shadow' version '5.2.0' apply false - id 'com.github.spotbugs' version '4.0.5' apply false + id 'com.github.johnrengelman.shadow' version '6.1.0' apply false + id 'com.github.spotbugs' version '4.6.0' apply false id "de.marcphilipp.nexus-publish" version "0.4.0" apply false + id "com.diffplug.spotless" version "5.8.2" apply false } def appAndSourceUrl = 'https://github.com/TNG/ArchUnit' @@ -32,7 +33,7 @@ ext { googleRelocationPackage = "${thirdPartyRelocationPackage}.com.google" dependency = [ - asm : [group: 'org.ow2.asm', name: 'asm', version: '8.0.1'], + asm : [group: 'org.ow2.asm', name: 'asm', version: '9.0'], guava : [group: 'com.google.guava', name: 'guava', version: '20.0'], slf4j : [group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25'], log4j_api : [group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1'], @@ -89,44 +90,21 @@ ext { project(':archunit-junit5')] createModuleDescription = { description, proj -> "${description} - Module '${proj.name}'" } - addTestJarTo = { proj -> - proj.with { - configurations { - tests.extendsFrom testRuntime - } - - task testJar(type: Jar) { - archiveClassifier = 'tests' - from sourceSets.test.output - } - - artifacts { - tests testJar - } - } - } - - configureSlowTestsFor = { proj -> - proj.with { - test { - if (!project.hasProperty('allTests')) { - useJUnit { - excludeCategories 'com.tngtech.archunit.Slow' - } - } - } - } - } - currentScriptRootOf = { it.buildscript.sourceFile.parentFile } } allprojects { group = 'com.tngtech.archunit' - version = '0.14.1' + version = '0.15.0' repositories { - mavenCentral() + mavenCentral { + metadataSources { + mavenPom() + artifact() + ignoreGradleMetadataRedirection() + } + } } } @@ -142,7 +120,7 @@ task clean { } } -configure(subprojects.findAll {it.name != 'docs'}) { +configure(subprojects.findAll { it.name != 'docs' }) { apply plugin: 'java-library' description createModuleDescription(rootProject.description, project) @@ -153,17 +131,6 @@ configure(subprojects.findAll {it.name != 'docs'}) { javadoc { options.addBooleanOption('html5', true) } - - test { - maxHeapSize = "2G" - - testLogging { - events "failed" - exceptionFormat "full" - } - - ignoreFailures = project.hasProperty('ignoreTestFailures') - } } apply from: 'build-steps/build-steps.gradle' diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index edc439cc16..f98c4f90ea 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -1,92 +1,98 @@ GEM remote: https://rubygems.org/ specs: - activesupport (5.2.0) + activesupport (6.0.3.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) + zeitwerk (~> 2.2, >= 2.2.2) addressable (2.5.2) public_suffix (>= 2.0.2, < 4.0) colorator (1.1.0) - concurrent-ruby (1.0.5) - em-websocket (0.5.1) + concurrent-ruby (1.1.7) + em-websocket (0.5.2) eventmachine (>= 0.12.9) http_parser.rb (~> 0.6.0) eventmachine (1.2.7) faraday (0.15.2) multipart-post (>= 1.2, < 3) - ffi (1.9.25) + ffi (1.13.1) forwardable-extended (2.6.0) - gemoji (3.0.0) - html-pipeline (2.8.0) + gemoji (3.0.1) + html-pipeline (2.12.3) activesupport (>= 2) nokogiri (>= 1.4) http_parser.rb (0.6.0) - i18n (0.9.5) + i18n (1.8.5) concurrent-ruby (~> 1.0) - jekyll (3.8.4) + jekyll (4.2.0) addressable (~> 2.4) colorator (~> 1.0) em-websocket (~> 0.5) - i18n (~> 0.7) - jekyll-sass-converter (~> 1.0) + i18n (~> 1.0) + jekyll-sass-converter (~> 2.0) jekyll-watch (~> 2.0) - kramdown (~> 1.14) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.0) liquid (~> 4.0) - mercenary (~> 0.3.3) + mercenary (~> 0.4.0) pathutil (~> 0.9) - rouge (>= 1.7, < 4) + rouge (~> 3.0) safe_yaml (~> 1.0) - jekyll-feed (0.10.0) - jekyll (~> 3.3) + terminal-table (~> 2.0) + jekyll-feed (0.15.1) + jekyll (>= 3.7, < 5.0) jekyll-gist (1.5.0) octokit (~> 4.2) jekyll-paginate (1.1.0) - jekyll-sass-converter (1.5.2) - sass (~> 3.4) - jekyll-sitemap (1.2.0) - jekyll (~> 3.3) - jekyll-watch (2.0.0) + jekyll-sass-converter (2.1.0) + sassc (> 2.0.1, < 3.0) + jekyll-sitemap (1.4.0) + jekyll (>= 3.7, < 5.0) + jekyll-watch (2.2.1) listen (~> 3.0) - jemoji (0.10.0) + jemoji (0.12.0) gemoji (~> 3.0) html-pipeline (~> 2.2) - jekyll (~> 3.0) - kramdown (1.17.0) - liquid (4.0.0) - listen (3.1.5) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - ruby_dep (~> 1.2) - mercenary (0.3.6) + jekyll (>= 3.0, < 5.0) + kramdown (2.3.0) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (4.0.3) + listen (3.3.3) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + mercenary (0.4.0) mini_portile2 (2.4.0) - minitest (5.11.3) + minitest (5.13.0) multipart-post (2.0.0) - nokogiri (1.10.8) + nokogiri (1.10.9) mini_portile2 (~> 2.4.0) octokit (4.9.0) sawyer (~> 0.8.0, >= 0.5.3) - pathutil (0.16.1) + pathutil (0.16.2) forwardable-extended (~> 2.6) - public_suffix (3.0.2) - rb-fsevent (0.10.3) - rb-inotify (0.9.10) - ffi (>= 0.5.0, < 2) - rouge (3.1.1) - ruby_dep (1.5.0) - safe_yaml (1.0.4) - sass (3.5.6) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) + public_suffix (3.1.1) + rb-fsevent (0.10.4) + rb-inotify (0.10.1) + ffi (~> 1.0) + rexml (3.2.4) + rouge (3.26.0) + safe_yaml (1.0.5) + sassc (2.4.0) + ffi (~> 1.9) sawyer (0.8.1) addressable (>= 2.3.5, < 2.6) faraday (~> 0.8, < 1.0) + terminal-table (2.0.0) + unicode-display_width (~> 1.1, >= 1.1.1) thread_safe (0.3.6) - tzinfo (1.2.5) + tzinfo (1.2.7) thread_safe (~> 0.1) + unicode-display_width (1.7.0) + zeitwerk (2.3.0) PLATFORMS ruby @@ -100,4 +106,4 @@ DEPENDENCIES jemoji BUNDLED WITH - 1.17.3 + 2.1.4 diff --git a/docs/_data/navigation.yml b/docs/_data/navigation.yml index 2edb00aa2e..ad70c9b9fd 100644 --- a/docs/_data/navigation.yml +++ b/docs/_data/navigation.yml @@ -3,12 +3,14 @@ main: url: /getting-started - title: "Motivation" url: /motivation + - title: "Use Cases" + url: /use-cases - title: "News" url: /news - title: "User Guide" url: /userguide/html/000_Index.html - title: "API" - url: https://javadoc.io/doc/com.tngtech.archunit/archunit/0.14.1 + url: https://javadoc.io/doc/com.tngtech.archunit/archunit/0.15.0 - title: "About" url: /about diff --git a/docs/_pages/getting-started.md b/docs/_pages/getting-started.md index 66a5255602..8f90c26eed 100644 --- a/docs/_pages/getting-started.md +++ b/docs/_pages/getting-started.md @@ -15,7 +15,7 @@ ArchUnit can be obtained from Maven Central. com.tngtech.archunit archunit - 0.14.1 + 0.15.0 test ``` @@ -23,7 +23,7 @@ ArchUnit can be obtained from Maven Central. #### Gradle ```groovy dependencies { - testImplementation 'com.tngtech.archunit:archunit:0.14.1' + testImplementation 'com.tngtech.archunit:archunit:0.15.0' } ``` diff --git a/docs/_pages/home.md b/docs/_pages/home.md index 684ae00b08..f223d4ce13 100644 --- a/docs/_pages/home.md +++ b/docs/_pages/home.md @@ -19,6 +19,9 @@ importing all classes into a Java code structure. You can find examples for the [ArchUnit Examples](https://github.com/TNG/ArchUnit-Examples) and the sources on [GitHub](https://github.com/TNG/ArchUnit). +There also exists a port for .NET/C#, which you can find [here](https://archunitnet.readthedocs.io/en/latest/). + + ### News {% for i in (0..2) %} diff --git a/docs/_pages/use-cases.md b/docs/_pages/use-cases.md new file mode 100644 index 0000000000..68f827ad63 --- /dev/null +++ b/docs/_pages/use-cases.md @@ -0,0 +1,95 @@ +--- +title: Use Cases +layout: splash +permalink: /use-cases +--- + +## Package Dependency Checks + +![diagram](http://www.plantuml.com/plantuml/svg/~h736b696e706172616d20636f6d706f6e656e745374796c6520756d6c320a736b696e706172616d20636f6d706f6e656e74207b0a2020426f72646572436f6c6f722023677265790a20204261636b67726f756e64436f6c6f72202377686974650a7d0a0a5b736f757263655d202d2d5b23677265656e5d72696768742d2d3e20205b7461726765745d3a616c6c6f7765640a5b736f757263655d202d2d5b236372696d736f6e5d6c6566742d2d3e205b666f6f5d0a6e6f746520746f70206f6e206c696e6b20236372696d736f6e3a20666f7262696464656e) + +``` +noClasses().that().resideInAPackage("..source..") + .should().dependOnClassesThat().resideInAPackage("..foo..") +``` + +![diagram](http://www.plantuml.com/plantuml/svg/~h736b696e706172616d20636f6d706f6e656e745374796c6520756d6c320a736b696e706172616d20636f6d706f6e656e74207b0a2020426f72646572436f6c6f722023677265790a20204261636b67726f756e64436f6c6f72202377686974650a7d0a0a5b736f757263652e6f6e655d202d2d5b23677265656e5d72696768742d2d3e20205b736f757263652e74776f5d3a616c6c6f7765640a5b736f757263652e6f6e655d202d2d5b23677265656e5d2d2d3e205b666f6f5d3a616c6c6f7765640a5b736f757263652e74776f5d202d2d5b236372696d736f6e5d646f776e2d2d3e205b666f6f5d0a6e6f746520746f70206f6e206c696e6b20236372696d736f6e3a20666f7262696464656e0a5b736f757263652e616e795d202d2d5b236372696d736f6e5d646f776e2d2d3e205b666f6f5d0a6e6f746520746f70206f6e206c696e6b20236372696d736f6e3a20666f7262696464656e) + +``` +classes().that().resideInAPackage("..foo..") + .should().onlyHaveDependentClassesThat().resideInAnyPackage("..source.one..", "..foo..") +``` + + +## Class Dependency Checks + +![diagram](http://www.plantuml.com/plantuml/svg/~h736b696e706172616d20636f6d706f6e656e745374796c6520756d6c320a0a736b696e706172616d20636f6d706f6e656e74207b0a2020426f72646572436f6c6f722023677265790a20204261636b67726f756e64436f6c6f72202377686974650a7d0a0a736b696e706172616d20636c617373207b0a2020426f72646572436f6c6f722023677265790a20204261636b67726f756e64436f6c6f72202377686974650a7d0a0a636c617373204f746865720a636c61737320466f6f4261720a0a4f74686572202d3e20466f6f4261720a6e6f746520746f70206f6e206c696e6b20236372696d736f6e3a20666f7262696464656e0a0a426172202d2d3e20466f6f4261722023677265656e0a6e6f7465206c656674206f6e206c696e6b2023677265656e3a20616c6c6f776564) + +``` +classes().that().haveNameMatching(".*Bar") + .should().onlyHaveDependentClassesThat().haveSimpleName("Bar") +``` + + +## Class and Package Containment Checks + +![diagram](http://www.plantuml.com/plantuml/svg/~h736b696e706172616d20636f6d706f6e656e745374796c6520756d6c320a0a736b696e706172616d20636f6d706f6e656e74207b0a2020426f72646572436f6c6f722023677265790a20204261636b67726f756e64436f6c6f72202377686974650a7d0a0a736b696e706172616d20636c617373207b0a2020426f72646572436f6c6f722023677265790a20204261636b67726f756e64436f6c6f72202377686974650a7d0a0a7061636b61676520636f6d2e666f6f207b0a20202020636c61737320466f6f536572766963650a7d0a0a7061636b61676520636f6d2e77726f6e67207b0a20202020636c61737320466f6f436f6e74726f6c6c65720a7d0a0a6e6f746520227265736964657320696e2077726f6e67207061636b616765222061732057726f6e675061636b61676520236372696d736f6e0a466f6f436f6e74726f6c6c6572202e2e2057726f6e675061636b616765) + +``` +classes().that().haveSimpleNameStartingWith("Foo") + .should().resideInAPackage("com.foo") +``` + + +## Inheritance Checks + +![diagram](http://www.plantuml.com/plantuml/svg/~h736b696e706172616d20636f6d706f6e656e745374796c6520756d6c320a0a736b696e706172616d20636f6d706f6e656e74207b0a2020426f72646572436f6c6f722023677265790a20204261636b67726f756e64436f6c6f72202377686974650a7d0a0a736b696e706172616d20636c617373207b0a2020426f72646572436f6c6f722023677265790a20204261636b67726f756e64436f6c6f72202377686974650a7d0a0a636c61737320436f6e6e656374696f6e203c3c696e746572666163653e3e0a636c6173732048746d6c436f6e6e656374696f6e203c3c636f6e63726574653e3e0a636c61737320467470436f6e6e656374696f6e203c3c636f6e63726574653e3e0a636c617373205373685468696e67203c3c636f6e63726574653e3e0a0a48746d6c436f6e6e656374696f6e202d2d7c3e20436f6e6e656374696f6e2023677265656e0a467470436f6e6e656374696f6e202d2d7c3e20436f6e6e656374696f6e2023677265656e0a5373685468696e67202d2d7c3e20436f6e6e656374696f6e20236372696d736f6e0a0a6e6f7465207269676874206f6e206c696e6b20236372696d736f6e3a204861732077726f6e67206e616d65) + +``` +classes().that().implement(Connection.class) + .should().haveSimpleNameEndingWith("Connection") +``` + +![diagram](http://www.plantuml.com/plantuml/svg/~h736b696e706172616d20636f6d706f6e656e745374796c6520756d6c320a0a736b696e706172616d20636f6d706f6e656e74207b0a2020426f72646572436f6c6f722023677265790a20204261636b67726f756e64436f6c6f72202377686974650a7d0a0a736b696e706172616d20636c617373207b0a2020426f72646572436f6c6f722023677265790a20204261636b67726f756e64436f6c6f72202377686974650a7d0a0a7061636b61676520636f6d2e6d796170702e70657273697374656e6365207b0a20202020636c6173732056616c696450657273697374656e6365557365720a7d0a0a7061636b61676520636f6d2e6d796170702e736f6d657768657265656c7365207b0a20202020636c61737320496c6c6567616c50657273697374656e6365557365720a7d0a0a636c61737320456e746974794d616e616765720a0a56616c696450657273697374656e636555736572202d2d3e20456e746974794d616e616765722023677265656e0a496c6c6567616c50657273697374656e636555736572202d2d3e20456e746974794d616e6167657220236372696d736f6e0a0a6e6f7465207269676874206f6e206c696e6b20236372696d736f6e3a204163636573736f72207265736964657320696e2077726f6e67207061636b616765) + +``` +classes().that().areAssignableTo(EntityManager.class) + .should().onlyHaveDependentClassesThat().resideInAnyPackage("..persistence..") +``` + + +## Annotation Checks + +![diagram](http://www.plantuml.com/plantuml/svg/~h736b696e706172616d20636f6d706f6e656e745374796c6520756d6c320a0a736b696e706172616d20636f6d706f6e656e74207b0a2020426f72646572436f6c6f722023677265790a20204261636b67726f756e64436f6c6f72202377686974650a7d0a0a736b696e706172616d20636c617373207b0a2020426f72646572436f6c6f722023677265790a20204261636b67726f756e64436f6c6f72202377686974650a7d0a0a636c6173732056616c696450657273697374656e636555736572203c3c405472616e73616374696f6e616c3e3e0a636c61737320496c6c6567616c50657273697374656e636555736572203c3c6e6f74207472616e73616374696f6e616c3e3e0a0a636c61737320456e746974794d616e616765720a0a56616c696450657273697374656e636555736572202d2d3e20456e746974794d616e616765722023677265656e0a496c6c6567616c50657273697374656e636555736572202d2d3e20456e746974794d616e6167657220236372696d736f6e0a0a6e6f7465207269676874206f6e206c696e6b20236372696d736f6e3a204163636573736f72206973206e6f7420616e6e6f7461746564207769746820405472616e73616374696f6e616c) + +``` +classes().that().areAssignableTo(EntityManager.class) + .should().onlyHaveDependentClassesThat().areAnnotatedWith(Transactional.class) +``` + + +## Layer Checks + +![diagram](http://www.plantuml.com/plantuml/svg/~h736b696e706172616d20636f6d706f6e656e745374796c6520756d6c320a0a736b696e706172616d20636f6d706f6e656e74207b0a2020426f72646572436f6c6f722023677265790a20204261636b67726f756e64436f6c6f72202377686974650a7d0a0a736b696e706172616d20636c617373207b0a2020426f72646572436f6c6f722023677265790a20204261636b67726f756e64436f6c6f72202377686974650a7d0a0a7061636b61676520636f6d2e6d796170702e636f6e74726f6c6c6572207b0a20202020636c61737320536f6d65436f6e74726f6c6c65724f6e650a20202020636c61737320536f6d65436f6e74726f6c6c657254776f0a7d0a7061636b61676520636f6d2e6d796170702e73657276696365207b0a20202020636c61737320536f6d65536572766963654f6e650a20202020636c61737320536f6d655365727669636554776f0a7d0a7061636b61676520636f6d2e6d796170702e70657273697374656e6365207b0a20202020636c61737320536f6d6550657273697374656e63654d616e616765720a7d0a0a536f6d65436f6e74726f6c6c65724f6e65202d2d3e20536f6d65536572766963654f6e652023677265656e0a536f6d655365727669636554776f202d646f776e2d3e20536f6d6550657273697374656e63654d616e616765722023677265656e0a0a536f6d65436f6e74726f6c6c65724f6e65202d646f776e2d3e20536f6d6550657273697374656e63654d616e6167657220236372696d736f6e0a6e6f7465207269676874206f6e206c696e6b20236372696d736f6e3a20416363657373206279706173736573206c61796572730a0a536f6d655365727669636554776f202d75702d2d3e20536f6d65436f6e74726f6c6c657254776f20236372696d736f6e0a6e6f7465207269676874206f6e206c696e6b20236372696d736f6e3a2041636365737320676f657320616761696e7374206c61796572730a0a536f6d6550657273697374656e63654d616e61676572202d75702d2d3e20536f6d65536572766963654f6e6520236372696d736f6e0a6e6f7465207269676874206f6e206c696e6b20236372696d736f6e3a2041636365737320676f657320616761696e7374206c6179657273) + +``` +layeredArchitecture() + .layer("Controller").definedBy("..controller..") + .layer("Service").definedBy("..service..") + .layer("Persistence").definedBy("..persistence..") + + .whereLayer("Controller").mayNotBeAccessedByAnyLayer() + .whereLayer("Service").mayOnlyBeAccessedByLayers("Controller") + .whereLayer("Persistence").mayOnlyBeAccessedByLayers("Service") +``` + + +## Cycle Checks + +![diagram](http://www.plantuml.com/plantuml/svg/~h736b696e706172616d20636f6d706f6e656e745374796c6520756d6c320a0a736b696e706172616d20636f6d706f6e656e74207b0a2020426f72646572436f6c6f722023677265790a20204261636b67726f756e64436f6c6f72202377686974650a7d0a0a736b696e706172616d20636c617373207b0a2020426f72646572436f6c6f722023677265790a20204261636b67726f756e64436f6c6f72202377686974650a7d0a0a7061636b61676520636f6d2e6d796170702e6d6f64756c656f6e65207b0a20202020636c61737320436c6173734f6e65496e4d6f64756c654f6e650a20202020636c61737320436c61737354776f496e4d6f64756c654f6e650a7d0a7061636b61676520636f6d2e6d796170702e6d6f64756c6574776f207b0a20202020636c61737320436c6173734f6e65496e4d6f64756c6554776f0a20202020636c61737320436c61737354776f496e4d6f64756c6554776f0a7d0a7061636b61676520636f6d2e6d796170702e6d6f64756c657468726565207b0a20202020636c61737320436c6173734f6e65496e4d6f64756c6554687265650a20202020636c61737320436c61737354776f496e4d6f64756c6554687265650a7d0a0a436c6173734f6e65496e4d6f64756c654f6e65202d2d3e20436c61737354776f496e4d6f64756c6554776f20236372696d736f6e0a436c6173734f6e65496e4d6f64756c6554776f202d2d3e20436c6173734f6e65496e4d6f64756c65546872656520236372696d736f6e0a436c61737354776f496e4d6f64756c655468726565202d2d3e20436c6173734f6e65496e4d6f64756c654f6e6520236372696d736f6e0a6e6f7465207269676874206f6e206c696e6b20236372696d736f6e3a20436f6d62696e6174696f6e206f6620616363657373657320666f726d73206379636c65) + +``` +slices().matching("com.myapp.(*)..").should().beFreeOfCycles() +``` + + diff --git a/docs/_posts/2020-12-17-release-v0.15.0.markdown b/docs/_posts/2020-12-17-release-v0.15.0.markdown new file mode 100644 index 0000000000..c82334513f --- /dev/null +++ b/docs/_posts/2020-12-17-release-v0.15.0.markdown @@ -0,0 +1,8 @@ +--- +layout: splash +title: "New release of ArchUnit (v0.15.0)" +date: 2020-12-17 00:00:00 +categories: news release +--- + +A new release of ArchUnit (v0.15.0) is out. For details see [the release on GitHub](https://github.com/TNG/ArchUnit/releases/tag/v0.15.0 "ArchUnit v0.15.0 on GitHub"). diff --git a/docs/build.gradle b/docs/build.gradle index 5cdb7eba39..42e182f538 100644 --- a/docs/build.gradle +++ b/docs/build.gradle @@ -1,9 +1,13 @@ +import groovy.transform.Canonical + +import static java.nio.charset.StandardCharsets.UTF_8 + plugins { id "org.asciidoctor.convert" version "2.4.0" } dependencies { - asciidoctor 'org.asciidoctor:asciidoctorj-diagram:2.0.2' + asciidoctor 'org.asciidoctor:asciidoctorj-diagram:2.0.5' } task cleanUserGuide(type: Delete) { @@ -30,4 +34,63 @@ asciidoctorj { } asciidoctor.dependsOn cleanUserGuide -task renderUserGuide(dependsOn: asciidoctor) + +task createUseCasesPage { + doLast { + def useCases = file('userguide/004_What_to_Check.adoc').text + .split(/(?m)^=== /).tail() + .collect { String section -> + String heading = section.lines().findFirst().get() + String[] sourceCodeSnippets = section.split(/(\[source,)|(\[plantuml,)/).tail() + .collect { String snippet -> snippet.split(/(?m)^----$/)[1].trim() } + + List snippets = sourceCodeSnippets.collate(2).collect { plantUmlAndJavaSnippetPair -> + new Snippet(plantUmlSourceCode: plantUmlAndJavaSnippetPair[0], javaSourceCode: plantUmlAndJavaSnippetPair[1]) + } + new UseCase(heading: heading, snippets: snippets) + } + + file('_pages/use-cases.md').text = """ +--- +title: Use Cases +layout: splash +permalink: /use-cases +--- + +${useCases.collect {it.toMarkDown() }.join('\n')} +""".stripLeading() + } +} + +task renderUserGuide(dependsOn: [asciidoctor, createUseCasesPage]) + +@Canonical +class UseCase { + String heading + List snippets + + String toMarkDown() { + """ +## ${heading} + +${snippets.collect { it.toMarkDown() }.join('\n') } + """.stripLeading() + } +} + +@Canonical +class Snippet { + String plantUmlSourceCode + String javaSourceCode + + String toMarkDown() { + String plantUmlUrl = "http://www.plantuml.com/plantuml/svg/~h${plantUmlSourceCode.getBytes(UTF_8).encodeHex()}" + """ +![diagram](${plantUmlUrl}) + +``` +${javaSourceCode} +``` + """.stripLeading() + } +} \ No newline at end of file diff --git a/docs/userguide/004_What_to_Check.adoc b/docs/userguide/004_What_to_Check.adoc index 6ce1e92f68..980993c925 100644 --- a/docs/userguide/004_What_to_Check.adoc +++ b/docs/userguide/004_What_to_Check.adoc @@ -74,7 +74,7 @@ note left on link #green: allowed [source,java] ---- classes().that().haveNameMatching(".*Bar") - .should().onlyBeAccessed().byClassesThat().haveSimpleName("Bar") + .should().onlyHaveDependentClassesThat().haveSimpleName("Bar") ---- === Class and Package Containment Checks @@ -178,7 +178,7 @@ note right on link #crimson: Accessor resides in wrong package [source,java] ---- classes().that().areAssignableTo(EntityManager.class) - .should().onlyBeAccessed().byAnyPackage("..persistence..") + .should().onlyHaveDependentClassesThat().resideInAnyPackage("..persistence..") ---- === Annotation Checks @@ -211,7 +211,7 @@ note right on link #crimson: Accessor is not annotated with @Transactional [source,java] ---- classes().that().areAssignableTo(EntityManager.class) - .should().onlyBeAccessed().byClassesThat().areAnnotatedWith(Transactional.class) + .should().onlyHaveDependentClassesThat().areAnnotatedWith(Transactional.class) ---- === Layer Checks @@ -305,4 +305,4 @@ note right on link #crimson: Combination of accesses forms cycle [source,java] ---- slices().matching("com.myapp.(*)..").should().beFreeOfCycles() ----- \ No newline at end of file +---- diff --git a/docs/userguide/009_JUnit_Support.adoc b/docs/userguide/009_JUnit_Support.adoc index a1010ee72c..01c48bac18 100644 --- a/docs/userguide/009_JUnit_Support.adoc +++ b/docs/userguide/009_JUnit_Support.adoc @@ -156,6 +156,9 @@ public class ArchitectureTest { } ---- +Note for users of JUnit 5: the annotation `@Disabled` has no effect here. +Instead, `@ArchIgnore` should be used. + ==== Grouping Rules Often a project might end up with different categories of rules, for example "service rules" diff --git a/docs/userguide/html/000_Index.html b/docs/userguide/html/000_Index.html index 48a0dda8a7..ec8cb42a16 100644 --- a/docs/userguide/html/000_Index.html +++ b/docs/userguide/html/000_Index.html @@ -449,7 +449,7 @@ @@ -644,7 +644,7 @@

2.2. JUnit 5

<dependency>
     <groupId>com.tngtech.archunit</groupId>
     <artifactId>archunit-junit5</artifactId>
-    <version>0.14.1</version>
+    <version>0.15.0</version>
     <scope>test</scope>
 </dependency>
@@ -653,7 +653,7 @@

2.2. JUnit 5

build.gradle
dependencies {
-    testImplementation 'com.tngtech.archunit:archunit-junit5:0.14.1'
+    testImplementation 'com.tngtech.archunit:archunit-junit5:0.15.0'
 }
@@ -670,7 +670,7 @@

<dependency> <groupId>com.tngtech.archunit</groupId> <artifactId>archunit</artifactId> - <version>0.14.1</version> + <version>0.15.0</version> <scope>test</scope> </dependency> @@ -679,7 +679,7 @@

build.gradle
dependencies {
-   testImplementation 'com.tngtech.archunit:archunit:0.14.1'
+   testImplementation 'com.tngtech.archunit:archunit:0.15.0'
 }
@@ -877,7 +877,7 @@

classes().that().haveNameMatching(".*Bar")
-    .should().onlyBeAccessed().byClassesThat().haveSimpleName("Bar")
+ .should().onlyHaveDependentClassesThat().haveSimpleName("Bar")
@@ -885,7 +885,7 @@

4.3. Class and Package Containment Checks

-class package contain +class package contain
@@ -910,13 +910,13 @@

4

-inheritance access check +inheritance access check
classes().that().areAssignableTo(EntityManager.class)
-    .should().onlyBeAccessed().byAnyPackage("..persistence..")
+ .should().onlyHaveDependentClassesThat().resideInAnyPackage("..persistence..")
@@ -930,7 +930,7 @@

4.5
classes().that().areAssignableTo(EntityManager.class)
-    .should().onlyBeAccessed().byClassesThat().areAnnotatedWith(Transactional.class)
+ .should().onlyHaveDependentClassesThat().areAnnotatedWith(Transactional.class)
@@ -938,7 +938,7 @@

4.5

4.6. Layer Checks

-layer check +layer check
@@ -958,7 +958,7 @@

4.6. Layer Ch

4.7. Cycle Checks

-cycle check +cycle check
@@ -1780,7 +1780,7 @@

8

-onion architecture check +onion architecture check
@@ -2329,6 +2329,10 @@

9.1.4. Ig } +
+

Note for users of JUnit 5: the annotation @Disabled has no effect here. +Instead, @ArchIgnore should be used.

+

9.1.5. Grouping Rules

diff --git a/docs/userguide/html/class-naming-deps.png b/docs/userguide/html/class-naming-deps.png index 6b263e4e5e..f0ee9cecdb 100644 Binary files a/docs/userguide/html/class-naming-deps.png and b/docs/userguide/html/class-naming-deps.png differ diff --git a/docs/userguide/html/class-package-contain.png b/docs/userguide/html/class-package-contain.png index 57427037a1..5b6186c51d 100644 Binary files a/docs/userguide/html/class-package-contain.png and b/docs/userguide/html/class-package-contain.png differ diff --git a/docs/userguide/html/cycle-check.png b/docs/userguide/html/cycle-check.png index 908c03ddfa..1981095177 100644 Binary files a/docs/userguide/html/cycle-check.png and b/docs/userguide/html/cycle-check.png differ diff --git a/docs/userguide/html/diamond-example.png b/docs/userguide/html/diamond-example.png index 891ee258c6..01d1debe81 100644 Binary files a/docs/userguide/html/diamond-example.png and b/docs/userguide/html/diamond-example.png differ diff --git a/docs/userguide/html/domain-overview.png b/docs/userguide/html/domain-overview.png index 7b7d57d9a2..12cb24b9a2 100644 Binary files a/docs/userguide/html/domain-overview.png and b/docs/userguide/html/domain-overview.png differ diff --git a/docs/userguide/html/import-vs-lang.png b/docs/userguide/html/import-vs-lang.png index 3087d7bf12..f04f072f50 100644 Binary files a/docs/userguide/html/import-vs-lang.png and b/docs/userguide/html/import-vs-lang.png differ diff --git a/docs/userguide/html/inheritance-access-check.png b/docs/userguide/html/inheritance-access-check.png index e44934bb1a..fe7f40eb24 100644 Binary files a/docs/userguide/html/inheritance-access-check.png and b/docs/userguide/html/inheritance-access-check.png differ diff --git a/docs/userguide/html/inheritance-annotation-check.png b/docs/userguide/html/inheritance-annotation-check.png index 60da19e79b..d2c77ca736 100644 Binary files a/docs/userguide/html/inheritance-annotation-check.png and b/docs/userguide/html/inheritance-annotation-check.png differ diff --git a/docs/userguide/html/inheritance-naming-check.png b/docs/userguide/html/inheritance-naming-check.png index f0e145ffb3..b7a93234b0 100644 Binary files a/docs/userguide/html/inheritance-naming-check.png and b/docs/userguide/html/inheritance-naming-check.png differ diff --git a/docs/userguide/html/layer-check.png b/docs/userguide/html/layer-check.png index 494b83d17a..185b9a32cd 100644 Binary files a/docs/userguide/html/layer-check.png and b/docs/userguide/html/layer-check.png differ diff --git a/docs/userguide/html/onion-architecture-check.png b/docs/userguide/html/onion-architecture-check.png index 577bb5eb9e..555b5ac4da 100644 Binary files a/docs/userguide/html/onion-architecture-check.png and b/docs/userguide/html/onion-architecture-check.png differ diff --git a/docs/userguide/html/package-deps-no-access.png b/docs/userguide/html/package-deps-no-access.png index 9769384e37..b223b7a321 100644 Binary files a/docs/userguide/html/package-deps-no-access.png and b/docs/userguide/html/package-deps-no-access.png differ diff --git a/docs/userguide/html/package-deps-only-access.png b/docs/userguide/html/package-deps-only-access.png index d6f26df943..41b7d420e0 100644 Binary files a/docs/userguide/html/package-deps-only-access.png and b/docs/userguide/html/package-deps-only-access.png differ diff --git a/docs/userguide/html/resolution-example.png b/docs/userguide/html/resolution-example.png index d879ee4d1b..63c8638006 100644 Binary files a/docs/userguide/html/resolution-example.png and b/docs/userguide/html/resolution-example.png differ diff --git a/docs/userguide/html/resolution-overview.png b/docs/userguide/html/resolution-overview.png index 63216d73c5..f06eabc548 100644 Binary files a/docs/userguide/html/resolution-overview.png and b/docs/userguide/html/resolution-overview.png differ diff --git a/docs/userguide/html/simple-plantuml-archrule-example.png b/docs/userguide/html/simple-plantuml-archrule-example.png index 73a4e9a6fe..fe432d60ea 100644 Binary files a/docs/userguide/html/simple-plantuml-archrule-example.png and b/docs/userguide/html/simple-plantuml-archrule-example.png differ diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index f3d88b1c2f..e708b1c023 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a4b4429748..4d9ca16491 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 2fe81a7d95..4f906e0c81 100755 --- a/gradlew +++ b/gradlew @@ -82,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -129,6 +130,7 @@ fi if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath diff --git a/gradlew.bat b/gradlew.bat index 24467a141f..ac1b06f938 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -51,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -61,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell