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, Set super SomeEnum>>> {
+
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 extends RuntimeException> exceptionSupplier);
+
@PublicAPI(usage = ACCESS)
public abstract Optional transform(Function super T, U> function);
@@ -114,6 +120,11 @@ public T getOrThrow(RuntimeException e) {
throw e;
}
+ @Override
+ public T getOrThrow(Supplier extends RuntimeException> exceptionSupplier) {
+ throw exceptionSupplier.get();
+ }
+
@Override
public Optional transform(Function super T, U> function) {
return absent();
@@ -172,6 +183,11 @@ public T getOrThrow(RuntimeException e) {
return object;
}
+ @Override
+ public T getOrThrow(Supplier extends RuntimeException> exceptionSupplier) {
+ return object;
+ }
+
@Override
public Optional transform(Function super T, U> 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