diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..f30507a10 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1 @@ +efede348e99941d50fb85ea2937078abf6befb11 diff --git a/.github/check-md-links.json b/.github/check-md-links.json index 546c036a5..58ba01e55 100644 --- a/.github/check-md-links.json +++ b/.github/check-md-links.json @@ -1,11 +1,9 @@ { - "httpHeaders": [ - { - "urls": ["https://github.com/", "https://guides.github.com/", "https://help.github.com/", "https://docs.github.com/", "https://classroom.github.com"], - "headers": { - "Accept-Encoding": "zstd, br, gzip, deflate" - } + "httpHeaders": [ { + "urls": [ "https://github.com/", "https://guides.github.com/", "https://help.github.com/", "https://docs.github.com/", "https://classroom.github.com" ], + "headers": { + "Accept-Encoding": "zstd, br, gzip, deflate" } - ], - "aliveStatusCodes": [200, 500, 501, 502, 503, 429] -} + } ], + "aliveStatusCodes": [ 200, 500, 501, 502, 503, 429 ] +} \ No newline at end of file diff --git a/.github/quality-gates-pr.json b/.github/quality-gates-pr.json index 358bf314c..a2f0e594e 100644 --- a/.github/quality-gates-pr.json +++ b/.github/quality-gates-pr.json @@ -1,43 +1,36 @@ { - "qualityGates": [ - { - "metric": "test-success-rate", - "name": "Overall Tests Success Rate", - "threshold": 100.0, - "criticality": "FAILURE" - }, - { - "metric": "line", - "name": "Line Coverage in New Code", - "scope": "new", - "threshold": 90.0, - "criticality": "UNSTABLE" - }, - { - "metric": "branch", - "name": "Branch Coverage in New Code", - "scope": "new", - "threshold": 90.0, - "criticality": "UNSTABLE" - }, - { - "metric": "mutation", - "name": "Mutation Coverage in New Code", - "scope": "new", - "threshold": 90.0, - "criticality": "UNSTABLE" - }, - { - "metric": "bugs", - "name": "Potential Bugs in Whole Project", - "threshold": 0.0, - "criticality": "FAILURE" - }, - { - "metric": "style", - "name": "Style Violation in Whole Project", - "threshold": 0.0, - "criticality": "FAILURE" - } - ] -} + "qualityGates": [ { + "metric": "test-success-rate", + "name": "Overall Tests Success Rate", + "threshold": 100.0, + "criticality": "FAILURE" + }, { + "metric": "line", + "name": "Line Coverage in New Code", + "scope": "new", + "threshold": 90.0, + "criticality": "UNSTABLE" + }, { + "metric": "branch", + "name": "Branch Coverage in New Code", + "scope": "new", + "threshold": 90.0, + "criticality": "UNSTABLE" + }, { + "metric": "mutation", + "name": "Mutation Coverage in New Code", + "scope": "new", + "threshold": 90.0, + "criticality": "UNSTABLE" + }, { + "metric": "bugs", + "name": "Potential Bugs in Whole Project", + "threshold": 0.0, + "criticality": "FAILURE" + }, { + "metric": "style", + "name": "Style Violation in Whole Project", + "threshold": 0.0, + "criticality": "FAILURE" + } ] +} \ No newline at end of file diff --git a/.github/quality-gates.json b/.github/quality-gates.json index 24094919f..5d70a6e6a 100644 --- a/.github/quality-gates.json +++ b/.github/quality-gates.json @@ -1,34 +1,28 @@ { - "qualityGates": [ - { - "metric": "test-success-rate", - "name": "Tests Success Rate", - "threshold": 100.0, - "criticality": "FAILURE" - }, - { - "metric": "line", - "name": "Line Coverage", - "threshold": 80.0, - "criticality": "UNSTABLE" - }, - { - "metric": "branch", - "name": "Branch Coverage", - "threshold": 80.0, - "criticality": "UNSTABLE" - }, - { - "metric": "bugs", - "name": "Potential Bugs", - "threshold": 0.0, - "criticality": "FAILURE" - }, - { - "metric": "style", - "name": "Style Violations", - "threshold": 0.0, - "criticality": "FAILURE" - } - ] -} + "qualityGates": [ { + "metric": "test-success-rate", + "name": "Tests Success Rate", + "threshold": 100.0, + "criticality": "FAILURE" + }, { + "metric": "line", + "name": "Line Coverage", + "threshold": 80.0, + "criticality": "UNSTABLE" + }, { + "metric": "branch", + "name": "Branch Coverage", + "threshold": 80.0, + "criticality": "UNSTABLE" + }, { + "metric": "bugs", + "name": "Potential Bugs", + "threshold": 0.0, + "criticality": "FAILURE" + }, { + "metric": "style", + "name": "Style Violations", + "threshold": 0.0, + "criticality": "FAILURE" + } ] +} \ No newline at end of file diff --git a/.github/quality-monitor-pr.json b/.github/quality-monitor-pr.json index 26bd47c08..b9015abb5 100644 --- a/.github/quality-monitor-pr.json +++ b/.github/quality-monitor-pr.json @@ -1,183 +1,143 @@ { "tests": { "name": "Tests", - "tools": [ - { - "id": "junit", - "name": "Unit Tests", - "pattern": "**/target/surefire-reports/TEST*.xml" - }, - { - "id": "junit", - "icon": "no_entry", - "name": "Architecture Tests", - "pattern": "**/target/archunit-reports/TEST*.xml" - } - ] + "tools": [ { + "id": "junit", + "name": "Unit Tests", + "pattern": "**/target/surefire-reports/TEST*.xml" + }, { + "id": "junit", + "icon": "no_entry", + "name": "Architecture Tests", + "pattern": "**/target/archunit-reports/TEST*.xml" + } ] }, - "analysis": [ - { - "name": "Style", - "id": "style", - "tools": [ - { - "id": "checkstyle", - "pattern": "**/target/**checkstyle-result.xml" - }, - { - "id": "pmd", - "pattern": "**/target/pmd-*/pmd.xml" - }, - { - "id": "java", - "icon": "coffee", - "pattern": "**/maven.log" - } - ] - }, - { - "name": "Bugs", - "id": "bugs", - "icon": "bug", - "tools": [ - { - "id": "spotbugs", - "sourcePath": "src/main/java", - "pattern": "**/target/spotbugsXml.xml" - }, - { - "id": "error-prone", - "pattern": "**/maven.log" - } - ] - }, - { - "name": "API Problems", - "id": "api", - "icon": "no_entry_sign", - "tools": [ - { - "id": "revapi", - "sourcePath": "src/main/java", - "pattern": "**/target/revapi-result.json" - } - ] - }, - { - "name": "Vulnerabilities", - "id": "vulnerabilities", + "analysis": [ { + "name": "Style", + "id": "style", + "tools": [ { + "id": "checkstyle", + "pattern": "**/target/**checkstyle-result.xml" + }, { + "id": "pmd", + "pattern": "**/target/pmd-*/pmd.xml" + }, { + "id": "java", + "icon": "coffee", + "pattern": "**/maven.log" + } ] + }, { + "name": "Bugs", + "id": "bugs", + "icon": "bug", + "tools": [ { + "id": "spotbugs", + "sourcePath": "src/main/java", + "pattern": "**/target/spotbugsXml.xml" + }, { + "id": "error-prone", + "pattern": "**/maven.log" + } ] + }, { + "name": "API Problems", + "id": "api", + "icon": "no_entry_sign", + "tools": [ { + "id": "revapi", + "sourcePath": "src/main/java", + "pattern": "**/target/revapi-result.json" + } ] + }, { + "name": "Vulnerabilities", + "id": "vulnerabilities", + "icon": "shield", + "tools": [ { + "id": "owasp-dependency-check", "icon": "shield", - "tools": [ - { - "id": "owasp-dependency-check", - "icon": "shield", - "pattern": "**/target/dependency-check-report.json" - } - ] - } - ], - "coverage": [ - { - "name": "Coverage for New Code", - "tools": [ - { - "id": "jacoco", - "metric": "line", - "scope": "new", - "sourcePath": "src/main/java", - "pattern": "**/target/site/jacoco/jacoco.xml" - }, - { - "id": "jacoco", - "metric": "branch", - "scope": "new", - "sourcePath": "src/main/java", - "pattern": "**/target/site/jacoco/jacoco.xml" - }, - { - "id": "pit", - "scope": "new", - "metric": "mutation", - "sourcePath": "src/main/java", - "pattern": "**/target/pit-reports/mutations.xml" - }, - { - "id": "pit", - "metric": "test-strength", - "scope": "new", - "sourcePath": "src/main/java", - "pattern": "**/target/pit-reports/mutations.xml" - } - ] - }, - { - "name": "Coverage for Whole Project", - "tools": [ - { - "id": "jacoco", - "metric": "line", - "sourcePath": "src/main/java", - "pattern": "**/target/site/jacoco/jacoco.xml" - }, - { - "id": "jacoco", - "metric": "branch", - "sourcePath": "src/main/java", - "pattern": "**/target/site/jacoco/jacoco.xml" - }, - { - "id": "pit", - "metric": "mutation", - "sourcePath": "src/main/java", - "pattern": "**/target/pit-reports/mutations.xml" - }, - { - "id": "pit", - "metric": "test-strength", - "sourcePath": "src/main/java", - "pattern": "**/target/pit-reports/mutations.xml" - } - ] - } - ], + "pattern": "**/target/dependency-check-report.json" + } ] + } ], + "coverage": [ { + "name": "Coverage for New Code", + "tools": [ { + "id": "jacoco", + "metric": "line", + "scope": "new", + "sourcePath": "src/main/java", + "pattern": "**/target/site/jacoco/jacoco.xml" + }, { + "id": "jacoco", + "metric": "branch", + "scope": "new", + "sourcePath": "src/main/java", + "pattern": "**/target/site/jacoco/jacoco.xml" + }, { + "id": "pit", + "scope": "new", + "metric": "mutation", + "sourcePath": "src/main/java", + "pattern": "**/target/pit-reports/mutations.xml" + }, { + "id": "pit", + "metric": "test-strength", + "scope": "new", + "sourcePath": "src/main/java", + "pattern": "**/target/pit-reports/mutations.xml" + } ] + }, { + "name": "Coverage for Whole Project", + "tools": [ { + "id": "jacoco", + "metric": "line", + "sourcePath": "src/main/java", + "pattern": "**/target/site/jacoco/jacoco.xml" + }, { + "id": "jacoco", + "metric": "branch", + "sourcePath": "src/main/java", + "pattern": "**/target/site/jacoco/jacoco.xml" + }, { + "id": "pit", + "metric": "mutation", + "sourcePath": "src/main/java", + "pattern": "**/target/pit-reports/mutations.xml" + }, { + "id": "pit", + "metric": "test-strength", + "sourcePath": "src/main/java", + "pattern": "**/target/pit-reports/mutations.xml" + } ] + } ], "metrics": { "name": "Software Metrics", - "tools": [ - { - "id": "metrics", - "pattern": "**/metrics/pmd.xml", - "metric": "CYCLOMATIC_COMPLEXITY" - }, - { - "id": "metrics", - "pattern": "**/metrics/pmd.xml", - "metric": "COGNITIVE_COMPLEXITY" - }, - { - "id": "metrics", - "pattern": "**/metrics/pmd.xml", - "metric": "NPATH_COMPLEXITY" - }, - { - "id": "metrics", - "pattern": "**/metrics/pmd.xml", - "metric": "LOC" - }, - { - "id": "metrics", - "pattern": "**/metrics/pmd.xml", - "metric": "NCSS" - }, - { - "id": "metrics", - "pattern": "**/metrics/pmd.xml", - "metric": "COHESION" - }, - { - "id": "metrics", - "pattern": "**/metrics/pmd.xml", - "metric": "WEIGHT_OF_CLASS" - } - ] + "tools": [ { + "id": "metrics", + "pattern": "**/metrics/pmd.xml", + "metric": "CYCLOMATIC_COMPLEXITY" + }, { + "id": "metrics", + "pattern": "**/metrics/pmd.xml", + "metric": "COGNITIVE_COMPLEXITY" + }, { + "id": "metrics", + "pattern": "**/metrics/pmd.xml", + "metric": "NPATH_COMPLEXITY" + }, { + "id": "metrics", + "pattern": "**/metrics/pmd.xml", + "metric": "LOC" + }, { + "id": "metrics", + "pattern": "**/metrics/pmd.xml", + "metric": "NCSS" + }, { + "id": "metrics", + "pattern": "**/metrics/pmd.xml", + "metric": "COHESION" + }, { + "id": "metrics", + "pattern": "**/metrics/pmd.xml", + "metric": "WEIGHT_OF_CLASS" + } ] } -} +} \ No newline at end of file diff --git a/.github/quality-monitor.json b/.github/quality-monitor.json index 8a437389a..e3b7d82d4 100644 --- a/.github/quality-monitor.json +++ b/.github/quality-monitor.json @@ -1,155 +1,119 @@ { "tests": { "name": "Tests", - "tools": [ - { - "id": "junit", - "name": "Unit Tests", - "pattern": "**/target/surefire-reports/TEST*.xml" - }, - { - "id": "junit", - "icon": "no_entry", - "name": "Architecture Tests", - "pattern": "**/target/archunit-reports/TEST*.xml" - } - ] + "tools": [ { + "id": "junit", + "name": "Unit Tests", + "pattern": "**/target/surefire-reports/TEST*.xml" + }, { + "id": "junit", + "icon": "no_entry", + "name": "Architecture Tests", + "pattern": "**/target/archunit-reports/TEST*.xml" + } ] }, - "analysis": [ - { - "name": "Style", - "id": "style", - "tools": [ - { - "id": "checkstyle", - "pattern": "**/target/**checkstyle-result.xml" - }, - { - "id": "pmd", - "pattern": "**/target/pmd-*/pmd.xml" - }, - { - "id": "java", - "icon": "coffee", - "pattern": "**/maven.log" - } - ] - }, - { - "name": "Bugs", - "id": "bugs", - "icon": "bug", - "tools": [ - { - "id": "spotbugs", - "sourcePath": "src/main/java", - "pattern": "**/target/spotbugsXml.xml" - }, - { - "id": "error-prone", - "pattern": "**/maven.log" - } - ] - }, - { - "name": "API Problems", - "id": "api", - "icon": "no_entry_sign", - "tools": [ - { - "id": "revapi", - "sourcePath": "src/main/java", - "pattern": "**/target/revapi-result.json" - } - ] - }, - { - "name": "Vulnerabilities", - "id": "vulnerabilities", + "analysis": [ { + "name": "Style", + "id": "style", + "tools": [ { + "id": "checkstyle", + "pattern": "**/target/**checkstyle-result.xml" + }, { + "id": "pmd", + "pattern": "**/target/pmd-*/pmd.xml" + }, { + "id": "java", + "icon": "coffee", + "pattern": "**/maven.log" + } ] + }, { + "name": "Bugs", + "id": "bugs", + "icon": "bug", + "tools": [ { + "id": "spotbugs", + "sourcePath": "src/main/java", + "pattern": "**/target/spotbugsXml.xml" + }, { + "id": "error-prone", + "pattern": "**/maven.log" + } ] + }, { + "name": "API Problems", + "id": "api", + "icon": "no_entry_sign", + "tools": [ { + "id": "revapi", + "sourcePath": "src/main/java", + "pattern": "**/target/revapi-result.json" + } ] + }, { + "name": "Vulnerabilities", + "id": "vulnerabilities", + "icon": "shield", + "tools": [ { "icon": "shield", - "tools": [ - { - "icon": "shield", - "id": "owasp-dependency-check", - "pattern": "**/target/dependency-check-report.json" - } - ] - } - ], - "coverage": [ - { - "name": "Code Coverage", - "tools": [ - { - "id": "jacoco", - "metric": "line", - "sourcePath": "src/main/java", - "pattern": "**/target/site/jacoco/jacoco.xml" - }, - { - "id": "jacoco", - "metric": "branch", - "sourcePath": "src/main/java", - "pattern": "**/target/site/jacoco/jacoco.xml" - } - ] - }, - { - "name": "Mutation Coverage", - "tools": [ - { - "id": "pit", - "metric": "mutation", - "sourcePath": "src/main/java", - "pattern": "**/target/pit-reports/mutations.xml" - }, - { - "id": "pit", - "metric": "test-strength", - "sourcePath": "src/main/java", - "pattern": "**/target/pit-reports/mutations.xml" - } - ] - } - ], + "id": "owasp-dependency-check", + "pattern": "**/target/dependency-check-report.json" + } ] + } ], + "coverage": [ { + "name": "Code Coverage", + "tools": [ { + "id": "jacoco", + "metric": "line", + "sourcePath": "src/main/java", + "pattern": "**/target/site/jacoco/jacoco.xml" + }, { + "id": "jacoco", + "metric": "branch", + "sourcePath": "src/main/java", + "pattern": "**/target/site/jacoco/jacoco.xml" + } ] + }, { + "name": "Mutation Coverage", + "tools": [ { + "id": "pit", + "metric": "mutation", + "sourcePath": "src/main/java", + "pattern": "**/target/pit-reports/mutations.xml" + }, { + "id": "pit", + "metric": "test-strength", + "sourcePath": "src/main/java", + "pattern": "**/target/pit-reports/mutations.xml" + } ] + } ], "metrics": { "name": "Software Metrics", - "tools": [ - { - "id": "metrics", - "pattern": "**/metrics/pmd.xml", - "metric": "CYCLOMATIC_COMPLEXITY" - }, - { - "id": "metrics", - "pattern": "**/metrics/pmd.xml", - "metric": "COGNITIVE_COMPLEXITY" - }, - { - "id": "metrics", - "pattern": "**/metrics/pmd.xml", - "metric": "NPATH_COMPLEXITY" - }, - { - "id": "metrics", - "pattern": "**/metrics/pmd.xml", - "metric": "LOC" - }, - { - "id": "metrics", - "pattern": "**/metrics/pmd.xml", - "metric": "NCSS" - }, - { - "id": "metrics", - "pattern": "**/metrics/pmd.xml", - "metric": "COHESION" - }, - { - "id": "metrics", - "pattern": "**/metrics/pmd.xml", - "metric": "WEIGHT_OF_CLASS" - } - ] + "tools": [ { + "id": "metrics", + "pattern": "**/metrics/pmd.xml", + "metric": "CYCLOMATIC_COMPLEXITY" + }, { + "id": "metrics", + "pattern": "**/metrics/pmd.xml", + "metric": "COGNITIVE_COMPLEXITY" + }, { + "id": "metrics", + "pattern": "**/metrics/pmd.xml", + "metric": "NPATH_COMPLEXITY" + }, { + "id": "metrics", + "pattern": "**/metrics/pmd.xml", + "metric": "LOC" + }, { + "id": "metrics", + "pattern": "**/metrics/pmd.xml", + "metric": "NCSS" + }, { + "id": "metrics", + "pattern": "**/metrics/pmd.xml", + "metric": "COHESION" + }, { + "id": "metrics", + "pattern": "**/metrics/pmd.xml", + "metric": "WEIGHT_OF_CLASS" + } ] } -} +} \ No newline at end of file diff --git a/.github/renovate.json b/.github/renovate.json index 70a0d9a19..313298b87 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,6 +1,6 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["config:recommended"], + "extends": [ "config:recommended" ], "commitMessageAction": "Bump", - "labels": ["dependencies"] -} + "labels": [ "dependencies" ] +} \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index a50a2f642..26f1026e2 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,64 +1,53 @@ - - + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index df6cc9490..04a95ba24 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,6 @@ + diff --git a/CHANGELOG.md b/CHANGELOG.md index 64368d540..0a612a61e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ -# Changelog +# Changelog -All notable changes of this coding style will be automatically -logged by release drafter in [GitHub releases](https://github.com/uhafner/codingstyle/releases). +All notable changes of this coding style will be automatically +logged by release drafter in [GitHub releases](https://github.com/uhafner/codingstyle/releases). diff --git a/LIESMICH.md b/LIESMICH.md index 697abf4df..fca505cc9 100644 --- a/LIESMICH.md +++ b/LIESMICH.md @@ -8,10 +8,10 @@ In jedem Java Projekt sollte der gesamte Quelltext die gleichen Kriterien bei Stil, Formatierung, etc. verwenden. In diesem Projekt werden die Kodierungsrichtlinien zu meinen Vorlesungen an der Hochschule -München zusammengefasst. +München zusammengefasst. -Dieses Projekt enthält neben der Dokumentation der wichtigsten Kodierungsrichtlinien auch gleichzeitig eine sinnvolle -Konfiguration aller für Java kostenlos verfügbaren statischen Codeanalyse Tools mittels Maven. Diese dort enthaltenen und automatisch +Dieses Projekt enthält neben der Dokumentation der wichtigsten Kodierungsrichtlinien auch gleichzeitig eine sinnvolle +Konfiguration aller für Java kostenlos verfügbaren statischen Codeanalyse Tools mittels Maven. Diese dort enthaltenen und automatisch prüfbaren Richtlinien werden - soweit wie möglich - nicht mehr extra im Text erwähnt. Damit kann diese Projekt gleichzeitig als Vorlage für neue Projekte genutzt werden. Unterstützt werden aktuell folgende Tools: - [Checkstyle](https://checkstyle.org) @@ -19,21 +19,21 @@ Vorlage für neue Projekte genutzt werden. Unterstützt werden aktuell folgende - [SpotBugs](https://spotbugs.github.io) - [Error Prone](https://errorprone.info) -Die automatisch prüfbaren Richtlinien können für CheckStyle und Error Prone auch direkt als Warnungen in der -Entwicklungsumgebung [IntelliJ](https://www.jetbrains.com/idea/) angezeigt werden (nach der Installation des -entsprechenden IntelliJ Plugins). Zusätzlich sind die -[IntelliJ Code Inspections](https://www.jetbrains.com/help/idea/code-inspection.html) gemäß meiner Richtlinien konfiguriert. -Aktuell können diese allerdings noch nicht automatisch im Build überprüft werden +Die automatisch prüfbaren Richtlinien können für CheckStyle und Error Prone auch direkt als Warnungen in der +Entwicklungsumgebung [IntelliJ](https://www.jetbrains.com/idea/) angezeigt werden (nach der Installation des +entsprechenden IntelliJ Plugins). Zusätzlich sind die +[IntelliJ Code Inspections](https://www.jetbrains.com/help/idea/code-inspection.html) gemäß meiner Richtlinien konfiguriert. +Aktuell können diese allerdings noch nicht automatisch im Build überprüft werden (siehe [#7](https://github.com/uhafner/codingstyle/issues/7)). Insgesamt ist damit sichergestellt, -dass immer die gleichen Warnungen angezeigt werden - egal wie und wo die Java-Dateien weiterverarbeitet werden. -Für SpotBugs und PMD ist der Umweg über das Build Management Tool [Maven](http://maven.apache.org/) erforderlich -(die entsprechenden IntelliJ Plugins sind leider aus meiner Sicht noch nicht ausgereift genug bzw. verwenden eine separate Konfiguration). -Die Verwendung von Maven hat zudem den Vorteil, dass die Ergebnisse hinterher leicht in den Continuous Integration Server -[Jenkins](https://jenkins.io/) eingebunden werden können. Eine beispielhafte Integration in GitHub Actions und Jenkins ist auch bereits vorhanden. +dass immer die gleichen Warnungen angezeigt werden - egal wie und wo die Java-Dateien weiterverarbeitet werden. +Für SpotBugs und PMD ist der Umweg über das Build Management Tool [Maven](http://maven.apache.org/) erforderlich +(die entsprechenden IntelliJ Plugins sind leider aus meiner Sicht noch nicht ausgereift genug bzw. verwenden eine separate Konfiguration). +Die Verwendung von Maven hat zudem den Vorteil, dass die Ergebnisse hinterher leicht in den Continuous Integration Server +[Jenkins](https://jenkins.io/) eingebunden werden können. Eine beispielhafte Integration in GitHub Actions und Jenkins ist auch bereits vorhanden. Diese ist im eigenen Abschnitt [Continuous Integration](doc/Continuous-Integration.md) -ausführlicher beschrieben. Ebenso sind mehrere externe Tools konfiguriert, die die Qualität der Pull Requests -in diesem Repository bewerten, Details dazu sind im Abschnitt [Integration externer Tools](doc/Externe-Tool-Integration.md) -beschrieben. +ausführlicher beschrieben. Ebenso sind mehrere externe Tools konfiguriert, die die Qualität der Pull Requests +in diesem Repository bewerten, Details dazu sind im Abschnitt [Integration externer Tools](doc/Externe-Tool-Integration.md) +beschrieben. Die Richtlinien sind in den Vorlesungen 2014/2015 entstanden und werden laufend ergänzt. Aktuell bestehen diese aus den folgenden Abschnitten: @@ -42,26 +42,26 @@ Aktuell bestehen diese aus den folgenden Abschnitten: - [Namensgebung](doc/Namensgebung.md) - [Kommentare](doc/Kommentare.md) - Testen - - [Allgemeine Tipps zum Testen](doc/Testen.md) - - [State Based vs. Interaction Based Testing](doc/State-Based-Vs-Interaction-Based.md) - - [Testen von Schnittstellen und Basisklassen](doc/Abstract-Test-Pattern.md) + - [Allgemeine Tipps zum Testen](doc/Testen.md) + - [State Based vs. Interaction Based Testing](doc/State-Based-Vs-Interaction-Based.md) + - [Testen von Schnittstellen und Basisklassen](doc/Abstract-Test-Pattern.md) - [Fehlerbehandlung](doc/Fehlerbehandlung.md) - [Best Practice](doc/Best-Practice.md) -Zur besseren Verdeutlichung der angesprochenen Themen sind diesem Projekt auch [Java Beispiele](./src/) angefügt, +Zur besseren Verdeutlichung der angesprochenen Themen sind diesem Projekt auch [Java Beispiele](./src/) angefügt, die sich möglichst genau an diese Richtlinien halten. -Ideen und Inhalte für diesen Styleguide lieferten verschiedene Bücher, insbesondere aber das Buch -"The Elements of Java Style" [1]. Diese Bücher sind allesamt wegweisend für die Softwareentwicklung und sind +Ideen und Inhalte für diesen Styleguide lieferten verschiedene Bücher, insbesondere aber das Buch +"The Elements of Java Style" [1]. Diese Bücher sind allesamt wegweisend für die Softwareentwicklung und sind damit Pflichtlektüre für Berufstätige in der Softwareentwicklung: - [1] "The Elements of Java Style", Vermeulen, Ambler, Bumgardner, Metz, Misfeldt, Shur und Thompson, Cambridge University Press, 2000 - [2] "The Pragmatic Programmer. From Journeyman to Master", Andrew Hunt, David Thomas, Ward Cunningham, Addison Wesley, 1999 - [3] "Code Complete: A Practical Handbook of Software Construction", Steve McConnell, Microsoft Press, 2004 - [4] "Clean Code: A Handbook of Agile Software Craftsmanship", Robert C. Martin, Prentice Hall, 2008 - [5] "Effective Java", Third Edition, Joshua Bloch, Addison Wesley, 2017 -- [6] "Refactoring: Improving the Design of Existing Code", Martin Fowler, Addison Wesley, 1999 +- [6] "Refactoring: Improving the Design of Existing Code", Martin Fowler, Addison Wesley, 1999 - [7] "Java by Comparison", Simon Harrer, Jörg Lenhard, Linus Dietz, Pragmatic Programmers, 2018 Die gesamten Dokumente dieser Kodierungsrichtlinien unterliegen der -[Creative Commons Attribution 4.0 International Lizenz](https://creativecommons.org/licenses/by/4.0/). Der +[Creative Commons Attribution 4.0 International Lizenz](https://creativecommons.org/licenses/by/4.0/). Der Quelltext aller Beispiele und Klassen unterliegt der [MIT Lizenz](https://en.wikipedia.org/wiki/MIT_License). diff --git a/README.md b/README.md index 193f8d636..06661f4b6 100644 --- a/README.md +++ b/README.md @@ -8,24 +8,25 @@ Each Java project should follow a consistent coding style. All contributions should follow the same formatting rules, design principles, code patterns, and idioms. -This coding style provides the set of rules that I am using in my lectures about software development at Munich University of Applied Sciences. +This coding style provides the set of rules that I am using in my lectures about software development at Munich University of Applied Sciences. -This project describes the coding style in detail (currently only available in German) and serves as a template project. +This project describes the coding style in detail (currently only available in German) and serves as a template project. It provides all necessary resources for a Java project to enforce this coding style using the following static analysis tools via Maven (and partly in IntelliJ): - [Checkstyle](https://checkstyle.org) +- [Error Prone](https://errorprone.info) - [PMD](https://pmd.github.io/) - [SpotBugs](https://spotbugs.github.io) -- [Error Prone](https://errorprone.info) +- [Spotless](https://github.com/diffplug/spotless) -❗This project requires a JDK version of 21 or higher.❗ +❗This project requires a JDK version of 21 or higher.❗ -Moreover, this project provides some sample classes that already use this style guide. -These classes can be used as such but are not required in this project. -These classes also use some additional libraries that are included using the Maven dependency mechanism. +Moreover, this project provides some sample classes that already use this style guide. +These classes can be used as such but are not required in this project. +These classes also use some additional libraries that are included using the Maven dependency mechanism. If the sample classes are deleted, then the dependencies can be safely deleted, too. -This project and the associated static analysis tools are already running in continuous integration: an example CI pipeline is active for GitHub Actions. -For [Jenkins](https://jenkins.io/) a full CI pipeline has been configured that includes stages to compile, test, run static code analysis, run code coverage analysis, and run mutation coverage analysis, see section [Continuous Integration](doc/Continuous-Integration.md) for details. +This project and the associated static analysis tools are already running in continuous integration: an example CI pipeline is active for GitHub Actions. +For [Jenkins](https://jenkins.io/) a full CI pipeline has been configured that includes stages to compile, test, run static code analysis, run code coverage analysis, and run mutation coverage analysis, see section [Continuous Integration](doc/Continuous-Integration.md) for details. Additionally, some development tools are configured in this GitHub project to evaluate the quality of pull requests, see section [integration of external tools](doc/Externe-Tool-Integration.md). Content of the style guide (only in German): @@ -33,23 +34,23 @@ Content of the style guide (only in German): - [Namensgebung](doc/Namensgebung.md) - [Kommentare](doc/Kommentare.md) - Testen - - [Allgemeine Tipps zum Testen](doc/Testen.md) - - [State Based vs. Interaction Based Testing](doc/State-Based-Vs-Interaction-Based.md) - - [Testen von Schnittstellen und Basisklassen](doc/Abstract-Test-Pattern.md) +- [Allgemeine Tipps zum Testen](doc/Testen.md) +- [State Based vs. Interaction Based Testing](doc/State-Based-Vs-Interaction-Based.md) +- [Testen von Schnittstellen und Basisklassen](doc/Abstract-Test-Pattern.md) - [Fehlerbehandlung](doc/Fehlerbehandlung.md) - [Best Practice](doc/Best-Practice.md) -A lot of ideas in this style are based on the following path-breaking books about software development: +A lot of ideas in this style are based on the following path-breaking books about software development: - [1] "The Elements of Java Style", Vermeulen, Ambler, Bumgardner, Metz, Misfeldt, Shur und Thompson, Cambridge University Press, 2000 - [2] "The Pragmatic Programmer: journey to mastery", Second Edition, Andrew Hunt, David Thomas, Addison Wesley, 2019 - [3] "Code Complete: A Practical Handbook of Software Construction", Steve McConnell, Microsoft Press, 2004 - [4] "Clean Code: A Handbook of Agile Software Craftsmanship", Robert C. Martin, Prentice Hall, 2008 - [5] "Effective Java", Third Edition, Joshua Bloch, Addison Wesley, 2017 -- [6] "Refactoring: Improving the Design of Existing Code", Martin Fowler, Addison Wesley, 1999 +- [6] "Refactoring: Improving the Design of Existing Code", Martin Fowler, Addison Wesley, 1999 - [7] "Java by Comparison", Simon Harrer, Jörg Lenhard, Linus Dietz, Pragmatic Programmers, 2018 -All documents in this project use the [Creative Commons Attribution 4.0 International License](https://creativecommons.org/licenses/by/4.0/). +All documents in this project use the [Creative Commons Attribution 4.0 International License](https://creativecommons.org/licenses/by/4.0/). Source code (snippets, examples, and classes) are using the [MIT license](https://en.wikipedia.org/wiki/MIT_License). [![License: MIT](https://img.shields.io/badge/license-MIT-yellow.svg)](https://en.wikipedia.org/wiki/MIT_License) diff --git a/doc/Abstract-Test-Pattern.md b/doc/Abstract-Test-Pattern.md index baebe4cf6..ffc3bfd4b 100644 --- a/doc/Abstract-Test-Pattern.md +++ b/doc/Abstract-Test-Pattern.md @@ -1,17 +1,17 @@ # Abstract Test Pattern -Mit dem **Abstract Test Pattern** lassen sich Schnittstellenverträge (d.h. Interfaces) und abstrakte Klassen +Mit dem **Abstract Test Pattern** lassen sich Schnittstellenverträge (d.h. Interfaces) und abstrakte Klassen testen. Damit kann sichergestellt werden, dass Subklassen (bzw. Klassen, die ein gegebenes Interface implementieren) sich an den vereinbarten Vertrag halten. Das **Abstract Test Pattern** ist prinzipiell eine Komposition -aus zwei anderen Design Patterns: dem **Template Method Pattern** und dem **Factory Method Pattern**. -Der konkrete Testfall wird als eine Template Method umgesetzt. Das darin benötigte Subject under Test wird durch eine +aus zwei anderen Design Patterns: dem **Template Method Pattern** und dem **Factory Method Pattern**. +Der konkrete Testfall wird als eine Template Method umgesetzt. Das darin benötigte Subject under Test wird durch eine abstrakte Factory Method erzeugt, die von der jeweiligen konkreten Subtestklasse überschrieben werden muss. An Beispielen lässt sich diese Herangehensweise am besten zeigen. ## Testen des Schnittstellenvertrags von equals Im JDK ist für die Methode `Object.equals` ein recht umfangreiche Vertrag im JavaDoc formuliert. Hier der wichtigste - Teil als Ausschnitt: +Teil als Ausschnitt: ```java /** @@ -73,9 +73,9 @@ Jede Klasse, die `equals` überschreibt, kann den Schnittstellenvertrag mit folg Die restlichen Tests der zu überprüfenden Klassen werden anschließend wie gewohnt in der Testklasse kodiert. -## Testen des Comparable Schnittstellenvertrags +## Testen des Comparable Schnittstellenvertrags -Das gleiche Verfahren lässt sich auch mit Interfaces umsetzen. +Das gleiche Verfahren lässt sich auch mit Interfaces umsetzen. Z.B. muss das Interface `Comparable` so implementiert werden, dass die Operation `compareTo` symmetrisch ist: ```java @@ -93,9 +93,9 @@ Z.B. muss das Interface `Comparable` so implementiert werden, dass die Operation public int compareTo(T o); ``` -Ein dazu passender abstrakter Test könnte als -[AbstractComparableTest](../src/test/java/edu/hm/hafner/util/AbstractComparableTest.java) -folgendermaßen umgesetzt werden: +Ein dazu passender abstrakter Test könnte als +[AbstractComparableTest](../src/test/java/edu/hm/hafner/util/AbstractComparableTest.java) +folgendermaßen umgesetzt werden: ```java /** @@ -150,14 +150,14 @@ public abstract class AbstractComparableTest > { } ``` -## Testen des Serializable Schnittstellenvertrags +## Testen des Serializable Schnittstellenvertrags Als letztes Beispiel für das Abstract Test Pattern soll das `Serializable` Interface dienen. Interessanterweise ist dies -ein Markerinterface, d.h. ein Interface ohne Definition eigener Methoden. Der JavaDoc für das Interface ist allerdings +ein Markerinterface, d.h. ein Interface ohne Definition eigener Methoden. Der JavaDoc für das Interface ist allerdings sehr umfangreich und umfasst mehrere Seiten. Die Frage ist auch hier, wie man einen Test zur Verfügung stellen kann, der den Vertrag einer Klasse überprüft, die `Serializable` ist. U.A. muss folgender Punkt erfüllt sein: -Eine Instanz der Klasse muss mit einem `ObjectOutputStream` in einen Bytestream umgewandelt werden können. Anschließend -muss dieser Bytestream mit einem `ObjectInputStream` wieder zurück in eine Instanz der Klasse verwandelt werden können. +Eine Instanz der Klasse muss mit einem `ObjectOutputStream` in einen Bytestream umgewandelt werden können. Anschließend +muss dieser Bytestream mit einem `ObjectInputStream` wieder zurück in eine Instanz der Klasse verwandelt werden können. Die beiden Instanzen müssen hinterher gleich sein (bezüglich `equals`). ```java @@ -190,17 +190,17 @@ public abstract class SerializableTest extends ResourceT ``` Dieser Test kann auch als Ausgangsbasis für weitere Tests im Bereich `Serializable` verwendet werden. Z.B. wird in -dieser Test meinem Projekt [Static Analysis Model and Parsers Library](https://github.com/jenkinsci/analysis-model/) +dieser Test meinem Projekt [Static Analysis Model and Parsers Library](https://github.com/jenkinsci/analysis-model/) benutzt, um sicherzustellen, dass sich die Serialisierung einer Klasse sich nicht aus Versehen verändert, so -dass die mit einer alten Version der Bibliothek serialisierten Daten plötzlich in der neuen Version nicht mehr -einlesbar sind: -[IssueTest#shouldReadIssueFromOldSerialization](https://github.com/jenkinsci/analysis-model/blob/master/src/test/java/edu/hm/hafner/analysis/IssueTest.java#L306). +dass die mit einer alten Version der Bibliothek serialisierten Daten plötzlich in der neuen Version nicht mehr +einlesbar sind: +[IssueTest#shouldReadIssueFromOldSerialization](https://github.com/jenkinsci/analysis-model/blob/master/src/test/java/edu/hm/hafner/analysis/IssueTest.java#L306). Dazu wird der gleiche Mechanismus benötigt, zusätzlich muss die Serialisierung einer alten Instanz -als Datei abgelegt werden. - +als Datei abgelegt werden. + ## Typische Anwendungsgebiete des Abstract Test Patterns -Neben solchen API Tests wird das Pattern hauptsächlich genutzt, um für den Code von abstrakten Klassen auch Testfälle +Neben solchen API Tests wird das Pattern hauptsächlich genutzt, um für den Code von abstrakten Klassen auch Testfälle zur Verfügung zu stellen. Diese Testfälle können dann von Subklassen einfach mitbenutzt werden. So kann sicher -gestellt werden, dass Subklassen den Vertrag einer Vererbungshierarchie nicht brechen und damit nicht unbewusst das +gestellt werden, dass Subklassen den Vertrag einer Vererbungshierarchie nicht brechen und damit nicht unbewusst das [Liskov Substitution Principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle) verletzen. diff --git a/doc/Arbeiten-mit-GitHub.md b/doc/Arbeiten-mit-GitHub.md index f398f7bef..709eb56b3 100644 --- a/doc/Arbeiten-mit-GitHub.md +++ b/doc/Arbeiten-mit-GitHub.md @@ -9,22 +9,23 @@ Schritte erforderlich. ## Einen Fork erstellen -Wer einen Pull Request mit seinen Ergebnissen stellen will, braucht zunächst einen +Wer einen Pull Request mit seinen Ergebnissen stellen will, braucht zunächst einen [Fork](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) des entsprechenden Projektes. Dies -geht nicht mit der Kommandozeile, sondern nur in der GitHub Oberfläche, siehe +geht nicht mit der Kommandozeile, sondern nur in der GitHub Oberfläche, siehe [Anleitung](https://help.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository). Dieser neu erstellte Fork ist zunächst eine exakte Kopie des Ausgangsprojektes, d.h. er enthält alle Commits, -Branches und Tags. +Branches und Tags. ## Mit dem Fork arbeiten Sobald der Fork erstellt wurde, ist dieser unter dem eigenen GitHub Benutzerkonto als Kopie sichtbar. Diese Kopie kann -dann mit folgendem Kommando auf den eigenen Rechner geholt werden: +dann mit folgendem Kommando auf den eigenen Rechner geholt werden: ```shell # Clone your fork to your local machine using SSH git clone git@github.com:USERNAME/FORKED-PROJECT.git ``` + Falls noch kein SSH Schlüssel auf GitHub hinterlegt ist, lässt sich das alternativ auch mit HTTPS erledigen: ```shell @@ -32,8 +33,8 @@ Falls noch kein SSH Schlüssel auf GitHub hinterlegt ist, lässt sich das altern git clone https://github.com/USERNAME/FORKED-PROJECT.git ``` -Für die einfache passwort-freie Nutzung von GitHub empfehle ich die -[Einrichtung von SSH](https://help.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh) +Für die einfache passwort-freie Nutzung von GitHub empfehle ich die +[Einrichtung von SSH](https://help.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh) möglichst schnell nachzuholen. ## Eigene Änderungen entwickeln @@ -54,14 +55,14 @@ git branch newfeature git checkout newfeature ``` -Nun geht es ans Programmieren und alle Änderungen werden Schritt für Schritt erstellt. +Nun geht es ans Programmieren und alle Änderungen werden Schritt für Schritt erstellt. Hier hat sich das Test Driven Development bewährt, doch das soll nicht Teil dieser Anleitung sein (siehe [Kapitel Testen](Testen.md) in meinen Kodierungsrichtlinien). Ein weitere sinnvolle Vorgehensweise ist das schrittweise Entwickeln: Die Entwicklung wird nicht in einem Rutsch durchgeführt und dann mit einem Commit abgeschlossen, sondern in mehreren Iterationen. Jeder Schritt, der fehlerfrei übersetzt werden kann und bei dem danach alle Tests durchlaufen, sollte einzeln mit einem -Commit abgeschlossen werden. Dann lassen sich die Änderungen hinterher besser nachvollziehen. +Commit abgeschlossen werden. Dann lassen sich die Änderungen hinterher besser nachvollziehen. Beim Commit ist noch wichtig, eine gute Commit-Message zu vergeben, Chris Beam hat hierzu den hilfreichen Artikel [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/) @@ -71,7 +72,7 @@ geschrieben, der dies gut erklärt. Sobald alle Änderungen lokal mit einem Commit abgeschlossen wurden, können diese in den Fork auf GitHub integriert werden. Dazu ist lediglich ein Push erforderlich: - + ```shell # Push the local branch newfeature to a remote branch in the forked repository (using the same name) git push --set-upstream origin newfeature @@ -79,29 +80,29 @@ git push --set-upstream origin newfeature Nun sind diese Änderungen auch Online im eigenen GitHub Projekt sichtbar. GitHub erkennt dort automatisch, dass ein neuer Branch angelegt wurde und bietet eine entsprechende Schaltfläche in der Oberfläche an. -Alternativ kann auch über den +Alternativ kann auch über den [Pull Request Dialog](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork) -ein neuer Pull Request angelegt werden. +ein neuer Pull Request angelegt werden. Beim Anlegen des Pull Request muss nun ein Titel und eine Beschreibung eingegeben werden. Der Titel sollte den Namen der Aufgabe enthalten, die Beschreibung ggf. weitere Details dazu. Verwenden von Anrede, Grußformel oder Schlussformel -sind nicht sinnvoll. +sind nicht sinnvoll. -**Vor** dem finalen Anlegen des Pull Request muss geprüft werden, ob der Pull Request die gewünschten Änderungen -enthält - und auch nur diese! Dazu den Abschnitt Files im Dialog öffnen und die einzelnen Änderungen durchgehen. Tauchen dort +**Vor** dem finalen Anlegen des Pull Request muss geprüft werden, ob der Pull Request die gewünschten Änderungen +enthält - und auch nur diese! Dazu den Abschnitt Files im Dialog öffnen und die einzelnen Änderungen durchgehen. Tauchen dort Änderungen auf, die nichts mit der Abgabe zu tun haben, so sind diese zu entfernen. Typischerweise sind dies Umformatierungen oder Leerzeilenänderungen -an nicht beteiligten Abschnitten oder gar komplett andere Dateien. +an nicht beteiligten Abschnitten oder gar komplett andere Dateien. -Um solche Änderungen in den Pull Request zu integrieren, müssen diese mit dem bereits beschriebenen Workflow umgesetzt +Um solche Änderungen in den Pull Request zu integrieren, müssen diese mit dem bereits beschriebenen Workflow umgesetzt werden: im Editor die Änderungen an den entsprechenden Dateien vornehmen, Commit lokal ausführen und dann wieder -mit Push auf das GitHub Projekt bringen. +mit Push auf das GitHub Projekt bringen. -Schaut der Pull Request dann wie gewünscht aus, so kann er mit *Create* erzeugt werden. +Schaut der Pull Request dann wie gewünscht aus, so kann er mit *Create* erzeugt werden. ## Den Pull Request aktualisieren -Sobald der Pull Request erzeugt wurde, wird dieser mit verschiedenen Tools automatisch überprüft. Welche Tools zum Tragen -kommen, hängt individuell vom Projekt ab. Typischerweise wird eine [Continuous Integration](Continuous-Integration.md) +Sobald der Pull Request erzeugt wurde, wird dieser mit verschiedenen Tools automatisch überprüft. Welche Tools zum Tragen +kommen, hängt individuell vom Projekt ab. Typischerweise wird eine [Continuous Integration](Continuous-Integration.md) gestartet, die einen Entwicklungs-Lebenszyklus ausführt: 1. Compile @@ -110,18 +111,18 @@ gestartet, die einen Entwicklungs-Lebenszyklus ausführt: Jeder dieser Schritte wird in GitHub mit einem *Ok* oder *Failed* Status markiert. Ist einer der Schritte mit *Failed* markiert, muss der Pull Request überarbeitet werden. Dazu muss der Fehler analysiert werden und dann der Quelltext -an den passenden Stellen aktualisiert werden, sei es bei Compile- oder Testfehlern, bei Unterschreitung der geforderten +an den passenden Stellen aktualisiert werden, sei es bei Compile- oder Testfehlern, bei Unterschreitung der geforderten Testabdeckung oder bei Verstößen gegen die Kodierungsrichtlinien. -Sind alle automatischen Tests auf *Ok*, fehlt nur noch das Review des Autors des Original Projektes. Dies erfolgt +Sind alle automatischen Tests auf *Ok*, fehlt nur noch das Review des Autors des Original Projektes. Dies erfolgt zeilenweise ebenfalls im Pull Request und kann mit den gleichen Schritten wie oben beschrieben eingearbeitet werden. -Normalerweise erkennt GitHub diese Änderungen automatisch, diese müssen daher nicht explizit als *Gelöst* markiert -werden. +Normalerweise erkennt GitHub diese Änderungen automatisch, diese müssen daher nicht explizit als *Gelöst* markiert +werden. ## Den Fork aktuell halten Wichtig ist, diesen Fork immer aktuell zu halten, d.h. die Änderungen des Ausgangsprojektes -immer nachzuziehen. In den meisten Fällen genügt es, den sogenannten `master` Branch synchron zu halten. +immer nachzuziehen. In den meisten Fällen genügt es, den sogenannten `master` Branch synchron zu halten. Um dies zu ermöglichen, muss das Original Projekt als ein weiteres Remote hinzugefügt werden. Dazu hat sich der Name `upstream` eingebürgert. Dies lässt sich mit folgendem Kommando umsetzen: @@ -146,7 +147,7 @@ upstream https://github.com/UPSTREAM-USER/ORIGINAL-PROJECT.git (fetch) upstream https://github.com/UPSTREAM-USER/ORIGINAL-PROJECT.git (push) ``` -Immer dann, wenn die Änderungen des `master` Branches vom Originalprojekt integriert werden sollen, können diese +Immer dann, wenn die Änderungen des `master` Branches vom Originalprojekt integriert werden sollen, können diese mit folgendem Kommando eingebunden werden: ```shell @@ -161,5 +162,5 @@ git checkout master git merge upstream/master ``` -Normalerweise sollte auf dem lokalen `master` Branch keine anderen Commits sein, daher wird ein -[fast-forward](https://git-scm.com/book/de/v2/Git-Branching-Einfaches-Branching-und-Merging) angewendet. +Normalerweise sollte auf dem lokalen `master` Branch keine anderen Commits sein, daher wird ein +[fast-forward](https://git-scm.com/book/de/v2/Git-Branching-Einfaches-Branching-und-Merging) angewendet. diff --git a/doc/Arbeiten-mit-GitLab.md b/doc/Arbeiten-mit-GitLab.md index 6a67f9f19..626d210b5 100644 --- a/doc/Arbeiten-mit-GitLab.md +++ b/doc/Arbeiten-mit-GitLab.md @@ -4,83 +4,83 @@ Disclaimer: Die folgende Anleitung ist unter großer Hilfe der folgenden beiden # Arbeiten mit GitLab -Für Abgaben zu allen meinen Veranstaltungen benutze ich das vom LRZ betriebene [GitLab](https://gitlab.lrz.de/): -GitLab bietet Studierenden **und** Lehrenden eine einfache Möglichkeit, Aufgaben für Praktika in einem Git Projekt zu verwalten. +Für Abgaben zu allen meinen Veranstaltungen benutze ich das vom LRZ betriebene [GitLab](https://gitlab.lrz.de/): +GitLab bietet Studierenden **und** Lehrenden eine einfache Möglichkeit, Aufgaben für Praktika in einem Git Projekt zu verwalten. Die Nutzung bietet folgende Vorteile: -- Sie lernen die gleiche Arbeitsweise kennen, die auch in der Industrie und vielen Open-Source-Projekten verwendet wird. -So sind Sie ideal auf die Praxis vorbereitet. +- Sie lernen die gleiche Arbeitsweise kennen, die auch in der Industrie und vielen Open-Source-Projekten verwendet wird. +So sind Sie ideal auf die Praxis vorbereitet. - Sie haben eine ausgereifte Oberfläche, mit der Sie Ihre Abgaben verwalten können: - - Darstellung von Commits - - Reviews von Merge Requests - - Nachverfolgung von offenen Punkten und Fehlern - - Automatische Builds - - Automatische Sicherung durch Backups -- Ich habe eine einfache Möglichkeit, private Repositories auf Basis eines Templates für Abgaben zu erstellen. -Die Aufgaben können sowohl als Einzel- oder Teamaufgabe konzipiert sein. +- Darstellung von Commits +- Reviews von Merge Requests +- Nachverfolgung von offenen Punkten und Fehlern +- Automatische Builds +- Automatische Sicherung durch Backups +- Ich habe eine einfache Möglichkeit, private Repositories auf Basis eines Templates für Abgaben zu erstellen. +Die Aufgaben können sowohl als Einzel- oder Teamaufgabe konzipiert sein. Die Steuerung der Berechtigungen erfolgt automatisch. -Die Voraussetzung zur Nutzung von GitLab in unserer Veranstaltung ist der normale Account unserer Hochschule. -Dieser Account wird automatisch für Sie eingerichtet, wenn Sie sich an der Hochschule einschreiben. +Die Voraussetzung zur Nutzung von GitLab in unserer Veranstaltung ist der normale Account unserer Hochschule. +Dieser Account wird automatisch für Sie eingerichtet, wenn Sie sich an der Hochschule einschreiben. Ich verwende die E-Mail-Adresse, die Sie von der Hochschule erhalten, um Sie in das jeweilige GitLab Projekt einzuladen. -Je nach Aufgabenstellung und Kurs erhalten Sie pro Abgabe eigene Projekte oder ein Projekt, das über mehrere Abgaben geht. -In jedem Fall erhalten Sie (oder Ihr Team) dann ein eigenes Git Repository, indem Sie alle Dateien (Dokumente, Programmtexte etc.) Ihrer Abgaben hinzufügen. -Die Repositories sind immer als "privat" markiert, d. h. nur Sie (und ggf. Ihre Teammitglieder) können den Inhalt sehen und verändern. -Im ersten Semester können Sie zum Hochladen die GitLab Oberfläche nutzen. +Je nach Aufgabenstellung und Kurs erhalten Sie pro Abgabe eigene Projekte oder ein Projekt, das über mehrere Abgaben geht. +In jedem Fall erhalten Sie (oder Ihr Team) dann ein eigenes Git Repository, indem Sie alle Dateien (Dokumente, Programmtexte etc.) Ihrer Abgaben hinzufügen. +Die Repositories sind immer als "privat" markiert, d. h. nur Sie (und ggf. Ihre Teammitglieder) können den Inhalt sehen und verändern. +Im ersten Semester können Sie zum Hochladen die GitLab Oberfläche nutzen. Je erfahrener Sie werden, umso schneller können Sie den direkten Zugang über die Versionsverwaltung Git nutzen, das macht dann vieles einfacher und komfortabler. Ab dem dritten Semester ist die Nutzung von Merge Requests Pflicht für alle Abgaben. -Wenn Sie weitere Fragen zu GitLab haben, nutzen Sie bitte auch die [Onlinehilfe](https://docs.gitlab.com). -Fragen können Sie auch direkt im Praktikum (oder im jeweiligen Moodle Forum der Veranstaltung) stellen. +Wenn Sie weitere Fragen zu GitLab haben, nutzen Sie bitte auch die [Onlinehilfe](https://docs.gitlab.com). +Fragen können Sie auch direkt im Praktikum (oder im jeweiligen Moodle Forum der Veranstaltung) stellen. In den nachfolgenden Abschnitten sind die wichtigsten Punkte kurz zusammengefasst. ## Mit dem Repository arbeiten -Damit die Aufgaben bewertet werden können, müssen Sie Ihren Quelltext bzw. Ihre Dokumente in das jeweilige GitLab Repository hochladen. +Damit die Aufgaben bewertet werden können, müssen Sie Ihren Quelltext bzw. Ihre Dokumente in das jeweilige GitLab Repository hochladen. Git ist ein [verteiltes Versionsmanagement System](https://git-scm.com/book/de/v2/Verteiltes-Git-Verteilter-Arbeitsablauf), d. h. Sie finden eine Kopie dieses Repositories (neben dem Original auf GitLab) auf Ihrem Rechner und auf den Rechnern Ihrer Teammitglieder. ### Über die Entwicklungsumgebung das Projekt verwalten -Am einfachsten nutzen Sie nur die Entwicklungsumgebung IntelliJ, um Ihr Projekt mit GitLab zu synchronisieren. +Am einfachsten nutzen Sie nur die Entwicklungsumgebung IntelliJ, um Ihr Projekt mit GitLab zu synchronisieren. #### Import mit der Entwicklungsumgebung -Nach dem Start der Entwicklungsumgebung haben Sie die Möglichkeit, Ihr Projekt direkt zu importieren: Mit der Aktion **Get from VCS** (im Startup-Wizard) oder dem Menüpunkt **File->New->Project from Version Control...** lässt sich das GitLab Projekt automatisch nach IntelliJ importieren. -IntelliJ kümmert sich ab dann automatisch über die Verbindung zu GitLab. -Kopieren Sie dazu Ihren Repository Link in das Feld **Repository URL->URL** und bestätigen Sie den Import mit **Clone**. -Beachten Sie, dass Sie sich beim Import über HTTPS authentifizieren müssen. -Dazu müssen Sie ein Personal-Access-Token in GitLab erzeugen, die Authentifizierung via Browser funktioniert **nicht**. +Nach dem Start der Entwicklungsumgebung haben Sie die Möglichkeit, Ihr Projekt direkt zu importieren: Mit der Aktion **Get from VCS** (im Startup-Wizard) oder dem Menüpunkt **File->New->Project from Version Control...** lässt sich das GitLab Projekt automatisch nach IntelliJ importieren. +IntelliJ kümmert sich ab dann automatisch über die Verbindung zu GitLab. +Kopieren Sie dazu Ihren Repository Link in das Feld **Repository URL->URL** und bestätigen Sie den Import mit **Clone**. +Beachten Sie, dass Sie sich beim Import über HTTPS authentifizieren müssen. +Dazu müssen Sie ein Personal-Access-Token in GitLab erzeugen, die Authentifizierung via Browser funktioniert **nicht**. Das Access-Token können Sie einfach über den IntelliJ Dialog erzeugen, der dann die passende Einstellungsseite auf GitLab aufruft. -Nachdem das Projekt auf Ihren Rechner übertragen wurde, wird es in IntelliJ importiert und gebaut. -Je nach bestehender Konfiguration müssen Sie dazu noch im Konfigurationsdialog **File->Project Structure->SDKs** ein JDK 21 unter dem Namen "21" anlegen und referenzieren. -Achtung: das aktuelle JDK 25 nutze ich noch nicht, da viele Bibliotheken und Werkzeuge noch nicht darauf angepasst sind. -Ist das erledigt, können Sie die Tests in Ihrem Projekt starten. -Dazu können Sie auf das Projekt mit rechts klicken und **Run All Tests** auswählen. +Nachdem das Projekt auf Ihren Rechner übertragen wurde, wird es in IntelliJ importiert und gebaut. +Je nach bestehender Konfiguration müssen Sie dazu noch im Konfigurationsdialog **File->Project Structure->SDKs** ein JDK 25 unter dem Namen "25" anlegen und referenzieren. +Achtung: Bitte immer nur ein JDK 25 (LTS Release) nutzen und keine neuere Version verwenden, da viele Bibliotheken und Werkzeuge evtl. noch nicht darauf angepasst sind. +Ist das erledigt, können Sie die Tests in Ihrem Projekt starten. +Dazu können Sie auf das Projekt mit rechts klicken und **Run All Tests** auswählen. In den meisten Projekten habe ich schon eine spezielle **All Tests** Run-Konfiguration angelegt, die Sie auch direkt nutzen können. Im Spezifikationsprojekt sind noch keine Tests enthalten, dort finden sich nur die Spezifikationsdateien als AsciiDoc Dateien. #### Hochladen von Änderungen -Nach Veränderung Ihrer Dateien werden diese farblich in IntelliJ markiert. -Mit dem Kommando **Git->Commit** spielen Sie Ihre Änderungen zurück nach GitLab. -Geben Sie dann im neuen Dialog eine Commitbeschreibung ein (was haben Sie geändert?) und bestätigen Sie den Dialog mit **Commit and Push**. -IntelliJ führt dann einen lokalen **Commit** aus und speichert Ihre Änderung im Git Repository **lokal**. -Damit die Änderungen auch in GitLab sichtbar werden, wird anschließend Ihr komplettes Repository nach GitLab übertragen. +Nach Veränderung Ihrer Dateien werden diese farblich in IntelliJ markiert. +Mit dem Kommando **Git->Commit** spielen Sie Ihre Änderungen zurück nach GitLab. +Geben Sie dann im neuen Dialog eine Commitbeschreibung ein (was haben Sie geändert?) und bestätigen Sie den Dialog mit **Commit and Push**. +IntelliJ führt dann einen lokalen **Commit** aus und speichert Ihre Änderung im Git Repository **lokal**. +Damit die Änderungen auch in GitLab sichtbar werden, wird anschließend Ihr komplettes Repository nach GitLab übertragen. Achtung: Ab dem dritten Semester ist die Nutzung von Merge Requests Pflicht für alle Abgaben. Das heißt, Sie müssen Ihre Änderungen in einem neuen Branch speichern und dann einen Merge Request in GitLab anlegen. Ein Commit direkt in den `main` Branch ist dann nicht mehr erlaubt. ### Mit der Kommandozeile arbeiten -Alternativ können Sie für diese Schritte auch die Kommandozeile nutzen. -Dazu sind die in den nachfolgenden Abschnitten beschriebenen Schritte erforderlich. +Alternativ können Sie für diese Schritte auch die Kommandozeile nutzen. +Dazu sind die in den nachfolgenden Abschnitten beschriebenen Schritte erforderlich. #### Das Repository auf den eigenen Rechner holen -Zunächst müssen Sie das Repository auf den eigenen Rechner holen. -Git ist ein [verteiltes Versionsmanagement System](https://git-scm.com/book/de/v2/Verteiltes-Git-Verteilter-Arbeitsablauf), d. h. Sie finden eine Kopie dieses Repositories (neben dem Original auf GitLab) auf Ihrem Rechner und auf den Rechnern Ihrer Teampartner. -Diese Kopie kann mit folgendem Kommando auf den eigenen Rechner geholt werden: +Zunächst müssen Sie das Repository auf den eigenen Rechner holen. +Git ist ein [verteiltes Versionsmanagement System](https://git-scm.com/book/de/v2/Verteiltes-Git-Verteilter-Arbeitsablauf), d. h. Sie finden eine Kopie dieses Repositories (neben dem Original auf GitLab) auf Ihrem Rechner und auf den Rechnern Ihrer Teampartner. +Diese Kopie kann mit folgendem Beispielkommando auf den eigenen Rechner geholt werden (die URL müssen Sie durch Ihr persönliches Projekt ersetzen): ```shell # Clone your repository to your local machine using SSH @@ -99,7 +99,7 @@ Für die einfache passwort-freie Nutzung von GitLab empfehle ich die [Einrichtun ### Eigene Änderungen entwickeln Für jede Abgabe (und für jede noch so kleine Änderung am Projekt) **muss** ein neuer Branch angelegt werden. -Die direkte Arbeit auf dem `main` Branch ist nicht sinnvoll und daher verboten. +Die direkte Arbeit auf dem `main` Branch ist nicht sinnvoll und daher verboten. Dieses Vorgehen nennt sich "Feature-Branch-Workflow" und ist in der [Atlassian Bitbucket Hilfe](https://www.atlassian.com/git/tutorials/comparing-workflows/feature-branch-workflow) umfassend beschrieben. Um einen Branch anzulegen, sind folgende Schritte nötig: @@ -115,62 +115,62 @@ git branch newfeature git checkout newfeature ``` -Nun geht es ans Editieren bzw. Programmieren und alle Änderungen werden Schritt für Schritt erstellt. +Nun geht es ans Editieren bzw. Programmieren und alle Änderungen werden Schritt für Schritt erstellt. Hier hat sich das Test-Driven-Development (TDD) bewährt, doch das soll nicht Teil dieser Anleitung sein (siehe [Kapitel Testen](Testen.md) in meinen Kodierungsrichtlinien). -Eine weitere sinnvolle Vorgehensweise ist das schrittweise Entwickeln: Die Entwicklung wird nicht in einem Rutsch durchgeführt und dann mit einem Commit abgeschlossen, sondern in mehreren Iterationen. -Jeder Schritt, der fehlerfrei übersetzt werden kann und bei dem alle Tests durchlaufen, sollte einzeln mit einem Commit abgeschlossen werden. -Dann lassen sich die Änderungen hinterher besser nachvollziehen. +Eine weitere sinnvolle Vorgehensweise ist das schrittweise Entwickeln: Die Entwicklung wird nicht in einem Rutsch durchgeführt und dann mit einem Commit abgeschlossen, sondern in mehreren Iterationen. +Jeder Schritt, der fehlerfrei übersetzt werden kann und bei dem alle Tests durchlaufen, sollte einzeln mit einem Commit abgeschlossen werden. +Dann lassen sich die Änderungen hinterher besser nachvollziehen. -Beim Commit ist es noch wichtig, eine gute Commit-Message zu vergeben, Chris Beam hat hierzu den hilfreichen Artikel [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/) geschrieben, der dies gut erklärt. +Beim Commit ist es noch wichtig, eine gute Commit-Message zu vergeben, Chris Beam hat hierzu den hilfreichen Artikel [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/) geschrieben, der dies gut erklärt. Das muss nicht so formal sein, wie dort beschrieben, hier sehen Sie einige Beispiele in meinem Projekt [codingstyle](https://github.com/uhafner/codingstyle/commits/main). -Je nach Aufgabenstellung gibt es im Projekt eine [GitLab Pipeline](https://docs.gitlab.com/ee/ci/pipelines/), die das Projekt nach jedem Commit neu baut und überprüft. -Siehe dazu auch das [Kapitel Continuous Integration](Continuous-Integration.md) bzw. [Autograding](Autograding.md). +Je nach Aufgabenstellung gibt es im Projekt eine [GitLab Pipeline](https://docs.gitlab.com/ee/ci/pipelines/), die das Projekt nach jedem Commit neu baut und überprüft. +Siehe dazu auch das [Kapitel Continuous Integration](Continuous-Integration.md) bzw. [Autograding](Autograding.md). ## Merge Request vorbereiten und stellen -Sobald alle Änderungen lokal mit einem Commit abgeschlossen wurden, können diese in das GitLab Projekt integriert werden. +Sobald alle Änderungen lokal mit einem Commit abgeschlossen wurden, können diese in das GitLab Projekt integriert werden. Dazu ist lediglich ein Push erforderlich: - + ```shell # Push the local branch newfeature to a remote branch in the forked repository (using the same name) git push --set-upstream origin newfeature ``` -Nun sind diese Änderungen auch Online im eigenen GitLab Projekt sichtbar. -GitLab erkennt dort automatisch, dass ein neuer Branch angelegt wurde und bietet eine entsprechende Schaltfläche in der Oberfläche an. -Alternativ kann auch über den [Merge Request Dialog](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) ein neuer Merge Request angelegt werden. +Nun sind diese Änderungen auch Online im eigenen GitLab Projekt sichtbar. +GitLab erkennt dort automatisch, dass ein neuer Branch angelegt wurde und bietet eine entsprechende Schaltfläche in der Oberfläche an. +Alternativ kann auch über den [Merge Request Dialog](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) ein neuer Merge Request angelegt werden. + +Beim Anlegen des Merge Request müssen nun ein Titel und eine Beschreibung eingegeben werden. +Der Titel sollte den Namen der Aufgabe enthalten, die Beschreibung ggf. weitere Details dazu. Verwenden von Anrede, Grußformel oder Schlussformel ist nicht sinnvoll. -Beim Anlegen des Merge Request müssen nun ein Titel und eine Beschreibung eingegeben werden. -Der Titel sollte den Namen der Aufgabe enthalten, die Beschreibung ggf. weitere Details dazu. Verwenden von Anrede, Grußformel oder Schlussformel ist nicht sinnvoll. +Noch ein Hinweis in eigener Sache: Bitte weisen Sie den Merge Request **nie** mir zu. +Ebenso bitte **keine Review Wünsche** an mich eintragen, bei mehr als 50 Personen pro Veranstaltung und vielen Abgaben pro Semester wird es sonst in meinem Postfach schnell unübersichtlich. +Ich bewerte Ihre Abgaben automatisch nach Ablauf der Abgabefrist. +Das kann je nach Personenzahl auch einmal dauern. -Noch ein Hinweis in eigener Sache: Bitte weisen Sie den Merge Request **nie** mir zu. -Ebenso bitte **keine Review Wünsche** an mich eintragen, bei mehr als 50 Personen pro Veranstaltung und vielen Abgaben pro Semester wird es sonst in meinem Postfach schnell unübersichtlich. -Ich bewerte Ihre Abgaben automatisch nach Ablauf der Abgabefrist. -Das kann je nach Personenzahl auch einmal dauern. - -**Vor** dem finalen Anlegen des Merge Request muss geprüft werden, ob der Merge Request die gewünschten Änderungen enthält – und auch nur diese! -Dazu den Abschnitt Files im Dialog öffnen und die einzelnen Änderungen durchgehen. -Tauchen dort Änderungen auf, die nichts mit der Abgabe zu tun haben, so sind diese zu entfernen. -Typischerweise sind dies Umformatierungen oder Leerzeilenänderungen an nicht beteiligten Abschnitten oder gar komplett andere Dateien (z. B. aus der Entwicklungsumgebung). +**Vor** dem finalen Anlegen des Merge Request muss geprüft werden, ob der Merge Request die gewünschten Änderungen enthält – und auch nur diese! +Dazu den Abschnitt Files im Dialog öffnen und die einzelnen Änderungen durchgehen. +Tauchen dort Änderungen auf, die nichts mit der Abgabe zu tun haben, so sind diese zu entfernen. +Typischerweise sind dies Umformatierungen oder Leerzeilenänderungen an nicht beteiligten Abschnitten oder gar komplett andere Dateien (z. B. aus der Entwicklungsumgebung). -Um solche Änderungen zu entfernen und damit den Merge Request zu säubern, müssen diese mit dem bereits beschriebenen Workflow umgesetzt werden: im Editor die Änderungen an den entsprechenden Dateien vornehmen, Commit lokal ausführen und dann wieder mit Push auf das GitLab Projekt bringen. +Um solche Änderungen zu entfernen und damit den Merge Request zu säubern, müssen diese mit dem bereits beschriebenen Workflow umgesetzt werden: im Editor die Änderungen an den entsprechenden Dateien vornehmen, Commit lokal ausführen und dann wieder mit Push auf das GitLab Projekt bringen. Schaut der Merge Request dann wie gewünscht aus, so kann er mit *Create* erzeugt werden. -Falls Sie noch nicht sicher sind, ob dieser MR fertig ist, können Sie diesen auch zunächst als *Draft* erstellen. -Dann ist mir (und den Teammitgliedern) klar, dass der MR noch in Arbeit ist und kein abschließendes Feedback erwartet. +Falls Sie noch nicht sicher sind, ob dieser MR fertig ist, können Sie diesen auch zunächst als *Draft* erstellen. +Dann ist mir (und den Teammitgliedern) klar, dass der MR noch in Arbeit ist und kein abschließendes Feedback erwartet. ### Den Merge Request aktualisieren -Sobald der Merge Request erzeugt wurde, wird dieser i.A. mit verschiedenen Tools automatisch überprüft. +Sobald der Merge Request erzeugt wurde, wird dieser i.A. mit verschiedenen Tools automatisch überprüft. Welche Tools zum Tragen kommen, hängt individuell vom Projekt ab. Typischerweise wird eine [Continuous Integration](Continuous-Integration.md) gestartet, die einen Entwicklungs-Lebenszyklus ausführt: 1. Compile 2. Test 3. Analyze -Jeder dieser Schritte wird in GitLab mit einem *Ok* oder *Failed* Status markiert. +Jeder dieser Schritte wird in GitLab mit einem *Ok* oder *Failed* Status markiert. Ist einer der Schritte mit *Failed* markiert, muss der Merge Request überarbeitet werden. Dazu muss der Fehler analysiert und dann der Quelltext an den passenden Stellen aktualisiert werden, sei es bei Compile- oder Testfehlern, bei Unterschreitung der geforderten Testabdeckung oder bei Verstößen gegen die Kodierungsrichtlinien. Details dazu finden sich im Abschnitt [Autograding](Autograding.md). diff --git a/doc/Autograding.md b/doc/Autograding.md index ff7de4e50..6acc9190b 100644 --- a/doc/Autograding.md +++ b/doc/Autograding.md @@ -8,16 +8,16 @@ Für Abgaben zu allen meinen Veranstaltungen benutze ich das vom LRZ betriebene GitLab bietet Studierenden **und** Lehrenden eine einfache Möglichkeit, Aufgaben für Praktika in einem Git Projekt zu verwalten. Die Nutzung bietet folgende Vorteile: - Sie lernen die gleiche Arbeitsweise kennen, die auch in der Industrie und vielen Open-Source-Projekten verwendet wird. - So sind Sie ideal auf die Praxis vorbereitet. +So sind Sie ideal auf die Praxis vorbereitet. - Sie haben eine ausgereifte Oberfläche, mit der Sie Ihre Abgaben verwalten können: - - Darstellung von Commits - - Reviews von Merge Requests - - Nachverfolgung von offenen Punkten und Fehlern - - Automatische Builds - - Automatische Sicherung durch Backups +- Darstellung von Commits +- Reviews von Merge Requests +- Nachverfolgung von offenen Punkten und Fehlern +- Automatische Builds +- Automatische Sicherung durch Backups - Ich habe eine einfache Möglichkeit, private Repositories auf Basis eines Templates für Abgaben zu erstellen. - Die Aufgaben können sowohl als Einzel- oder Teamaufgabe konzipiert sein. - Die Steuerung der Berechtigungen erfolgt automatisch. +Die Aufgaben können sowohl als Einzel- oder Teamaufgabe konzipiert sein. +Die Steuerung der Berechtigungen erfolgt automatisch. Die Voraussetzung zur Nutzung von GitLab in unserer Veranstaltung ist der normale Account unserer Hochschule. Dieser Account wird automatisch für Sie eingerichtet, wenn Sie sich an der Hochschule einschreiben. @@ -35,75 +35,75 @@ Fragen können Sie auch direkt im Praktikum, in Moodle oder dem jeweiligen Matri ## Autograding Im Laufe Ihres Studiums lernen Sie in meinen Lehrveranstaltungen, dass Softwareentwicklung auch ein Handwerk ist, auf das man stolz sein kann. -Damit Sie das erreichen, ist es wichtig, nicht nur auf Funktionalität, sondern auch auf Qualität zu achten. -In meinem [Coding Style](https://github.com/uhafner/codingstyle) versuche ich, die dazu aus meiner Sicht wichtigsten Elemente vorzustellen. -Damit das nicht nur trockene Theorie bleibt, haben Sie die Möglichkeit, Ihre Abgaben automatisiert von verschiedenen Tools bewerten zu lassen. -So bekommen Sie ein schnelles Feedback zu Ihrer Lösung: +Damit Sie das erreichen, ist es wichtig, nicht nur auf Funktionalität, sondern auch auf Qualität zu achten. +In meinem [Coding Style](https://github.com/uhafner/codingstyle) versuche ich, die dazu aus meiner Sicht wichtigsten Elemente vorzustellen. +Damit das nicht nur trockene Theorie bleibt, haben Sie die Möglichkeit, Ihre Abgaben automatisiert von verschiedenen Tools bewerten zu lassen. +So bekommen Sie ein schnelles Feedback zu Ihrer Lösung: - Haben Sie bzw. Ihr Team alle Dateien korrekt hochgeladen, sodass alles ohne Fehler übersetzt werden kann? - Ist ihr Ergebnis richtig? D.h. besteht ihr Code die von mir vorgegebenen Tests? - Hält sich Ihr Code an den vorgegebenen Styleguide? - Enthält Ihr Code Bugs oder mögliche Fehlerquellen? - Haben Sie Ihren Code ausreichend getestet? -Diese Überprüfung - im Folgenden *Autograding* genannt - erfolgt auf dem Kubernetes-Cluster der Fakultät. +Diese Überprüfung - im Folgenden *Autograding* genannt - erfolgt auf dem Kubernetes-Cluster der Fakultät. Dort werden alle meine Projekte in GitLab überwacht: sobald ein neuer Commit entdeckt wird (d.h. Sie haben eine Änderung an einer Ihrer Dateien hochgeladen), werden diese Änderungen abgeholt und Ihr Projekt wird analysiert. -Nach der Analyse werden die Ergebnisse in Ihrem Projekt veröffentlicht (und je nach Veranstaltung) auch in Punkte umgerechnet, die in die Bewertung Ihrer Abgaben eingehen. +Nach der Analyse werden die Ergebnisse in Ihrem Projekt veröffentlicht (und je nach Veranstaltung) auch in Punkte umgerechnet, die in die Bewertung Ihrer Abgaben eingehen. In meiner Lehrveranstaltung Softwareengineering lernen Sie, dass diese Vorgehensweise inzwischen State-of-the-Art in Industrieprojekten ist: dort wird diese Technik *Continuous Integration* und *Continuous Deployment* genannt. -Die Analyse im *Autograding* umfasst folgende Schritte: - -1. Ihr Projekt wird kompiliert. Das ist prinzipiell das gleiche wie in Ihrer Entwicklungsumgebung. -Hier überprüft der Java Compiler, ob Sie sich an die Syntax der Sprache Java halten. -Haben Sie hier einen Fehler in Ihrer Lösung, dann bricht hier die Verarbeitung mit einer Fehlermeldung ab. -Prüfen Sie daher vorher lokal, ob Ihr Projekt fehlerfrei ist. -Das können Sie auf der Console über das Tool *Maven* erreichen, indem Sie das Kommando `mvn clean compile` aufrufen. -Am Ende der Verarbeitung sollte das Maven mit `[INFO] BUILD SUCCESS` bestätigen. -2. Automatisierte Tests zu den Aufgaben werden ausgeführt. -Zu jeder Abgabe habe ich einen oder mehrere Tests verfasst, die Ihre Lösung prüfen. -Diese Tests zeigen, ob Sie an alle Fallstricke gedacht haben. -Je nach Aufgabenstellung müssen Sie auch eigene Tests schreiben und hochladen: Diese Tests werden dann ebenfalls ausgeführt. -Auch diese Verarbeitung können Sie lokal prüfen, indem Sie das Kommando `mvn clean test` ausführen. +Die Analyse im *Autograding* umfasst folgende Schritte: + +1. Ihr Projekt wird kompiliert. Das ist prinzipiell das gleiche wie in Ihrer Entwicklungsumgebung. + Hier überprüft der Java Compiler, ob Sie sich an die Syntax der Sprache Java halten. + Haben Sie hier einen Fehler in Ihrer Lösung, dann bricht hier die Verarbeitung mit einer Fehlermeldung ab. + Prüfen Sie daher vorher lokal, ob Ihr Projekt fehlerfrei ist. + Das können Sie auf der Console über das Tool *Maven* erreichen, indem Sie das Kommando `mvn clean compile` aufrufen. + Am Ende der Verarbeitung sollte das Maven mit `[INFO] BUILD SUCCESS` bestätigen. +2. Automatisierte Tests zu den Aufgaben werden ausgeführt. + Zu jeder Abgabe habe ich einen oder mehrere Tests verfasst, die Ihre Lösung prüfen. + Diese Tests zeigen, ob Sie an alle Fallstricke gedacht haben. + Je nach Aufgabenstellung müssen Sie auch eigene Tests schreiben und hochladen: Diese Tests werden dann ebenfalls ausgeführt. + Auch diese Verarbeitung können Sie lokal prüfen, indem Sie das Kommando `mvn clean test` ausführen. 3. Ihre Klassen werden einer statischen Analyse unterzogen: dabei untersuchen verschiedene Tools Ihre Abgaben auf -typische Programmierfehler und auf die Einhaltung meiner [Kodierungsrichtlinien](https://github.com/uhafner/codingstyle). -Zum lokalen Starten dieser Analyse müssen Sie das Kommando `mvn clean verify` ausführen. -4. Falls Sie eigenen Tests geschrieben haben: Wie gut ist die Qualität dieser Tests? -Haben Sie alle Zeilen oder Zweige Ihres Codes benutzt (technisch: *Line und Branch Code Coverage*)? -Finden Ihre Tests Fehler, wenn Ihr Programm von mir mutwillig sabotiert wird (technisch: *Mutation Coverage*)? -Zum lokalen Starten dieser beiden Analysen müssen Sie das Kommando `mvn verify` (für die Code Coverage) bzw. `mvn verify -Ppit` (für die Mutation Coverage) ausführen. - -Für technisch Interessierte: Damit das ganze funktioniert, benötigt es eine [GitLab Autograding Pipeline](https://github.com/uhafner/autograding-gitlab-action/blob/main/.gitlab-ci.yml), die das Projekt kompiliert und dann mit meiner [Autograding GitLab Action](https://github.com/uhafner/autograding-gitlab-action) anreichert. + typische Programmierfehler und auf die Einhaltung meiner [Kodierungsrichtlinien](https://github.com/uhafner/codingstyle). + Zum lokalen Starten dieser Analyse müssen Sie das Kommando `mvn clean verify` ausführen. +4. Falls Sie eigenen Tests geschrieben haben: Wie gut ist die Qualität dieser Tests? + Haben Sie alle Zeilen oder Zweige Ihres Codes benutzt (technisch: *Line und Branch Code Coverage*)? + Finden Ihre Tests Fehler, wenn Ihr Programm von mir mutwillig sabotiert wird (technisch: *Mutation Coverage*)? + Zum lokalen Starten dieser beiden Analysen müssen Sie das Kommando `mvn verify` (für die Code Coverage) bzw. `mvn verify -Ppit` (für die Mutation Coverage) ausführen. + +Für technisch Interessierte: Damit das ganze funktioniert, benötigt es eine [GitLab Autograding Pipeline](https://github.com/uhafner/autograding-gitlab-action/blob/main/.gitlab-ci.yml), die das Projekt kompiliert und dann mit meiner [Autograding GitLab Action](https://github.com/uhafner/autograding-gitlab-action) anreichert. Diese Action ist Open Source und kann gerne auch in anderen Projekte verwendet werden. -Die Ergebnisse dieser Schritte können Sie für Ihre Abgaben sehr einfach nachvollziehen, indem Sie den bewerteten Commit oder Merge Request öffnen. +Die Ergebnisse dieser Schritte können Sie für Ihre Abgaben sehr einfach nachvollziehen, indem Sie den bewerteten Commit oder Merge Request öffnen. Um den Commit zu öffnen, muss man den Commit-Hash auswählen, z.B. in der Detailansicht eines Pipeline-Ergebnisses: ![Autograding Kommentar](images/gitlab-commit.png) Dann finden Sie dort jeweils einen GitLab Kommentar mit den Ergebnissen. - + ![Autograding Kommentar](images/gitlab-autograding.png) -Wenn neben dem Commit ein grüner Haken (✅) angezeigt wird, haben Sie Schritt 1 schon mal erfolgreich absolviert: Ihr Programm kompiliert fehlerfrei. +Wenn neben dem Commit ein grüner Haken (✅) angezeigt wird, haben Sie Schritt 1 schon mal erfolgreich absolviert: Ihr Programm kompiliert fehlerfrei. Bei einem roten Kreuz (❌) müssen Sie den Fehler beheben und nochmal neu hochladen. Die Ausgabe des Compilers finden Sie in der Konsole des jeweiligen GitLab Steps: ![Compiler Build Log](images/gitlab-console.png) -Eine Abgabe, die nicht übersetzbar ist, wird automatisch mit 0 Punkten bewertet. -Die Fehlermeldungen des Java Compiler sind manchmal für Neulinge etwas kryptisch, meist steht aber Zeilennummer und Ursache dabei, sodass das Problem hoffentlich schnell zu finden ist. +Eine Abgabe, die nicht übersetzbar ist, wird automatisch mit 0 Punkten bewertet. +Die Fehlermeldungen des Java Compiler sind manchmal für Neulinge etwas kryptisch, meist steht aber Zeilennummer und Ursache dabei, sodass das Problem hoffentlich schnell zu finden ist. Wenn nicht, wenn Sie sich an uns im Chat oder Praktikum, dann bekommen Sie auch dort Hilfe. Die Ergebnisse der Schritte 2 bis 4 (und damit die eigentlichen Punkte Ihrer Abgabe) können Sie durch die Detailansicht einsehen. -Das wichtigste Ergebnis für die Abgabe ist dann die Zusammenfassung aus dem Autograding. -Dort sehen Sie eine Punktezahl für die jeweilige Abgabe. -Die Punkte berechnen sich aus den jeweils konfigurierten Bestandteilen: aus den Testergebnissen (Anzahl der Testfehler), aus den Warnungen der statischen Analyse und (falls konfiguriert) aus der Coverage Ihrer Testfälle. -In den ersten Abgaben starten wir mit einer festen Punktezahl, von der jeweils Punkte bei Testfehlern oder bei Warnungen abgezogen werden. +Das wichtigste Ergebnis für die Abgabe ist dann die Zusammenfassung aus dem Autograding. +Dort sehen Sie eine Punktezahl für die jeweilige Abgabe. +Die Punkte berechnen sich aus den jeweils konfigurierten Bestandteilen: aus den Testergebnissen (Anzahl der Testfehler), aus den Warnungen der statischen Analyse und (falls konfiguriert) aus der Coverage Ihrer Testfälle. +In den ersten Abgaben starten wir mit einer festen Punktezahl, von der jeweils Punkte bei Testfehlern oder bei Warnungen abgezogen werden. Im späteren Verlauf kann das auch umgekehrt funktionieren: also Start bei 0 und jeder erfüllte Test bekommt Pluspunkte. ![Test and Analysis Results](images/actions-autograding.png) -Bei Testfehlern wird die Ausgabe der Tests direkt unter den Testpunkten als ausklappbarer Text angezeigt. -Bei den Warnungen erhalten Sie gezieltes Feedback direkt als Markierung innerhalb des Quelltextes. - +Bei Testfehlern wird die Ausgabe der Tests direkt unter den Testpunkten als ausklappbarer Text angezeigt. +Bei den Warnungen erhalten Sie gezieltes Feedback direkt als Markierung innerhalb des Quelltextes. + ![Annotation](images/actions-annotation.png) diff --git a/doc/Best-Practice.md b/doc/Best-Practice.md index 3eed390bd..9b044dcef 100644 --- a/doc/Best-Practice.md +++ b/doc/Best-Practice.md @@ -5,16 +5,18 @@ Diese sind in diesem Dokument unsortiert aufgeführt. ## Verwendung von redundanten Klammern -Runde Klammern steigern die Lesbarkeit, wenn in einer boolesche Bedingungen verschiedene Operatoren verwendet werden: +Runde Klammern steigern die Lesbarkeit, wenn in einer boolesche Bedingungen verschiedene Operatoren verwendet werden: + ```java if (onLeaf() || (treeLeft() && treeRight())) { ... } ``` -Klammern helfen die Intention zu verdeutlichen, auch wenn diese - wie in diesem Beispiel - nicht nötig wären, + +Klammern helfen die Intention zu verdeutlichen, auch wenn diese - wie in diesem Beispiel - nicht nötig wären, da über die Priorität der Operatoren das selbe Resultat erzielt -würde. Aber nicht jeder hat die -[Operatorreihenfolgetabelle](http://docs.oracle.com/javase/tutorial/java/nutsandbolts/operators.html) im Kopf. +würde. Aber nicht jeder hat die +[Operatorreihenfolgetabelle](http://docs.oracle.com/javase/tutorial/java/nutsandbolts/operators.html) im Kopf. Für unäre Operatoren wie die Negation `!` oder einfache binäre Bedingungen mit 2 Operanden sollten allerdings möglichst keine Klammern genutzt werden. @@ -68,6 +70,7 @@ auf eine Bildschirmseite passen. D.h. Scrolling ist weder horizontal noch vertik daher zwischen 1 und 10 Zeilen lang. Hin und wieder kann sich auch mal eine Methode mit 20 Zeilen einschleichen... Hier ein schönes Beispiel: + ```java boolean isEven(final long value) { return value % 2 == 0; @@ -110,16 +113,16 @@ public class Queue { ## Variablendefinition und -initialisierung -In Java werden Variablen möglichst erst dann definiert, wenn Sie gebraucht werden. -Dies erhöht die Übersicht und minimiert den Gültigkeitsbereich. Dadurch ist es i.A. auch immer möglich, eine Variable +In Java werden Variablen möglichst erst dann definiert, wenn Sie gebraucht werden. +Dies erhöht die Übersicht und minimiert den Gültigkeitsbereich. Dadurch ist es i.A. auch immer möglich, eine Variable sofort zu initialisieren. (Siehe auch Item 45 in [5].) -Dies wird in anderen Programmiersprachen wie C und C++ anders gehandhabt, dort werden diese als Block am Anfang -einer Methode definiert. +Dies wird in anderen Programmiersprachen wie C und C++ anders gehandhabt, dort werden diese als Block am Anfang +einer Methode definiert. ## Nutzung von final -Das Schlüsselwort `final` wird in Java an zwei Stellen verwendet. +Das Schlüsselwort `final` wird in Java an zwei Stellen verwendet. Es können damit Variablen bzw. Methoden und Klassen als unveränderlich markiert werden. ### final für unveränderliche Variablenreferenzen bzw. -werte @@ -127,55 +130,55 @@ Es können damit Variablen bzw. Methoden und Klassen als unveränderlich markier Wird eine Variable mit `final` ausgezeichnet (Objektvariable, lokale Variable oder Parameter), dann ist der Wert der Variable nach der ersten Zuweisung nicht mehr änderbar. Gerade Java Neulinge interpretieren dies oft nur bei primitiven Datentypen richtig: hier ist tatsächlich der Wert nicht mehr änderbar. Bei Variablen, die ein Objekt referenzieren, ist allerdings -nur gesichert, dass die bestehende Objektreferenz nicht mehr geändert wird. D.h. der Zustand des referenzierten Objektes kann -sich trotzdem ändern. Nur bei immutable Klassen ist auch das Objekt nicht mehr veränderbar, dies muss aber wie bei +nur gesichert, dass die bestehende Objektreferenz nicht mehr geändert wird. D.h. der Zustand des referenzierten Objektes kann +sich trotzdem ändern. Nur bei immutable Klassen ist auch das Objekt nicht mehr veränderbar, dies muss aber wie bei [Immutable Classes](#immutable-classes) beschrieben, selbst sicher gestellt werden. Java selbst bietet dazu kein Sprachmittel an, um neben der Objektreferenz auch den Inhalt als unveränderlich zu markieren. Folgende Richtlinien haben sich in Java als sinnvoll herausgestellt: -- Objektvariablen (d.h. Fields) **sollten immer** mit `final` ausgezeichnet werden, wenn dies möglich ist. +- Objektvariablen (d.h. Fields) **sollten immer** mit `final` ausgezeichnet werden, wenn dies möglich ist. - Parameter **müssen immer** mit `final` ausgezeichnet werden, nur so ist beim Lesen des Quelltextes (Code Review, Debugging) - sofort klar, welchen Wert die Variablen z.B. am Ende einer Methode haben. -- Lokale Variable **werden nie** mit `final` ausgezeichnet. Andernfalls geht der Blick auf das Wesentliche verloren. Im - Englischen spricht man hier häufig von *clutter* oder *noise*, die die Verwendung von `final` an jeder möglichen Stelle - erzeugt. Die [Scala](https://www.scala-lang.org/) Erfinder haben dies besser gemacht: - hier gibt die Sprache gleich zwei verschiedene Schlüsselwörter - für die zwei Varianten vor (`var` und `val`). - +sofort klar, welchen Wert die Variablen z.B. am Ende einer Methode haben. +- Lokale Variable **werden nie** mit `final` ausgezeichnet. Andernfalls geht der Blick auf das Wesentliche verloren. Im +Englischen spricht man hier häufig von *clutter* oder *noise*, die die Verwendung von `final` an jeder möglichen Stelle +erzeugt. Die [Scala](https://www.scala-lang.org/) Erfinder haben dies besser gemacht: +hier gibt die Sprache gleich zwei verschiedene Schlüsselwörter +für die zwei Varianten vor (`var` und `val`). + ### final für Methoden und Klassen - -Wird eine Methode mit `final` ausgezeichnet, so ist ein Überschreiben dieser Methode in einer Subklasse nicht möglich. -Ist die gesamte Klasse mit `final` ausgezeichnet, so kann von dieser Klasse gar nicht abgeleitet werden. -Während in [5] empfohlen wird, Klassen oder Methoden möglichst immer mit `final` zu kennzeichnen, wenn man sich nicht -wirklich Gedanken über die Nutzung in Subklassen gemacht hat, sehen viele andere Java Architekten dies nicht so +Wird eine Methode mit `final` ausgezeichnet, so ist ein Überschreiben dieser Methode in einer Subklasse nicht möglich. +Ist die gesamte Klasse mit `final` ausgezeichnet, so kann von dieser Klasse gar nicht abgeleitet werden. + +Während in [5] empfohlen wird, Klassen oder Methoden möglichst immer mit `final` zu kennzeichnen, wenn man sich nicht +wirklich Gedanken über die Nutzung in Subklassen gemacht hat, sehen viele andere Java Architekten dies nicht so puristisch. Daher lautet die pragmatische Empfehlung: - Klassen sollten in den seltensten Fällen als `final` gekennzeichnet werden. Durch TDD lassen sich durch Vererbung - verursachte Fehler recht schnell finden. Ein "Versiegeln" von Klassen ist nicht wirklich erforderlich und hemmt die - Wiederverwendung. -- Methoden sollten nur dann als `final` gekennzeichnet, wenn durch das Überschreiben tatsächlich ein Fehler entstehen wird. - Z.B. dürfen in Konstruktoren **niemals** Methoden aufgerufen werden, die nicht `final` sind! - +verursachte Fehler recht schnell finden. Ein "Versiegeln" von Klassen ist nicht wirklich erforderlich und hemmt die +Wiederverwendung. +- Methoden sollten nur dann als `final` gekennzeichnet, wenn durch das Überschreiben tatsächlich ein Fehler entstehen wird. +Z.B. dürfen in Konstruktoren **niemals** Methoden aufgerufen werden, die nicht `final` sind! + ## Nutzung von anonymen Klassen In Java hat es sich gerade in Lehrbüchern eingebürgert, anonyme Klassen für Callbacks zu verwenden: man spart -sich einige Zeilen Quelltext und das Buch wird wohl dadurch einige Cents billiger. +sich einige Zeilen Quelltext und das Buch wird wohl dadurch einige Cents billiger. Anonyme Klassen machen den Quelltext leider schwer lesbar und unübersichtlich. Daher gilt grundsätzlich, dass diese nur in wenigen Ausnahmefällen verwendet werden sollten. Wenn trotzdem eine anonyme Klasse benötigt wird, dann sollte diese genau -eine Methode implementieren und die Implementierung selbst sollte wenn möglich genau eine Anweisung enthalten. Mit den +eine Methode implementieren und die Implementierung selbst sollte wenn möglich genau eine Anweisung enthalten. Mit den [Lambda-Ausdrücken](http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html) aus Java 8 lassen sich solche Anforderungen deutlich eleganter umsetzen. ## Nutzung von Methodenreferenzen (d.h. Delegates) -Häufig muss sich eine Klasse als Listener für Events registrieren. Beispielsweise registrieren sich UI Actions immer am +Häufig muss sich eine Klasse als Listener für Events registrieren. Beispielsweise registrieren sich UI Actions immer am Modell, um den eigenen Zustand zu aktualisieren. Java bietet seit Java 8 endlich die Möglichkeit, an dieser Stelle -Methoden-Referenzen zu verwenden (siehe auch Delegates in C#). Damit ist es nicht mehr erforderlich, dass die registrierende +Methoden-Referenzen zu verwenden (siehe auch Delegates in C#). Damit ist es nicht mehr erforderlich, dass die registrierende Klasse das erforderliche Interface selbst implementiert: es reicht wenn eine Referenz auf eine private Methode übergeben wird, die die Schnittstelle umsetzt. -Bisher wurde gerade in vielen Java Lehrbüchern das folgende Anti-Pattern benutzt, das konzeptionell falsch ist: +Bisher wurde gerade in vielen Java Lehrbüchern das folgende Anti-Pattern benutzt, das konzeptionell falsch ist: ```java public class BrokenObserverImplementation implements Observer { @@ -190,7 +193,7 @@ public class BrokenObserverImplementation implements Observer { } ``` -In dieser unsauberen Variante wird die Methode `update` Teil des API, da sie wegen des Interfaces `public` sein muss. +In dieser unsauberen Variante wird die Methode `update` Teil des API, da sie wegen des Interfaces `public` sein muss. Die Methode kann daher später nie wieder entfernt werden. Weitere Nachteile dieses Anti-Patterns: - Die Methode kann von Nutzern der Klasse zu jedem Zeitpunkt aufgerufen werden, was unerwartete Seiteneffekte nach sich ziehen kann. - Muss auf mehrere Events reagiert werden, funktioniert das Pattern nur mit Einsatz von `if-else` Kaskaden, was wiederum die @@ -209,5 +212,6 @@ public class CorrectObserverImplementation { } } ``` -D.h. die Callback Methode kann nun als private markiert werden und ist nach außen nicht mehr sichtbar. + +D.h. die Callback Methode kann nun als private markiert werden und ist nach außen nicht mehr sichtbar. Je nach Anwendungsfall kann statt der Methoden-Referenz auch ein Lambda Ausdruck verwendet werden. diff --git a/doc/CheatSheet-Java.md b/doc/CheatSheet-Java.md index fdb980b86..88b4b65f9 100644 --- a/doc/CheatSheet-Java.md +++ b/doc/CheatSheet-Java.md @@ -1,9 +1,9 @@ String-Formatierung, Konvertierungen - Erstellen von Strings mit `String.format(String format, Object... arguments)` - - `%n` ist Zeilenumbruch - - `%s` Platzhalter für String - - `%d` Platzhalter für Ganzzahl, `%03d` mit drei Stellen und führender Null - - `%f` Platzhalter für Fließkommazahl, `%.3f` mit 3 Nachkommastellen +- `%n` ist Zeilenumbruch +- `%s` Platzhalter für String +- `%d` Platzhalter für Ganzzahl, `%03d` mit drei Stellen und führender Null +- `%f` Platzhalter für Fließkommazahl, `%.3f` mit 3 Nachkommastellen - String zu Ganzzahl: `int number = Integer.parseInt(String number)` (kann Exception werfen) - Ganzzahl zu String: `String text = String.valueOf(int number)` - String zu Fließkommazahl: `double number = Double.parseDouble(String number)` (kann Exception werfen) @@ -18,7 +18,7 @@ Statische Methoden der Klasse Math Listen vs. Arrays -| Operation | Array | List (ArrayList oder LinkedList) | +| Operation | Array | List (ArrayList oder LinkedList) | |------------|---------------------------------------|----------------------------------| | Definition | `Type[] array` | `List list` | | Erstellen | `array = new Type[length]` | `list = new ArrayList()` | @@ -39,7 +39,7 @@ Listen vs. Arrays Listen vs. Sets -| Operation | Set (HashSet oder TreeSet) | List (ArrayList oder LinkedList) | +| Operation | Set (HashSet oder TreeSet) | List (ArrayList oder LinkedList) | |---------------|-----------------------------|----------------------------------| | Definition | `Set set` | `List list` | | Erstellen | `set = new HashSet()` | `list = new ArrayList()` | @@ -60,7 +60,7 @@ Listen vs. Sets Statische Methoden der Klasse Collections -- Sortieren: +- Sortieren: - `Collections.sort(List list)` (`Type` muss `Comparable` sein) - `Collections.sort(List list, Comparator c)` - Reihenfolge umdrehen: `Collections.reverse(List list)` @@ -76,7 +76,7 @@ Methoden rund um Strings - `int indexOf(String pattern)` - Größe: `length()`, `isEmpty()`, `isBlank()` - Ersetzen: `String replace(String pattern, String replacement)` -- Teilbereich: +- Teilbereich: - `String substring(int from, int to)` (Index) - `String StringUtils.substringBetween(String str, String open, String close)` (Text) - Zusammenfügen: `String join(String delimiter, String... texts)` @@ -87,30 +87,31 @@ Methoden rund um Strings - Splitten: `String[] StringUtils.split(String separator)` Statische Methoden der Klasse Character -- `boolean Character.isLetter(char c)` +- `boolean Character.isLetter(char c)` - `boolean Character.isDigit(char c)` - `boolean Character.isWhitespace(char c)` -- `boolean Character.isUpperCase(char c)` +- `boolean Character.isUpperCase(char c)` - `boolean Character.isLowerCase(char c)` - `for (char ch : text.toCharArray()) { ... }` Testen - Markierung einer Testmethode: `@Test` - Assertions - - `assertThat(Type actual).isSame(Type expected)` - - `assertThat(Type actual).isEqualTo(Type expected)` (equals überschrieben) - - `assertThat(Type actual).usingRecursiveComparison().isEqualTo(Type expected)` (sonst) - - `assertThat(boolean actual).isTrue()` oder `assertThat(boolean expected).isFalse()` - - `assertThat(String actual).contains("o").startsWith("Hello").endsWith("World")` - - `assertThat(Collection actual).contains(Type expected1, Type expected2, ...)` - - `assertThat(Collection actual).containsExactly(Type expected1, Type expected2, ...)` - - `assertThat(Collection actual).first().isEqualTo(Type expected)` - - `assertThatExceptionOfType(Type.class).isThrownBy(() -> code).withMessageContaining("Error");` +- `assertThat(Type actual).isSame(Type expected)` +- `assertThat(Type actual).isEqualTo(Type expected)` (equals überschrieben) +- `assertThat(Type actual).usingRecursiveComparison().isEqualTo(Type expected)` (sonst) +- `assertThat(boolean actual).isTrue()` oder `assertThat(boolean expected).isFalse()` +- `assertThat(String actual).contains("o").startsWith("Hello").endsWith("World")` +- `assertThat(Collection actual).contains(Type expected1, Type expected2, ...)` +- `assertThat(Collection actual).containsExactly(Type expected1, Type expected2, ...)` +- `assertThat(Collection actual).first().isEqualTo(Type expected)` +- `assertThatExceptionOfType(Type.class).isThrownBy(() -> code).withMessageContaining("Error");` Exceptions - Werfen mit `throw new Type("Meldungstext mit Kontext")` - Testen mit: `assertThatExceptionOfType(Type.class).isThrownBy(() -> [CODE]).withMessageContaining("Error");` - Fehlerbehandlung mit + ``` try { ... regulärer Code ... diff --git a/doc/Continuous-Integration.md b/doc/Continuous-Integration.md index e71ca5faf..59f3cd7c7 100644 --- a/doc/Continuous-Integration.md +++ b/doc/Continuous-Integration.md @@ -1,22 +1,22 @@ # Continuous Integration des Coding Style -Gemäß dem Grundsatz **eat your own dogfood** ist dieser Coding Style bereits für die Continuous Integration in [GitHub Actions](https://github.com/features/actions), [GitLab CI](https://docs.gitlab.com/ee/ci/) und [Jenkins](https://jenkins.io) vorbereitet. +Gemäß dem Grundsatz **eat your own dogfood** ist dieser Coding Style bereits für die Continuous Integration in [GitHub Actions](https://github.com/features/actions), [GitLab CI](https://docs.gitlab.com/ee/ci/) und [Jenkins](https://jenkins.io) vorbereitet. ## Maven Konfiguration -Sowohl für GitHub Actions als auch für Jenkins erfolgt die Automatisierung des Builds über Maven. -Im zugehörigen [POM](../pom.xml) sind alle Versionen der benutzten Maven Plugins und der benötigten Abhängigkeiten über Properties definiert, d. h. eine Aktualisierung lässt sich im entsprechenden Abschnitt leicht selbst durchführen bzw. wird über den [Dependabot](https://dependabot.com) Roboter von GitHub automatisch über einen Pull Request aktualisiert. +Sowohl für GitHub Actions als auch für Jenkins erfolgt die Automatisierung des Builds über Maven. +Im zugehörigen [POM](../pom.xml) sind alle Versionen der benutzten Maven Plugins und der benötigten Abhängigkeiten über Properties definiert, d. h. eine Aktualisierung lässt sich im entsprechenden Abschnitt leicht selbst durchführen bzw. wird über den [Dependabot](https://dependabot.com) Roboter von GitHub automatisch über einen Pull Request aktualisiert. U. a. sind die folgenden Plugins vorkonfiguriert: -- maven-compiler-plugin: konfiguriert die Java Version auf Java 17 und legt alle Error Prone Regeln fest. Die Java Version kann beliebig aktualisiert werden. +- maven-compiler-plugin: konfiguriert die Java Version auf Java 17 und legt alle Error Prone Regeln fest. Die Java Version kann beliebig aktualisiert werden. - maven-javadoc-plugin: aktiviert die strikte Prüfung von JavaDoc Kommentaren - maven-jar-plugin: legt einen Modulnamen fest. Außerdem wird ein test-jar konfiguriert, sodass alle Tests (und abstrakte Testklassen) auch als Dependencies genutzt werden können. - maven-pmd-plugin: prüft das Projekt mit [PMD](https://pmd.github.io/), die Regeln liegen in den Dateien [pmd-java-configuration.xml](../etc/pmd-java-configuration.xml), [pmd-tests-configuration.xml](../etc/pmd-tests-configuration.xml) und [pmd-javascript-configuration.xml](../etc/pmd-javascript-configuration.xml). - maven-checkstyle-plugin: prüft das Projekt mit [CheckStyle](https://checkstyle.org/), die Regeln liegen in den Dateien [checkstyle-java-configuration.xml](../etc/checkstyle-java-configuration.xml) und [checkstyle-tests-configuration.xml](../etc/checkstyle-tests-configuration.xml). - spotbugs-maven-plugin: prüft das Projekt mit [SpotBugs](https://spotbugs.github.io/), alle Regeln werden verwendet mit den Ausnahmen definiert in der Datei [spotbugs-exclusion-filter.xml](../etc/spotbugs-exclusion-filter.xml). - revapi-maven-plugin: prüft, ob die aktuelle Versionsnummer die [semantische Versionierung](https://semver.org) berücksichtigt (source and binary). D.h. es gilt: - 1. Eine neue **Major** Version wurde definiert, wenn das API nicht mehr abwärtskompatibel ist. - 2. Eine neue **Minor** Version wurde definiert, wenn eine neue Funktionalität abwärtskompatibel hinzugefügt wurde. - 3. Eine neue **Patch** Version wurde definiert, wenn Fehler abwärtskompatibel behoben wurden. +1. Eine neue **Major** Version wurde definiert, wenn das API nicht mehr abwärtskompatibel ist. +2. Eine neue **Minor** Version wurde definiert, wenn eine neue Funktionalität abwärtskompatibel hinzugefügt wurde. +3. Eine neue **Patch** Version wurde definiert, wenn Fehler abwärtskompatibel behoben wurden. - maven-surefire-plugin: aktiviert das Erkennen der Annotationen der Architekturtests mit [ArchUnit](https://www.archunit.org) - jacoco-maven-plugin: misst die Code Coverage der Testfälle mit [JaCoCo](https://www.jacoco.org) - pitest-maven: misst die Mutation Coverage der Testfälle mit [PITest](http://pitest.org) @@ -25,31 +25,31 @@ U. a. sind die folgenden Plugins vorkonfiguriert: [![GitHub Actions](https://github.com/uhafner/codingstyle/workflows/GitHub%20CI/badge.svg)](https://github.com/uhafner/codingstyle/actions) -Die Konfiguration der Continuous Integration in GitHub Actions ist sehr [einfach](../.github/workflows/ci.yml) über eine Pipeline möglich. -Da der gesamte Build über Maven automatisiert ist, besteht die Pipeline eigentlich nur aus einem Maven Aufruf, der das Projekt baut, alle Tests (Unit und Integrationstests) ausgeführt, die statische Code Analyse durchführt und schließlich die Coverage misst. -GitHub Actions bietet auch die Möglichkeit, Matrix Builds durchzuführen: D. h., der Build wird z. B. auf den Plattformen Linux, Windows und macOS oder mit den Java-Versionen 21 und 25 parallel durchgeführt. +Die Konfiguration der Continuous Integration in GitHub Actions ist sehr [einfach](../.github/workflows/ci.yml) über eine Pipeline möglich. +Da der gesamte Build über Maven automatisiert ist, besteht die Pipeline eigentlich nur aus einem Maven Aufruf, der das Projekt baut, alle Tests (Unit und Integrationstests) ausgeführt, die statische Code Analyse durchführt und schließlich die Coverage misst. +GitHub Actions bietet auch die Möglichkeit, Matrix Builds durchzuführen: D. h., der Build wird z. B. auf den Plattformen Linux, Windows und macOS oder mit den Java-Versionen 21 und 25 parallel durchgeführt. Ein Beispiel für die Konfiguration eines Matrix Builds ist in der Datei [ci.yml](../.github/workflows/ci.yml) zu finden. -Wenn gewünscht, können die Ergebnisse der statischen Codeanalyse und der Code Coverage Tools auch direkt im Commit oder Pull-Request angezeigt werden. -Dazu muss in der Pipeline meine [Quality Monitor Action](https://github.com/uhafner/quality-monitor) aktiviert werden. +Wenn gewünscht, können die Ergebnisse der statischen Codeanalyse und der Code Coverage Tools auch direkt im Commit oder Pull-Request angezeigt werden. +Dazu muss in der Pipeline meine [Quality Monitor Action](https://github.com/uhafner/quality-monitor) aktiviert werden. Eine Beispielkonfiguration ist in den Dateien [/quality-monitor-build.yml](../.github/workflows/quality-monitor-build.yml) und [quality-monitor-comment.yml](../.github/workflows/quality-monitor-comment.yml) zu finden, das Ergebnis in der nachfolgenden Abbildung: ![Quality Monitor](images/quality-monitor.png) ## Jenkins -Eine Beispielintegration mit Jenkins ist auch bereits vorhanden. -Diese ist im [Jenkinsfile](../Jenkinsfile) hinterlegt und startet die Integration in mehreren Schritten (Stages). -Zunächst werden auch hier alle Schritte wie in GitHub Actions aufgerufen. -Anschließend erfolgt noch ein Start der Mutation Coverage mit [PIT](http://pitest.org). -Insgesamt ist die CI-Konfiguration für Jenkins umfangreicher, da nicht nur der eigentliche Build konfiguriert wird, sondern auch die Darstellung der Ergebnisse im Jenkins UI über die entsprechenden Jenkins Plugins konfiguriert wird. +Eine Beispielintegration mit Jenkins ist auch bereits vorhanden. +Diese ist im [Jenkinsfile](../Jenkinsfile) hinterlegt und startet die Integration in mehreren Schritten (Stages). +Zunächst werden auch hier alle Schritte wie in GitHub Actions aufgerufen. +Anschließend erfolgt noch ein Start der Mutation Coverage mit [PIT](http://pitest.org). +Insgesamt ist die CI-Konfiguration für Jenkins umfangreicher, da nicht nur der eigentliche Build konfiguriert wird, sondern auch die Darstellung der Ergebnisse im Jenkins UI über die entsprechenden Jenkins Plugins konfiguriert wird. ### Lokale CI in Jenkins (über Docker Compose) -Da es für Jenkins keinen öffentlichen Service wie bei GitHub Actions gibt, um eigene Projekte zu bauen, muss die Jenkins Integration lokal auf einem Team-Server durchgeführt werden. -Zur Vereinfachung des Jenkins Setup ist in diesem Coding Style eine lauffähige Jenkins Installation enthalten (im Sinne von *Infrastructure as Code*). -Diese kann über `bin/jenkins.sh` gestartet werden. -Anschließend wird die aktuelle Jenkins LTS Version mit allen benötigten Plugins in einem Docker Container gebaut und gestartet (das dauert beim ersten Aufruf etwas). +Da es für Jenkins keinen öffentlichen Service wie bei GitHub Actions gibt, um eigene Projekte zu bauen, muss die Jenkins Integration lokal auf einem Team-Server durchgeführt werden. +Zur Vereinfachung des Jenkins Setup ist in diesem Coding Style eine lauffähige Jenkins Installation enthalten (im Sinne von *Infrastructure as Code*). +Diese kann über `bin/jenkins.sh` gestartet werden. +Anschließend wird die aktuelle Jenkins LTS Version mit allen benötigten Plugins in einem Docker Container gebaut und gestartet (das dauert beim ersten Aufruf etwas). Dazu wird ebenso ein als Docker Container initialisierter Java Agent verbunden, der die Builds ausführt. @@ -57,7 +57,7 @@ Nach einem erfolgreichen Start von Jenkins sind dann unter [http://localhost:808 Einer dieser Jobs baut das vorliegende Coding Style Projekt. Der Zugang auf diesen lokalen Rechner erfolgt zur Vereinfachung mit Benutzer `admin` und Passwort `admin`, anschließend hat man volle Jenkins Administrationsrechte. Die jeweiligen Jobs müssen danach manuell gestartet werden, die Ergebnisse der Tests, Code und Mutation Coverage sowie der statischen Analyse werden dann automatisch visualisiert. -Das Jenkins Home Verzeichnis ist im Docker Container als externes Volume angelegt: d.h. der Zugriff kann auf dem Host direkt im Verzeichnis `docker/volumes/jenkins-home` erfolgen. +Das Jenkins Home Verzeichnis ist im Docker Container als externes Volume angelegt: d.h. der Zugriff kann auf dem Host direkt im Verzeichnis `docker/volumes/jenkins-home` erfolg--> Nach einem ersten Build in Jenkins sollte sich dann in etwa folgendes Bild ergeben: diff --git a/doc/Externe-Tool-Integration.md b/doc/Externe-Tool-Integration.md index d0638ff9f..6541e4d63 100644 --- a/doc/Externe-Tool-Integration.md +++ b/doc/Externe-Tool-Integration.md @@ -6,19 +6,19 @@ Zur Unterstützung des Entwicklungsworkflows sind bereits verschiedene externe T Die Abhängigkeiten des Projektes (Dependencies) werden über Maven verwaltet und sind im [POM](../pom.xml) konfiguriert. Um das Projekt immer auf dem laufenden Stand zu halten, ist die GitHub App [Dependabot](https://dependabot.com) aktiv geschaltet. -Diese prüft automatisch, on neue Versionen einer Bibliothek (oder eines Maven Plugins) zur Verfügung stehen. +Diese prüft automatisch, on neue Versionen einer Bibliothek (oder eines Maven Plugins) zur Verfügung stehen. Ist dies der Fall, erstellt der Roboter automatisch einen [Pull Request](https://github.com/uhafner/codingstyle/pulls), der die Version aktualisiert. ## Automatisierte Generierung eines Changelog -Damit die Nutzer des Projekts immer über alle Änderungen im Projekt im Bild sind, ist die GitHub App [Release Drafter](https://github.com/toolmantim/release-drafter) aktiviert. -Diese erstellt automatisch neue Changelog Einträge im [GitHub Releases Bereich](https://github.com/uhafner/codingstyle/releases). +Damit die Nutzer des Projekts immer über alle Änderungen im Projekt im Bild sind, ist die GitHub App [Release Drafter](https://github.com/toolmantim/release-drafter) aktiviert. +Diese erstellt automatisch neue Changelog Einträge im [GitHub Releases Bereich](https://github.com/uhafner/codingstyle/releases). Diese Einträge werden automatisch aus den Titeln der Pull Requests generiert, d. h. jede Änderung am Projekt sollte über Pull Requests erfolgen und nicht über direkte Git Commits – dies entspricht auch dem Vorgehen des [GitHub Flows](https://guides.github.com/introduction/flow/). ## Continuous Integration (inklusive Überwachung von Quality Gates) Wie bereits im Abschnitt [Continuous Integration](Continuous-Integration.md) erwähnt, wird das Projekt bei jeder Änderung mit einer GitHub Pipeline automatisiert gebaut. -Diese [Pipeline](https://github.com/uhafner/codingstyle/blob/main/.github/workflows/ci.yml) läuft auf Rechnern von GitHub und führt einen Build unter JDK 21 und 25 aus. +Diese [Pipeline](https://github.com/uhafner/codingstyle/blob/main/.github/workflows/ci.yml) läuft auf Rechnern von GitHub und führt einen Build unter JDK 21 und 25 aus. Aktuell wird der Build auf virtualisierten Rechnern der Betriebssysteme Linux, Windows und macOS durchgeführt. Anschließend werden die Build Ergebnisse mit meinem [Quality Monitor](https://github.com/uhafner/quality-monitor) analysiert. Dieser Service sammelt die Ergebnisse der statischen Code-Analyse (CheckStyle, PMD, SpotBugs) und der Code-Coverage (JaCoCo) und stellt diese als Kommentar im Pull Request dar. diff --git a/doc/Fehlerbehandlung.md b/doc/Fehlerbehandlung.md index 4b4d3a535..729251490 100644 --- a/doc/Fehlerbehandlung.md +++ b/doc/Fehlerbehandlung.md @@ -1,4 +1,4 @@ -# Fehlerbehandlung mit Exceptions +# Fehlerbehandlung mit Exceptions In einem Java-Programm können zur Laufzeit verschiedene Fehler auftreten: - logische Fehler (z.B. Programmierfehler) @@ -6,22 +6,22 @@ In einem Java-Programm können zur Laufzeit verschiedene Fehler auftreten: - Probleme mit der Peripherie (z.B. mit Internet, Datenbank, Dateisystem) - fehlerhafte Bedienung (z.B. Benutzereingaben) -Zum Melden eines solchen Fehlers benutzen wir i.A. Exceptions. D.h. bei Auftritt eines dieser Fehler wird das +Zum Melden eines solchen Fehlers benutzen wir i.A. Exceptions. D.h. bei Auftritt eines dieser Fehler wird das Programm an der aktuellen Stelle abgebrochen und eine Exception wird geworfen. Dies hat den Vorteil, dass diese -Laufzeitfehler behandelt werden müssen, und somit nicht ignoriert werden können. +Laufzeitfehler behandelt werden müssen, und somit nicht ignoriert werden können. Auf diese Fehler kann anschließend an geeigneter Stelle im Programm reagiert werden. Je nach Fehler kann - die zum Fehler führende Handlung wiederholt werden (Internet ist wieder verfügbar, Benutzereingabe verbessert) -- der Fehler in einem Dialog angezeigt werden -- der Fehler ignoriert werden +- der Fehler in einem Dialog angezeigt werden +- der Fehler ignoriert werden - das Programm abgebrochen werden Wird keine Fehlerbehandlung umgesetzt, wird das Programm mit einem Stacktrace beendet. ## Validieren von Eingabeparametern -Sichere und robuste Software vertraut niemals Eingabewerten. Es gilt der Grundsatz: „all input is evil“. D.h. in -öffentlichen Methoden und Konstruktoren müssen Parameter immer validiert werden. +Sichere und robuste Software vertraut niemals Eingabewerten. Es gilt der Grundsatz: „all input is evil“. D.h. in +öffentlichen Methoden und Konstruktoren müssen Parameter immer validiert werden. Genügen diese Parameter nicht dem erwarteten Vertrag, muss eine Exception geworfen werfen. I.A. ist dafür die `IllegalArgumentExeption` geeignet. Ggf. kann davon abgewichen werden, um z.B. mit der `IndexOutOfBoundsException` oder der `IllegalStateException` eine genauere Fehlerursache aufzuzeigen. Die `NullPointerException` hat einen Sonderstatus, @@ -30,8 +30,8 @@ Parameter ohne direkte Nutzung in einer Objektvariable gespeichert wird. ## Exception-Typen -Im JDK ist eine Vielzahl von Exception Klassen vordefiniert, diese haben alle die Endung `Exception` im Klassennamen. -Es macht selten Sinn, eigene weitere Exceptions zu definieren. Wird eine Exception benötigt, ist i.A. im JDK +Im JDK ist eine Vielzahl von Exception Klassen vordefiniert, diese haben alle die Endung `Exception` im Klassennamen. +Es macht selten Sinn, eigene weitere Exceptions zu definieren. Wird eine Exception benötigt, ist i.A. im JDK immer eine passende dabei. Java bietet als einzige Programmiersprache zwei verschiedene Exception Kategorien an: @@ -39,51 +39,51 @@ Java bietet als einzige Programmiersprache zwei verschiedene Exception Kategorie - unchecked Exceptions: können deklariert und gefangen werden Das Konzept hat sich in der Praxis nicht bewährt (Details gibt es in Artikeln wie [Checked Exceptions are Evil](https://phauer.com/2015/checked-exceptions-are-evil/) -oder [The Trouble with Checked Exceptions](https://www.artima.com/intv/handcuffs.html)), +oder [The Trouble with Checked Exceptions](https://www.artima.com/intv/handcuffs.html)), daher nutzen wir möglichst immer unchecked Exceptions. Werden Bibliotheken genutzt, die mit checked Exceptions arbeiten, -bietet es sich an, diese an der Aufrufstelle zu fangen und in eine äquivalente unchecked Exception umzuwandeln. +bietet es sich an, diese an der Aufrufstelle zu fangen und in eine äquivalente unchecked Exception umzuwandeln. ## Dokumentation von Exceptions -Wenn Methoden oder Konstruktoren eine Exception werfen können, sollte dies im Methodenkopf mit einer `throws` Klausel -und im JavaDoc mit einem `@throws` Tag dokumentiert werden. Dort sollte auch immer der Grund beschrieben sein. Dies ist +Wenn Methoden oder Konstruktoren eine Exception werfen können, sollte dies im Methodenkopf mit einer `throws` Klausel +und im JavaDoc mit einem `@throws` Tag dokumentiert werden. Dort sollte auch immer der Grund beschrieben sein. Dies ist nicht kaskadierend erforderlich, d.h. eine Methode die mehrere Methoden aufruft, die Exceptions werfen muss diese nicht -mehr aufführen, sondern nur die selbst geworfenen Exceptions. +mehr aufführen, sondern nur die selbst geworfenen Exceptions. ## Fehlerursache (Kontext) -Wird eine Exception geworfen, muss die Fehlerursache (d.h. der Kontext) genau lokalisiert werden, und als Text im -Konstruktor der Exception übergeben werden. D.h. was ist das Problem? Wie konnte das Problem auftreten? +Wird eine Exception geworfen, muss die Fehlerursache (d.h. der Kontext) genau lokalisiert werden, und als Text im +Konstruktor der Exception übergeben werden. D.h. was ist das Problem? Wie konnte das Problem auftreten? Welche Parameterwerte sind Ursache? Diese Meldung ist i.A. nur sichtbar für das Entwicklungsteam und kann z.B. auch dafür passend formuliert werden. Wird darüber hinaus eine andere Exception gefangen und umgewandelt, -so ist diese auch im Konstruktor der neuen Exception zu übergeben. Generell gilt: Der Default-Konstruktor einer -Exception darf **nie** verwendet werden. +so ist diese auch im Konstruktor der neuen Exception zu übergeben. Generell gilt: Der Default-Konstruktor einer +Exception darf **nie** verwendet werden. ## Testen von Exceptions -Das korrekte Werfen von Exceptions sollte generell getestet werden, siehe dazu den passenden Abschnitt im Kapitel zum +Das korrekte Werfen von Exceptions sollte generell getestet werden, siehe dazu den passenden Abschnitt im Kapitel zum [Testen](Testen.md#testen-von-exceptions). -## Best practice +## Best practice -Exceptions dürfen nur für außergewöhnliche Ereignisse verwendet werden, d.h. die Programmflusssteuerung darf +Exceptions dürfen nur für außergewöhnliche Ereignisse verwendet werden, d.h. die Programmflusssteuerung darf niemals über Exceptions durchgeführt werden. Dies lässt sich umso leichter erreichen, wenn es zu jeder Methode **x** eine -zweite Methode **y** gibt, die prüft, ob die Methode **x** mit den gegebenen Eingabeparametern +zweite Methode **y** gibt, die prüft, ob die Methode **x** mit den gegebenen Eingabeparametern eine Exception werfen würde. -Beispiele: Eine `IndexOutOfBoundsException` lässt sich bei `list.get(0)` vermeiden, wenn vorab die Anzahl der Elemente +Beispiele: Eine `IndexOutOfBoundsException` lässt sich bei `list.get(0)` vermeiden, wenn vorab die Anzahl der Elemente geprüft wird (mit `isEmpty()` oder `size()`). Analoge Methodenpaare finden sich z.B. bei `get(object)` und `contains(object)`, oder `new FileInputStream(file)` und `file.exists()`, oder `iterator.next()` und `iterator.hasNext()`. - -Exceptions können mit try/catch/finally Blöcken gefangen werden. Dadurch wird der Code recht schnell -unübersichtlich, da das **Single Responsibility Principle** (siehe [4, S. 138-139]) verletzt wird. + +Exceptions können mit try/catch/finally Blöcken gefangen werden. Dadurch wird der Code recht schnell +unübersichtlich, da das **Single Responsibility Principle** (siehe [4, S. 138-139]) verletzt wird. Sinnvoll ist daher das Aufteilen des Programmstücks in die folgenden Teile: - im try Block: Aufruf einer Untermethode (keine Fehlerbehandlung) - im catch Block: Fehlerbehandlung - im finally Block: ggf. Aufräumen - in der Untermethode: Implementierung der Anforderungen ohne Rücksicht auf Exceptions -In einem finally Block sollte niemals eine Exception geworfen werden. Außerdem sollte im finally Block niemals die +In einem finally Block sollte niemals eine Exception geworfen werden. Außerdem sollte im finally Block niemals die Methode mit return beendet werden. Ebenso sollten Exceptions niemals ignoriert werden. Sollte es erforderlich sein, einen leeren catch Block zu verwenden, so muss diff --git a/doc/Formatierung.md b/doc/Formatierung.md index b8198b2ca..aa83501b4 100644 --- a/doc/Formatierung.md +++ b/doc/Formatierung.md @@ -15,9 +15,9 @@ Kommando der Entwicklungsumgebung aufzurufen. Die öffnende Klammer eines Blocks steht immer auf der gleichen Zeile wie die Anweisung davor. Die folgenden Anweisungen eines geschachtelten Blocks werden alle mit 4 Leerzeichen eingerückt. Tabs dürfen nicht verwendet werden, da -diese nicht überall mit der gleichen Leerzeichenanzahl dargestellt werden (z.B. im Browser). +diese nicht überall mit der gleichen Leerzeichenanzahl dargestellt werden (z.B. im Browser). Die schließende Klammer steht dann genau unterhalb der Anweisung, die die öffnende Klammer enthält. - + An Beispielen wird das leichter deutlich, das zum Einrücken verwendete Leerzeichen wird zur besseren Lesbarkeit durch das Sonderzeichen `⋅` hervorgehoben: @@ -40,8 +40,8 @@ while (expression2) { } ``` -**Achtung:** Zur besseren Unterstützung der visuellen Struktur steht gemäß [3] -bei einem `if-else` und `try-catch` Konstrukt die schließende Klammer immer alleine auf einer Zeile. +**Achtung:** Zur besseren Unterstützung der visuellen Struktur steht gemäß [3] +bei einem `if-else` und `try-catch` Konstrukt die schließende Klammer immer alleine auf einer Zeile. Viele Java Entwicklungsteams (z.B. das Oracle JDK Team) halten dies anders. ```java @@ -74,17 +74,17 @@ finally { ## Leerzeichen -Quelltext ohne Leerzeichen lässt sich deutlich schlechter lesen und verstehen. Daher nutzen wir **genau** +Quelltext ohne Leerzeichen lässt sich deutlich schlechter lesen und verstehen. Daher nutzen wir **genau** ein Leerzeichen an den folgenden Stellen: -- Zwischen einer Anweisung und der folgenden öffnenden runden ( oder geschweiften { Klammer -- Zwischen einer schließenden runden ) und einer öffnenden geschweiften { Klammer +- Zwischen einer Anweisung und der folgenden öffnenden runden ( oder geschweiften { Klammer +- Zwischen einer schließenden runden ) und einer öffnenden geschweiften { Klammer - Zwischen binärem Operator und seinen beiden Operanden - Nach jedem Komma in der Parameterliste einer Methode Für die folgenden Konstrukte wird kein Leerzeichen verwendet: - Zwischen unärem Operator und seinem Operand -- Zwischen Methodenname und öffnender runden ( Klammer -- Bedingung innerhalb der runden Klammern () im `if` oder `while` +- Zwischen Methodenname und öffnender runden ( Klammer +- Bedingung innerhalb der runden Klammern () im `if` oder `while` Hier ein echtes Beispiel, in denen das Leerzeichen durch das Sonderzeichen `⋅` hervorgehoben wurde: @@ -135,7 +135,7 @@ public static boolean containsAnyIgnoreCase(@Nullable final CharSequence input, Für Kommentare gibt es auch einige Richtlinien, die im Abschnitt [Kommentare](Kommentare.md) aufgeführt sind. Bei der Formatierung ist zusätzlich auf die folgenden Punkte zu achten: - Zu lange Zeilen werden wie normaler Code nach 120 Zeichen umgebrochen (siehe [oben](#zeilenumbruch)) -- Abschnitte nutzen korrekte Kennzeichnung mittels der XHTML Tags \Text\ +- Abschnitte nutzen korrekte Kennzeichnung mittels der XHTML Tags \Text\ - Parameter werden auf einer neuen Zeile beschrieben (mit korrektem Einrücken) Ein Beispiel sagt auch hier mehr als tausend Worte: @@ -177,7 +177,7 @@ public static boolean containsAnyIgnoreCase(@Nullable final CharSequence input, } ``` - + ## Leerzeilen Auch Leerzeilen können die Struktur von Programmen verbessern. Zusammenhängende Anweisungen sollten gruppiert werden @@ -203,7 +203,7 @@ public void foo() { Die erste Anweisung beginnt dabei direkt nach dem Methodenkopf, die letzte hört direkt vor der schließenden Klammer auf, hier werden keine extra Leerzeilen mehr eingefügt. -Innerhalb einer Klasse hat es sich eingebürgert, zwei Methoden oder Konstruktoren durch eine Leerzeile zu trennen. +Innerhalb einer Klasse hat es sich eingebürgert, zwei Methoden oder Konstruktoren durch eine Leerzeile zu trennen. Nach dem Klassenkopf und vor der schließenden Klammer einer Klasse steht keine extra Leerzeile, ebenso nicht nach dem Methodenkopf und der schließenden Klammer einer Methode. Instanzvariablen können wie Anweisungen gruppiert werden, wenn dies thematisch sinnvoll ist. Zwischen Instanzvariablen @@ -277,3 +277,4 @@ public final class StringContainsUtils { } } ``` + diff --git a/doc/Git.md b/doc/Git.md index 84668a3b2..f0fcc3479 100644 --- a/doc/Git.md +++ b/doc/Git.md @@ -1,18 +1,17 @@ -Alle Dokumente und Quelltexte, die im Semester erstellt werden, müssen in einer Versionsverwaltung abgelegt werden – dies bezieht sich auf Texte, UML-Diagramme und Source Code. -Wir benutzen dafür [Git](https://git-scm.com). -Git ist eine verteilte Versionsverwaltung, d. h., das Repository, das alle Änderungen an unseren Dateien abspeichert, ist auf mehreren Rechnern verteilt. -Zum einen auf den eigenen lokalen Rechnern der Teammitglieder und zum anderen auf einem zentralen Server, der als Synchronisierungsknoten für das Team verwendet wird. -Dieser Server wird typischerweise von einem sogenannten Hoster (oder Service Provider) zur Verfügung gestellt. -Aktuell ist GitHub der bekannteste Hoster, aber auch GitLab oder BitBucket sind in der Industrie weit verbreitet. -Die Hochschule bietet selbst auch ein Hosting über das LRZ an: hier kommt die Software von GitLab zum Einsatz, allerdings auf unserer eigenen Hardware. +Alle Dokumente und Quelltexte, die im Semester erstellt werden, müssen in einer Versionsverwaltung abgelegt werden – dies bezieht sich auf Texte, UML-Diagramme und Source Code. +Wir benutzen dafür [Git](https://git-scm.com). +Git ist eine verteilte Versionsverwaltung, d. h., das Repository, das alle Änderungen an unseren Dateien abspeichert, ist auf mehreren Rechnern verteilt. +Zum einen auf den eigenen lokalen Rechnern der Teammitglieder und zum anderen auf einem zentralen Server, der als Synchronisierungsknoten für das Team verwendet wird. +Dieser Server wird typischerweise von einem sogenannten Hoster (oder Service Provider) zur Verfügung gestellt. +Aktuell ist GitHub der bekannteste Hoster, aber auch GitLab oder BitBucket sind in der Industrie weit verbreitet. +Die Hochschule bietet selbst auch ein Hosting über das LRZ an: hier kommt die Software von GitLab zum Einsatz, allerdings auf unserer eigenen Hardware. Die Arbeit mit Git ist schnell gelernt, es gibt dazu eine Vielzahl an Online verfügbaren Quellen, die im Folgenden aufgelistet sind: -- [Learn Version Control with Git](https://www.git-tower.com/learn/git/videos/) Video Tutorials vom - Tower Team (inkl. passendem [Git Cheat Sheet](https://www.git-tower.com/blog/git-cheat-sheet/)) +- [Learn Version Control with Git](https://www.git-tower.com/learn/git/videos/) Video Tutorials vom +Tower Team (inkl. passendem [Git Cheat Sheet](https://www.git-tower.com/blog/git-cheat-sheet/)) - [Learn Git with GitKraken](https://www.gitkraken.com/resources/learn-git) Video Tutorials vom GitKraken Team - Das [Pro Git](https://git-scm.com/book/de/v2) Buch von Scott Chacon -- [Git and GitHub learning resources](https://docs.github.com/en/free-pro-team@latest/github/getting-started-with-github/git-and-github-learning-resources) vom GitHub Team - (inkl. passendem [Git Cheat Sheet](https://education.github.com/git-cheat-sheet-education.pdf)) +- [Git and GitHub learning resources](https://docs.github.com/en/free-pro-team@latest/github/getting-started-with-github/git-and-github-learning-resources) vom GitHub Team +(inkl. passendem [Git Cheat Sheet](https://education.github.com/git-cheat-sheet-education.pdf)) - [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/) von Chris Beams - diff --git a/doc/Kommentare.md b/doc/Kommentare.md index 70902411e..c80d99027 100644 --- a/doc/Kommentare.md +++ b/doc/Kommentare.md @@ -1,20 +1,20 @@ # Kommentare Java kennt drei verschiedene Varianten von Kommentaren: - - der einzeilige Kommentar wird mit `//` eingeleitet und geht bis zum Zeilenende - - der mehrzeilige Kommentar wird mit `/*` gestartet und mit `*/` beendet - - der JavaDoc Kommentar wird mit `/**` gestartet und mit `*/` beendet. - +- der einzeilige Kommentar wird mit `//` eingeleitet und geht bis zum Zeilenende +- der mehrzeilige Kommentar wird mit `/*` gestartet und mit `*/` beendet +- der JavaDoc Kommentar wird mit `/**` gestartet und mit `*/` beendet. + ## Kommentare zum Quelltext -Wohlüberlegte Kommentare *können* die Qualität des Quelltextes steigern. Sie sind i.A. ein notwendiges Übel und +Wohlüberlegte Kommentare *können* die Qualität des Quelltextes steigern. Sie sind i.A. ein notwendiges Übel und helfen uns darüber hinweg, dass wir nicht alles durch Code alleine ausdrücken können. Dafür kann eine der beiden ersten Varianten genutzt werden. Diese erklären das Ziel des Quelltextes und verdeutlichen damit unsere Absicht. Wichtig zu beachten sind aber die folgenden Aussagen von Brian W. Kernighan: - - Make sure comments and code agree. - - Don't just echo the code with comments - make every comment count. - - Don't comment bad code - rewrite it. +- Make sure comments and code agree. +- Don't just echo the code with comments - make every comment count. +- Don't comment bad code - rewrite it. (Aus dem Klassiker: B. W. Kernighan and P. J. Plauger, The Elements of Programming Style, McGraw-Hill, New York, 1974) @@ -24,12 +24,11 @@ Programmausschnitt erstellt wird, sollte dieser Ausschnitt in eine Methode ausge mit dem beabsichtigten Kommentar benannt. Eine ausführlichere Behandlung dieses Themas findet sich in [4], dort sind viele Beispiele und Negativbeispiele aufgeführt. - ## Spezielle Kommentare Häufig findet man auch Kommentare zur Kennzeichnung des Copyrights. Auch wenn diese den obigen Regeln widersprechen, müssen diese aus juristischen Gründen in vielen Dateien vorhanden sein. - + Werden in einem Programmabschnitt kleine Verbesserungsmöglichkeiten entdeckt, die im Moment nicht behoben werden können, so können diese ebenso mit einem Kommentar beschrieben werden. Hier ist wichtig, die Kommentare mit einer Markierung wie TODO oder FIXME zu versehen. In den meisten Projekten gilt: mit FIXME werden Stellen markiert, die noch vor @@ -38,29 +37,29 @@ verbleiben. Wichtig bleibt: größere Änderungswünsche sollten immer in einem auch mit in die Planung einfließen können. Auf der anderen Seite sind Kommentare zur Versionshistorie einer Datei nicht sinnvoll, diese werden sowieso in der -Versionsverwaltung abgelegt und sind somit redundant. +Versionsverwaltung abgelegt und sind somit redundant. ## JavaDoc -JavaDoc wird genutzt um die öffentliche Schnittstelle eines Programms zu dokumentieren. Diese Kommentare sind unerlässlich +JavaDoc wird genutzt um die öffentliche Schnittstelle eines Programms zu dokumentieren. Diese Kommentare sind unerlässlich und müssen für alle Klassen und Methoden verfasst werden, die mindestens die Sichtbarkeit `protected` haben. Wird eine Klasse serialisiert (z.B. wenn sie die Schnittstelle `Serializable` implementiert), dann müssen auch -private Attribute kommentiert werden, da in diesem Fall auch diese zur öffentlichen Schnittstelle einer Klasse gehören. +private Attribute kommentiert werden, da in diesem Fall auch diese zur öffentlichen Schnittstelle einer Klasse gehören. -Die [Java Bibliotheken](https://docs.oracle.com/javase/8/docs/api/) selbst bieten schöne Beispiele, wie solche Kommentare +Die [Java Bibliotheken](https://docs.oracle.com/javase/8/docs/api/) selbst bieten schöne Beispiele, wie solche Kommentare auszusehen haben und wie nützlich diese sind. Eine kleine Einführung zu diesem Thema ist auf den [Oracle Seiten](https://www.oracle.com/java/technologies/javase/javadoc.html) zu finden. JavaDoc Kommentare werden im aktiv geschrieben. Der erste Satz (der mit einem Punkt abgeschlossen wird) muss eine -Zusammenfassung sein. Dieser wird im generierten HTML Dokument als Überschrift dargestellt. Das zu beschreibende Element +Zusammenfassung sein. Dieser wird im generierten HTML Dokument als Überschrift dargestellt. Das zu beschreibende Element wird dabei nicht noch einmal wiederholt, der Kommentar wird dadurch möglichst knapp. -D.h. **"An ordered collection"** statt **"This interface defines an ordered collection"**, oder +D.h. **"An ordered collection"** statt **"This interface defines an ordered collection"**, oder **"Returns whether this list contains no elements"** statt **"This method returns whether this list contains no elements"**. -Die folgenden Sätze können dann das Element genauer beschreiben, hilfreich ist hierbei oft die Angabe eines -Anwendungsbeispiels. Werden dabei Codestücke bzw. Variablen in die Beschreibung eingebettet, so müssen diese die Syntax -`{@code ...}` nutzen. Werden Klassen oder Methoden referenziert, so werden diese mit der Syntax `{@link ...}` bzw. +Die folgenden Sätze können dann das Element genauer beschreiben, hilfreich ist hierbei oft die Angabe eines +Anwendungsbeispiels. Werden dabei Codestücke bzw. Variablen in die Beschreibung eingebettet, so müssen diese die Syntax +`{@code ...}` nutzen. Werden Klassen oder Methoden referenziert, so werden diese mit der Syntax `{@link ...}` bzw. `{@linkplain ...}` eingefügt, nur so kann die IDE die Kommentare mit entsprechenden Hyperlinks anzeigen (*linkplain* verwendet -einen Zeichensatz mit variabler, *link* mit fester Breite). +einen Zeichensatz mit variabler, *link* mit fester Breite). Ganze Quelltext-Abschnitte, die über mehrere Zeilen gehen, werden mit der Syntax `
{@code ...}
` eingebettet. Beispiel: @@ -87,3 +86,4 @@ public static boolean isEmpty(final CharSequence text) { } ``` + diff --git a/doc/Namensgebung.md b/doc/Namensgebung.md index 38cfe970f..a66b1fc1f 100644 --- a/doc/Namensgebung.md +++ b/doc/Namensgebung.md @@ -1,31 +1,31 @@ # Namensgebung -Bezeichner (*Identifier*) sind in Java beliebig lang und bestehen aus einer Zeichenkette -von großen und kleinen Buchstaben, Ziffern oder dem Underscore `_`. Hierbei werden Groß und Kleinschreibung -unterschieden (klein ist nicht Klein). Folgende Einschränkungen sind dabei zu beachten: -Das erste Zeichen darf keine Ziffer sein und -[ca. 50 Schlüsselwörter](http://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html) +Bezeichner (*Identifier*) sind in Java beliebig lang und bestehen aus einer Zeichenkette +von großen und kleinen Buchstaben, Ziffern oder dem Underscore `_`. Hierbei werden Groß und Kleinschreibung +unterschieden (klein ist nicht Klein). Folgende Einschränkungen sind dabei zu beachten: +Das erste Zeichen darf keine Ziffer sein und +[ca. 50 Schlüsselwörter](http://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html) sind vom System reserviert und für eigenen Namen verboten, z.B. class, import, public, etc. -Neben dieser formalen Syntax haben sich folgende Konventionen eingebürgert. +Neben dieser formalen Syntax haben sich folgende Konventionen eingebürgert. ## Allgemeine Konventionen - + Bezeichner in Java verwenden American English und nutzen damit automatisch nur ASCII Zeichen (keine Umlaute). Der Underscore `_` wird i.A. nicht verwendet. Auch angehängte Zahlen sind untypisch und meistens ein Zeichen für schlechten Stil (*Code Smell* [6]). - -Bezeichner sind stets aussagekräftig und bestehen damit oft aus mehreren Teilwörtern. Wir verwenden dann die Schreibweise -[CamelCase](https://en.wikipedia.org/wiki/Camel_case). Bezeichner nutzen i.A. keine Abkürzungen, die Autovervollständigung der + +Bezeichner sind stets aussagekräftig und bestehen damit oft aus mehreren Teilwörtern. Wir verwenden dann die Schreibweise +[CamelCase](https://en.wikipedia.org/wiki/Camel_case). Bezeichner nutzen i.A. keine Abkürzungen, die Autovervollständigung der Entwicklungsumgebungen ergänzt lange Bezeichner komfortabel. Wenn ein Bezeichner doch einmal eine Abkürzung enthält, dann wird auch hier nur der erste Buchstabe groß geschrieben, z.B. `loadXmlDocument`, `writeAsJson`. -Zum Thema Abkürzung noch ein schönes Zitat von Ken Thompson auf die Frage was er ändern würde, wenn er UNIX +Zum Thema Abkürzung noch ein schönes Zitat von Ken Thompson auf die Frage was er ändern würde, wenn er UNIX nochmals erfinden dürfte: “I‘d spell creat with an e.“ Zum Thema Namensgebung finden sich einige schöne Anti-Beispiele im Essay ["How To Write Unmaintainable Code"](https://github.com/Droogans/unmaintainable-code) von Roedy Green. - + ### Methodennamen Methodennamen enthalten ein Verb im Aktiv, z.B. `computeSum`, `moveForward`, `turnRight`, `compareToIgnoreCase`. Sie beginnen @@ -36,7 +36,7 @@ immer mit einem kleinen Buchstaben. Liefert eine Methode einen `boolean` zurück ## Variablennamen -Variablennamen beginnen mit einem kleinen Buchstaben. Variablen vom Typ `boolean` nutzen meist den Präfix `is`, siehe +Variablennamen beginnen mit einem kleinen Buchstaben. Variablen vom Typ `boolean` nutzen meist den Präfix `is`, siehe Abschnitt zu booleschen Methodennamen. Alle anderen Variablennamen sind im Allgemeinen ein Substantiv, da ein Objekt gespeichert wird. Werden in einer Variablen mehrere Objekte gespeichert (Array, Listen, etc.), dann wird die Mehrzahl verwendet. Beispiele: counter, isLeaf, numberOfTrees, months, etc. @@ -44,7 +44,7 @@ verwendet. Beispiele: counter, isLeaf, numberOfTrees, months, etc. ## Klassennamen Klassennamen sind ein Substantiv und beginnen mit einem großen Buchstaben. Vor oder nach dem Substantiv können -ggf. weitere beschreibende Wörter verwendet werden, z.B. `Counter`, `LimitedCounter`, `OpenCounter`, `HashMap`, +ggf. weitere beschreibende Wörter verwendet werden, z.B. `Counter`, `LimitedCounter`, `OpenCounter`, `HashMap`, `ConcurrentHashMap`. Abstrakte Klassen halten sich i.A. auch an dieses Schema - manchmal macht es aber auch Sinn diese durch den Präfix `Abstract` als solche zu markieren, z.B. `AbstractList` oder `AbstractDocument`. Testklassen haben immer den Suffix `Test` nach dem eigentlichen Namen der Klasse, die getestet werden soll, z.B. `CounterTest` @@ -54,8 +54,8 @@ oder `HashMapTest`. Interfacenamen sind entweder ein Substantiv (siehe Abschnitt Klassennamen) oder ein Adverb und beginnen mit einem großen Buchstaben. Vor oder nach dem Substantiv bzw. Adverb können -ggf. weitere beschreibende Wörter verwendet werden, z.B. `Counter`, `Observable`, `WeakListener`, +ggf. weitere beschreibende Wörter verwendet werden, z.B. `Counter`, `Observable`, `WeakListener`, `Set`, `SortedSet`. Manche Projekte (z.B. Eclipse) verwenden das Anti-Pattern der -[Ungarischen Notation](http://msdn.microsoft.com/de-de/library/aa260976(VS.60).aspx) -und stellen jedem Interface den Präfix `I` voraus. Das ist nur in seltenen Fällen sinnvoll und sollte +[Ungarischen Notation](http://msdn.microsoft.com/de-de/library/aa260976(VS.60).aspx) +und stellen jedem Interface den Präfix `I` voraus. Das ist nur in seltenen Fällen sinnvoll und sollte vermieden werden. diff --git a/doc/State-Based-Vs-Interaction-Based.md b/doc/State-Based-Vs-Interaction-Based.md index 7a3c82d96..e3ab6ee25 100644 --- a/doc/State-Based-Vs-Interaction-Based.md +++ b/doc/State-Based-Vs-Interaction-Based.md @@ -4,9 +4,9 @@ Prinzipiell gibt es zwei Varianten des Testings: das **State Based Testing** und ## State Based Testing -Beim **State Based Testing** wird das Testobjekt nach Aufruf der zu -testenden Methoden durch Abfrage seines internen Zustands verifiziert. Analog dazu kann natürlich auch der Zustand -der im Test verwendeten Parameter bzw. Rückgabewerte analysiert werden. Die meisten Tests eines Projekts +Beim **State Based Testing** wird das Testobjekt nach Aufruf der zu +testenden Methoden durch Abfrage seines internen Zustands verifiziert. Analog dazu kann natürlich auch der Zustand +der im Test verwendeten Parameter bzw. Rückgabewerte analysiert werden. Die meisten Tests eines Projekts laufen nach diesem Muster ab und können folgendermaßen formuliert werden: ```java @@ -25,7 +25,7 @@ void should[restlicher Methodenname der den Test fachlich beschreibt]() { Die folgenden beiden Tests aus diesem Projekt zeigen die zwei unterschiedlichen Varianten des State Based Testing. -### Verifizieren der Rückgabewerte +### Verifizieren der Rückgabewerte Im folgenden Test wird der Rückgabewert einer Methode überprüft. @@ -42,7 +42,7 @@ void shouldConvertToAbsolute() { } ``` -### Verifizieren der Objektzustands +### Verifizieren der Objektzustands Im folgenden Test wird der Zustand eines Objekts überprüft. @@ -61,7 +61,7 @@ void shouldCreateSimpleTreeStringsWithBuilder() { ## Interaction Based Testing -Im Gegensatz dazu wird beim **Interaction Based Testing** nicht der Zustand des SUT analysiert. Statt dessen werden die +Im Gegensatz dazu wird beim **Interaction Based Testing** nicht der Zustand des SUT analysiert. Statt dessen werden die Aufrufe aller am Test beteiligten Objekte mit einem Mocking Framework wie [Mockito](https://site.mockito.org) überprüft. D.h. hier steht nicht der Zustand des Testobjekts im Vordergrund, sondern die Interaktion mit beteiligten Objekten. Ein typischer Testfall nach dem Interaction Based Testing ist folgendermaßen aufgebaut: @@ -83,7 +83,7 @@ void should[restlicher Methodenname der den Test fachliche beschreibt]() { Ein typisches Beispiel für solch einen Test ist in der folgenden Klasse zu finden: - ```java +```java package edu.hm.hafner.util; import java.io.PrintStream; @@ -95,21 +95,22 @@ import static java.util.Collections.*; import static org.mockito.Mockito.*; /** - * Tests the class {@link PrefixLogger}. - */ +* Tests the class {@link PrefixLogger}. +*/ class PrefixLoggerTest { - private static final String LOG_MESSAGE = "Hello PrefixLogger!"; - private static final String PREFIX = "test"; - private static final String EXPECTED_PREFIX = "[test]"; + private static final String LOG_MESSAGE = "Hello PrefixLogger!"; + private static final String PREFIX = "test"; + private static final String EXPECTED_PREFIX = "[test]"; - @Test - void shouldLogSingleAndMultipleLines() { - PrintStream printStream = mock(PrintStream.class); - PrefixLogger logger = new PrefixLogger(printStream, PREFIX); + @Test + void shouldLogSingleAndMultipleLines() { + PrintStream printStream = mock(PrintStream.class); + PrefixLogger logger = new PrefixLogger(printStream, PREFIX); - logger.log(LOG_MESSAGE); + logger.log(LOG_MESSAGE); - verify(printStream).println(EXPECTED_PREFIX + " " + LOG_MESSAGE); - } + verify(printStream).println(EXPECTED_PREFIX + " " + LOG_MESSAGE); + } } ``` + diff --git a/doc/Testen.md b/doc/Testen.md index 1c260c9f9..72178a65d 100644 --- a/doc/Testen.md +++ b/doc/Testen.md @@ -4,52 +4,53 @@ Wenn möglich, nutzen wir das Prinzip des Test Driven Development. D.h. Tests we den zu erstellenden Klassen geschrieben. Details zu diesem Ansatz finden sich in [4] in Kapitel 9. Dies hat viele Vorteile: - Es werden nur die Anforderungen umgesetzt, die auch wirklich nötig sind. - - **YAGNI**: You ain‘t gonna need it! - - **KISS**: Keep it simple, stupid! +- **YAGNI**: You ain‘t gonna need it! +- **KISS**: Keep it simple, stupid! - Das Softwaredesign orientiert sich an der Nutzung von Klassen, da die Tests die ersten "Anwender" des API sind. - Testfälle dokumentieren eine Klasse (und ergänzen somit die Spezifikation). - Testbarkeit eines Programms ist per Definition garantiert und zudem erhalten wir automatisch eine hohe Testabdeckung. ## Konventionen beim Schreiben von Modultests -Wir nutzen für Modultests (d.h. Unittests) die [JUnit](https://junit.org/) Bibliothek in der Version 5. Alle Modultests +Wir nutzen für Modultests (d.h. Unittests) die [JUnit](https://junit.org/) Bibliothek in der Version 5. Alle Modultests einer Klasse `Foo` legen wir in der zugehörigen Klasse `FooTest` ab. (Alternativ könnte auch der Suffix `Tests` verwendet -werden, da ja eine Testklasse typischerweise mehrere Tests enthält - das ist aber Geschmackssache.) -Testklassen verwenden dasselbe Package wie die zu testende Klasse. Die Tests werden im Verzeichnis `src/test/java` -abgelegt, damit sie separat von den eigentlichen Klassen liegen (diese liegen unter `src/main/java`). +werden, da ja eine Testklasse typischerweise mehrere Tests enthält - das ist aber Geschmackssache.) +Testklassen verwenden dasselbe Package wie die zu testende Klasse. Die Tests werden im Verzeichnis `src/test/java` +abgelegt, damit sie separat von den eigentlichen Klassen liegen (diese liegen unter `src/main/java`). -Gemäß der in JUnit 5 eingeführten Konventionen haben Test Klassen und Methoden die Sichtbarkeit *package private*. +Gemäß der in JUnit 5 eingeführten Konventionen haben Test Klassen und Methoden die Sichtbarkeit *package private*. Damit Testfälle als solches erkannt werden, müssen sie mit der Annotation `@org.junit.jupiter.api.Test` markiert werden. Ein Modultest besteht immer aus drei Schritten, die ggf. zusammenfallen können: -1. **Given**: Das zu testende Objekt wird erzeugt (auch Subject Under Test genannt und SUT abgekürzt). Sind dazu weitere Objekte nötig, -so werden diese in diesem Schritt ebenso erzeugt. Sollte das Erzeugen dieser zusätzlich erforderlichen Objekte mehr -als ein paar Zeilen Code erfordern, so sollten diese Objekte in einzelnen `create` Methoden erzeugt werden. Damit -ist eine Wiederverwendung in anderen Testfällen leichter möglich. +1. **Given**: Das zu testende Objekt wird erzeugt (auch Subject Under Test genannt und SUT abgekürzt). Sind dazu weitere Objekte nötig, + so werden diese in diesem Schritt ebenso erzeugt. Sollte das Erzeugen dieser zusätzlich erforderlichen Objekte mehr + als ein paar Zeilen Code erfordern, so sollten diese Objekte in einzelnen `create` Methoden erzeugt werden. Damit + ist eine Wiederverwendung in anderen Testfällen leichter möglich. 2. **When**: Die zu überprüfende Funktionalität wird aufgerufen. Sind dazu weitere Objekte nötig (z.B. als Methodenparameter), -sollten diese bereits in Schritt 1.) erzeugt werden. + sollten diese bereits in Schritt 1.) erzeugt werden. 3. **Then**: Es wird überprüft, ob die im letzten Schritt aufgerufene Funktionalität korrekt ist. Dazu kann z.B. der -Rückgabewert einer Methode oder der innere Zustand einer Klasse herangezogen werden. Zum Prüfen verwenden wir Assertions -des JUnit Frameworks [AssertJ](https://assertj.github.io/doc/) bzw. -die `verify` Methoden eines mocks. - -Die Benennung der drei Schritte in **Given-When-Then** stammt aus dem -[Behavior-Driven-Development](https://dannorth.net/introducing-bdd/) und ist in einem -[Artikel von Martin Fowler](https://martinfowler.com/bliki/GivenWhenThen.html) gut beschrieben. -Es gibt auch noch die Begriffe **Arrange-Act-Assert** und **Setup-Excercise-Verify**, die synonym dazu verwendet + Rückgabewert einer Methode oder der innere Zustand einer Klasse herangezogen werden. Zum Prüfen verwenden wir Assertions + des JUnit Frameworks [AssertJ](https://assertj.github.io/doc/) bzw. + die `verify` Methoden eines mocks. + +Die Benennung der drei Schritte in **Given-When-Then** stammt aus dem +[Behavior-Driven-Development](https://dannorth.net/introducing-bdd/) und ist in einem +[Artikel von Martin Fowler](https://martinfowler.com/bliki/GivenWhenThen.html) gut beschrieben. +Es gibt auch noch die Begriffe **Arrange-Act-Assert** und **Setup-Excercise-Verify**, die synonym dazu verwendet werden können. -Damit im Fehlerfall schnell die Ursache gefunden wird, benennen wir eine Testmethode mit einem sinnvollen +Damit im Fehlerfall schnell die Ursache gefunden wird, benennen wir eine Testmethode mit einem sinnvollen (und ausreichend langem) Namen -und ergänzen im JavaDoc in einem knappen Satz das Ziel des Tests. Eine sinnvolle Namenskonvention für Tests ist der -Präfix *should* mit einer angehängten Beschreibung, die die Eigenschaften des SUT beschreiben, die im Test überprüft werden +und ergänzen im JavaDoc in einem knappen Satz das Ziel des Tests. Eine sinnvolle Namenskonvention für Tests ist der +Präfix *should* mit einer angehängten Beschreibung, die die Eigenschaften des SUT beschreiben, die im Test überprüft werden (bzw. das Ziel des Tests). Dies ist aber keine Pflicht, wichtig ist eine gute Benennung. -Ein Test kann durchaus mehrere Szenarien enthalten, die aufeinander aufbauen. Ebenso ist die Verwendung von mehreren -Assertions im **When** Teil erlaubt. +Ein Test kann durchaus mehrere Szenarien enthalten, die aufeinander aufbauen. Ebenso ist die Verwendung von mehreren +Assertions im **When** Teil erlaubt. An einem Beispiel lassen sich diese Konventionen am besten erkennen: + ```java package edu.hm.hafner.util; @@ -92,38 +93,38 @@ class TreeStringBuilderTest { Kann ein Konstruktor oder eine Methode eine Exception werfen, so muss dies auch getestet werden. Dazu wird das gleiche Vorgehen verwendet, d.h. der Test wird in **Given-When-Then** aufgeteilt. Am elegantesten wird ein Exception -Test mit [Lambda-Ausdrücken](http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html) +Test mit [Lambda-Ausdrücken](http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html) und der Assertion `assertThatThrownBy` aus AssertJ: - + ```java - @Test - void shouldThrowAssertionErrorIfLabelIsEmpty() { - assertThatThrownBy(() -> new TreeString(new TreeString(), "")) - .isInstanceOf(AssertionError.class); - } -``` +@Test +void shouldThrowAssertionErrorIfLabelIsEmpty() { + assertThatThrownBy(() -> new TreeString(new TreeString(), "")) + .isInstanceOf(AssertionError.class); +} +``` Alternativ kann auch die Syntax `assertThatExceptionOfType` genutzt werden: ```java - @Test - void shouldThrowAssertionErrorIfLabelIsEmpty() { - assertThatExceptionOfType(AssertionError.class) - .isThrownBy(() -> new TreeString(new TreeString(), "")); - } -``` +@Test +void shouldThrowAssertionErrorIfLabelIsEmpty() { + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> new TreeString(new TreeString(), "")); +} +``` Wichtig ist, dass der Lambda Block nur genau die Anweisung enthält, die die Exception wirft. Dies hat den Vorteil - auch gegenüber dem JUnit Pedant `@Test(expected = Exception.class)` - dass die Exception nur an der genau bestimmten - Stelle überprüft wird. Wird eine Exception zufällig an einer anderen Stelle geworfen, so wird das als Testfehler - markiert. +Stelle überprüft wird. Wird eine Exception zufällig an einer anderen Stelle geworfen, so wird das als Testfehler +markiert. ## Bedingte Ausführung von Tests In Ausnahmefällen macht es Sinn, dass ein Testfall nur unter bestimmten Voraussetzungen gestartet wird. Ein typischer Anwendungsfall ist die Behandlung unterschiedlicher Betriebssysteme. In so einem Fall können Tests mit einem sogenannten *Guard* versehen werden, der den Test überspringt, falls die Voraussetzungen nicht erfüllt sind: wir verwenden in -so einem Fall die Methode `assumeThat` der AssertJ Bibliothek. +so einem Fall die Methode `assumeThat` der AssertJ Bibliothek. Beispielweise gibt es für die Klasse `PathUtils` jeweils einen Testfall für Windows und Unix Systeme: @@ -146,27 +147,27 @@ void shouldSkipAlreadyAbsoluteOnWindows() { assertThat(pathUtil.createAbsolutePath("C:\\tmp", "C:\\tmp\\file.txt")).isEqualTo("C:/tmp/file.txt"); } ``` - + ## Allgemeine Testszenarien In JUnit gibt es die Möglichkeit, sich ein oder mehrere Testszenarien über speziell dafür markierte Methoden aufzubauen. -Dazu müssen diese Methoden mit `@BeforeEach`, `@BeforeAll`, `@AfterEach`, `@AfterAll`, etc. annotiert werden, und die -erzeugten SUT (und abhängigen Objekte) in Objektvariablen (Fields) gespeichert werden. Dieses Vorgehen ist bequem, -macht Testfälle jedoch unübersichtlich und schwer verständlich, da die im Test verwendeten Objekte nicht direkt sichtbar sind. -Daher verwenden wir diese Annotationen nicht. Generell gilt: Test Klassen sollen keine Objektvariablen besitzen. Statt -dessen sollten benötigte Objekte immer neu mit passenden `create` Methoden erzeugt werden: so können die erzeugten +Dazu müssen diese Methoden mit `@BeforeEach`, `@BeforeAll`, `@AfterEach`, `@AfterAll`, etc. annotiert werden, und die +erzeugten SUT (und abhängigen Objekte) in Objektvariablen (Fields) gespeichert werden. Dieses Vorgehen ist bequem, +macht Testfälle jedoch unübersichtlich und schwer verständlich, da die im Test verwendeten Objekte nicht direkt sichtbar sind. +Daher verwenden wir diese Annotationen nicht. Generell gilt: Test Klassen sollen keine Objektvariablen besitzen. Statt +dessen sollten benötigte Objekte immer neu mit passenden `create` Methoden erzeugt werden: so können die erzeugten Objekte in den einzelnen Tests unabhängig voneinander geändert werden können. ## Aussagekräftige Fehlermeldungen -Ein wichtiger Schritt im TDD ist die Validierung, ob der **Test** überhaupt korrekt ist. D.h. wir müssen als erstes -sicherstellen, dass ein Test zunächst einmal fehlschlägt, wenn die zu testende Methode noch unvollständig ist. -An dieser Stelle lohnt es sich, die Fehlermeldung zu analysieren: ist diese nicht aussagekräftig, -sollte diese mit der Methode `as` entsprechend verbessert werden: +Ein wichtiger Schritt im TDD ist die Validierung, ob der **Test** überhaupt korrekt ist. D.h. wir müssen als erstes +sicherstellen, dass ein Test zunächst einmal fehlschlägt, wenn die zu testende Methode noch unvollständig ist. +An dieser Stelle lohnt es sich, die Fehlermeldung zu analysieren: ist diese nicht aussagekräftig, +sollte diese mit der Methode `as` entsprechend verbessert werden: ```java assertThat(list.size()).as("Wrong number of list elements").isEqualTo(5); -``` +``` ## Eigenschaften von Modultests @@ -183,7 +184,7 @@ Hier noch einige Anregungen bei der Gestaltung von Modultests: - Eine Testmethode sollte nur einen Aspekt testen: d.h. wir testen ein bestimmtes Verhalten und nur indirekt eine Methode. - Testmethoden sollten ca. 5-15 Zeilen umfassen. - Modultests greifen i.A. nie auf Datenbank, Dateisystem oder Web Services zu. -- Häufig verwendete Eingangsparameter sollten als Konstanten definiert werden. +- Häufig verwendete Eingangsparameter sollten als Konstanten definiert werden. - Tests nutzen die selben Kodierungsrichtlinien wie normale Klassen. ## Testfall Anzahl @@ -191,27 +192,27 @@ Hier noch einige Anregungen bei der Gestaltung von Modultests: Die Anzahl der erforderlichen Tests für eine Klasse lässt sich schwer herleiten. Daher sollten folgende Kriterien herangezogen werden: - Jede nicht-triviale public Methode einer Klasse mit mindestens einem Testfall überprüfen - - Randwerte (0, -1, 1, etc.) verwenden - - Eingabeparameter mit unerwarteten Werten (null, {}, etc.) belegen +- Randwerte (0, -1, 1, etc.) verwenden +- Eingabeparameter mit unerwarteten Werten (null, {}, etc.) belegen - Äquivalenzklassen bilden: minimale Anzahl Tests für maximale Variation der Testdaten - Zu jedem entdeckten Fehler (z.B. durch einen Bug Report) einen passenden Testfall erstellen -Sinnvollerweise nutzt man die [Coverage Übersicht](https://www.jetbrains.com/idea/help/code-coverage.html) +Sinnvollerweise nutzt man die [Coverage Übersicht](https://www.jetbrains.com/idea/help/code-coverage.html) der Entwicklungsumgebung, um zu überprüfen, welcher Teil des Quelltextes bereits getestet wurde. ## Weiterführende Themen Die folgenden Abschnitte referenzieren einige weiterführende Themen. - + ### Testen von Basisklassen -Abstrakte Klassen und Schnittstellen lassen sich ebenso testen: dazu wird das +Abstrakte Klassen und Schnittstellen lassen sich ebenso testen: dazu wird das Abstract Test Pattern benutzt, das in einem [eigenen Abschnitt](Abstract-Test-Pattern.md) beschrieben ist. ### State Based vs. Interaction Based Testing -Prinzipiell gibt es zwei Varianten des Testings. Beim **State Based Testing** wird das Testobjekt nach Aufruf der zu -testenden Methoden durch Abfrage seines internen Zustands verifiziert. Im Gegensatz dazu wird beim -**Interaction Based Testing** nicht der Zustand des SUT analysiert. Statt dessen werden die Aufrufe aller am Test +Prinzipiell gibt es zwei Varianten des Testings. Beim **State Based Testing** wird das Testobjekt nach Aufruf der zu +testenden Methoden durch Abfrage seines internen Zustands verifiziert. Im Gegensatz dazu wird beim +**Interaction Based Testing** nicht der Zustand des SUT analysiert. Statt dessen werden die Aufrufe aller am Test beteiligten Objekte überprüft. Details dazu sind im eigenen Abschnitt [State Based vs. Interaction Based Testing](State-Based-Vs-Interaction-Based.md) beschrieben. diff --git a/doc/Working-with-Github.md b/doc/Working-with-Github.md index 7aedd8824..0e25f18f5 100644 --- a/doc/Working-with-Github.md +++ b/doc/Working-with-Github.md @@ -4,12 +4,15 @@ Disclaimer: The following guide was created with significant help from the two t - [GitHub Standard Fork & Pull Request Workflow](https://gist.github.com/Chaser324/ce0505fbed06b947d962) # Working with Pull Requests in GitHub + Submissions in GitHub are made through pull requests. The steps described in the following sections are necessary for this process. ### Creating a Fork + To submit a pull request with your results, you first need to create a [fork](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) of the respective project. This cannot be done through the command line but only in the GitHub interface, as outlined [in the guide](https://help.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository). This newly created fork is initially an exact copy of the original project, meaning it contains all commits, branches, and tags. ### Working with the Fork + Once the fork is created, it becomes visible under your own GitHub user account as a copy. This copy can be retrieved to your local machine using the following command: ```bash @@ -27,6 +30,7 @@ git clone https://github.com/USERNAME/FORKED-PROJECT.git For easy password-free use of GitHub, I recommend setting up SSH as quickly as possible. ### Developing Your Own Changes + For each submission (and for every small change to the project), a new branch must be created. Working on the `master` branch is not advisable, as discussed in the "Keeping Fork Updated" chapter. To create a branch, the following steps are necessary: @@ -50,6 +54,7 @@ Another useful approach is incremental development: development is not done in o When committing, it is important to give a good commit message. Chris Beam has written a helpful article on this, titled ["How to Write a Git Commit Message."](https://chris.beams.io/posts/git-commit/) # Prepare and Submit a Pull Request + Once all changes have been locally completed with a commit, they can be integrated into the fork on GitHub. Only a push is required for this: ```bash @@ -66,6 +71,7 @@ Before finally creating the pull request, it must be checked whether the pull re If the pull request looks as desired, it can be created with "Create." # Update the Pull Request + Once the pull request is created, it is automatically checked with various tools. The tools used depend on the project. Typically, [continuous integration](Continuous-Integration.md) is started, executing a development lifecycle: 1. Compile @@ -77,6 +83,7 @@ Each of these steps is marked in GitHub with an Ok or Failed status. If any of t If all automatic tests are Ok, only the review by the author of the original project is missing. This is also done line by line in the pull request and can be incorporated with the same steps as described above. GitHub usually detects these changes automatically, so they do not need to be explicitly marked as resolved. # Keeping the Fork Updated + It is important to keep this fork up to date, i.e., always sync the changes from the original project. In most cases, it is sufficient to keep the so-called master branch synchronized. To enable this, the original project must be added as another remote. The name "upstream" has become common for this. This can be implemented with the following command: ```bash @@ -85,6 +92,7 @@ git remote add upstream https://github.com/UPSTREAM-USER/ORIGINAL-PROJECT.git ``` The following command can be used to check the configuration: + ```bash # Verify the new remote named 'upstream' git remote -v @@ -98,6 +106,7 @@ origin git@github.com:USERNAME/FORKED-PROJECT.git (push) upstream https://github.com/UPSTREAM-USER/ORIGINAL-PROJECT.git (fetch) upstream https://github.com/UPSTREAM-USER/ORIGINAL-PROJECT.git (push) ``` + Whenever the changes from the master branch of the original project need to be integrated, they can be incorporated with the following command: ```bash @@ -111,4 +120,5 @@ git branch -va git checkout master git merge upstream/master ``` + Normally, there should be no other commits on the local master branch, so a [fast-forward](https://git-scm.com/book/de/v2/Git-Branching-Einfaches-Branching-und-Merging) will be applied. diff --git a/etc/checkstyle-java-configuration.xml b/etc/checkstyle-java-configuration.xml index a207a42c4..63941c1ff 100644 --- a/etc/checkstyle-java-configuration.xml +++ b/etc/checkstyle-java-configuration.xml @@ -33,10 +33,6 @@ - - - - @@ -82,13 +78,11 @@ - - @@ -103,6 +97,8 @@ + + @@ -111,17 +107,14 @@ - + + + value="LITERAL_DO,LITERAL_ELSE,LITERAL_FINALLY,LITERAL_IF,LITERAL_FOR,LITERAL_TRY,LITERAL_WHILE,STATIC_INIT,INSTANCE_INIT"/> - - - - diff --git a/etc/checkstyle-tests-configuration.xml b/etc/checkstyle-tests-configuration.xml index f3653bedd..316785e82 100644 --- a/etc/checkstyle-tests-configuration.xml +++ b/etc/checkstyle-tests-configuration.xml @@ -33,10 +33,6 @@ - - - - @@ -82,13 +78,11 @@ - - @@ -103,6 +97,8 @@ + + @@ -111,17 +107,14 @@ - + + + value="LITERAL_DO,LITERAL_ELSE,LITERAL_FINALLY,LITERAL_IF,LITERAL_FOR,LITERAL_TRY,LITERAL_WHILE,STATIC_INIT,INSTANCE_INIT"/> - - - - diff --git a/etc/pmd-java-configuration.xml b/etc/pmd-java-configuration.xml index 33f2811a1..462547557 100644 --- a/etc/pmd-java-configuration.xml +++ b/etc/pmd-java-configuration.xml @@ -38,6 +38,7 @@ + diff --git a/package.json b/package.json index 70c2adf0f..ca7f60984 100644 --- a/package.json +++ b/package.json @@ -10,14 +10,12 @@ "remark-lint": "^10.0.0", "remark-preset-lint-recommended": "^7.0.0" }, - "devDependencies": {}, + "devDependencies": { }, "scripts": { "lint-md": "remark ." }, "remarkConfig": { - "plugins": [ - "remark-preset-lint-recommended" - ] + "plugins": [ "remark-preset-lint-recommended" ] }, "repository": { "type": "git", @@ -26,4 +24,4 @@ "author": "Ullrich Hafner", "license": "MIT", "homepage": "https://github.com/jenkinsci/analysis-model#readme" -} +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index ba56839c6..c7154c2ef 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ edu.hm.hafner codingstyle - 6.19.0-SNAPSHOT + 7.0.0-SNAPSHOT jar Java coding style @@ -106,6 +106,7 @@ 4.0.0 10.0.1 13.0.0 + 3.10.2 6.46.1 @@ -117,12 +118,6 @@ - - - net.bytebuddy - byte-buddy - ${byte-buddy.version} - org.junit @@ -138,6 +133,12 @@ pom import + + + net.bytebuddy + byte-buddy + ${byte-buddy.version} + @@ -152,15 +153,31 @@ error_prone_annotations ${error-prone.version} + + commons-io + commons-io + ${commons.io.version} + org.apache.commons commons-lang3 ${commons.lang.version} - commons-io - commons-io - ${commons.io.version} + com.tngtech.archunit + archunit-junit5 + ${archunit.version} + test + + + org.junit.platform + junit-platform-engine + + + org.slf4j + slf4j-api + + @@ -170,6 +187,12 @@ ${equalsverifier.version} test + + org.assertj + assertj-core + ${assertj.version} + test + org.junit.jupiter junit-jupiter @@ -180,28 +203,6 @@ mockito-core test - - org.assertj - assertj-core - ${assertj.version} - test - - - com.tngtech.archunit - archunit-junit5 - ${archunit.version} - test - - - org.slf4j - slf4j-api - - - org.junit.platform - junit-platform-engine - - - org.slf4j slf4j-simple @@ -377,23 +378,23 @@ org.openrewrite.recipe - rewrite-testing-frameworks - ${rewrite-testing-frameworks.version} + rewrite-migrate-java + ${rewrite-migrate-java.version} org.openrewrite.recipe - rewrite-static-analysis - ${rewrite-static-analysis.version} + rewrite-recommendations + ${rewrite-recommendations.version} org.openrewrite.recipe - rewrite-migrate-java - ${rewrite-migrate-java.version} + rewrite-static-analysis + ${rewrite-static-analysis.version} org.openrewrite.recipe - rewrite-recommendations - ${rewrite-recommendations.version} + rewrite-testing-frameworks + ${rewrite-testing-frameworks.version} @@ -422,16 +423,16 @@ - - org.pitest - pitest-junit5-plugin - ${pitest-junit5-plugin.version} - edu.hm.hafner pitmute ${pitmute.version} + + org.pitest + pitest-junit5-plugin + ${pitest-junit5-plugin.version} + @@ -452,6 +453,103 @@ + + com.diffplug.spotless + spotless-maven-plugin + ${spotless-maven-plugin.version} + + + + **/*.adoc + + + target/** + docker/volumes/** + src/test/resources/** + + + true + + + + + target/** + docker/volumes/** + src/test/resources/** + + + + + true + + + true + + + + + + + + + **/*.json + + + target/** + docker/volumes/** + src/test/resources/** + + + + + + **/*.md + + + target/** + docker/volumes/** + src/test/resources/** + + + + + + false + scope,groupId,artifactId + groupId,artifactId + true + + + + + **/*.js + + + target/** + docker/volumes/** + src/test/resources/** + + + + + + **/*.yaml + + + target/** + docker/volumes/** + src/test/resources/** + + + + + + + check + + + + org.apache.maven.plugins maven-surefire-plugin @@ -466,10 +564,10 @@ + default-test test - default-test test @@ -478,10 +576,10 @@ + architecture-test test - architecture-test test diff --git a/src/main/java/edu/hm/hafner/util/CSharpNamespaceDetector.java b/src/main/java/edu/hm/hafner/util/CSharpNamespaceDetector.java index ce3dfe834..baa8466ba 100644 --- a/src/main/java/edu/hm/hafner/util/CSharpNamespaceDetector.java +++ b/src/main/java/edu/hm/hafner/util/CSharpNamespaceDetector.java @@ -1,7 +1,6 @@ package edu.hm.hafner.util; import edu.hm.hafner.util.PackageDetectorFactory.FileSystemFacade; - import java.util.regex.Pattern; /** diff --git a/src/main/java/edu/hm/hafner/util/Ensure.java b/src/main/java/edu/hm/hafner/util/Ensure.java index e5f74d307..92df0d19c 100644 --- a/src/main/java/edu/hm/hafner/util/Ensure.java +++ b/src/main/java/edu/hm/hafner/util/Ensure.java @@ -1,50 +1,41 @@ package edu.hm.hafner.util; -import org.apache.commons.lang3.StringUtils; - import com.google.errorprone.annotations.FormatMethod; - import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.CheckReturnValue; - import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Formatter; import java.util.List; import java.util.Objects; +import org.apache.commons.lang3.StringUtils; /** * Provides several helper methods to validate method arguments and class invariants, thus supporting the * design-by-contract concept (DBC). * - *

- * Note: the static methods provided by this class use a fluent interface, i.e., to verify an assertion, a method + *

Note: the static methods provided by this class use a fluent interface, i.e., to verify an assertion, a method * sequence needs to be called. - *

* - *

- * Available checks: - *

+ *

Available checks: + * *

    - *
  • Boolean assertions, e.g., {@code Ensure.that(condition).isTrue(); }
  • - *
  • String assertions, e.g., {@code Ensure.that(string).isNotEmpty(); }
  • - *
  • Object assertions, e.g., {@code Ensure.that(element).isNotNull(); }
  • - *
  • Array assertions, e.g., {@code Ensure.that(array).isNotEmpty(); }
  • - *
  • Iterable assertions, e.g., {@code Ensure.that(collection).isNotNull(); }
  • + *
  • Boolean assertions, e.g., {@code Ensure.that(condition).isTrue(); } + *
  • String assertions, e.g., {@code Ensure.that(string).isNotEmpty(); } + *
  • Object assertions, e.g., {@code Ensure.that(element).isNotNull(); } + *
  • Array assertions, e.g., {@code Ensure.that(array).isNotEmpty(); } + *
  • Iterable assertions, e.g., {@code Ensure.that(collection).isNotNull(); } *
* * @author Ullrich Hafner - * @see Design by Contract (Meyer, - * Bertrand) + * @see Design by Contract (Meyer, Bertrand) */ public final class Ensure { /** * Returns a boolean condition. * - * @param value - * the value to check - * + * @param value the value to check * @return a boolean condition */ @CheckReturnValue @@ -55,27 +46,21 @@ public static BooleanCondition that(final boolean value) { /** * Returns an object condition. * - * @param value - * the value to check - * @param additionalValues - * the additional values to check - * @param - * type to check - * + * @param value the value to check + * @param additionalValues the additional values to check + * @param type to check * @return an object condition */ @CheckReturnValue - public static ObjectCondition that(@CheckForNull final T value, - @CheckForNull final Object... additionalValues) { + public static ObjectCondition that( + @CheckForNull final T value, @CheckForNull final Object... additionalValues) { return new ObjectCondition<>(value, additionalValues); } /** * Returns an iterable condition. * - * @param value - * the value to check - * + * @param value the value to check * @return an iterable condition */ @CheckReturnValue @@ -86,9 +71,7 @@ public static IterableCondition that(@CheckForNull final Iterable value) { /** * Returns a collection condition. * - * @param value - * the value to check - * + * @param value the value to check * @return a collection condition */ @CheckReturnValue @@ -99,9 +82,7 @@ public static CollectionCondition that(@CheckForNull final Collection value) /** * Returns an array condition. * - * @param value - * the value to check - * + * @param value the value to check * @return an array condition */ @SuppressWarnings({"PMD.UseVarargs", "AvoidObjectArrays"}) @@ -113,9 +94,7 @@ public static ArrayCondition that(@CheckForNull final Object[] value) { /** * Returns a string condition. * - * @param value - * the value to check - * + * @param value the value to check * @return a string condition */ @CheckReturnValue @@ -126,9 +105,7 @@ public static StringCondition that(@CheckForNull final String value) { /** * Returns an exception condition. * - * @param value - * the value to check - * + * @param value the value to check * @return an exception condition */ @CheckReturnValue @@ -136,9 +113,7 @@ public static ExceptionCondition that(@CheckForNull final Throwable value) { return new ExceptionCondition(value); } - /** - * Always throws an {@link AssertionError}. - */ + /** Always throws an {@link AssertionError}. */ public static void thatStatementIsNeverReached() { throwException("This statement should never be reached."); } @@ -146,12 +121,10 @@ public static void thatStatementIsNeverReached() { /** * Always throws an {@link AssertionError}. * - * @param explanation - * a {@link Formatter formatted message} explaining the assertion - * @param args - * Arguments referenced by the format specifiers in the formatted explanation. If there are more arguments - * than format specifiers, the extra arguments are ignored. The number of arguments is variable and may be - * zero. + * @param explanation a {@link Formatter formatted message} explaining the assertion + * @param args Arguments referenced by the format specifiers in the formatted explanation. If there are more + * arguments than format specifiers, the extra arguments are ignored. The number of arguments is variable and + * may be zero. */ @FormatMethod public static void thatStatementIsNeverReached(final String explanation, final Object... args) { @@ -161,14 +134,10 @@ public static void thatStatementIsNeverReached(final String explanation, final O /** * Throws an {@link AssertionError} with the specified detail message. * - * @param message - * a {@link Formatter formatted message} with the description of the error - * @param args - * Arguments referenced by the format specifiers in the formatted message. If there are more arguments than - * format specifiers, the extra arguments are ignored. The number of arguments is variable and may be zero. - * - * @throws AssertionError - * always thrown + * @param message a {@link Formatter formatted message} with the description of the error + * @param args Arguments referenced by the format specifiers in the formatted message. If there are more arguments + * than format specifiers, the extra arguments are ignored. The number of arguments is variable and may be zero. + * @throws AssertionError always thrown */ @FormatMethod private static void throwException(final String message, @CheckForNull final Object... args) { @@ -179,15 +148,12 @@ private Ensure() { // prevents instantiation } - /** - * Assertions for iterables. - */ + /** Assertions for iterables. */ public static class IterableCondition extends ObjectCondition> { /** * Creates a new instance of {@code IterableCondition}. * - * @param value - * value of the condition + * @param value value of the condition */ public IterableCondition(@CheckForNull final Iterable value) { super(value); @@ -197,8 +163,8 @@ public IterableCondition(@CheckForNull final Iterable value) { * Ensures that the given iterable is not {@code null} and contains at least one element. Additionally, ensures * that each element of the iterable is not {@code null}. * - * @throws AssertionError - * if the iterable is empty (or {@code null}), or at least one iterable element is {@code null} + * @throws AssertionError if the iterable is empty (or {@code null}), or at least one iterable element is + * {@code null} */ public void isNotEmpty() { isNotEmpty("Iterable is empty or NULL"); @@ -207,8 +173,7 @@ public void isNotEmpty() { /** * Ensures that the given iterable is not {@code null} but empty. * - * @throws AssertionError - * if the iterable is not empty (or {@code null}) + * @throws AssertionError if the iterable is not empty (or {@code null}) */ public void isEmpty() { isEmpty("Iterable '%s' is not empty or NULL", renderValue()); @@ -218,36 +183,30 @@ public void isEmpty() { * Ensures that the given iterable is not {@code null} and contains the specified number of elements. * Additionally, ensures that each element of the iterable is not {@code null}. * - * @param expectedSize - * the expected number of elements in the iterable - * - * @throws AssertionError - * if the iterable does not contain the expected number of elements (or is {@code null}), or at least - * one iterable element is {@code null} + * @param expectedSize the expected number of elements in the iterable + * @throws AssertionError if the iterable does not contain the expected number of elements (or is {@code null}), + * or at least one iterable element is {@code null} */ public void hasSize(final int expectedSize) { - hasSize(expectedSize, + hasSize( + expectedSize, "Iterable does not contain the expected number of elements. " + "Actual value: %s. Expected size: %d", - renderValue(), expectedSize); + renderValue(), + expectedSize); } /** * Ensures that the given iterable is not {@code null} and contains the specified number of elements. * Additionally, ensures that each element of the iterable is not {@code null}. * - * @param expectedSize - * the expected number of elements in the iterable - * @param explanation - * a {@link Formatter formatted message} explaining the assertion - * @param args - * Arguments referenced by the format specifiers in the formatted explanation. If there are more - * arguments than format specifiers, the extra arguments are ignored. The number of arguments is - * variable and may be zero. - * - * @throws AssertionError - * if the iterable does not contain the expected number of elements (or is {@code null}), or at least - * one iterable element is {@code null} + * @param expectedSize the expected number of elements in the iterable + * @param explanation a {@link Formatter formatted message} explaining the assertion + * @param args Arguments referenced by the format specifiers in the formatted explanation. If there are more + * arguments than format specifiers, the extra arguments are ignored. The number of arguments is variable + * and may be zero. + * @throws AssertionError if the iterable does not contain the expected number of elements (or is {@code null}), + * or at least one iterable element is {@code null} */ @FormatMethod public void hasSize(final int expectedSize, final String explanation, final Object... args) { @@ -275,15 +234,12 @@ private int computeSize(final String explanation, final Object... args) { * Ensures that the given iterable is not {@code null} and contains at least one element. Additionally, ensures * that each element of the iterable is not {@code null}. * - * @param explanation - * a {@link Formatter formatted message} explaining the assertion - * @param args - * Arguments referenced by the format specifiers in the formatted explanation. If there are more - * arguments than format specifiers, the extra arguments are ignored. The number of arguments is - * variable and may be zero. - * - * @throws AssertionError - * if the iterable is empty (or {@code null}), or at least one iterable element is {@code null} + * @param explanation a {@link Formatter formatted message} explaining the assertion + * @param args Arguments referenced by the format specifiers in the formatted explanation. If there are more + * arguments than format specifiers, the extra arguments are ignored. The number of arguments is variable + * and may be zero. + * @throws AssertionError if the iterable is empty (or {@code null}), or at least one iterable element is + * {@code null} */ @FormatMethod public void isNotEmpty(final String explanation, final Object... args) { @@ -297,15 +253,11 @@ public void isNotEmpty(final String explanation, final Object... args) { /** * Ensures that the given iterable is not {@code null} but has no elements. * - * @param explanation - * a {@link Formatter formatted message} explaining the assertion - * @param args - * Arguments referenced by the format specifiers in the formatted explanation. If there are more - * arguments than format specifiers, the extra arguments are ignored. The number of arguments is - * variable and may be zero. - * - * @throws AssertionError - * if the iterable is not empty (or {@code null}) + * @param explanation a {@link Formatter formatted message} explaining the assertion + * @param args Arguments referenced by the format specifiers in the formatted explanation. If there are more + * arguments than format specifiers, the extra arguments are ignored. The number of arguments is variable + * and may be zero. + * @throws AssertionError if the iterable is not empty (or {@code null}) */ @FormatMethod public void isEmpty(final String explanation, final Object... args) { @@ -317,15 +269,12 @@ public void isEmpty(final String explanation, final Object... args) { } } - /** - * Assertions for iterables. - */ + /** Assertions for iterables. */ public static class CollectionCondition extends IterableCondition { /** * Creates a new instance of {@code CollectionCondition}. * - * @param value - * value of the condition + * @param value value of the condition */ public CollectionCondition(@CheckForNull final Collection value) { super(value); @@ -339,11 +288,8 @@ Collection getValue() { /** * Ensures that the given collection is not {@code null} and contains the specified element. * - * @param element - * the element to find - * - * @throws AssertionError - * if the collection is {@code null} or if the specified element is not found + * @param element the element to find + * @throws AssertionError if the collection is {@code null} or if the specified element is not found */ public void contains(final Object element) { contains(element, "Collection '%s' does not contain element '%s'", renderValue(), element); @@ -352,17 +298,12 @@ public void contains(final Object element) { /** * Ensures that the given collection is not {@code null} and contains the specified element. * - * @param element - * the element to find - * @param explanation - * a {@link Formatter formatted message} explaining the assertion - * @param args - * Arguments referenced by the format specifiers in the formatted explanation. If there are more - * arguments than format specifiers, the extra arguments are ignored. The number of arguments is - * variable and may be zero. - * - * @throws AssertionError - * if the collection is {@code null} or if the specified element is not found + * @param element the element to find + * @param explanation a {@link Formatter formatted message} explaining the assertion + * @param args Arguments referenced by the format specifiers in the formatted explanation. If there are more + * arguments than format specifiers, the extra arguments are ignored. The number of arguments is variable + * and may be zero. + * @throws AssertionError if the collection is {@code null} or if the specified element is not found */ @FormatMethod public void contains(final Object element, final String explanation, final Object... args) { @@ -376,11 +317,9 @@ public void contains(final Object element, final String explanation, final Objec /** * Ensures that the given collection is not {@code null} and does not contain the specified element. * - * @param element - * the element that must not be in the collection - * - * @throws AssertionError - * if the collection is {@code null} or if the specified element is part of the collection + * @param element the element that must not be in the collection + * @throws AssertionError if the collection is {@code null} or if the specified element is part of the + * collection */ public void doesNotContain(final Object element) { doesNotContain(element, "Collection '%s' contains element '%s' but should not", renderValue(), element); @@ -389,17 +328,13 @@ public void doesNotContain(final Object element) { /** * Ensures that the given collection is not {@code null} and does not contain the specified element. * - * @param element - * the element that must not be in the collection - * @param explanation - * a {@link Formatter formatted message} explaining the assertion - * @param args - * Arguments referenced by the format specifiers in the formatted explanation. If there are more - * arguments than format specifiers, the extra arguments are ignored. The number of arguments is - * variable and may be zero. - * - * @throws AssertionError - * if the collection is {@code null} or if the specified element is part of the collection + * @param element the element that must not be in the collection + * @param explanation a {@link Formatter formatted message} explaining the assertion + * @param args Arguments referenced by the format specifiers in the formatted explanation. If there are more + * arguments than format specifiers, the extra arguments are ignored. The number of arguments is variable + * and may be zero. + * @throws AssertionError if the collection is {@code null} or if the specified element is part of the + * collection */ @FormatMethod public void doesNotContain(final Object element, final String explanation, final Object... args) { @@ -411,15 +346,12 @@ public void doesNotContain(final Object element, final String explanation, final } } - /** - * Assertions for arrays. - */ + /** Assertions for arrays. */ public static class ArrayCondition extends ObjectCondition { /** * Creates a new instance of {@link IterableCondition}. * - * @param values - * value of the condition + * @param values value of the condition */ @SuppressWarnings({"PMD.UseVarargs", "AvoidObjectArrays"}) public ArrayCondition(@CheckForNull final Object[] values) { @@ -430,8 +362,7 @@ public ArrayCondition(@CheckForNull final Object[] values) { * Ensures that the given array is not {@code null} and contains at least one element. Additionally, ensures * that each element of the array is not {@code null}. * - * @throws AssertionError - * if the array is empty (or {@code null}), or at least one array element is {@code null} + * @throws AssertionError if the array is empty (or {@code null}), or at least one array element is {@code null} */ public void isNotEmpty() { isNotEmpty("Array is empty or NULL"); @@ -440,8 +371,7 @@ public void isNotEmpty() { /** * Ensures that the given array is not {@code null} but empty. * - * @throws AssertionError - * if the array is not empty (or {@code null}) + * @throws AssertionError if the array is not empty (or {@code null}) */ public void isEmpty() { isEmpty("Array '%s' is not empty or NULL", renderValue()); @@ -451,32 +381,27 @@ public void isEmpty() { * Ensures that the given array is not {@code null} and has the specified number of elements. Additionally, * ensures that each element of the array is not {@code null}. * - * @param expectedSize - * the expected number of elements in the array - * - * @throws AssertionError - * if the array does not contain the expected number of elements (or is {@code null}), or at least one - * array element is {@code null} + * @param expectedSize the expected number of elements in the array + * @throws AssertionError if the array does not contain the expected number of elements (or is {@code null}), or + * at least one array element is {@code null} */ public void hasSize(final int expectedSize) { - hasSize(expectedSize, - "Array does not contain the expected number of elements. " - + "Actual value: %s. Expected size: %d", - renderValue(), expectedSize); + hasSize( + expectedSize, + "Array does not contain the expected number of elements. " + "Actual value: %s. Expected size: %d", + renderValue(), + expectedSize); } /** * Ensures that the given array is not {@code null} but empty. * - * @param explanation - * a {@link Formatter formatted message} explaining the assertion - * @param args - * Arguments referenced by the format specifiers in the formatted explanation. If there are more - * arguments than format specifiers, the extra arguments are ignored. The number of arguments is - * variable and may be zero. - * - * @throws AssertionError - * if the array is empty (or {@code null}), or at least one array element is {@code null}. + * @param explanation a {@link Formatter formatted message} explaining the assertion + * @param args Arguments referenced by the format specifiers in the formatted explanation. If there are more + * arguments than format specifiers, the extra arguments are ignored. The number of arguments is variable + * and may be zero. + * @throws AssertionError if the array is empty (or {@code null}), or at least one array element is + * {@code null}. */ @FormatMethod public void isEmpty(final String explanation, final Object... args) { @@ -491,15 +416,12 @@ public void isEmpty(final String explanation, final Object... args) { * Ensures that the given array is not {@code null} and contains at least one element. Additionally, ensures * that each element of the array is not {@code null}. * - * @param explanation - * a {@link Formatter formatted message} explaining the assertion - * @param args - * Arguments referenced by the format specifiers in the formatted explanation. If there are more - * arguments than format specifiers, the extra arguments are ignored. The number of arguments is - * variable and may be zero. - * - * @throws AssertionError - * if the array is empty (or {@code null}), or at least one array element is {@code null}. + * @param explanation a {@link Formatter formatted message} explaining the assertion + * @param args Arguments referenced by the format specifiers in the formatted explanation. If there are more + * arguments than format specifiers, the extra arguments are ignored. The number of arguments is variable + * and may be zero. + * @throws AssertionError if the array is empty (or {@code null}), or at least one array element is + * {@code null}. */ @FormatMethod public void isNotEmpty(final String explanation, final Object... args) { @@ -507,8 +429,7 @@ public void isNotEmpty(final String explanation, final Object... args) { if (getValue().length == 0) { throwException(explanation, args); - } - else { + } else { doesNotContainNull(explanation, args); } } @@ -517,17 +438,13 @@ public void isNotEmpty(final String explanation, final Object... args) { * Ensures that the given array is not {@code null} and contains the specified number of elements. Additionally, * ensures that each element of the array is not {@code null}. * - * @param expectedSize - * the expected number of elements in the array - * @param explanation - * a {@link Formatter formatted message} explaining the assertion - * @param args - * Arguments referenced by the format specifiers in the formatted explanation. If there are more - * arguments than format specifiers, the extra arguments are ignored. The number of arguments is - * variable and may be zero. - * - * @throws AssertionError - * if the array is empty (or {@code null}), or at least one array element is {@code null}. + * @param expectedSize the expected number of elements in the array + * @param explanation a {@link Formatter formatted message} explaining the assertion + * @param args Arguments referenced by the format specifiers in the formatted explanation. If there are more + * arguments than format specifiers, the extra arguments are ignored. The number of arguments is variable + * and may be zero. + * @throws AssertionError if the array is empty (or {@code null}), or at least one array element is + * {@code null}. */ @FormatMethod public void hasSize(final int expectedSize, final String explanation, final Object... args) { @@ -535,8 +452,7 @@ public void hasSize(final int expectedSize, final String explanation, final Obje if (getValue().length == expectedSize) { doesNotContainNull(explanation, args); - } - else { + } else { throwException(explanation, args); } } @@ -551,15 +467,12 @@ private void doesNotContainNull(final String explanation, final Object... args) } } - /** - * Assertions for strings. - */ + /** Assertions for strings. */ public static class StringCondition extends ObjectCondition { /** * Creates a new instance of {@code StringCondition}. * - * @param value - * value of the condition + * @param value value of the condition */ public StringCondition(@CheckForNull final String value) { super(value); @@ -568,8 +481,7 @@ public StringCondition(@CheckForNull final String value) { /** * Ensures that the given string is not {@code null} and contains at least one character. * - * @throws AssertionError - * if the string is empty (or {@code null}) + * @throws AssertionError if the string is empty (or {@code null}) */ public void isNotEmpty() { isNotEmpty("The string is empty or NULL"); @@ -578,15 +490,11 @@ public void isNotEmpty() { /** * Ensures that the given string is not {@code null} and contains at least one character. * - * @param explanation - * a {@link Formatter formatted message} explaining the assertion - * @param args - * Arguments referenced by the format specifiers in the formatted explanation. If there are more - * arguments than format specifiers, the extra arguments are ignored. The number of arguments is - * variable and may be zero. - * - * @throws AssertionError - * if the string is empty (or {@code null}) + * @param explanation a {@link Formatter formatted message} explaining the assertion + * @param args Arguments referenced by the format specifiers in the formatted explanation. If there are more + * arguments than format specifiers, the extra arguments are ignored. The number of arguments is variable + * and may be zero. + * @throws AssertionError if the string is empty (or {@code null}) */ @FormatMethod public void isNotEmpty(final String explanation, final Object... args) { @@ -600,8 +508,7 @@ public void isNotEmpty(final String explanation, final Object... args) { /** * Ensures that the given string is not {@code null} and contains at least one non-whitespace character. * - * @throws AssertionError - * if the string is empty (or {@code null}) + * @throws AssertionError if the string is empty (or {@code null}) */ public void isNotBlank() { isNotBlank("The string is blank"); @@ -610,15 +517,11 @@ public void isNotBlank() { /** * Ensures that the given string is not {@code null} and contains at least one non-whitespace character. * - * @param explanation - * a {@link Formatter formatted message} explaining the assertion - * @param args - * Arguments referenced by the format specifiers in the formatted explanation. If there are more - * arguments than format specifiers, the extra arguments are ignored. The number of arguments is - * variable and may be zero. - * - * @throws AssertionError - * if the string is empty (or {@code null}) + * @param explanation a {@link Formatter formatted message} explaining the assertion + * @param args Arguments referenced by the format specifiers in the formatted explanation. If there are more + * arguments than format specifiers, the extra arguments are ignored. The number of arguments is variable + * and may be zero. + * @throws AssertionError if the string is empty (or {@code null}) */ @FormatMethod public void isNotBlank(final String explanation, final Object... args) { @@ -646,20 +549,19 @@ private boolean isBlank() { /** * Assertions for objects. * - * @param - * type to check + * @param type to check */ public static class ObjectCondition { @CheckForNull private final T value; + @CheckForNull private final Object[] additionalValues; /** * Creates a new instance of {@code ObjectCondition}. * - * @param value - * value of the condition + * @param value value of the condition */ @SuppressWarnings("ExplicitArrayForVarargs") public ObjectCondition(@CheckForNull final T value) { @@ -669,10 +571,8 @@ public ObjectCondition(@CheckForNull final T value) { /** * Creates a new instance of {@code ObjectCondition}. * - * @param value - * value of the condition - * @param additionalValues - * additional values of the condition + * @param value value of the condition + * @param additionalValues additional values of the condition */ @SuppressWarnings("PMD.ArrayIsStoredDirectly") public ObjectCondition(@CheckForNull final T value, @CheckForNull final Object... additionalValues) { @@ -683,8 +583,7 @@ public ObjectCondition(@CheckForNull final T value, @CheckForNull final Object.. /** * Ensures that the given object is not {@code null}. * - * @throws AssertionError - * if the object is {@code null} + * @throws AssertionError if the object is {@code null} */ public void isNotNull() { isNotNull("Object is NULL"); @@ -693,23 +592,18 @@ public void isNotNull() { /** * Ensures that the given object is not {@code null}. * - * @param explanation - * a {@link Formatter formatted message} explaining the assertion - * @param args - * Arguments referenced by the format specifiers in the formatted explanation. If there are more - * arguments than format specifiers, the extra arguments are ignored. The number of arguments is - * variable and may be zero. - * - * @throws AssertionError - * if the object is {@code null} + * @param explanation a {@link Formatter formatted message} explaining the assertion + * @param args Arguments referenced by the format specifiers in the formatted explanation. If there are more + * arguments than format specifiers, the extra arguments are ignored. The number of arguments is variable + * and may be zero. + * @throws AssertionError if the object is {@code null} */ @FormatMethod public void isNotNull(final String explanation, final Object... args) { var nullPointerException = new NullPointerException(explanation.formatted(args)); if (value == null || additionalValues == null) { throw nullPointerException; // NOPMD - } - else { + } else { for (Object additionalValue : additionalValues) { if (additionalValue == null) { throw nullPointerException; // NOPMD @@ -729,8 +623,7 @@ String renderValue() { /** * Ensures that the given object is {@code null}. * - * @throws AssertionError - * if the object is not {@code null} + * @throws AssertionError if the object is not {@code null} */ public void isNull() { isNull("Object is not NULL"); @@ -739,15 +632,11 @@ public void isNull() { /** * Ensures that the given object is {@code null}. * - * @param explanation - * a {@link Formatter formatted message} explaining the assertion - * @param args - * Arguments referenced by the format specifiers in the formatted explanation. If there are more - * arguments than format specifiers, the extra arguments are ignored. The number of arguments is - * variable and may be zero. - * - * @throws AssertionError - * if the object is not {@code null} + * @param explanation a {@link Formatter formatted message} explaining the assertion + * @param args Arguments referenced by the format specifiers in the formatted explanation. If there are more + * arguments than format specifiers, the extra arguments are ignored. The number of arguments is variable + * and may be zero. + * @throws AssertionError if the object is not {@code null} */ @FormatMethod public void isNull(final String explanation, final Object... args) { @@ -759,13 +648,9 @@ public void isNull(final String explanation, final Object... args) { /** * Ensures that the given object is an instance of one of the specified types. * - * @param type - * the type to check the specified object for - * @param additionalTypes - * the additional types to check the specified object for - * - * @throws AssertionError - * the specified object is not an instance of the given type (or {@code null}) + * @param type the type to check the specified object for + * @param additionalTypes the additional types to check the specified object for + * @throws AssertionError the specified object is not an instance of the given type (or {@code null}) */ public void isInstanceOf(final Class type, final Class... additionalTypes) { isNotNull(); @@ -785,17 +670,12 @@ public void isInstanceOf(final Class type, final Class... additionalTypes) /** * Ensures that the given object is an instance of the specified type. * - * @param type - * the type to check the specified object for - * @param explanation - * a {@link Formatter formatted message} explaining the assertion - * @param args - * Arguments referenced by the format specifiers in the formatted explanation. If there are more - * arguments than format specifiers, the extra arguments are ignored. The number of arguments is - * variable and may be zero. - * - * @throws AssertionError - * the specified object is not an instance of the given type (or {@code null}) + * @param type the type to check the specified object for + * @param explanation a {@link Formatter formatted message} explaining the assertion + * @param args Arguments referenced by the format specifiers in the formatted explanation. If there are more + * arguments than format specifiers, the extra arguments are ignored. The number of arguments is variable + * and may be zero. + * @throws AssertionError the specified object is not an instance of the given type (or {@code null}) */ @FormatMethod public void isInstanceOf(final Class type, final String explanation, final Object... args) { @@ -807,9 +687,7 @@ public void isInstanceOf(final Class type, final String explanation, final Ob } } - /** - * Assertions for booleans. - */ + /** Assertions for booleans. */ public static class BooleanCondition { /** The value of the condition. */ private final boolean value; @@ -817,8 +695,7 @@ public static class BooleanCondition { /** * Creates a new instance of {@code BooleanCondition}. * - * @param value - * value of the condition + * @param value value of the condition */ public BooleanCondition(final boolean value) { this.value = value; @@ -827,15 +704,11 @@ public BooleanCondition(final boolean value) { /** * Ensures that the given condition is {@code false}. * - * @param explanation - * a {@link Formatter formatted message} explaining the assertion - * @param args - * Arguments referenced by the format specifiers in the formatted explanation. If there are more - * arguments than format specifiers, the extra arguments are ignored. The number of arguments is - * variable and may be zero. - * - * @throws AssertionError - * if the condition is {@code true} + * @param explanation a {@link Formatter formatted message} explaining the assertion + * @param args Arguments referenced by the format specifiers in the formatted explanation. If there are more + * arguments than format specifiers, the extra arguments are ignored. The number of arguments is variable + * and may be zero. + * @throws AssertionError if the condition is {@code true} */ @FormatMethod public void isFalse(final String explanation, final Object... args) { @@ -847,8 +720,7 @@ public void isFalse(final String explanation, final Object... args) { /** * Ensures that the given condition is {@code false}. * - * @throws AssertionError - * if the condition is {@code true} + * @throws AssertionError if the condition is {@code true} */ public void isFalse() { isFalse("Value is not FALSE"); @@ -857,15 +729,11 @@ public void isFalse() { /** * Ensures that the given condition is {@code true}. * - * @param explanation - * a {@link Formatter formatted message} explaining the assertion - * @param args - * Arguments referenced by the format specifiers in the formatted explanation. If there are more - * arguments than format specifiers, the extra arguments are ignored. The number of arguments is - * variable and may be zero. - * - * @throws AssertionError - * if the condition is {@code false} + * @param explanation a {@link Formatter formatted message} explaining the assertion + * @param args Arguments referenced by the format specifiers in the formatted explanation. If there are more + * arguments than format specifiers, the extra arguments are ignored. The number of arguments is variable + * and may be zero. + * @throws AssertionError if the condition is {@code false} */ @FormatMethod public void isTrue(final String explanation, final Object... args) { @@ -877,17 +745,14 @@ public void isTrue(final String explanation, final Object... args) { /** * Ensures that the given condition is {@code true}. * - * @throws AssertionError - * if the condition is {@code false} + * @throws AssertionError if the condition is {@code false} */ public void isTrue() { isTrue("Value is not TRUE"); } } - /** - * Assertions for exceptions. - */ + /** Assertions for exceptions. */ public static class ExceptionCondition { @CheckForNull private final Throwable value; @@ -895,8 +760,7 @@ public static class ExceptionCondition { /** * Creates a new instance of {@link BooleanCondition}. * - * @param value - * value of the condition + * @param value value of the condition */ public ExceptionCondition(@CheckForNull final Throwable value) { this.value = value; @@ -905,15 +769,11 @@ public ExceptionCondition(@CheckForNull final Throwable value) { /** * Ensures that the exception is never thrown. I.e., this method will always throw an {@link AssertionError}. * - * @param explanation - * a {@link Formatter formatted message} explaining the assertion - * @param args - * Arguments referenced by the format specifiers in the formatted explanation. If there are more - * arguments than format specifiers, the extra arguments are ignored. The number of arguments is - * variable and may be zero. - * - * @throws AssertionError - * always thrown + * @param explanation a {@link Formatter formatted message} explaining the assertion + * @param args Arguments referenced by the format specifiers in the formatted explanation. If there are more + * arguments than format specifiers, the extra arguments are ignored. The number of arguments is variable + * and may be zero. + * @throws AssertionError always thrown */ @FormatMethod public void isNeverThrown(final String explanation, final Object... args) { diff --git a/src/main/java/edu/hm/hafner/util/FilteredLog.java b/src/main/java/edu/hm/hafner/util/FilteredLog.java index 86ba3e702..5fbcae2ee 100644 --- a/src/main/java/edu/hm/hafner/util/FilteredLog.java +++ b/src/main/java/edu/hm/hafner/util/FilteredLog.java @@ -1,10 +1,6 @@ package edu.hm.hafner.util; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.exception.ExceptionUtils; - import com.google.errorprone.annotations.FormatMethod; - import java.io.Serial; import java.io.Serializable; import java.util.ArrayList; @@ -13,11 +9,12 @@ import java.util.Locale; import java.util.Objects; import java.util.concurrent.locks.ReentrantLock; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; /** * Provides a log of info messages and a limited number of error messages. If the number of errors exceeds this limit, - * then further error messages will be skipped. This class is thread-safe and can be used in a distributed - * environment. + * then further error messages will be skipped. This class is thread-safe and can be used in a distributed environment. * * @author Ullrich Hafner */ @@ -33,6 +30,7 @@ public class FilteredLog implements Serializable { @SuppressWarnings("serial") private final List infoMessages = new ArrayList<>(); + @SuppressWarnings("serial") private final List errorMessages = new ArrayList<>(); @@ -40,8 +38,8 @@ public class FilteredLog implements Serializable { /** * Creates a new {@link FilteredLog}. The error messages will not have a pre-defined title, you need to make sure - * that there is a meaningful title before each error message. The maximum number of printed errors is given - * by {@link #DEFAULT_MAX_LINES}. + * that there is a meaningful title before each error message. The maximum number of printed errors is given by + * {@link #DEFAULT_MAX_LINES}. */ public FilteredLog() { this(StringUtils.EMPTY, DEFAULT_MAX_LINES); @@ -50,8 +48,7 @@ public FilteredLog() { /** * Creates a new {@link FilteredLog}. The maximum number of printed errors is given by {@link #DEFAULT_MAX_LINES}. * - * @param title - * the title of the error messages + * @param title the title of the error messages */ public FilteredLog(final String title) { this(title, DEFAULT_MAX_LINES); @@ -60,10 +57,8 @@ public FilteredLog(final String title) { /** * Creates a new {@link FilteredLog}. * - * @param title - * the title of the error messages - * @param maxLines - * the maximum number of lines to log + * @param title the title of the error messages + * @param maxLines the maximum number of lines to log */ public FilteredLog(final String title, final int maxLines) { this.title = title; @@ -85,15 +80,13 @@ protected Object readResolve() { /** * Logs the specified information message. Use this method to log any useful information when composing this log. * - * @param message - * the message to log + * @param message the message to log */ public void logInfo(final String message) { lock.lock(); try { infoMessages.add(message); - } - finally { + } finally { lock.unlock(); } } @@ -101,12 +94,9 @@ public void logInfo(final String message) { /** * Logs the specified information message. Use this method to log any useful information when composing this log. * - * @param format - * a format string - * @param args - * Arguments referenced by the format specifiers in the format string. If there are more arguments than - * format specifiers, the extra arguments are ignored. The number of arguments is variable and may be - * zero. + * @param format a format string + * @param args Arguments referenced by the format specifiers in the format string. If there are more arguments than + * format specifiers, the extra arguments are ignored. The number of arguments is variable and may be zero. */ @FormatMethod public void logInfo(final String format, final Object... args) { @@ -116,15 +106,13 @@ public void logInfo(final String format, final Object... args) { /** * Logs the specified error message. Use this method to log any error when composing this log. * - * @param message - * the error message + * @param message the error message */ public void logError(final String message) { lock.lock(); try { logErrorWithGuard(message); - } - finally { + } finally { lock.unlock(); } } @@ -132,12 +120,9 @@ public void logError(final String message) { /** * Logs the specified error message. Use this method to log any error when composing this log. * - * @param format - * a format string - * @param args - * Arguments referenced by the format specifiers in the format string. If there are more arguments than - * format specifiers, the extra arguments are ignored. The number of arguments is variable and may be - * zero. + * @param format a format string + * @param args Arguments referenced by the format specifiers in the format string. If there are more arguments than + * format specifiers, the extra arguments are ignored. The number of arguments is variable and may be zero. */ @FormatMethod public void logError(final String format, final Object... args) { @@ -147,14 +132,10 @@ public void logError(final String format, final Object... args) { /** * Logs the specified exception. Use this method to log any exception when composing this log. * - * @param exception - * the exception to log - * @param format - * A format string - * @param args - * Arguments referenced by the format specifiers in the format string. If there are more arguments than - * format specifiers, the extra arguments are ignored. The number of arguments is variable and may be - * zero. + * @param exception the exception to log + * @param format A format string + * @param args Arguments referenced by the format specifiers in the format string. If there are more arguments than + * format specifiers, the extra arguments are ignored. The number of arguments is variable and may be zero. */ @FormatMethod public void logException(final Exception exception, final String format, final Object... args) { @@ -164,8 +145,7 @@ public void logException(final Exception exception, final String format, final O if (lines <= maxLines) { errorMessages.addAll(Arrays.asList(ExceptionUtils.getRootCauseStackTrace(exception))); } - } - finally { + } finally { lock.unlock(); } } @@ -189,8 +169,7 @@ public int size() { lock.lock(); try { return lines; - } - finally { + } finally { lock.unlock(); } } @@ -204,8 +183,7 @@ public List getInfoMessages() { lock.lock(); try { return List.copyOf(infoMessages); - } - finally { + } finally { lock.unlock(); } } @@ -224,11 +202,11 @@ public List getErrorMessages() { } messages.addAll(errorMessages); if (lines > maxLines) { - messages.add(String.format(Locale.ENGLISH, " ... skipped logging of %d additional errors ...", lines - maxLines)); + messages.add(String.format( + Locale.ENGLISH, " ... skipped logging of %d additional errors ...", lines - maxLines)); } return messages; - } - finally { + } finally { lock.unlock(); } } @@ -242,8 +220,7 @@ public boolean hasErrors() { lock.lock(); try { return !errorMessages.isEmpty(); - } - finally { + } finally { lock.unlock(); } } @@ -251,16 +228,14 @@ public boolean hasErrors() { /** * Merges the info and error messages of the other log. * - * @param other - * the log to merge + * @param other the log to merge */ public void merge(final FilteredLog other) { lock.lock(); try { infoMessages.addAll(other.getInfoMessages()); errorMessages.addAll(other.getErrorMessages()); - } - finally { + } finally { lock.unlock(); } } diff --git a/src/main/java/edu/hm/hafner/util/Generated.java b/src/main/java/edu/hm/hafner/util/Generated.java index 4f7122f62..103184b76 100644 --- a/src/main/java/edu/hm/hafner/util/Generated.java +++ b/src/main/java/edu/hm/hafner/util/Generated.java @@ -1,11 +1,18 @@ package edu.hm.hafner.util; +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PACKAGE; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + import java.lang.annotation.Retention; import java.lang.annotation.Target; -import static java.lang.annotation.ElementType.*; -import static java.lang.annotation.RetentionPolicy.*; - /** * This annotation is used to mark source code that has been generated or is somehow not relevant for style checking or * code coverage analysis. It is quite similar to the annotation of the abandoned JSR305 project. The main difference is diff --git a/src/main/java/edu/hm/hafner/util/JavaPackageDetector.java b/src/main/java/edu/hm/hafner/util/JavaPackageDetector.java index f7a44d2a9..bb315008c 100644 --- a/src/main/java/edu/hm/hafner/util/JavaPackageDetector.java +++ b/src/main/java/edu/hm/hafner/util/JavaPackageDetector.java @@ -1,7 +1,6 @@ package edu.hm.hafner.util; import edu.hm.hafner.util.PackageDetectorFactory.FileSystemFacade; - import java.util.regex.Pattern; /** @@ -10,8 +9,7 @@ * @author Ullrich Hafner */ class JavaPackageDetector extends PackageDetector { - private static final Pattern PACKAGE_PATTERN = Pattern.compile( - "^\\s*package\\s*([a-z]+[.\\w]*)\\s*;.*"); + private static final Pattern PACKAGE_PATTERN = Pattern.compile("^\\s*package\\s*([a-z]+[.\\w]*)\\s*;.*"); JavaPackageDetector(final FileSystemFacade fileSystem) { super(fileSystem); diff --git a/src/main/java/edu/hm/hafner/util/KotlinPackageDetector.java b/src/main/java/edu/hm/hafner/util/KotlinPackageDetector.java index 4fd7abcbe..8147a5a65 100644 --- a/src/main/java/edu/hm/hafner/util/KotlinPackageDetector.java +++ b/src/main/java/edu/hm/hafner/util/KotlinPackageDetector.java @@ -1,8 +1,7 @@ package edu.hm.hafner.util; -import java.util.regex.Pattern; - import edu.hm.hafner.util.PackageDetectorFactory.FileSystemFacade; +import java.util.regex.Pattern; /** * Detects the package name of a Kotlin file. @@ -10,8 +9,7 @@ * @author Bastian Kersting */ class KotlinPackageDetector extends PackageDetector { - private static final Pattern PACKAGE_PATTERN = Pattern.compile( - "^\\s*package\\s*([a-z]+[.\\w]*)\\s*.*"); + private static final Pattern PACKAGE_PATTERN = Pattern.compile("^\\s*package\\s*([a-z]+[.\\w]*)\\s*.*"); @VisibleForTesting KotlinPackageDetector(final FileSystemFacade fileSystem) { diff --git a/src/main/java/edu/hm/hafner/util/LineRange.java b/src/main/java/edu/hm/hafner/util/LineRange.java index 8bc2812f8..fdcbbefe3 100644 --- a/src/main/java/edu/hm/hafner/util/LineRange.java +++ b/src/main/java/edu/hm/hafner/util/LineRange.java @@ -22,8 +22,7 @@ public final class LineRange implements Serializable { /** * Creates a new instance of {@link LineRange}. * - * @param line - * the single line of this range + * @param line the single line of this range */ public LineRange(final int line) { this(line, line); @@ -32,22 +31,18 @@ public LineRange(final int line) { /** * Creates a new instance of {@link LineRange}. * - * @param start - * start of the range - * @param end - * end of the range + * @param start start of the range + * @param end end of the range */ @SuppressMutation(mutator = PitMutator.CONDITIONALS_BOUNDARY, justification = "False positive") public LineRange(final int start, final int end) { if (start <= 0) { this.start = 0; this.end = 0; - } - else if (start < end) { + } else if (start < end) { this.start = start; this.end = end; - } - else { + } else { this.start = end; this.end = start; } @@ -113,8 +108,7 @@ public boolean equals(final Object o) { return false; } var lineRange = (LineRange) o; - return start == lineRange.start - && end == lineRange.end; + return start == lineRange.start && end == lineRange.end; } @Override diff --git a/src/main/java/edu/hm/hafner/util/LineRangeList.java b/src/main/java/edu/hm/hafner/util/LineRangeList.java index 5821d5fba..653885ed1 100644 --- a/src/main/java/edu/hm/hafner/util/LineRangeList.java +++ b/src/main/java/edu/hm/hafner/util/LineRangeList.java @@ -1,10 +1,8 @@ package edu.hm.hafner.util; import com.google.errorprone.annotations.CanIgnoreReturnValue; - import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - import java.io.Serial; import java.io.Serializable; import java.util.AbstractList; @@ -19,24 +17,22 @@ /** * {@link List} of {@link LineRange} that stores values more efficiently at runtime. * - *

- * This class thinks of {@link LineRange} as two integers (start and end-start), hence a list of {@link LineRange} + *

This class thinks of {@link LineRange} as two integers (start and end-start), hence a list of {@link LineRange} * becomes a list of integers. The class then stores those integers in {@code byte[]}. Each number is packed to UTF-8 * like variable length format. To store a long value N, we first split into 7 bit chunk, and store each 7 bit chunk as * a byte, in the little endian order. The last byte gets its 8th bit set to indicate that that's the last byte. Thus in * this format, 0x0 gets stored as 0x80, 0x1234 gets stored as {0x34,0xA4(0x24|0x80)}. - *

* - *

- * This variable length mode stores data most efficiently, since most line numbers are small. Access characteristic gets - * close to that of {@link LinkedList}, since we can only traverse this packed byte[] from the start or from the end. - *

+ *

This variable length mode stores data most efficiently, since most line numbers are small. Access characteristic + * gets close to that of {@link LinkedList}, since we can only traverse this packed byte[] from the start or from the + * end. * * @author Kohsuke Kawaguchi */ public class LineRangeList extends AbstractList implements Serializable { @Serial private static final long serialVersionUID = -1123973098942984623L; + private static final int DEFAULT_CAPACITY = 16; private static final boolean SEQUENTIAL = false; @@ -45,9 +41,7 @@ public class LineRangeList extends AbstractList implements Serializab /** Number of bytes in {@link #data} that's already used. This is not {@link List#size()}. */ private int len; - /** - * Creates an empty {@link LineRangeList}. It uses a capacity of {@link LineRangeList#DEFAULT_CAPACITY}. - */ + /** Creates an empty {@link LineRangeList}. It uses a capacity of {@link LineRangeList#DEFAULT_CAPACITY}. */ public LineRangeList() { this(DEFAULT_CAPACITY); } @@ -55,8 +49,7 @@ public LineRangeList() { /** * Creates an empty {@link LineRangeList} with the specified capacity. * - * @param capacity - * the initial capacity of the list + * @param capacity the initial capacity of the list */ public LineRangeList(final int capacity) { super(); @@ -68,8 +61,7 @@ public LineRangeList(final int capacity) { /** * Creates a new {@link LineRangeList} with the specified elements. * - * @param copy - * the initial elements + * @param copy the initial elements */ public LineRangeList(final Collection copy) { this(copy.size() * 4); // guess @@ -80,8 +72,7 @@ public LineRangeList(final Collection copy) { /** * Creates a new {@link LineRangeList} with the specified elements. * - * @param initialElements - * the initial elements + * @param initialElements the initial elements */ @SuppressWarnings("PMD.UseArraysAsList") public LineRangeList(final LineRange... initialElements) { @@ -93,20 +84,17 @@ public LineRangeList(final LineRange... initialElements) { } /** - * Appends all the elements in the specified collection to the end of this list, in the order that they are - * returned by the specified collection's iterator (optional operation). The behavior of this operation is - * undefined if the specified collection is modified while the operation is in progress. (Note that this will occur - * if the specified collection is this list, and it's nonempty.) - * - * @param ranges - * collection containing elements to be added to this list + * Appends all the elements in the specified collection to the end of this list, in the order that they are returned + * by the specified collection's iterator (optional operation). The behavior of this operation is undefined if the + * specified collection is modified while the operation is in progress. (Note that this will occur if the specified + * collection is this list, and it's nonempty.) * + * @param ranges collection containing elements to be added to this list * @return {@code true} if this list changed as a result of the call - * @throws NullPointerException - * if the specified collection contains one or more null elements and this list does not permit null - * elements, or if the specified collection is null - * @throws IllegalArgumentException - * if some property of an element of the specified collection prevents it from being added to this list + * @throws NullPointerException if the specified collection contains one or more null elements and this list does + * not permit null elements, or if the specified collection is null + * @throws IllegalArgumentException if some property of an element of the specified collection prevents it from + * being added to this list * @see #add(Object) */ public final boolean addAll(final Iterable ranges) { @@ -119,8 +107,7 @@ public final boolean addAll(final Iterable ranges) { /** * Makes sure that the buffer has capability to store N bytes. * - * @param n - * capacity + * @param n capacity */ private void ensure(final int n) { if (data.length < n) { @@ -133,7 +120,7 @@ private void ensure(final int n) { @Override public boolean contains(final Object o) { if (o instanceof final LineRange other) { - for (var cursor = new Cursor(); cursor.hasNext();) { + for (var cursor = new Cursor(); cursor.hasNext(); ) { if (cursor.compare(other)) { return true; } @@ -196,9 +183,7 @@ public ListIterator listIterator(final int index) { return new Cursor().skip(index); } - /** - * Minimizes the memory waste by throwing away excess capacity. - */ + /** Minimizes the memory waste by throwing away excess capacity. */ public void trim() { if (len != data.length) { var small = new byte[len]; @@ -207,9 +192,7 @@ public void trim() { } } - /** - * Navigates through the ranges and performs the conversion to and from {@link LineRange}. - */ + /** Navigates through the ranges and performs the conversion to and from {@link LineRange}. */ @SuppressWarnings("PMD.AssignmentInOperand") private class Cursor implements ListIterator { private int position; @@ -222,9 +205,7 @@ private class Cursor implements ListIterator { this(0); } - /** - * Does the opposite of {@link #read()} and skips back one int. - */ + /** Does the opposite of {@link #read()} and skips back one int. */ private void prev() { if (position == 0) { throw new NoSuchElementException("Cursor is a the beginning."); @@ -239,7 +220,8 @@ private void prev() { * * @return the current element */ - @Override @SuppressFBWarnings(value = "IT_NO_SUCH_ELEMENT", justification = "thrown in read()") + @Override + @SuppressFBWarnings(value = "IT_NO_SUCH_ELEMENT", justification = "thrown in read()") public LineRange next() { int s = read(); int d = read(); @@ -253,9 +235,7 @@ public LineRange previous() { return copy().next(); } - /** - * Removes the last returned value. - */ + /** Removes the last returned value. */ @Override public void remove() { prev(); @@ -310,9 +290,7 @@ private void write(final LineRange r) { /** * Reads the current value at the cursor and compares it. * - * @param other - * the line range to compare with - * + * @param other the line range to compare with * @return {@code true} if the read value is equal to the specified range */ private boolean compare(final LineRange other) { @@ -324,9 +302,7 @@ private boolean compare(final LineRange other) { /** * Skips forward and gets the pointer to N-th element. * - * @param n - * number of elements to skip - * + * @param n number of elements to skip * @return this cursor */ @CanIgnoreReturnValue @@ -372,8 +348,7 @@ private void adjust(final int diff) { ensure(len + diff); if (diff > 0) { System.arraycopy(data, position, data, position + diff, len - position); - } - else { + } else { System.arraycopy(data, position - diff, data, position, len - position + diff); } len += diff; @@ -382,9 +357,7 @@ private void adjust(final int diff) { /** * Rewrites the value at the current cursor position. * - * @param other - * the line range to rewrite - * + * @param other the line range to rewrite * @return the changed line range */ private LineRange rewrite(final LineRange other) { @@ -402,9 +375,7 @@ public void set(final LineRange v) { rewrite(v); } - /** - * Inserts the value at the current cursor position. - */ + /** Inserts the value at the current cursor position. */ @Override public void add(final LineRange v) { int newSize = sizeOf(v); @@ -431,9 +402,7 @@ private int sizeOf(final LineRange v) { /** * Computes the number of bytes that the value 'index' would occupy in its encoded form. * - * @param index - * the index to check - * + * @param index the index to check * @return the number of bytes */ private int sizeOf(final int index) { diff --git a/src/main/java/edu/hm/hafner/util/LookaheadStream.java b/src/main/java/edu/hm/hafner/util/LookaheadStream.java index afc96318c..da8559661 100644 --- a/src/main/java/edu/hm/hafner/util/LookaheadStream.java +++ b/src/main/java/edu/hm/hafner/util/LookaheadStream.java @@ -4,7 +4,6 @@ import java.util.NoSuchElementException; import java.util.regex.Pattern; import java.util.stream.Stream; - import org.apache.commons.lang3.StringUtils; /** @@ -25,8 +24,7 @@ public class LookaheadStream implements AutoCloseable { /** * Wraps the specified stream of lines into a {@link LookaheadStream}. * - * @param stream - * the lines to wrap + * @param stream the lines to wrap */ public LookaheadStream(final Stream stream) { this(stream, StringUtils.EMPTY); @@ -35,10 +33,8 @@ public LookaheadStream(final Stream stream) { /** * Wraps the specified stream of lines into a {@link LookaheadStream}. * - * @param stream - * the lines to wrap - * @param fileName - * the file name of the stream + * @param stream the lines to wrap + * @param fileName the file name of the stream */ public LookaheadStream(final Stream stream, final String fileName) { this.stream = stream; @@ -68,9 +64,7 @@ public boolean hasNext() { /** * Returns {@code true} if the stream has at least one more element that matches the given regular expression. * - * @param regexp - * the regular expression - * + * @param regexp the regular expression * @return {@code true} if the stream has more elements that match the regexp */ public boolean hasNext(final String regexp) { @@ -89,8 +83,7 @@ public boolean hasNext(final String regexp) { * the next call of {@link #next()} will again return this value. * * @return the next element in the stream - * @throws NoSuchElementException - * if the stream has no more elements + * @throws NoSuchElementException if the stream has no more elements */ public String peekNext() { if (!isLookaheadFilled) { @@ -108,8 +101,7 @@ private void fillLookahead() { * Returns the next element in the stream. * * @return the next element in the stream - * @throws NoSuchElementException - * if the stream has no more elements + * @throws NoSuchElementException if the stream has no more elements */ public String next() { line++; @@ -130,7 +122,8 @@ public int getLine() { return line; } - @Override @Generated + @Override + @Generated public String toString() { return "[%d] -> '%s'".formatted(line, lookaheadLine); } diff --git a/src/main/java/edu/hm/hafner/util/PackageDetector.java b/src/main/java/edu/hm/hafner/util/PackageDetector.java index 0c55ee05b..fab52e9bd 100644 --- a/src/main/java/edu/hm/hafner/util/PackageDetector.java +++ b/src/main/java/edu/hm/hafner/util/PackageDetector.java @@ -1,9 +1,6 @@ package edu.hm.hafner.util; -import org.apache.commons.io.input.BOMInputStream; - import edu.hm.hafner.util.PackageDetectorFactory.FileSystemFacade; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -14,6 +11,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; +import org.apache.commons.io.input.BOMInputStream; /** * Base class for package detectors. @@ -26,8 +24,7 @@ abstract class PackageDetector { /** * Creates a new instance of {@link PackageDetector}. * - * @param fileSystem - * file system facade + * @param fileSystem file system facade */ PackageDetector(final FileSystemFacade fileSystem) { this.fileSystem = fileSystem; @@ -36,26 +33,22 @@ abstract class PackageDetector { /** * Detects the package or namespace name of the specified file. * - * @param fileName - * the file name of the file to scan - * @param charset - * the charset to use when reading the source files - * + * @param fileName the file name of the file to scan + * @param charset the charset to use when reading the source files * @return the detected package or namespace name */ Optional detectPackageName(final String fileName, final Charset charset) { try (var stream = fileSystem.openFile(fileName)) { return detectPackageName(stream, charset); - } - catch (IOException | InvalidPathException ignore) { + } catch (IOException | InvalidPathException ignore) { // ignore IO errors } return Optional.empty(); } private Optional detectPackageName(final InputStream stream, final Charset charset) throws IOException { - try (var buffer = new BufferedReader( - new InputStreamReader(BOMInputStream.builder().setInputStream(stream).get(), charset))) { + try (var buffer = new BufferedReader(new InputStreamReader( + BOMInputStream.builder().setInputStream(stream).get(), charset))) { return detectPackageName(buffer.lines()); } } @@ -64,9 +57,7 @@ private Optional detectPackageName(final InputStream stream, final Chars * Detects the package or namespace name of the specified input stream. The stream will be closed automatically by * the caller of this method. * - * @param lines - * the content of the file to scan - * + * @param lines the content of the file to scan * @return the detected package or namespace name */ private Optional detectPackageName(final Stream lines) { @@ -88,9 +79,7 @@ private Optional detectPackageName(final Stream lines) { /** * Returns whether this classifier accepts the specified file for processing. * - * @param fileName - * the file name - * + * @param fileName the file name * @return {@code true} if the classifier accepts the specified file for processing. */ abstract boolean accepts(String fileName); diff --git a/src/main/java/edu/hm/hafner/util/PackageDetectorFactory.java b/src/main/java/edu/hm/hafner/util/PackageDetectorFactory.java index fe9281af3..ad55182ff 100644 --- a/src/main/java/edu/hm/hafner/util/PackageDetectorFactory.java +++ b/src/main/java/edu/hm/hafner/util/PackageDetectorFactory.java @@ -1,7 +1,6 @@ package edu.hm.hafner.util; import com.google.errorprone.annotations.MustBeClosed; - import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; @@ -27,9 +26,7 @@ public static PackageDetectorRunner createPackageDetectors() { /** * Creates a new package detector runner that uses the detectors for Java, Kotlin, and C#. * - * @param facade - * the file system facade to use - * + * @param facade the file system facade to use * @return the package detector runner */ @VisibleForTesting @@ -44,23 +41,17 @@ private PackageDetectorFactory() { // prevents instantiation } - /** - * Facade for file system operations. May be replaced by stubs in test cases. - */ + /** Facade for file system operations. May be replaced by stubs in test cases. */ @VisibleForTesting @SuppressMutation(justification = "This method is not tested directly because it accesses the file system.") public static class FileSystemFacade { /** * Opens the specified file. * - * @param fileName - * the name of the file to open - * + * @param fileName the name of the file to open * @return the input stream to read the file - * @throws IOException - * if the file could not be opened - * @throws InvalidPathException - * the file name is invalid + * @throws IOException if the file could not be opened + * @throws InvalidPathException the file name is invalid */ @MustBeClosed public InputStream openFile(final String fileName) throws IOException, InvalidPathException { diff --git a/src/main/java/edu/hm/hafner/util/PackageDetectorRunner.java b/src/main/java/edu/hm/hafner/util/PackageDetectorRunner.java index bf3dc3d17..b1b0e040d 100644 --- a/src/main/java/edu/hm/hafner/util/PackageDetectorRunner.java +++ b/src/main/java/edu/hm/hafner/util/PackageDetectorRunner.java @@ -20,11 +20,8 @@ public class PackageDetectorRunner { /** * Detects the package name of the specified file based on several detector strategies. * - * @param fileName - * the filename of the file to scan - * @param charset - * the charset to use when reading the source files - * + * @param fileName the filename of the file to scan + * @param charset the charset to use when reading the source files * @return the detected package name or {@link Optional#empty()} if no package name could be detected */ public Optional detectPackageName(final String fileName, final Charset charset) { diff --git a/src/main/java/edu/hm/hafner/util/PathUtil.java b/src/main/java/edu/hm/hafner/util/PathUtil.java index 05e79836a..a8e2f9609 100644 --- a/src/main/java/edu/hm/hafner/util/PathUtil.java +++ b/src/main/java/edu/hm/hafner/util/PathUtil.java @@ -1,5 +1,6 @@ package edu.hm.hafner.util; +import edu.umd.cs.findbugs.annotations.CheckForNull; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; @@ -7,15 +8,12 @@ import java.nio.file.LinkOption; import java.nio.file.Path; import java.util.Objects; - import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; -import edu.umd.cs.findbugs.annotations.CheckForNull; - /** - * Utilities for {@link Path} instances. These methods handle file paths in Windows and Unix file system - * implementations transparently. Moreover, these methods do not throw exceptions when illegal paths are specified. + * Utilities for {@link Path} instances. These methods handle file paths in Windows and Unix file system implementations + * transparently. Moreover, these methods do not throw exceptions when illegal paths are specified. * * @author Ullrich Hafner */ @@ -27,23 +25,18 @@ public class PathUtil { /** * Tests whether a file exists. * - *

- * Note that the result of this method is immediately outdated. If this method indicates the file exists then there - * is no guarantee that a subsequence access will succeed. Care should be taken when using this method in security - * sensitive applications. - *

- * - * @param fileName - * the absolute path of the file + *

Note that the result of this method is immediately outdated. If this method indicates the file exists then + * there is no guarantee that a subsequence access will succeed. Care should be taken when using this method in + * security sensitive applications. * + * @param fileName the absolute path of the file * @return {@code true} if the file exists; {@code false} if the file does not exist or its existence cannot be - * determined. + * determined. */ public boolean exists(final String fileName) { try { return Files.exists(Path.of(fileName)); - } - catch (IllegalArgumentException ignore) { + } catch (IllegalArgumentException ignore) { return false; } } @@ -51,19 +44,14 @@ public boolean exists(final String fileName) { /** * Tests whether a file exists. * - *

- * Note that the result of this method is immediately outdated. If this method indicates the file exists then there - * is no guarantee that a subsequence access will succeed. Care should be taken when using this method in security - * sensitive applications. - *

- * - * @param fileName - * the file name - * @param directory - * the directory that contains the file + *

Note that the result of this method is immediately outdated. If this method indicates the file exists then + * there is no guarantee that a subsequence access will succeed. Care should be taken when using this method in + * security sensitive applications. * + * @param fileName the file name + * @param directory the directory that contains the file * @return {@code true} if the file exists; {@code false} if the file does not exist or its existence cannot be - * determined. + * determined. */ public boolean exists(final String fileName, final String directory) { return exists(createAbsolutePath(directory, fileName)); @@ -75,16 +63,13 @@ public boolean exists(final String fileName, final String directory) { * provided {@code path} will be returned unchanged (but normalized using the UNIX path separator and upper case * drive letter). * - * @param path - * the path to get the absolute path for - * + * @param path the path to get the absolute path for * @return the absolute path */ public String getAbsolutePath(final String path) { try { return getAbsolutePath(Path.of(path)); - } - catch (IllegalArgumentException ignored) { + } catch (IllegalArgumentException ignored) { return makeUnixPath(path); } } @@ -95,78 +80,64 @@ public String getAbsolutePath(final String path) { * provided {@code path} will be returned unchanged (but normalized using the UNIX path separator and upper case * drive letter). * - * @param path - * the path to get the absolute path for - * + * @param path the path to get the absolute path for * @return the absolute path */ public String getAbsolutePath(final Path path) { try { return makeUnixPath(normalize(path).toString()); - } - catch (IOException | IllegalArgumentException ignored) { + } catch (IOException | IllegalArgumentException ignored) { return makeUnixPath(path.toString()); } } /** - * Returns the relative path of the specified path with respect to the provided base directory. The given path will be - * actually resolved in the file system (which may lead to a different fully qualified absolute path). Then the base - * directory prefix will be removed (if possible). In case of an error, i.e., if the file is not found or could not - * be resolved in the parent, then the provided {@code path} will be returned unchanged (but normalized using the - * UNIX path separator and upper case drive letter). - * - * @param base - * the base directory that should be used to get the absolute path for - * @param path - * the path to get the absolute path for - * + * Returns the relative path of the specified path with respect to the provided base directory. The given path will + * be actually resolved in the file system (which may lead to a different fully qualified absolute path). Then the + * base directory prefix will be removed (if possible). In case of an error, i.e., if the file is not found or could + * not be resolved in the parent, then the provided {@code path} will be returned unchanged (but normalized using + * the UNIX path separator and upper case drive letter). + * + * @param base the base directory that should be used to get the absolute path for + * @param path the path to get the absolute path for * @return the relative path */ public String getRelativePath(final Path base, final String path) { try { return getRelativePath(base, Path.of(path)); - } - catch (IllegalArgumentException ignored) { + } catch (IllegalArgumentException ignored) { return makeUnixPath(path); } } /** - * Returns the relative path of the specified path with respect to the provided base directory. The given path will be - * actually resolved in the file system (which may lead to a different fully qualified absolute path). Then the base - * directory prefix will be removed (if possible). In case of an error, i.e., if the file is not found or could not - * be resolved in the parent, then the provided {@code path} will be returned unchanged (but normalized using the - * UNIX path separator and upper case drive letter). - * - * @param base - * the base directory that should be to get the absolute path for - * @param path - * the path to get the absolute path for - * + * Returns the relative path of the specified path with respect to the provided base directory. The given path will + * be actually resolved in the file system (which may lead to a different fully qualified absolute path). Then the + * base directory prefix will be removed (if possible). In case of an error, i.e., if the file is not found or could + * not be resolved in the parent, then the provided {@code path} will be returned unchanged (but normalized using + * the UNIX path separator and upper case drive letter). + * + * @param base the base directory that should be to get the absolute path for + * @param path the path to get the absolute path for * @return the relative path */ public String getRelativePath(final String base, final String path) { try { return getRelativePath(Path.of(base), Path.of(path)); - } - catch (IllegalArgumentException ignored) { + } catch (IllegalArgumentException ignored) { return makeUnixPath(path); } } /** - * Returns the relative path of the specified path with respect to the provided base directory. The given path will be - * actually resolved in the file system (which may lead to a different fully qualified absolute path). Then the base - * directory prefix will be removed (if possible). In case of an error, i.e., if the file is not found or could not - * be resolved in the parent, then the provided {@code path} will be returned unchanged (but normalized using the - * UNIX path separator and upper case drive letter). - * - * @param base - * the base directory that should be to get the absolute path for - * @param path - * the path to get the absolute path for - * + * Returns the relative path of the specified path with respect to the provided base directory. The given path will + * be actually resolved in the file system (which may lead to a different fully qualified absolute path). Then the + * base directory prefix will be removed (if possible). In case of an error, i.e., if the file is not found or could + * not be resolved in the parent, then the provided {@code path} will be returned unchanged (but normalized using + * the UNIX path separator and upper case drive letter). + * + * @param base the base directory that should be to get the absolute path for + * @param path the path to get the absolute path for * @return the relative path */ public String getRelativePath(final Path base, final Path path) { @@ -175,23 +146,21 @@ public String getRelativePath(final Path base, final Path path) { if (path.isAbsolute()) { return makeUnixPath(normalizedBase.relativize(normalize(path)).toString()); } - return makeUnixPath(normalizedBase.relativize(normalize(base.resolve(path))).toString()); - } - catch (IOException | IllegalArgumentException ignored) { + return makeUnixPath( + normalizedBase.relativize(normalize(base.resolve(path))).toString()); + } catch (IOException | IllegalArgumentException ignored) { // ignore and return the path as such } return makeUnixPath(path.toString()); } /** - * Returns a normalized relative path of the specified path. The given path will be actually resolved in the file system - * (which may lead to a different path). In case of an error, i.e., if the file is not found or could not be + * Returns a normalized relative path of the specified path. The given path will be actually resolved in the file + * system (which may lead to a different path). In case of an error, i.e., if the file is not found or could not be * resolved in the parent, then the provided {@code path} will be returned unchanged (but normalized using the UNIX * path separator and upper case drive letter). * - * @param relative - * the path to get the normalized path for - * + * @param relative the path to get the normalized path for * @return the normalized relative path */ public String getRelativePath(final Path relative) { @@ -199,21 +168,18 @@ public String getRelativePath(final Path relative) { } /** - * Returns a normalized relative path of the specified path. The given path will be actually resolved in the file system - * (which may lead to a different path). In case of an error, i.e., if the file is not found or could not be + * Returns a normalized relative path of the specified path. The given path will be actually resolved in the file + * system (which may lead to a different path). In case of an error, i.e., if the file is not found or could not be * resolved in the parent, then the provided {@code path} will be returned unchanged (but normalized using the UNIX * path separator and upper case drive letter). * - * @param relative - * the path to get the normalized path for - * + * @param relative the path to get the normalized path for * @return the normalized relative path */ public String getRelativePath(final String relative) { try { return getRelativePath(Path.of(relative)); - } - catch (IllegalArgumentException ignored) { + } catch (IllegalArgumentException ignored) { // ignore and return the path as such } return makeUnixPath(relative); @@ -222,11 +188,8 @@ public String getRelativePath(final String relative) { /** * Returns the absolute path of the specified file in the given directory. * - * @param directory - * the directory that contains the file - * @param fileName - * the file name - * + * @param directory the directory that contains the file + * @param fileName the file name * @return the absolute path */ public String createAbsolutePath(@CheckForNull final String directory, final String fileName) { @@ -238,16 +201,14 @@ public String createAbsolutePath(@CheckForNull final String directory, final Str String separator; if (path.endsWith(SLASH)) { separator = StringUtils.EMPTY; - } - else { + } else { separator = SLASH; } try { var normalized = FilenameUtils.normalize(String.join(separator, path, fileName)); return makeUnixPath(normalized == null ? fileName : normalized); - } - catch (IllegalArgumentException ignored) { + } catch (IllegalArgumentException ignored) { return makeUnixPath(fileName); } } @@ -255,9 +216,7 @@ public String createAbsolutePath(@CheckForNull final String directory, final Str /** * Returns whether the specified file name is an absolute path. * - * @param fileName - * the file name to test - * + * @param fileName the file name to test * @return {@code true} if this path is an absolute path, {@code false} if a relative path */ public boolean isAbsolute(final String fileName) { @@ -266,8 +225,7 @@ public boolean isAbsolute(final String fileName) { if (uri.isAbsolute()) { return true; } - } - catch (URISyntaxException ignored) { + } catch (URISyntaxException ignored) { // catch and ignore as system paths are not URI, and we need to check them separately } return FilenameUtils.getPrefixLength(fileName) > 0; diff --git a/src/main/java/edu/hm/hafner/util/PitMutator.java b/src/main/java/edu/hm/hafner/util/PitMutator.java index dd086de5e..781feea0d 100644 --- a/src/main/java/edu/hm/hafner/util/PitMutator.java +++ b/src/main/java/edu/hm/hafner/util/PitMutator.java @@ -1,9 +1,6 @@ package edu.hm.hafner.util; -/** - * Represents the mutators available in PIT. - * This enum maps each mutator name to its fully qualified class name. - */ +/** Represents the mutators available in PIT. This enum maps each mutator name to its fully qualified class name. */ public enum PitMutator { CONDITIONALS_BOUNDARY("org.pitest.mutationtest.engine.gregor.mutators.ConditionalsBoundaryMutator"), CONSTRUCTOR_CALLS("org.pitest.mutationtest.engine.gregor.mutators.ConstructorCallMutator"), @@ -19,28 +16,44 @@ public enum PitMutator { EMPTY_RETURNS("org.pitest.mutationtest.engine.gregor.mutators.returns.EmptyObjectReturnValsMutator"), NULL_RETURNS("org.pitest.mutationtest.engine.gregor.mutators.returns.NullReturnValsMutator"), PRIMITIVE_RETURNS("org.pitest.mutationtest.engine.gregor.mutators.returns.PrimitiveReturnsMutator"), - EXPERIMENTAL_ARGUMENT_PROPAGATION("org.pitest.mutationtest.engine.gregor.mutators.experimental.ArgumentPropagationMutator"), + EXPERIMENTAL_ARGUMENT_PROPAGATION( + "org.pitest.mutationtest.engine.gregor.mutators.experimental.ArgumentPropagationMutator"), EXPERIMENTAL_BIG_DECIMAL("org.pitest.mutationtest.engine.gregor.mutators.experimental.BigDecimalMutator"), EXPERIMENTAL_BIG_INTEGER("org.pitest.mutationtest.engine.gregor.mutators.experimental.BigIntegerMutator"), EXPERIMENTAL_MEMBER_VARIABLE("org.pitest.mutationtest.engine.gregor.mutators.experimental.MemberVariableMutator"), EXPERIMENTAL_NAKED_RECEIVER("org.pitest.mutationtest.engine.gregor.mutators.experimental.NakedReceiverMutator"), REMOVE_INCREMENTS("org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveIncrementsMutator"), REMOVE_CONDITIONALS_EQUAL_IF("org.pitest.mutationtest.engine.gregor.mutators.RemoveConditionalMutator_EQUAL_IF"), - REMOVE_CONDITIONALS_EQUAL_ELSE("org.pitest.mutationtest.engine.gregor.mutators.RemoveConditionalMutator_EQUAL_ELSE"), + REMOVE_CONDITIONALS_EQUAL_ELSE( + "org.pitest.mutationtest.engine.gregor.mutators.RemoveConditionalMutator_EQUAL_ELSE"), REMOVE_CONDITIONALS_ORDER_IF("org.pitest.mutationtest.engine.gregor.mutators.RemoveConditionalMutator_ORDER_IF"), - REMOVE_CONDITIONALS_ORDER_ELSE("org.pitest.mutationtest.engine.gregor.mutators.RemoveConditionalMutator_ORDER_ELSE"), + REMOVE_CONDITIONALS_ORDER_ELSE( + "org.pitest.mutationtest.engine.gregor.mutators.RemoveConditionalMutator_ORDER_ELSE"), EXPERIMENTAL_SWITCH("org.pitest.mutationtest.engine.gregor.mutators.experimental.SwitchMutator"), - EXPERIMENTAL_REMOVE_SWITCH_MUTATOR_0("org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_0"), //todo maybe find a better solution for n cases - EXPERIMENTAL_REMOVE_SWITCH_MUTATOR_1("org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_1"), - EXPERIMENTAL_REMOVE_SWITCH_MUTATOR_2("org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_2"), - EXPERIMENTAL_REMOVE_SWITCH_MUTATOR_3("org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_3"), - EXPERIMENTAL_REMOVE_SWITCH_MUTATOR_4("org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_4"), - EXPERIMENTAL_REMOVE_SWITCH_MUTATOR_5("org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_5"), - EXPERIMENTAL_REMOVE_SWITCH_MUTATOR_6("org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_6"), - EXPERIMENTAL_REMOVE_SWITCH_MUTATOR_7("org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_7"), - EXPERIMENTAL_REMOVE_SWITCH_MUTATOR_8("org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_8"), - EXPERIMENTAL_REMOVE_SWITCH_MUTATOR_9("org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_9"), - EXPERIMENTAL_REMOVE_SWITCH_MUTATOR_10("org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_10"), + EXPERIMENTAL_REMOVE_SWITCH_MUTATOR_0( + "org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_0"), // todo maybe find a + // better solution for + // n cases + EXPERIMENTAL_REMOVE_SWITCH_MUTATOR_1( + "org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_1"), + EXPERIMENTAL_REMOVE_SWITCH_MUTATOR_2( + "org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_2"), + EXPERIMENTAL_REMOVE_SWITCH_MUTATOR_3( + "org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_3"), + EXPERIMENTAL_REMOVE_SWITCH_MUTATOR_4( + "org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_4"), + EXPERIMENTAL_REMOVE_SWITCH_MUTATOR_5( + "org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_5"), + EXPERIMENTAL_REMOVE_SWITCH_MUTATOR_6( + "org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_6"), + EXPERIMENTAL_REMOVE_SWITCH_MUTATOR_7( + "org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_7"), + EXPERIMENTAL_REMOVE_SWITCH_MUTATOR_8( + "org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_8"), + EXPERIMENTAL_REMOVE_SWITCH_MUTATOR_9( + "org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_9"), + EXPERIMENTAL_REMOVE_SWITCH_MUTATOR_10( + "org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_10"), NONE(""); private final String fqcn; diff --git a/src/main/java/edu/hm/hafner/util/PrefixLogger.java b/src/main/java/edu/hm/hafner/util/PrefixLogger.java index 1edfc6f19..bfd4f062f 100644 --- a/src/main/java/edu/hm/hafner/util/PrefixLogger.java +++ b/src/main/java/edu/hm/hafner/util/PrefixLogger.java @@ -1,10 +1,9 @@ package edu.hm.hafner.util; +import com.google.errorprone.annotations.FormatMethod; import java.io.PrintStream; import java.util.Collection; -import com.google.errorprone.annotations.FormatMethod; - /** * A simple logger that prefixes each message with a given name. * @@ -17,16 +16,13 @@ public class PrefixLogger { /** * Creates a new {@link PrefixLogger}. * - * @param logger - * the logger to create - * @param prefix - * the prefix to print + * @param logger the logger to create + * @param prefix the prefix to print */ public PrefixLogger(final PrintStream logger, final String prefix) { if (prefix.contains("[")) { this.toolName = prefix + " "; - } - else { + } else { this.toolName = "[%s] ".formatted(prefix); } delegate = logger; @@ -35,12 +31,9 @@ public PrefixLogger(final PrintStream logger, final String prefix) { /** * Logs the specified message. * - * @param format - * A format string - * @param args - * Arguments referenced by the format specifiers in the format string. If there are more arguments than - * format specifiers, the extra arguments are ignored. The number of arguments is variable and may be - * zero. + * @param format A format string + * @param args Arguments referenced by the format specifiers in the format string. If there are more arguments than + * format specifiers, the extra arguments are ignored. The number of arguments is variable and may be zero. */ @FormatMethod public void log(final String format, final Object... args) { @@ -50,8 +43,7 @@ public void log(final String format, final Object... args) { /** * Logs the specified messages. * - * @param lines - * the messages to log + * @param lines the messages to log */ public void logEachLine(final Collection lines) { lines.forEach(this::print); diff --git a/src/main/java/edu/hm/hafner/util/ResourceExtractor.java b/src/main/java/edu/hm/hafner/util/ResourceExtractor.java index ab012091b..58185d226 100644 --- a/src/main/java/edu/hm/hafner/util/ResourceExtractor.java +++ b/src/main/java/edu/hm/hafner/util/ResourceExtractor.java @@ -1,8 +1,5 @@ package edu.hm.hafner.util; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; - import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; @@ -17,6 +14,8 @@ import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.stream.Collectors; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; /** * A proxy for resources. Extracts a given collection of files from the classpath and copies them to a target path. @@ -31,8 +30,7 @@ public class ResourceExtractor { /** * Creates a new {@link ResourceExtractor} that extracts resources from the classloader of the specified class. * - * @param targetClass - * the target class to use the classloader from + * @param targetClass the target class to use the classloader from */ public ResourceExtractor(final Class targetClass) { this(targetClass, targetClass.getProtectionDomain()); @@ -56,8 +54,7 @@ public ResourceExtractor(final Class targetClass) { readingFromJarFile = Files.isRegularFile(entryPoint); if (readingFromJarFile) { extractor = new JarExtractor(entryPoint); - } - else { + } else { extractor = new FolderExtractor(entryPoint); } resourcePath = entryPoint.toString(); @@ -74,12 +71,9 @@ public boolean isReadingFromJarFile() { /** * Extracts the specified source files from the classloader and saves them to the specified target folder. * - * @param targetDirectory - * the target path that will be the parent folder of all extracted files - * @param source - * the source file to extract - * @param sources - * the additional source files to extract + * @param targetDirectory the target path that will be the parent folder of all extracted files + * @param source the source file to extract + * @param sources the additional source files to extract */ public void extract(final Path targetDirectory, final String source, final String... sources) { if (!Files.isDirectory(targetDirectory)) { @@ -91,9 +85,7 @@ public void extract(final Path targetDirectory, final String source, final Strin extractor.extractFiles(targetDirectory, allSources); } - /** - * Extracts a collection of files and copies them to a given target path. - */ + /** Extracts a collection of files and copies them to a given target path. */ private abstract static class Extractor { private final Path entryPoint; @@ -108,9 +100,7 @@ Path getEntryPoint() { abstract void extractFiles(Path targetDirectory, String... sources); } - /** - * Extracts files from a folder, typically provided by the development environment or build system. - */ + /** Extracts files from a folder, typically provided by the development environment or build system. */ private static class FolderExtractor extends Extractor { FolderExtractor(final Path entryPoint) { super(entryPoint); @@ -124,8 +114,7 @@ void extractFiles(final Path targetDirectory, final String... sources) { Files.createDirectories(targetFile); copy(targetFile, source); } - } - catch (IOException exception) { + } catch (IOException exception) { throw new UncheckedIOException(exception); } } @@ -133,16 +122,13 @@ void extractFiles(final Path targetDirectory, final String... sources) { private void copy(final Path target, final String source) { try { Files.copy(getEntryPoint().resolve(source), target, StandardCopyOption.REPLACE_EXISTING); - } - catch (IOException exception) { + } catch (IOException exception) { throw new UncheckedIOException(exception); } } } - /** - * Extracts files from a deployed jar file. - */ + /** Extracts files from a deployed jar file. */ private static class JarExtractor extends Extractor { JarExtractor(final Path entryPoint) { super(entryPoint); @@ -161,8 +147,7 @@ void extractFiles(final Path targetDirectory, final String... sources) { remaining.remove(name); } } - } - catch (IOException exception) { + } catch (IOException exception) { throw new UncheckedIOException(exception); } if (!remaining.isEmpty()) { @@ -180,7 +165,8 @@ private void copy(final Path targetDirectory, final JarFile jar, final JarEntry if (parent != null) { Files.createDirectories(parent); } - try (var inputStream = jar.getInputStream(entry); var outputStream = Files.newOutputStream(targetFile)) { + try (var inputStream = jar.getInputStream(entry); + var outputStream = Files.newOutputStream(targetFile)) { IOUtils.copy(inputStream, outputStream); } } diff --git a/src/main/java/edu/hm/hafner/util/SecureXmlParserFactory.java b/src/main/java/edu/hm/hafner/util/SecureXmlParserFactory.java index 2557c4cbb..c934e144c 100644 --- a/src/main/java/edu/hm/hafner/util/SecureXmlParserFactory.java +++ b/src/main/java/edu/hm/hafner/util/SecureXmlParserFactory.java @@ -1,5 +1,16 @@ package edu.hm.hafner.util; +import static javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD; +import static javax.xml.XMLConstants.ACCESS_EXTERNAL_SCHEMA; +import static javax.xml.XMLConstants.ACCESS_EXTERNAL_STYLESHEET; +import static javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING; + +import com.google.errorprone.annotations.FormatMethod; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.io.IOException; +import java.io.Reader; +import java.io.Serial; +import java.nio.charset.Charset; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -12,7 +23,6 @@ import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerFactory; - import org.apache.commons.io.input.ReaderInputStream; import org.apache.commons.lang3.exception.ExceptionUtils; import org.w3c.dom.Document; @@ -22,34 +32,25 @@ import org.xml.sax.SAXNotSupportedException; import org.xml.sax.helpers.DefaultHandler; -import com.google.errorprone.annotations.FormatMethod; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - -import java.io.IOException; -import java.io.Reader; -import java.io.Serial; -import java.nio.charset.Charset; - -import static javax.xml.XMLConstants.*; - /** * Factory for XML Parsers that prevent XML External Entity attacks. Those attacks occur when untrusted XML input * containing a reference to an external entity is processed by a weakly configured XML parser. * * @author Ullrich Hafner * @see XML - * External Entity Prevention Cheat Sheet - * @see XML parsers should not be vulnerable to XXE - * attacks + * External Entity Prevention Cheat Sheet + * @see XML parsers should not be vulnerable to XXE attacks */ -@SuppressMutation(mutator = PitMutator.VOID_METHOD_CALLS, justification = "Setters are used to configure the parsers and factories") +@SuppressMutation( + mutator = PitMutator.VOID_METHOD_CALLS, + justification = "Setters are used to configure the parsers and factories") public class SecureXmlParserFactory { /** * The following constants are copied from the Xerces distribution 2.12.2. This avoids adding a dependency to * Xerces. */ private static final String SAX_FEATURE_PREFIX = "http://xml.org/sax/features/"; + private static final String XERCES_FEATURE_PREFIX = "http://apache.org/xml/features/"; private static final String EXTERNAL_GENERAL_ENTITIES_FEATURE = "external-general-entities"; private static final String EXTERNAL_PARAMETER_ENTITIES_FEATURE = "external-parameter-entities"; @@ -60,22 +61,21 @@ public class SecureXmlParserFactory { private static final String LOAD_EXTERNAL_DTD_FEATURE = "nonvalidating/load-external-dtd"; private static final String[] ENABLED_PROPERTIES = { -// XERCES_FEATURE_PREFIX + DISALLOW_DOCTYPE_DECL_FEATURE, - If this feature is activated we cannot parse any XML documents that use a DOCTYPE anymore - FEATURE_SECURE_PROCESSING + // XERCES_FEATURE_PREFIX + DISALLOW_DOCTYPE_DECL_FEATURE, - If this feature is activated we cannot + // parse any XML documents that use a DOCTYPE anymore + FEATURE_SECURE_PROCESSING }; private static final String[] DISABLED_PROPERTIES = { - SAX_FEATURE_PREFIX + EXTERNAL_GENERAL_ENTITIES_FEATURE, - SAX_FEATURE_PREFIX + EXTERNAL_PARAMETER_ENTITIES_FEATURE, - SAX_FEATURE_PREFIX + RESOLVE_DTD_URIS_FEATURE, - SAX_FEATURE_PREFIX + USE_ENTITY_RESOLVER2_FEATURE, - XERCES_FEATURE_PREFIX + CREATE_ENTITY_REF_NODES_FEATURE, - XERCES_FEATURE_PREFIX + LOAD_DTD_GRAMMAR_FEATURE, - XERCES_FEATURE_PREFIX + LOAD_EXTERNAL_DTD_FEATURE + SAX_FEATURE_PREFIX + EXTERNAL_GENERAL_ENTITIES_FEATURE, + SAX_FEATURE_PREFIX + EXTERNAL_PARAMETER_ENTITIES_FEATURE, + SAX_FEATURE_PREFIX + RESOLVE_DTD_URIS_FEATURE, + SAX_FEATURE_PREFIX + USE_ENTITY_RESOLVER2_FEATURE, + XERCES_FEATURE_PREFIX + CREATE_ENTITY_REF_NODES_FEATURE, + XERCES_FEATURE_PREFIX + LOAD_DTD_GRAMMAR_FEATURE, + XERCES_FEATURE_PREFIX + LOAD_EXTERNAL_DTD_FEATURE }; private static final String[] DISABLED_ATTRIBUTES = { - ACCESS_EXTERNAL_DTD, - ACCESS_EXTERNAL_SCHEMA, - ACCESS_EXTERNAL_STYLESHEET + ACCESS_EXTERNAL_DTD, ACCESS_EXTERNAL_SCHEMA, ACCESS_EXTERNAL_STYLESHEET }; private static final String CLEAR_ATTRIBUTE = ""; private static final String SUPPORTING_EXTERNAL_ENTITIES = "javax.xml.stream.isSupportingExternalEntities"; @@ -95,8 +95,7 @@ public DocumentBuilder createDocumentBuilder() { clearAttributes(factory); return factory.newDocumentBuilder(); - } - catch (ParserConfigurationException exception) { + } catch (ParserConfigurationException exception) { throw new IllegalArgumentException("Can't create instance of DocumentBuilder", exception); } } @@ -118,8 +117,7 @@ private void setFeatures(final DocumentBuilderFactory factory) { private void setFeature(final DocumentBuilderFactory factory, final String enabledProperty, final boolean value) { try { factory.setFeature(enabledProperty, value); - } - catch (ParserConfigurationException ignored) { + } catch (ParserConfigurationException ignored) { // ignore and continue } } @@ -128,8 +126,7 @@ private void clearAttributes(final DocumentBuilderFactory factory) { for (String securityAttribute : DISABLED_ATTRIBUTES) { try { factory.setAttribute(securityAttribute, CLEAR_ATTRIBUTE); - } - catch (IllegalArgumentException e) { + } catch (IllegalArgumentException e) { // ignore and continue } } @@ -139,8 +136,7 @@ private void clearAttributes(final TransformerFactory transformerFactory) { for (String securityAttribute : DISABLED_ATTRIBUTES) { try { transformerFactory.setAttribute(securityAttribute, CLEAR_ATTRIBUTE); - } - catch (IllegalArgumentException e) { + } catch (IllegalArgumentException e) { // ignore and continue } } @@ -159,8 +155,7 @@ public SAXParser createSaxParser() { var parser = factory.newSAXParser(); secureParser(parser); return parser; - } - catch (ParserConfigurationException | SAXException exception) { + } catch (ParserConfigurationException | SAXException exception) { throw new IllegalArgumentException("Can't create instance of SAXParser", exception); } } @@ -173,15 +168,13 @@ SAXParserFactory createSaxParserFactory() { /** * Secure the {@link SAXParser} so that it does not resolve external entities. * - * @param parser - * the parser to secure + * @param parser the parser to secure */ private void secureParser(final SAXParser parser) { for (String securityAttribute : DISABLED_ATTRIBUTES) { try { parser.setProperty(securityAttribute, CLEAR_ATTRIBUTE); - } - catch (SAXNotRecognizedException | SAXNotSupportedException e) { + } catch (SAXNotRecognizedException | SAXNotSupportedException e) { // ignore and continue } } @@ -190,8 +183,7 @@ private void secureParser(final SAXParser parser) { /** * Configures a {@link SAXParserFactory} so that it does not resolve external entities. * - * @param factory - * the facotry to configure + * @param factory the facotry to configure */ public void configureSaxParserFactory(final SAXParserFactory factory) { factory.setValidating(false); @@ -200,16 +192,14 @@ public void configureSaxParserFactory(final SAXParserFactory factory) { for (String enabledProperty : ENABLED_PROPERTIES) { try { factory.setFeature(enabledProperty, true); - } - catch (ParserConfigurationException | SAXException ignored) { + } catch (ParserConfigurationException | SAXException ignored) { // ignore and continue } } for (String disabledProperty : DISABLED_PROPERTIES) { try { factory.setFeature(disabledProperty, false); - } - catch (ParserConfigurationException | SAXException ignored) { + } catch (ParserConfigurationException | SAXException ignored) { // ignore and continue } } @@ -218,17 +208,14 @@ public void configureSaxParserFactory(final SAXParserFactory factory) { /** * Creates a new instance of a {@link XMLStreamReader} that does not resolve external entities. * - * @param reader - * the reader to wrap - * + * @param reader the reader to wrap * @return a new instance of a {@link XMLStreamReader} */ @SuppressFBWarnings(value = "XXE_XMLSTREAMREADER", justification = "The reader is secured in the called method") public XMLStreamReader createXmlStreamReader(final Reader reader) { try { return createSecureInputFactory().createXMLStreamReader(reader); - } - catch (XMLStreamException exception) { + } catch (XMLStreamException exception) { throw new IllegalArgumentException("Can't create instance of XMLStreamReader", exception); } } @@ -236,17 +223,14 @@ public XMLStreamReader createXmlStreamReader(final Reader reader) { /** * Creates a new instance of a {@link XMLStreamReader} that does not resolve external entities. * - * @param reader - * the reader to wrap - * + * @param reader the reader to wrap * @return a new instance of a {@link XMLStreamReader} */ @SuppressFBWarnings(value = "XXE_XMLSTREAMREADER", justification = "The reader is secured in the called method") public XMLEventReader createXmlEventReader(final Reader reader) { try { return createSecureInputFactory().createXMLEventReader(reader); - } - catch (XMLStreamException exception) { + } catch (XMLStreamException exception) { throw new IllegalArgumentException("Can't create instance of XMLEventReader", exception); } } @@ -267,22 +251,16 @@ XMLInputFactory createXmlInputFactory() { * Creates a {@link SAXParser} that does not resolve external entities and parses the provided content with the * given SAX {@link DefaultHandler}. * - * @param reader - * the content that should be parsed - * @param charset - * the charset to use when reading the content - * @param handler - * the SAX handler to parse the file - * - * @throws ParsingException - * if the file could not be parsed + * @param reader the content that should be parsed + * @param charset the charset to use when reading the content + * @param handler the SAX handler to parse the file + * @throws ParsingException if the file could not be parsed */ @SuppressFBWarnings(value = "XXE_SAXPARSER", justification = "The parser is secured in the called method") public void parse(final Reader reader, final Charset charset, final DefaultHandler handler) { try { createSaxParser().parse(createInputSource(reader, charset), handler); - } - catch (SAXException | IOException exception) { + } catch (SAXException | IOException exception) { throw new ParsingException(exception); } } @@ -290,27 +268,25 @@ public void parse(final Reader reader, final Charset charset, final DefaultHandl /** * Parses the provided content into a {@link Document}. * - * @param reader - * the content that should be parsed - * @param charset - * the charset to use when reading the content - * + * @param reader the content that should be parsed + * @param charset the charset to use when reading the content * @return the file content as a document - * @throws ParsingException - * if the file could not be parsed + * @throws ParsingException if the file could not be parsed */ @SuppressFBWarnings(value = "XXE_DOCUMENT", justification = "The parser is secured in the called method") public Document readDocument(final Reader reader, final Charset charset) { try { return createDocumentBuilder().parse(createInputSource(reader, charset)); - } - catch (SAXException | IOException exception) { + } catch (SAXException | IOException exception) { throw new ParsingException(exception); } } private InputSource createInputSource(final Reader reader, final Charset charset) throws IOException { - var inputStream = ReaderInputStream.builder().setReader(reader).setCharset(charset).get(); + var inputStream = ReaderInputStream.builder() + .setReader(reader) + .setCharset(charset) + .get(); return new InputSource(inputStream); } @@ -327,21 +303,20 @@ public Transformer createTransformer() { clearAttributes(transformerFactory); return transformerFactory.newTransformer(); - } - catch (TransformerConfigurationException exception) { + } catch (TransformerConfigurationException exception) { throw new IllegalArgumentException("Can't create instance of Transformer", exception); } } @VisibleForTesting - @SuppressFBWarnings(value = {"XXE_DTD_TRANSFORM_FACTORY", "XXE_XSLT_TRANSFORM_FACTORY"}, justification = "The transformer is secured in the called method") + @SuppressFBWarnings( + value = {"XXE_DTD_TRANSFORM_FACTORY", "XXE_XSLT_TRANSFORM_FACTORY"}, + justification = "The transformer is secured in the called method") TransformerFactory createTransformerFactory() { return TransformerFactory.newInstance(); } - /** - * Indicates that during parsing a non-recoverable error has been occurred. - */ + /** Indicates that during parsing a non-recoverable error has been occurred. */ public static class ParsingException extends RuntimeException { @Serial private static final long serialVersionUID = -9016364685084958944L; @@ -349,8 +324,7 @@ public static class ParsingException extends RuntimeException { /** * Constructs a new {@link ParsingException} with the specified cause. * - * @param cause - * the cause (which is saved for later retrieval by the {@link #getCause()} method). + * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). */ public ParsingException(final Throwable cause) { super(createMessage(cause, "Exception occurred during parsing"), cause); @@ -359,15 +333,13 @@ public ParsingException(final Throwable cause) { /** * Constructs a new {@link ParsingException} with the specified message. * - * @param messageFormat - * the message as a format string as described in Format string - * syntax - * @param args - * Arguments referenced by the format specifiers in the format string. If there are more arguments than - * format specifiers, the extra arguments are ignored. The number of arguments is variable and may be zero. - * The maximum number of arguments is limited by the maximum dimension of a Java array as defined by - * The Java™ Virtual Machine Specification. The behaviour on a {@code null} argument - * depends on the conversion. + * @param messageFormat the message as a format string as described in Format string syntax + * @param args Arguments referenced by the format specifiers in the format string. If there are more arguments + * than format specifiers, the extra arguments are ignored. The number of arguments is variable and may be + * zero. The maximum number of arguments is limited by the maximum dimension of a Java array as defined by + * The Java™ Virtual Machine Specification. The behaviour on a {@code null} argument + * depends on the conversion. */ @FormatMethod public ParsingException(final String messageFormat, final Object... args) { @@ -377,17 +349,14 @@ public ParsingException(final String messageFormat, final Object... args) { /** * Constructs a new {@link ParsingException} with the specified cause and message. * - * @param cause - * the cause (which is saved for later retrieval by the {@link #getCause()} method). - * @param messageFormat - * the message as a format string as described in Format string - * syntax - * @param args - * Arguments referenced by the format specifiers in the format string. If there are more arguments than - * format specifiers, the extra arguments are ignored. The number of arguments is variable and may be zero. - * The maximum number of arguments is limited by the maximum dimension of a Java array as defined by - * The Java™ Virtual Machine Specification. The behaviour on a {@code null} argument - * depends on the conversion. + * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). + * @param messageFormat the message as a format string as described in Format string syntax + * @param args Arguments referenced by the format specifiers in the format string. If there are more arguments + * than format specifiers, the extra arguments are ignored. The number of arguments is variable and may be + * zero. The maximum number of arguments is limited by the maximum dimension of a Java array as defined by + * The Java™ Virtual Machine Specification. The behaviour on a {@code null} argument + * depends on the conversion. */ @FormatMethod public ParsingException(final Throwable cause, final String messageFormat, final Object... args) { @@ -395,8 +364,8 @@ public ParsingException(final Throwable cause, final String messageFormat, final } private static String createMessage(final Throwable cause, final String message) { - return "%s%n%s%n%s".formatted(message, - ExceptionUtils.getMessage(cause), ExceptionUtils.getStackTrace(cause)); + return "%s%n%s%n%s" + .formatted(message, ExceptionUtils.getMessage(cause), ExceptionUtils.getStackTrace(cause)); } } } diff --git a/src/main/java/edu/hm/hafner/util/SuppressMutation.java b/src/main/java/edu/hm/hafner/util/SuppressMutation.java index 98df17731..6849cb179 100644 --- a/src/main/java/edu/hm/hafner/util/SuppressMutation.java +++ b/src/main/java/edu/hm/hafner/util/SuppressMutation.java @@ -1,20 +1,18 @@ package edu.hm.hafner.util; +import static edu.hm.hafner.util.PitMutator.NONE; + import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import static edu.hm.hafner.util.PitMutator.*; - /** * Suppresses specific mutations when the feature {@code FANNOT} is enabled in PitMute. * - *

- * This annotation can be applied to classes, methods, or constructors. When used without parameters, all mutations in - * that scope are suppressed. For more information, please see the README in PitMute. - *

+ *

This annotation can be applied to classes, methods, or constructors. When used without parameters, all mutations + * in that scope are suppressed. For more information, please see the README in PitMute. * * @see PitMute */ diff --git a/src/main/java/edu/hm/hafner/util/SuppressMutations.java b/src/main/java/edu/hm/hafner/util/SuppressMutations.java index 65df4b855..b35c1ade5 100644 --- a/src/main/java/edu/hm/hafner/util/SuppressMutations.java +++ b/src/main/java/edu/hm/hafner/util/SuppressMutations.java @@ -5,9 +5,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Container annotation for repeating the {@link SuppressMutation} annotation. - */ +/** Container annotation for repeating the {@link SuppressMutation} annotation. */ @Target({ElementType.METHOD, ElementType.TYPE, ElementType.CONSTRUCTOR}) @Retention(RetentionPolicy.RUNTIME) public @interface SuppressMutations { diff --git a/src/main/java/edu/hm/hafner/util/TreeString.java b/src/main/java/edu/hm/hafner/util/TreeString.java index 54c60146d..efcc7f538 100644 --- a/src/main/java/edu/hm/hafner/util/TreeString.java +++ b/src/main/java/edu/hm/hafner/util/TreeString.java @@ -1,20 +1,16 @@ package edu.hm.hafner.util; -import org.apache.commons.lang3.StringUtils; - import edu.umd.cs.findbugs.annotations.CheckForNull; - import java.io.Serial; import java.io.Serializable; import java.util.Map; +import org.apache.commons.lang3.StringUtils; /** * {@link TreeString} is an alternative string representation that saves the memory when you have a large number of * strings that share common prefixes (such as various file names.) * - *

- * {@link TreeString} can be built with {@link TreeStringBuilder}. - *

+ *

{@link TreeString} can be built with {@link TreeStringBuilder}. * * @author Kohsuke Kawaguchi */ @@ -29,9 +25,7 @@ public final class TreeString implements Serializable { /** {@link #parent} + {@code label} is the string value of this node. */ private char[] label; - /** - * Creates a new root {@link TreeString}. - */ + /** Creates a new root {@link TreeString}. */ TreeString() { this(null, ""); } @@ -39,10 +33,8 @@ public final class TreeString implements Serializable { /** * Creates a new {@link TreeString} with the given parent and suffix. * - * @param parent - * the parent - * @param label - * the suffix + * @param parent the parent + * @param label the suffix */ @SuppressWarnings("NullAway") TreeString(@CheckForNull final TreeString parent, final String label) { @@ -50,7 +42,8 @@ public final class TreeString implements Serializable { .isTrue("if there's a parent '%s', label '%s' can't be empty", parent, label); this.parent = parent; - this.label = label.toCharArray(); // String created as a substring of another string can have a lot of garbage attached to it. + this.label = label.toCharArray(); // String created as a substring of another string can have a lot of garbage + // attached to it. } String getLabel() { @@ -60,13 +53,9 @@ String getLabel() { /** * Inserts a new node between this node and its parent, and returns the newly inserted node. * - *

- * This operation doesn't change the string representation of this node. - *

- * - * @param prefix - * the prefix to remove + *

This operation doesn't change the string representation of this node. * + * @param prefix the prefix to remove * @return the new node in the middle */ @SuppressMutation(mutator = PitMutator.VOID_METHOD_CALLS, justification = "No need to check assertions") @@ -90,8 +79,8 @@ TreeString getParent() { } /** - * How many nodes do we have from the root to this node (including 'this' itself?). Thus, the depth of - * the root node is 1. + * How many nodes do we have from the root to this node (including 'this' itself?). Thus, the depth of the root node + * is 1. * * @return the depth */ @@ -119,9 +108,7 @@ public int hashCode() { return toString().hashCode(); } - /** - * Returns the full string representation. - */ + /** Returns the full string representation. */ @Override @SuppressWarnings("PMD.AssignmentInOperand") public String toString() { @@ -144,16 +131,14 @@ public String toString() { /** * Interns {@link #label}. * - * @param table - * the table containing the existing strings + * @param table the table containing the existing strings */ void dedup(final Map table) { var l = getLabel(); var v = table.get(l); if (v == null) { table.put(l, label); - } - else { + } else { label = v; } } @@ -163,12 +148,10 @@ public boolean isBlank() { } /** - * Creates a {@link TreeString}. Useful if you need to create one-off {@link TreeString} without {@link - * TreeStringBuilder}. Memory consumption is still about the same to {@code new String(string)}. - * - * @param string - * the tree string + * Creates a {@link TreeString}. Useful if you need to create one-off {@link TreeString} without + * {@link TreeStringBuilder}. Memory consumption is still about the same to {@code new String(string)}. * + * @param string the tree string * @return the new {@link TreeString} */ public static TreeString valueOf(final String string) { diff --git a/src/main/java/edu/hm/hafner/util/TreeStringBuilder.java b/src/main/java/edu/hm/hafner/util/TreeStringBuilder.java index 3b16f0f99..d760c18ac 100644 --- a/src/main/java/edu/hm/hafner/util/TreeStringBuilder.java +++ b/src/main/java/edu/hm/hafner/util/TreeStringBuilder.java @@ -9,12 +9,10 @@ * {@link TreeString} that represents the same string, but as you intern more strings that share the same prefixes, * those {@link TreeString}s that you get back start to share data. * - *

- * Because the internal state of {@link TreeString}s get mutated as new strings are interned (to exploit new-found + *

Because the internal state of {@link TreeString}s get mutated as new strings are interned (to exploit new-found * common prefixes), {@link TreeString}s returned from {@link #intern(String)} aren't thread-safe until * {@link TreeStringBuilder} is disposed. That is, you have to make sure other threads don't see those * {@link TreeString}s until you are done interning strings. - *

* * @author Kohsuke Kawaguchi */ @@ -31,9 +29,7 @@ private Child getRoot() { /** * Interns a string. * - * @param string - * the string to intern - * + * @param string the string to intern * @return the String as {@link TreeString} instance */ public TreeString intern(final String string) { @@ -43,26 +39,22 @@ public TreeString intern(final String string) { /** * Interns a {@link TreeString} created elsewhere. * - * @param treeString - * the {@link TreeString} to intern - * + * @param treeString the {@link TreeString} to intern * @return the String as {@link TreeString} instance */ public TreeString intern(final TreeString treeString) { return getRoot().intern(treeString.toString()).getNode(); } - /** - * Further reduces the memory footprint by finding the same labels across multiple {@link TreeString}s. - */ - @SuppressMutation(mutator = PitMutator.VOID_METHOD_CALLS, justification = "Memory optimization without visible side effect") + /** Further reduces the memory footprint by finding the same labels across multiple {@link TreeString}s. */ + @SuppressMutation( + mutator = PitMutator.VOID_METHOD_CALLS, + justification = "Memory optimization without visible side effect") public void dedup() { getRoot().dedup(new HashMap<>()); } - /** - * Child node that may store other elements. - */ + /** Child node that may store other elements. */ private static final class Child { private final TreeString node; @@ -75,9 +67,7 @@ private static final class Child { /** * Adds one edge and leaf to this tree node, or returns an existing node if any. * - * @param string - * the string to intern - * + * @param string the string to intern * @return the node */ private Child intern(final String string) { @@ -100,8 +90,7 @@ private Child intern(final String string) { children.put(prefix, middle); return middle.intern(string.substring(plen)); - } - else { + } else { return entry.getValue().intern(string.substring(plen)); // entire key is suffix } } @@ -116,9 +105,7 @@ private Child intern(final String string) { return t; } - /** - * Makes sure {@link #children} is writable. - */ + /** Makes sure {@link #children} is writable. */ @SuppressWarnings("ReferenceEquality") private void makeWritable() { if (children == NO_CHILDREN) { @@ -130,9 +117,7 @@ private void makeWritable() { * Inserts a new node between this node and its parent and returns that node. The newly inserted 'middle' node * will have this node as its sole child. * - * @param prefix - * the prefix - * + * @param prefix the prefix * @return the node */ private Child split(final String prefix) { @@ -148,11 +133,8 @@ private Child split(final String prefix) { /** * Returns the common prefix between two strings. * - * @param a - * a string - * @param b - * another string - * + * @param a a string + * @param b another string * @return the prefix in characters */ private int commonPrefix(final String a, final String b) { @@ -169,10 +151,11 @@ private int commonPrefix(final String a, final String b) { /** * Calls {@link TreeString#dedup(Map)} recursively. * - * @param table - * the table containing the existing strings + * @param table the table containing the existing strings */ - @SuppressMutation(mutator = PitMutator.VOID_METHOD_CALLS, justification = "Memory optimization without visible side effect") + @SuppressMutation( + mutator = PitMutator.VOID_METHOD_CALLS, + justification = "Memory optimization without visible side effect") private void dedup(final Map table) { getNode().dedup(table); for (Child child : children.values()) { diff --git a/src/main/java/edu/hm/hafner/util/VisibleForTesting.java b/src/main/java/edu/hm/hafner/util/VisibleForTesting.java index 1b4d4279c..7782f668e 100644 --- a/src/main/java/edu/hm/hafner/util/VisibleForTesting.java +++ b/src/main/java/edu/hm/hafner/util/VisibleForTesting.java @@ -1,10 +1,8 @@ package edu.hm.hafner.util; /** - * An annotation that indicates that the visibility of a type or member has - * been relaxed to make the code testable. + * An annotation that indicates that the visibility of a type or member has been relaxed to make the code testable. * * @author Johannes Henkel (copied from Google Guava Library) */ -public @interface VisibleForTesting { -} +public @interface VisibleForTesting {} diff --git a/src/main/java/edu/hm/hafner/util/package-info.java b/src/main/java/edu/hm/hafner/util/package-info.java index f609b3258..774456823 100644 --- a/src/main/java/edu/hm/hafner/util/package-info.java +++ b/src/main/java/edu/hm/hafner/util/package-info.java @@ -1,6 +1,6 @@ /** - * Provides highly reusable utility classes and static methods, chiefly concerned - * with adding value to java.lang, java.util, and other standard core classes. + * Provides highly reusable utility classes and static methods, chiefly concerned with adding value to java.lang, + * java.util, and other standard core classes. * * @author Ullrich Hafner */ diff --git a/src/test/java/edu/hm/hafner/archunit/ArchitectureRules.java b/src/test/java/edu/hm/hafner/archunit/ArchitectureRules.java index 0464ee600..1e40518e1 100644 --- a/src/test/java/edu/hm/hafner/archunit/ArchitectureRules.java +++ b/src/test/java/edu/hm/hafner/archunit/ArchitectureRules.java @@ -1,7 +1,11 @@ package edu.hm.hafner.archunit; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; +import static com.tngtech.archunit.core.domain.JavaAccess.Predicates.targetOwner; +import static com.tngtech.archunit.lang.conditions.ArchConditions.fullyQualifiedName; +import static com.tngtech.archunit.lang.conditions.ArchPredicates.has; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.JavaCall; @@ -15,16 +19,11 @@ import com.tngtech.archunit.lang.ArchRule; import com.tngtech.archunit.lang.ConditionEvents; import com.tngtech.archunit.lang.SimpleConditionEvent; - import edu.hm.hafner.util.VisibleForTesting; - import java.io.Serializable; import java.util.List; - -import static com.tngtech.archunit.core.domain.JavaAccess.Predicates.*; -import static com.tngtech.archunit.lang.conditions.ArchConditions.*; -import static com.tngtech.archunit.lang.conditions.ArchPredicates.*; -import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; /** * Defines several architecture rules that should be enforced in this project. @@ -33,62 +32,85 @@ */ public final class ArchitectureRules { /** No class should have non-private instance fields. */ - public static final ArchRule ONLY_PRIVATE_FIELDS = - fields().that().doNotHaveModifier(JavaModifier.STATIC) - .should().bePrivate().allowEmptyShould(true); + public static final ArchRule ONLY_PRIVATE_FIELDS = fields().that() + .doNotHaveModifier(JavaModifier.STATIC) + .should() + .bePrivate() + .allowEmptyShould(true); /** Tests should not use fields. Recommendation is to use factory methods for stubs and mocks. */ - public static final ArchRule NO_FIELDS_IN_TESTS = - fields().that().areDeclaredInClassesThat().haveSimpleNameEndingWith("Test") - .should().beFinal().andShould().haveModifier(JavaModifier.STATIC) - .because("use factory methods in favor of instance fields when creating stubs or mocks in tests") - .allowEmptyShould(true); + public static final ArchRule NO_FIELDS_IN_TESTS = fields().that() + .areDeclaredInClassesThat() + .haveSimpleNameEndingWith("Test") + .should() + .beFinal() + .andShould() + .haveModifier(JavaModifier.STATIC) + .because("use factory methods in favor of instance fields when creating stubs or mocks in tests") + .allowEmptyShould(true); /** Never create exception without any context. */ - public static final ArchRule NO_EXCEPTIONS_WITH_NO_ARG_CONSTRUCTOR = - noClasses().that().haveSimpleNameNotContaining("Benchmark") - .should().callConstructorWhere(exceptionHasNoContextAsParameter()) - .because("exceptions should include failure-capture information in detail messages (Effective Java Item 75)") - .allowEmptyShould(true); + public static final ArchRule NO_EXCEPTIONS_WITH_NO_ARG_CONSTRUCTOR = noClasses() + .that() + .haveSimpleNameNotContaining("Benchmark") + .should() + .callConstructorWhere(exceptionHasNoContextAsParameter()) + .because( + "exceptions should include failure-capture information in detail messages (Effective Java Item 75)") + .allowEmptyShould(true); /** Junit 5 test classes should not be public. */ - public static final ArchRule NO_PUBLIC_TEST_CLASSES = - noClasses().that().haveSimpleNameEndingWith("Test") - .and().haveSimpleNameNotContaining("_jmh") - .and().doNotHaveModifier(JavaModifier.ABSTRACT) - .should().bePublic() - .because("test classes are not part of the API and should be hidden in a package"); + public static final ArchRule NO_PUBLIC_TEST_CLASSES = noClasses() + .that() + .haveSimpleNameEndingWith("Test") + .and() + .haveSimpleNameNotContaining("_jmh") + .and() + .doNotHaveModifier(JavaModifier.ABSTRACT) + .should() + .bePublic() + .because("test classes are not part of the API and should be hidden in a package"); /** Junit 5 test methods should not be public. */ - public static final ArchRule ONLY_PACKAGE_PRIVATE_TEST_METHODS = - methods().that().areAnnotatedWith(Test.class) - .or().areAnnotatedWith(ParameterizedTest.class) - .and().areDeclaredInClassesThat() - .haveSimpleNameEndingWith("Test") - .should().bePackagePrivate() - .because("test methods are not part of the API and should be hidden in a package"); + public static final ArchRule ONLY_PACKAGE_PRIVATE_TEST_METHODS = methods() + .that() + .areAnnotatedWith(Test.class) + .or() + .areAnnotatedWith(ParameterizedTest.class) + .and() + .areDeclaredInClassesThat() + .haveSimpleNameEndingWith("Test") + .should() + .bePackagePrivate() + .because("test methods are not part of the API and should be hidden in a package"); /** ArchUnit tests should not be public. */ - public static final ArchRule ONLY_PACKAGE_PRIVATE_ARCHITECTURE_TESTS = - fields().that().areAnnotatedWith(ArchTest.class) - .should().bePackagePrivate() - .because("architecture tests are not part of the API and should be hidden in a package") - .allowEmptyShould(true); + public static final ArchRule ONLY_PACKAGE_PRIVATE_ARCHITECTURE_TESTS = fields().that() + .areAnnotatedWith(ArchTest.class) + .should() + .bePackagePrivate() + .because("architecture tests are not part of the API and should be hidden in a package") + .allowEmptyShould(true); /** * Methods or constructors that are annotated with {@link VisibleForTesting} must not be called by other classes. * These methods are meant to be {@code private}. Only test classes are allowed to call these methods. */ - public static final ArchRule NO_TEST_API_CALLED = - noClasses().that().haveSimpleNameNotEndingWith("Test") - .and().haveSimpleNameNotContaining("Benchmark") - .should().callCodeUnitWhere(accessIsRestrictedForTests()) - .because("Production code should never access methods that are marked with @VisibleForTesting") - .allowEmptyShould(true); + public static final ArchRule NO_TEST_API_CALLED = noClasses() + .that() + .haveSimpleNameNotEndingWith("Test") + .and() + .haveSimpleNameNotContaining("Benchmark") + .should() + .callCodeUnitWhere(accessIsRestrictedForTests()) + .because("Production code should never access methods that are marked with @VisibleForTesting") + .allowEmptyShould(true); /** Prevents that classes use visible but forbidden API. */ - public static final ArchRule NO_FORBIDDEN_PACKAGE_ACCESSED = - noClasses().should().dependOnClassesThat().resideInAnyPackage( + public static final ArchRule NO_FORBIDDEN_PACKAGE_ACCESSED = noClasses() + .should() + .dependOnClassesThat() + .resideInAnyPackage( "org.apache.commons.lang..", "org.joda.time..", "javax.xml.bind..", @@ -96,45 +118,48 @@ public final class ArchitectureRules { "junit..", "org.hamcrest..", "com.google.common..", - "org.junit" - ); + "org.junit"); /** Prevents that classes use visible but forbidden annotations. */ - public static final ArchRule NO_FORBIDDEN_ANNOTATION_USED = - noClasses().should() - .dependOnClassesThat() - .haveNameMatching("javax.annotation.Check.*") - .orShould() - .dependOnClassesThat() - .haveNameMatching("javax.annotation.Nonnull") - .orShould() - .dependOnClassesThat() - .haveNameMatching("jakarta.annotation.Nullable") - .orShould() - .dependOnClassesThat() - .haveNameMatching("javax.annotation.Nullable") - .orShould() - .dependOnClassesThat() - .haveNameMatching("javax.annotation.Parameters.*") - .orShould() - .dependOnClassesThat() - .haveNameMatching( - "edu.umd.cs.findbugs.annotations.Nullable") // only CheckForNull and NonNull is allowed - .because("JSR 305 annotations are forbidden, as well as the Nullable annotation from FindBugs"); + public static final ArchRule NO_FORBIDDEN_ANNOTATION_USED = noClasses() + .should() + .dependOnClassesThat() + .haveNameMatching("javax.annotation.Check.*") + .orShould() + .dependOnClassesThat() + .haveNameMatching("javax.annotation.Nonnull") + .orShould() + .dependOnClassesThat() + .haveNameMatching("jakarta.annotation.Nullable") + .orShould() + .dependOnClassesThat() + .haveNameMatching("javax.annotation.Nullable") + .orShould() + .dependOnClassesThat() + .haveNameMatching("javax.annotation.Parameters.*") + .orShould() + .dependOnClassesThat() + .haveNameMatching("edu.umd.cs.findbugs.annotations.Nullable") // only CheckForNull and NonNull is allowed + .because("JSR 305 annotations are forbidden, as well as the Nullable annotation from FindBugs"); /** Prevents that classes use visible but forbidden API. */ - public static final ArchRule NO_FORBIDDEN_CLASSES_CALLED = - noClasses().should().callCodeUnitWhere(targetOwner(has( - fullyQualifiedName("org.junit.jupiter.api.Assertions") - .or(fullyQualifiedName("org.junit.Assert"))))) - .because("only AssertJ should be used for assertions"); + public static final ArchRule NO_FORBIDDEN_CLASSES_CALLED = noClasses() + .should() + .callCodeUnitWhere(targetOwner(has( + fullyQualifiedName("org.junit.jupiter.api.Assertions").or(fullyQualifiedName("org.junit.Assert"))))) + .because("only AssertJ should be used for assertions"); /** Ensures that the {@code readResolve} methods are protected so subclasses can call the parent method. */ - public static final ArchRule READ_RESOLVE_SHOULD_BE_PROTECTED = - methods().that().haveName("readResolve").and().haveRawReturnType(Object.class) - .should().beDeclaredInClassesThat().implement(Serializable.class) - .andShould(beProtected()) - .allowEmptyShould(true); + public static final ArchRule READ_RESOLVE_SHOULD_BE_PROTECTED = methods() + .that() + .haveName("readResolve") + .and() + .haveRawReturnType(Object.class) + .should() + .beDeclaredInClassesThat() + .implement(Serializable.class) + .andShould(beProtected()) + .allowEmptyShould(true); private static ExceptionHasNoContext exceptionHasNoContextAsParameter() { return new ExceptionHasNoContext(IncompatibleClassChangeError.class); @@ -155,9 +180,10 @@ private static ArchCondition beProtected() { /** * Matches if a call from outside the defining class uses a method or constructor annotated with * {@link VisibleForTesting}. There are two exceptions: + * *
    - *
  • The method is called on the same class
  • - *
  • The method is called in a method also annotated with {@link VisibleForTesting}
  • + *
  • The method is called on the same class + *
  • The method is called in a method also annotated with {@link VisibleForTesting} *
*/ private static class AccessRestrictedToTests extends DescribedPredicate> { @@ -177,17 +203,14 @@ private boolean isVisibleForTesting(final CanBeAnnotated target) { } } - /** - * Matches if an exception has no context, i.e., the constructor is invoked without a message. - */ + /** Matches if an exception has no context, i.e., the constructor is invoked without a message. */ private static class ExceptionHasNoContext extends DescribedPredicate { private final List> allowedExceptions; /** * Creates a new predicate. * - * @param allowedExceptions - * exceptions that are allowed to be instantiated without arguments + * @param allowedExceptions exceptions that are allowed to be instantiated without arguments */ @SafeVarargs @SuppressWarnings("varargs") @@ -203,8 +226,7 @@ public boolean test(final JavaConstructorCall javaConstructorCall) { if (!target.getRawParameterTypes().isEmpty()) { return false; } - return target.getOwner().isAssignableTo(Throwable.class) - && !isPermittedException(target.getOwner()); + return target.getOwner().isAssignableTo(Throwable.class) && !isPermittedException(target.getOwner()); } private boolean isPermittedException(final JavaClass owner) { @@ -225,9 +247,10 @@ public void check(final JavaMethod method, final ConditionEvents events) { if (method.getOwner().getModifiers().contains(JavaModifier.FINAL)) { return; } - events.add(SimpleConditionEvent.violated(method, - "%s is not protected but the class might be extended in %s".formatted( - method.getDescription(), method.getSourceCodeLocation()))); + events.add(SimpleConditionEvent.violated( + method, + "%s is not protected but the class might be extended in %s" + .formatted(method.getDescription(), method.getSourceCodeLocation()))); } } } diff --git a/src/test/java/edu/hm/hafner/archunit/ArchitectureRulesTest.java b/src/test/java/edu/hm/hafner/archunit/ArchitectureRulesTest.java index c8dd74d1f..635ed55ef 100644 --- a/src/test/java/edu/hm/hafner/archunit/ArchitectureRulesTest.java +++ b/src/test/java/edu/hm/hafner/archunit/ArchitectureRulesTest.java @@ -1,17 +1,16 @@ package edu.hm.hafner.archunit; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatNoException; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; - import edu.hm.hafner.util.Generated; - import java.io.Serial; import java.io.Serializable; - -import static org.assertj.core.api.Assertions.*; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; /** * Verifies the architecture rules in {@link ArchitectureRules}. @@ -23,115 +22,123 @@ class ArchitectureRulesTest { @Test void shouldVerifyThatFieldsArePrivate() { - assertThatExceptionOfType(AssertionError.class).isThrownBy( - () -> ArchitectureRules.ONLY_PRIVATE_FIELDS.check(importBrokenClass())) - .withMessageContainingAll(BROKEN_CLASS_NAME, "fields that do not have modifier STATIC should be private' was violated"); + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> ArchitectureRules.ONLY_PRIVATE_FIELDS.check(importBrokenClass())) + .withMessageContainingAll( + BROKEN_CLASS_NAME, "fields that do not have modifier STATIC should be private' was violated"); - assertThatNoException().isThrownBy( - () -> ArchitectureRules.ONLY_PRIVATE_FIELDS.check(importPassingClass())); + assertThatNoException().isThrownBy(() -> ArchitectureRules.ONLY_PRIVATE_FIELDS.check(importPassingClass())); } @Test void shouldUseProtectedForReadResolve() { - assertThatExceptionOfType(AssertionError.class).isThrownBy( - () -> ArchitectureRules.READ_RESOLVE_SHOULD_BE_PROTECTED.check(importBrokenClass())) - .withMessageContainingAll(BROKEN_CLASS_NAME, "was violated (3 times)", + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> ArchitectureRules.READ_RESOLVE_SHOULD_BE_PROTECTED.check(importBrokenClass())) + .withMessageContainingAll( + BROKEN_CLASS_NAME, + "was violated (3 times)", "Method is not protected but the class might be extended in (ArchitectureRulesTest.java:", "Method is not declared in classes that implement java.io.Serializable in (ArchitectureRulesTest.java:", "Method is not protected but the class might be extended in (ArchitectureRulesTest.java:"); - assertThatNoException().isThrownBy( - () -> ArchitectureRules.READ_RESOLVE_SHOULD_BE_PROTECTED.check(importPassingClass())); + assertThatNoException() + .isThrownBy(() -> ArchitectureRules.READ_RESOLVE_SHOULD_BE_PROTECTED.check(importPassingClass())); } @Test void shouldNotUseJsr305Annotations() { - assertThatExceptionOfType(AssertionError.class).isThrownBy( - () -> ArchitectureRules.NO_FORBIDDEN_ANNOTATION_USED.check( - importClasses(ArchitectureRulesViolatedTest.class))) - .withMessageContainingAll("was violated (3 times)", "edu.umd.cs.findbugs.annotations", + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> ArchitectureRules.NO_FORBIDDEN_ANNOTATION_USED.check( + importClasses(ArchitectureRulesViolatedTest.class))) + .withMessageContainingAll( + "was violated (3 times)", + "edu.umd.cs.findbugs.annotations", "Field is annotated with ", "Method is annotated with ", "Parameter of method is annotated with "); - assertThatNoException().isThrownBy( - () -> ArchitectureRules.NO_FORBIDDEN_ANNOTATION_USED.check(importPassingClass())); + assertThatNoException() + .isThrownBy(() -> ArchitectureRules.NO_FORBIDDEN_ANNOTATION_USED.check(importPassingClass())); } @Test void shouldVerifyThatTestsDoNotUseFields() { - assertThatExceptionOfType(AssertionError.class).isThrownBy( - () -> ArchitectureRules.NO_FIELDS_IN_TESTS.check(importBrokenClass())) - .withMessageContainingAll(BROKEN_CLASS_NAME, "use factory methods in favor of instance fields when creating stubs or mocks in tests"); + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> ArchitectureRules.NO_FIELDS_IN_TESTS.check(importBrokenClass())) + .withMessageContainingAll( + BROKEN_CLASS_NAME, + "use factory methods in favor of instance fields when creating stubs or mocks in tests"); - assertThatNoException().isThrownBy( - () -> ArchitectureRules.NO_FIELDS_IN_TESTS.check(importPassingClass())); + assertThatNoException().isThrownBy(() -> ArchitectureRules.NO_FIELDS_IN_TESTS.check(importPassingClass())); } @Test void shouldVerifyForbiddenAnnotations() { - assertThatExceptionOfType(AssertionError.class).isThrownBy( - () -> ArchitectureRules.NO_FORBIDDEN_CLASSES_CALLED.check(importBrokenClass())) + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> ArchitectureRules.NO_FORBIDDEN_CLASSES_CALLED.check(importBrokenClass())) .withMessageContainingAll(BROKEN_CLASS_NAME, "only AssertJ should be used"); - assertThatNoException().isThrownBy( - () -> ArchitectureRules.NO_FORBIDDEN_CLASSES_CALLED.check(importPassingClass())); + assertThatNoException() + .isThrownBy(() -> ArchitectureRules.NO_FORBIDDEN_CLASSES_CALLED.check(importPassingClass())); } @Test void shouldVerifyExceptionWithNoArgConstructorCalled() { - assertThatExceptionOfType(AssertionError.class).isThrownBy( - () -> ArchitectureRules.NO_EXCEPTIONS_WITH_NO_ARG_CONSTRUCTOR.check(importBrokenClass())) + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> ArchitectureRules.NO_EXCEPTIONS_WITH_NO_ARG_CONSTRUCTOR.check(importBrokenClass())) .withMessageContainingAll(BROKEN_CLASS_NAME, "(Effective Java Item 75)"); - assertThatNoException().isThrownBy( - () -> ArchitectureRules.NO_EXCEPTIONS_WITH_NO_ARG_CONSTRUCTOR.check(importPassingClass())); + assertThatNoException() + .isThrownBy(() -> ArchitectureRules.NO_EXCEPTIONS_WITH_NO_ARG_CONSTRUCTOR.check(importPassingClass())); } @Test void shouldVerifyNoPublicTestClassesRule() { - assertThatExceptionOfType(AssertionError.class).isThrownBy( - () -> ArchitectureRules.NO_PUBLIC_TEST_CLASSES.check(importBrokenClass())) + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> ArchitectureRules.NO_PUBLIC_TEST_CLASSES.check(importBrokenClass())) .withMessageContainingAll(BROKEN_CLASS_NAME, "test classes are not part of the API"); - assertThatNoException().isThrownBy( - () -> ArchitectureRules.NO_PUBLIC_TEST_CLASSES.check(importPassingClass())); + assertThatNoException().isThrownBy(() -> ArchitectureRules.NO_PUBLIC_TEST_CLASSES.check(importPassingClass())); } @Test void shouldVerifyNoPublicTestMethodsRule() { - assertThatExceptionOfType(AssertionError.class).isThrownBy( - () -> ArchitectureRules.ONLY_PACKAGE_PRIVATE_TEST_METHODS.check(importBrokenClass())) + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> ArchitectureRules.ONLY_PACKAGE_PRIVATE_TEST_METHODS.check(importBrokenClass())) .withMessageContainingAll(BROKEN_CLASS_NAME, "test methods are not part of the API"); - assertThatNoException().isThrownBy( - () -> ArchitectureRules.ONLY_PACKAGE_PRIVATE_TEST_METHODS.check(importPassingClass())); + assertThatNoException() + .isThrownBy(() -> ArchitectureRules.ONLY_PACKAGE_PRIVATE_TEST_METHODS.check(importPassingClass())); } private JavaClasses importPassingClass() { - return new ClassFileImporter().importClasses(ArchitectureRulesPassedTest.class, - ArchitectureRulesAlsoPassedTest.class, ArchitectureRulesPassed.class); + return new ClassFileImporter() + .importClasses( + ArchitectureRulesPassedTest.class, + ArchitectureRulesAlsoPassedTest.class, + ArchitectureRulesPassed.class); } private JavaClasses importBrokenClass() { - return importClasses(ArchitectureRulesViolatedTest.class, - ArchitectureRulesAlsoViolatedTest.class); + return importClasses(ArchitectureRulesViolatedTest.class, ArchitectureRulesAlsoViolatedTest.class); } private JavaClasses importClasses(final Class... classes) { return new ClassFileImporter().importClasses(classes); } - @SuppressWarnings("all") @Generated // This class is just there to be used in architecture tests + @SuppressWarnings("all") + @Generated // This class is just there to be used in architecture tests public static class ArchitectureRulesViolatedTest { @edu.umd.cs.findbugs.annotations.Nullable private final String noNullable = null; int nonPrivate; - @Test @Disabled("This test is just there to be used in architecture tests") + @Test + @Disabled("This test is just there to be used in architecture tests") public void shouldFail() { - org.junit.jupiter.api.Assertions.assertEquals(1, 1); + Assertions.assertEquals(1, 1); throw new IllegalArgumentException(); } @@ -151,7 +158,8 @@ private Object readResolve() { } } - @SuppressWarnings("all") @Generated // This class is just there to be used in architecture tests + @SuppressWarnings("all") + @Generated // This class is just there to be used in architecture tests public static class ArchitectureRulesAlsoViolatedTest implements Serializable { @Serial private static final long serialVersionUID = 1L; @@ -166,12 +174,14 @@ private Object readResolve() { } } - @SuppressWarnings("all") @Generated // This class is just there to be used in architecture tests + @SuppressWarnings("all") + @Generated // This class is just there to be used in architecture tests static final class ArchitectureRulesPassedTest implements Serializable { @Serial private static final long serialVersionUID = 1L; - @Test @Disabled("This test is just there to be used in architecture tests") + @Test + @Disabled("This test is just there to be used in architecture tests") void shouldPass() { throw new IllegalArgumentException("context"); } diff --git a/src/test/java/edu/hm/hafner/archunit/ArchitectureTest.java b/src/test/java/edu/hm/hafner/archunit/ArchitectureTest.java index a901e76fe..7114850a8 100644 --- a/src/test/java/edu/hm/hafner/archunit/ArchitectureTest.java +++ b/src/test/java/edu/hm/hafner/archunit/ArchitectureTest.java @@ -5,7 +5,6 @@ import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; import com.tngtech.archunit.lang.ArchRule; - import edu.hm.hafner.archunit.ArchitectureTest.DoNotIncludeRulesUnderTest; /** @@ -13,10 +12,8 @@ * * @author Ullrich Hafner */ -@AnalyzeClasses(packages = "edu.hm.hafner", importOptions = DoNotIncludeRulesUnderTest.class) final class ArchitectureTest { - private ArchitectureTest() { - } - +@AnalyzeClasses(packages = "edu.hm.hafner", importOptions = DoNotIncludeRulesUnderTest.class) +class ArchitectureTest { @ArchTest static final ArchRule NO_PUBLIC_TEST_CLASSES = ArchitectureRules.NO_PUBLIC_TEST_CLASSES; @@ -24,7 +21,8 @@ private ArchitectureTest() { static final ArchRule ONLY_PACKAGE_PRIVATE_TEST_METHODS = ArchitectureRules.ONLY_PACKAGE_PRIVATE_TEST_METHODS; @ArchTest - static final ArchRule ONLY_PACKAGE_PRIVATE_ARCHITECTURE_TESTS = ArchitectureRules.ONLY_PACKAGE_PRIVATE_ARCHITECTURE_TESTS; + static final ArchRule ONLY_PACKAGE_PRIVATE_ARCHITECTURE_TESTS = + ArchitectureRules.ONLY_PACKAGE_PRIVATE_ARCHITECTURE_TESTS; @ArchTest static final ArchRule NO_FIELDS_IN_TESTS = ArchitectureRules.NO_FIELDS_IN_TESTS; @@ -42,7 +40,8 @@ private ArchitectureTest() { static final ArchRule NO_FORBIDDEN_ANNOTATION_USED = ArchitectureRules.NO_FORBIDDEN_ANNOTATION_USED; @ArchTest - static final ArchRule NO_EXCEPTIONS_WITH_NO_ARG_CONSTRUCTOR = ArchitectureRules.NO_EXCEPTIONS_WITH_NO_ARG_CONSTRUCTOR; + static final ArchRule NO_EXCEPTIONS_WITH_NO_ARG_CONSTRUCTOR = + ArchitectureRules.NO_EXCEPTIONS_WITH_NO_ARG_CONSTRUCTOR; static final class DoNotIncludeRulesUnderTest implements ImportOption { @Override diff --git a/src/test/java/edu/hm/hafner/archunit/PackageArchitectureTest.java b/src/test/java/edu/hm/hafner/archunit/PackageArchitectureTest.java index fbce25e42..63541f7c4 100644 --- a/src/test/java/edu/hm/hafner/archunit/PackageArchitectureTest.java +++ b/src/test/java/edu/hm/hafner/archunit/PackageArchitectureTest.java @@ -1,30 +1,28 @@ package edu.hm.hafner.archunit; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; +import static com.tngtech.archunit.library.plantuml.rules.PlantUmlArchCondition.Configuration.consideringOnlyDependenciesInAnyPackage; +import static com.tngtech.archunit.library.plantuml.rules.PlantUmlArchCondition.adhereToPlantUmlDiagram; + import com.tngtech.archunit.core.importer.ImportOption.DoNotIncludeTests; import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; import com.tngtech.archunit.lang.ArchRule; - import java.net.URL; import java.util.Objects; -import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*; -import static com.tngtech.archunit.library.plantuml.rules.PlantUmlArchCondition.Configuration.*; -import static com.tngtech.archunit.library.plantuml.rules.PlantUmlArchCondition.*; - /** * Checks the package architecture of this module. * * @author Ullrich Hafner */ -@AnalyzeClasses(packages = "edu.hm.hafner..", importOptions = DoNotIncludeTests.class) final class PackageArchitectureTest { +@AnalyzeClasses(packages = "edu.hm.hafner..", importOptions = DoNotIncludeTests.class) +final class PackageArchitectureTest { private static final URL PACKAGE_DESIGN = PackageArchitectureTest.class.getResource("/design.puml"); @ArchTest - static final ArchRule ADHERES_TO_PACKAGE_DESIGN - = classes().should(adhereToPlantUmlDiagram(Objects.requireNonNull(PACKAGE_DESIGN), + static final ArchRule ADHERES_TO_PACKAGE_DESIGN = classes() + .should(adhereToPlantUmlDiagram( + Objects.requireNonNull(PACKAGE_DESIGN), consideringOnlyDependenciesInAnyPackage("edu.hm.hafner.."))); - - private PackageArchitectureTest() { - } } diff --git a/src/test/java/edu/hm/hafner/util/AbstractComparableTest.java b/src/test/java/edu/hm/hafner/util/AbstractComparableTest.java index 2b582ee1f..56b62107f 100644 --- a/src/test/java/edu/hm/hafner/util/AbstractComparableTest.java +++ b/src/test/java/edu/hm/hafner/util/AbstractComparableTest.java @@ -1,8 +1,8 @@ package edu.hm.hafner.util; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.*; +import org.junit.jupiter.api.Test; /** * Verifies that comparable objects comply with the contract in {@link Comparable#compareTo(Object)}. @@ -28,9 +28,7 @@ void shouldBeNegativeIfThisIsSmaller() { assertThat(greater.compareTo(greater)).isZero(); } - /** - * Verifies that {@code sgn(x.compareTo(y)) == -sgn(y.compareTo(x))} for all {@code x} and {@code y}. - */ + /** Verifies that {@code sgn(x.compareTo(y)) == -sgn(y.compareTo(x))} for all {@code x} and {@code y}. */ @Test void shouldBeSymmetric() { var left = createSmallerSut(); @@ -43,16 +41,16 @@ void shouldBeSymmetric() { } /** - * Creates a subject under test. The SUT must be smaller than the SUT of the opposite method {@link - * #createGreaterSut()}. + * Creates a subject under test. The SUT must be smaller than the SUT of the opposite method + * {@link #createGreaterSut()}. * * @return the SUT */ protected abstract T createSmallerSut(); /** - * Creates a subject under test. The SUT must be greater than the SUT of the opposite method {@link - * #createSmallerSut()}. + * Creates a subject under test. The SUT must be greater than the SUT of the opposite method + * {@link #createSmallerSut()}. * * @return the SUT */ diff --git a/src/test/java/edu/hm/hafner/util/AbstractEqualsTest.java b/src/test/java/edu/hm/hafner/util/AbstractEqualsTest.java index d42893af7..d474f9810 100644 --- a/src/test/java/edu/hm/hafner/util/AbstractEqualsTest.java +++ b/src/test/java/edu/hm/hafner/util/AbstractEqualsTest.java @@ -1,8 +1,8 @@ package edu.hm.hafner.util; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.*; +import org.junit.jupiter.api.Test; /** * Verifies that objects of any Java class comply with the contract in {@link Object#equals(Object)}. @@ -17,9 +17,7 @@ public abstract class AbstractEqualsTest { */ protected abstract Object createSut(); - /** - * Verifies that for any non-null reference value {@code x}, {@code x.equals(null)} should return {@code false}. - */ + /** Verifies that for any non-null reference value {@code x}, {@code x.equals(null)} should return {@code false}. */ @Test @SuppressWarnings({"PMD.EqualsNull", "checkstyle:equalsavoidnull", "ConstantConditions"}) void shouldReturnFalseOnEqualsNull() { diff --git a/src/test/java/edu/hm/hafner/util/EnsureTest.java b/src/test/java/edu/hm/hafner/util/EnsureTest.java index 974c863e6..5ac7da3c7 100644 --- a/src/test/java/edu/hm/hafner/util/EnsureTest.java +++ b/src/test/java/edu/hm/hafner/util/EnsureTest.java @@ -1,14 +1,14 @@ package edu.hm.hafner.util; -import org.assertj.core.util.Lists; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.nio.file.Path; import java.util.Collection; import java.util.Collections; import java.util.Set; - -import static org.assertj.core.api.Assertions.*; +import org.assertj.core.util.Lists; +import org.junit.jupiter.api.Test; /** * Tests the class {@link Ensure}. @@ -19,166 +19,146 @@ class EnsureTest { private static final String SOME_STRING = "-"; private static final String ERROR_MESSAGE = "assertThatThrownBy Error."; - /** - * Checks whether no exception is thrown if we adhere to all contracts. - */ + /** Checks whether no exception is thrown if we adhere to all contracts. */ @Test @SuppressWarnings("checkstyle:LambdaBodyLength") void shouldNotThrowExceptionIfContractIsValid() { assertThatCode(() -> { - Ensure.that(false).isFalse(); - Ensure.that(true).isTrue(); - Ensure.that("").isNotNull(); - Ensure.that("", "").isNotNull(); - Ensure.that(null, (Object) null).isNull(); - Ensure.that(new String[]{""}).isNotEmpty(); - Ensure.that(new String[]{""}).hasSize(1); - Ensure.that(new String[0]).isEmpty(); - Ensure.that(Path.of("")).isNotEmpty(); - Ensure.that(new String[0]).hasSize(0); - Ensure.that(SOME_STRING).isNotEmpty(); - Ensure.that(SOME_STRING).isNotBlank(); - Ensure.that("").isInstanceOf(String.class); - Ensure.that(Set.of()).isEmpty(); - Ensure.that(Set.of("")).isNotEmpty(); - Ensure.that(Set.of("")).hasSize(1); - Ensure.that(Set.of("")).contains(""); - Ensure.that(Set.of("")).doesNotContain(SOME_STRING); - }).doesNotThrowAnyException(); + Ensure.that(false).isFalse(); + Ensure.that(true).isTrue(); + Ensure.that("").isNotNull(); + Ensure.that("", "").isNotNull(); + Ensure.that(null, (Object) null).isNull(); + Ensure.that(new String[] {""}).isNotEmpty(); + Ensure.that(new String[] {""}).hasSize(1); + Ensure.that(new String[0]).isEmpty(); + Ensure.that(Path.of("")).isNotEmpty(); + Ensure.that(new String[0]).hasSize(0); + Ensure.that(SOME_STRING).isNotEmpty(); + Ensure.that(SOME_STRING).isNotBlank(); + Ensure.that("").isInstanceOf(String.class); + Ensure.that(Set.of()).isEmpty(); + Ensure.that(Set.of("")).isNotEmpty(); + Ensure.that(Set.of("")).hasSize(1); + Ensure.that(Set.of("")).contains(""); + Ensure.that(Set.of("")).doesNotContain(SOME_STRING); + }) + .doesNotThrowAnyException(); } - /** - * Checks whether we throw an exception if a contract is violated. - */ + /** Checks whether we throw an exception if a contract is violated. */ @Test @SuppressWarnings("Convert2MethodRef") void shouldThrowExceptionIfContractIsViolated() { - assertThatThrownBy(() -> Ensure.that(new IllegalArgumentException(ERROR_MESSAGE)).isNeverThrown(ERROR_MESSAGE)) - .isInstanceOf(AssertionError.class).hasMessage(ERROR_MESSAGE); - assertThatThrownBy(() -> Ensure.that(true).isFalse()) - .isInstanceOf(AssertionError.class); + assertThatThrownBy(() -> + Ensure.that(new IllegalArgumentException(ERROR_MESSAGE)).isNeverThrown(ERROR_MESSAGE)) + .isInstanceOf(AssertionError.class) + .hasMessage(ERROR_MESSAGE); + assertThatThrownBy(() -> Ensure.that(true).isFalse()).isInstanceOf(AssertionError.class); assertThatThrownBy(() -> Ensure.that(true).isFalse(ERROR_MESSAGE)) - .isInstanceOf(AssertionError.class).hasMessage(ERROR_MESSAGE); - assertThatThrownBy(() -> Ensure.that(false).isTrue()) - .isInstanceOf(AssertionError.class); + .isInstanceOf(AssertionError.class) + .hasMessage(ERROR_MESSAGE); + assertThatThrownBy(() -> Ensure.that(false).isTrue()).isInstanceOf(AssertionError.class); assertThatThrownBy(() -> Ensure.that(false).isTrue(ERROR_MESSAGE)) - .isInstanceOf(AssertionError.class).hasMessage(ERROR_MESSAGE); - assertThatThrownBy(Ensure::thatStatementIsNeverReached) - .isInstanceOf(AssertionError.class); + .isInstanceOf(AssertionError.class) + .hasMessage(ERROR_MESSAGE); + assertThatThrownBy(Ensure::thatStatementIsNeverReached).isInstanceOf(AssertionError.class); assertThatThrownBy(() -> Ensure.thatStatementIsNeverReached(ERROR_MESSAGE)) - .isInstanceOf(AssertionError.class).hasMessage(ERROR_MESSAGE); - assertThatThrownBy(() -> Ensure.that(SOME_STRING).isNull()) - .isInstanceOf(AssertionError.class); + .isInstanceOf(AssertionError.class) + .hasMessage(ERROR_MESSAGE); + assertThatThrownBy(() -> Ensure.that(SOME_STRING).isNull()).isInstanceOf(AssertionError.class); assertThatThrownBy(() -> Ensure.that(SOME_STRING).isNull(ERROR_MESSAGE)) - .isInstanceOf(AssertionError.class).hasMessage(ERROR_MESSAGE); + .isInstanceOf(AssertionError.class) + .hasMessage(ERROR_MESSAGE); assertThatThrownBy(() -> Ensure.that(SOME_STRING, SOME_STRING).isNull(ERROR_MESSAGE)) - .isInstanceOf(AssertionError.class).hasMessage(ERROR_MESSAGE); + .isInstanceOf(AssertionError.class) + .hasMessage(ERROR_MESSAGE); assertThatThrownBy(() -> Ensure.that(Collections.emptySet()).contains("")) .isInstanceOf(AssertionError.class); - assertThatThrownBy(() -> Ensure.that(Set.of("")).contains(SOME_STRING)) - .isInstanceOf(AssertionError.class); - assertThatThrownBy(() -> Ensure.that(Set.of("")).doesNotContain("")) - .isInstanceOf(AssertionError.class); - assertThatThrownBy(() -> Ensure.that(Set.of("")).isEmpty()) - .isInstanceOf(AssertionError.class); - assertThatThrownBy(() -> Ensure.that(Set.of()).isNotEmpty()) + assertThatThrownBy(() -> Ensure.that(Set.of("")).contains(SOME_STRING)).isInstanceOf(AssertionError.class); + assertThatThrownBy(() -> Ensure.that(Set.of("")).doesNotContain("")).isInstanceOf(AssertionError.class); + assertThatThrownBy(() -> Ensure.that(Set.of("")).isEmpty()).isInstanceOf(AssertionError.class); + assertThatThrownBy(() -> Ensure.that(Set.of()).isNotEmpty()).isInstanceOf(AssertionError.class); + assertThatThrownBy(() -> Ensure.that(Set.of("")).hasSize(0)).isInstanceOf(AssertionError.class); + assertThatThrownBy(() -> Ensure.that(Set.of("")).hasSize(2)).isInstanceOf(AssertionError.class); + assertThatThrownBy(() -> Ensure.that(new String[] {"not-empty"}).isEmpty()) .isInstanceOf(AssertionError.class); - assertThatThrownBy(() -> Ensure.that(Set.of("")).hasSize(0)) + assertThatThrownBy(() -> Ensure.that(new String[] {"not-empty"}).hasSize(0)) .isInstanceOf(AssertionError.class); - assertThatThrownBy(() -> Ensure.that(Set.of("")).hasSize(2)) - .isInstanceOf(AssertionError.class); - assertThatThrownBy(() -> Ensure.that(new String[]{"not-empty"}).isEmpty()) - .isInstanceOf(AssertionError.class); - assertThatThrownBy(() -> Ensure.that(new String[]{"not-empty"}).hasSize(0)) - .isInstanceOf(AssertionError.class); - assertThatThrownBy(() -> Ensure.that(new String[]{"not-empty"}).hasSize(2)) + assertThatThrownBy(() -> Ensure.that(new String[] {"not-empty"}).hasSize(2)) .isInstanceOf(AssertionError.class); } - /** - * Checks whether we throw an exception if a contract is violated. - */ + /** Checks whether we throw an exception if a contract is violated. */ @Test @SuppressWarnings("NullAway") void shouldThrowNpeIfContractIsViolated() { assertThatThrownBy(() -> Ensure.that((Object) null).isNotNull(ERROR_MESSAGE)) - .isInstanceOf(NullPointerException.class).hasMessage(ERROR_MESSAGE); + .isInstanceOf(NullPointerException.class) + .hasMessage(ERROR_MESSAGE); assertThatThrownBy(() -> Ensure.that(SOME_STRING, (Object) null).isNotNull(ERROR_MESSAGE)) - .isInstanceOf(NullPointerException.class).hasMessage(ERROR_MESSAGE); + .isInstanceOf(NullPointerException.class) + .hasMessage(ERROR_MESSAGE); assertThatThrownBy(() -> Ensure.that(SOME_STRING, (Object[]) null).isNotNull(ERROR_MESSAGE)) - .isInstanceOf(NullPointerException.class).hasMessage(ERROR_MESSAGE); + .isInstanceOf(NullPointerException.class) + .hasMessage(ERROR_MESSAGE); assertThatThrownBy(() -> Ensure.that(null, SOME_STRING).isNotNull(ERROR_MESSAGE)) - .isInstanceOf(NullPointerException.class).hasMessage(ERROR_MESSAGE); + .isInstanceOf(NullPointerException.class) + .hasMessage(ERROR_MESSAGE); assertThatThrownBy(() -> Ensure.that(null, (Object[]) null).isNotNull(ERROR_MESSAGE)) - .isInstanceOf(NullPointerException.class).hasMessage(ERROR_MESSAGE); - assertThatThrownBy(() -> Ensure.that((Object) null).isNotNull()) - .isInstanceOf(NullPointerException.class); + .isInstanceOf(NullPointerException.class) + .hasMessage(ERROR_MESSAGE); + assertThatThrownBy(() -> Ensure.that((Object) null).isNotNull()).isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> Ensure.that((Collection) null).isNotNull()) .isInstanceOf(NullPointerException.class); - assertThatThrownBy(() -> Ensure.that((Iterable) null).isNotNull()) - .isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> Ensure.that((Iterable) null).isNotNull()).isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> Ensure.that(SOME_STRING, (Object) null).isNotNull()) .isInstanceOf(NullPointerException.class); - assertThatThrownBy(() -> Ensure.that(null, SOME_STRING).isNotNull()) - .isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> Ensure.that(null, SOME_STRING).isNotNull()).isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> Ensure.that(null, (Object[]) null).isNotNull()) .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> Ensure.that((Object[]) null).isNotEmpty(ERROR_MESSAGE)) - .isInstanceOf(NullPointerException.class).hasMessage(ERROR_MESSAGE); + .isInstanceOf(NullPointerException.class) + .hasMessage(ERROR_MESSAGE); assertThatThrownBy(() -> Ensure.that((String) null).isNotEmpty(ERROR_MESSAGE)) - .isInstanceOf(NullPointerException.class).hasMessage(ERROR_MESSAGE); - assertThatThrownBy(() -> Ensure.that((Object[]) null).isNotEmpty()) - .isInstanceOf(NullPointerException.class); + .isInstanceOf(NullPointerException.class) + .hasMessage(ERROR_MESSAGE); + assertThatThrownBy(() -> Ensure.that((Object[]) null).isNotEmpty()).isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> Ensure.that((Collection) null).isNotEmpty()) .isInstanceOf(NullPointerException.class); - assertThatThrownBy(() -> Ensure.that((Iterable) null).isNotEmpty()) - .isInstanceOf(NullPointerException.class); - assertThatThrownBy(() -> Ensure.that((String) null).isNotEmpty()) - .isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> Ensure.that((Iterable) null).isNotEmpty()).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> Ensure.that((String) null).isNotEmpty()).isInstanceOf(NullPointerException.class); } - /** - * Checks whether we throw an exception if something is empty. - */ + /** Checks whether we throw an exception if something is empty. */ @Test void shouldThrowExceptionIfEmpty() { - assertThatThrownBy(() -> - Ensure.that(new String[0]).isNotEmpty(ERROR_MESSAGE)).isInstanceOf(AssertionError.class); - assertThatThrownBy(() -> - Ensure.that(Lists.newArrayList("", null, "")).isNotEmpty(ERROR_MESSAGE)).isInstanceOf( - AssertionError.class); - assertThatThrownBy(() -> - Ensure.that(new String[]{"", null, ""}).isNotEmpty(ERROR_MESSAGE)).isInstanceOf(AssertionError.class); - assertThatThrownBy(() -> - Ensure.that("").isNotEmpty(ERROR_MESSAGE)).isInstanceOf(AssertionError.class); - assertThatThrownBy(() -> - Ensure.that(" ").isNotBlank(ERROR_MESSAGE)).isInstanceOf(AssertionError.class); - assertThatThrownBy(() -> - Ensure.that("").isNotBlank(ERROR_MESSAGE)).isInstanceOf(AssertionError.class); - assertThatThrownBy(() -> - Ensure.that("").isInstanceOf(Integer.class, ERROR_MESSAGE)).isInstanceOf(AssertionError.class); - assertThatThrownBy(() -> - Ensure.that(new String[0]).isNotEmpty()).isInstanceOf(AssertionError.class); - assertThatThrownBy(() -> - Ensure.that(Lists.newArrayList("", null, "")).isNotEmpty()).isInstanceOf(AssertionError.class); - assertThatThrownBy(() -> - Ensure.that(new String[]{"", null, ""}).isNotEmpty()).isInstanceOf(AssertionError.class); - assertThatThrownBy(() -> - Ensure.that("").isNotEmpty()).isInstanceOf(AssertionError.class); - assertThatThrownBy(() -> - Ensure.that(" ").isNotBlank()).isInstanceOf(AssertionError.class); - assertThatThrownBy(() -> - Ensure.that("").isInstanceOf(Integer.class, ERROR_MESSAGE)).isInstanceOf(AssertionError.class); + assertThatThrownBy(() -> Ensure.that(new String[0]).isNotEmpty(ERROR_MESSAGE)) + .isInstanceOf(AssertionError.class); + assertThatThrownBy(() -> Ensure.that(Lists.newArrayList("", null, "")).isNotEmpty(ERROR_MESSAGE)) + .isInstanceOf(AssertionError.class); + assertThatThrownBy(() -> Ensure.that(new String[] {"", null, ""}).isNotEmpty(ERROR_MESSAGE)) + .isInstanceOf(AssertionError.class); + assertThatThrownBy(() -> Ensure.that("").isNotEmpty(ERROR_MESSAGE)).isInstanceOf(AssertionError.class); + assertThatThrownBy(() -> Ensure.that(" ").isNotBlank(ERROR_MESSAGE)).isInstanceOf(AssertionError.class); + assertThatThrownBy(() -> Ensure.that("").isNotBlank(ERROR_MESSAGE)).isInstanceOf(AssertionError.class); + assertThatThrownBy(() -> Ensure.that("").isInstanceOf(Integer.class, ERROR_MESSAGE)) + .isInstanceOf(AssertionError.class); + assertThatThrownBy(() -> Ensure.that(new String[0]).isNotEmpty()).isInstanceOf(AssertionError.class); + assertThatThrownBy(() -> Ensure.that(Lists.newArrayList("", null, "")).isNotEmpty()) + .isInstanceOf(AssertionError.class); + assertThatThrownBy(() -> Ensure.that(new String[] {"", null, ""}).isNotEmpty()) + .isInstanceOf(AssertionError.class); + assertThatThrownBy(() -> Ensure.that("").isNotEmpty()).isInstanceOf(AssertionError.class); + assertThatThrownBy(() -> Ensure.that(" ").isNotBlank()).isInstanceOf(AssertionError.class); + assertThatThrownBy(() -> Ensure.that("").isInstanceOf(Integer.class, ERROR_MESSAGE)) + .isInstanceOf(AssertionError.class); } - /** - * Verifies that the message format is correctly interpreted. - */ + /** Verifies that the message format is correctly interpreted. */ @Test void shouldThrowExceptionWithCorrectMessage() { - assertThatThrownBy(() -> - Ensure.that("") - .isInstanceOf(Integer.class, "'%s' prints %d", "String.format", 42)) + assertThatThrownBy(() -> Ensure.that("").isInstanceOf(Integer.class, "'%s' prints %d", "String.format", 42)) .isInstanceOf(AssertionError.class) .hasMessage("'String.format' prints 42"); } diff --git a/src/test/java/edu/hm/hafner/util/FilteredLogTest.java b/src/test/java/edu/hm/hafner/util/FilteredLogTest.java index f19c945fb..92777369f 100644 --- a/src/test/java/edu/hm/hafner/util/FilteredLogTest.java +++ b/src/test/java/edu/hm/hafner/util/FilteredLogTest.java @@ -1,13 +1,12 @@ package edu.hm.hafner.util; -import org.apache.commons.lang3.StringUtils; -import org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration; -import org.junit.jupiter.api.Test; +import static edu.hm.hafner.util.assertions.Assertions.assertThat; import nl.jqno.equalsverifier.EqualsVerifier; import nl.jqno.equalsverifier.Warning; - -import static edu.hm.hafner.util.assertions.Assertions.*; +import org.apache.commons.lang3.StringUtils; +import org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration; +import org.junit.jupiter.api.Test; /** * Tests the class {@link FilteredLog}. @@ -67,20 +66,23 @@ void shouldSkipAdditionalErrorsWithTitle() { } private void verifyFiveErrorMessages(final FilteredLog filteredLog) { - assertThat(filteredLog).hasErrorMessages( - "1", "2", "3", "4", "5", - "java.lang.IllegalStateException: 1", - "java.lang.IllegalStateException: 2", - "java.lang.IllegalStateException: 3", - "java.lang.IllegalStateException: 4", - "java.lang.IllegalStateException: 5", - " ... skipped logging of 2 additional errors ..."); - - assertThat(filteredLog).doesNotHaveErrorMessages( - "6", - "java.lang.IllegalStateException: 6", - "7", - "java.lang.IllegalStateException: 7"); + assertThat(filteredLog) + .hasErrorMessages( + "1", + "2", + "3", + "4", + "5", + "java.lang.IllegalStateException: 1", + "java.lang.IllegalStateException: 2", + "java.lang.IllegalStateException: 3", + "java.lang.IllegalStateException: 4", + "java.lang.IllegalStateException: 5", + " ... skipped logging of 2 additional errors ..."); + + assertThat(filteredLog) + .doesNotHaveErrorMessages( + "6", "java.lang.IllegalStateException: 6", "7", "java.lang.IllegalStateException: 7"); } private FilteredLog create5ErrorsLogWithTitle(final String title) { @@ -112,7 +114,8 @@ void shouldMergeLogger() { parent.merge(child); - assertThat(parent).hasOnlyInfoMessages("parent Info 1", "child Info 1") + assertThat(parent) + .hasOnlyInfoMessages("parent Info 1", "child Info 1") .hasOnlyErrorMessages("Parent Errors", "parent Error 1", "Child Errors", "child Error 1"); assertThat(parent.size()).isEqualTo(1); } @@ -129,7 +132,8 @@ void shouldSkipEmptyErrorLogWhenMerging() { parent.merge(child); - assertThat(parent).hasOnlyInfoMessages("parent Info 1", "child Info 1") + assertThat(parent) + .hasOnlyInfoMessages("parent Info 1", "child Info 1") .hasOnlyErrorMessages("Child Errors", "child Error 1"); assertThat(parent.size()).isZero(); } @@ -141,28 +145,30 @@ void shouldLogExceptions() { filteredLog.logException(new IllegalArgumentException("Cause"), "Message"); filteredLog.logException(new IllegalArgumentException(""), "Message"); - assertThat(filteredLog).hasErrorMessages(TITLE, - "Message", "java.lang.IllegalArgumentException: Cause"); + assertThat(filteredLog).hasErrorMessages(TITLE, "Message", "java.lang.IllegalArgumentException: Cause"); } @Test void shouldLog20ErrorsByDefault() { var filteredLog = createLogWith20Elements(); - assertThat(filteredLog.getErrorMessages()).hasSize(22) + assertThat(filteredLog.getErrorMessages()) + .hasSize(22) .contains(TITLE) .contains("error19") .doesNotContain("error20") .contains(" ... skipped logging of 5 additional errors ..."); - assertThat(filteredLog.getInfoMessages()).hasSize(25) - .contains("info0") - .contains("info24"); + assertThat(filteredLog.getInfoMessages()).hasSize(25).contains("info0").contains("info24"); } @Override - protected void assertThatRestoredInstanceEqualsOriginalInstance(final FilteredLog original, - final FilteredLog restored) { - assertThat(original).usingRecursiveComparison(RecursiveComparisonConfiguration.builder().withIgnoredFields("lock").build()).isEqualTo(restored); + protected void assertThatRestoredInstanceEqualsOriginalInstance( + final FilteredLog original, final FilteredLog restored) { + assertThat(original) + .usingRecursiveComparison(RecursiveComparisonConfiguration.builder() + .withIgnoredFields("lock") + .build()) + .isEqualTo(restored); } private FilteredLog createLogWith20Elements() { diff --git a/src/test/java/edu/hm/hafner/util/LineRangeListTest.java b/src/test/java/edu/hm/hafner/util/LineRangeListTest.java index 2257f5ac1..71ff86492 100644 --- a/src/test/java/edu/hm/hafner/util/LineRangeListTest.java +++ b/src/test/java/edu/hm/hafner/util/LineRangeListTest.java @@ -1,10 +1,9 @@ package edu.hm.hafner.util; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.util.List; - -import static org.assertj.core.api.Assertions.*; +import org.junit.jupiter.api.Test; /** * Tests the class {@link LineRangeList}. diff --git a/src/test/java/edu/hm/hafner/util/LineRangeTest.java b/src/test/java/edu/hm/hafner/util/LineRangeTest.java index c81d30740..2f9b733e1 100644 --- a/src/test/java/edu/hm/hafner/util/LineRangeTest.java +++ b/src/test/java/edu/hm/hafner/util/LineRangeTest.java @@ -1,10 +1,9 @@ package edu.hm.hafner.util; -import org.junit.jupiter.api.Test; +import static edu.hm.hafner.util.assertions.Assertions.assertThat; import nl.jqno.equalsverifier.EqualsVerifier; - -import static edu.hm.hafner.util.assertions.Assertions.*; +import org.junit.jupiter.api.Test; /** * Tests the class {@link LineRangeList}. @@ -26,23 +25,30 @@ void shouldFindLinesInsideAndOutsideOfLineRange() { assertThat(lineRange.contains(1)).isTrue(); assertThat(lineRange.contains(2)).isTrue(); assertThat(lineRange.contains(3)).isFalse(); - assertThat(lineRange).hasStart(1).hasEnd(2) - .hasLines(1, 2).isNotSingleLine().hasToString("[1-2]"); + assertThat(lineRange) + .hasStart(1) + .hasEnd(2) + .hasLines(1, 2) + .isNotSingleLine() + .hasToString("[1-2]"); var wrongOrder = new LineRange(2, 1); assertThat(wrongOrder.contains(0)).isFalse(); assertThat(wrongOrder.contains(1)).isTrue(); assertThat(wrongOrder.contains(2)).isTrue(); assertThat(wrongOrder.contains(3)).isFalse(); - assertThat(wrongOrder).hasStart(1).hasEnd(2) - .hasLines(1, 2).isNotSingleLine().hasToString("[1-2]"); + assertThat(wrongOrder) + .hasStart(1) + .hasEnd(2) + .hasLines(1, 2) + .isNotSingleLine() + .hasToString("[1-2]"); var point = new LineRange(2); assertThat(point.contains(1)).isFalse(); assertThat(point.contains(2)).isTrue(); assertThat(point.contains(3)).isFalse(); - assertThat(point).hasStart(2).hasEnd(2) - .hasLines(2).isSingleLine().hasToString("[2-2]"); + assertThat(point).hasStart(2).hasEnd(2).hasLines(2).isSingleLine().hasToString("[2-2]"); } @Test diff --git a/src/test/java/edu/hm/hafner/util/LookaheadStreamTest.java b/src/test/java/edu/hm/hafner/util/LookaheadStreamTest.java index 8535d0816..f476871a7 100644 --- a/src/test/java/edu/hm/hafner/util/LookaheadStreamTest.java +++ b/src/test/java/edu/hm/hafner/util/LookaheadStreamTest.java @@ -1,13 +1,14 @@ package edu.hm.hafner.util; -import org.apache.commons.lang3.StringUtils; -import org.junit.jupiter.api.Test; +import static edu.hm.hafner.util.assertions.Assertions.assertThat; +import static edu.hm.hafner.util.assertions.Assertions.assertThatExceptionOfType; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import java.util.NoSuchElementException; import java.util.stream.Stream; - -import static edu.hm.hafner.util.assertions.Assertions.*; -import static org.mockito.Mockito.*; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.Test; /** * Tests the class {@link LookaheadStream}. diff --git a/src/test/java/edu/hm/hafner/util/PackageDetectorRunnerTest.java b/src/test/java/edu/hm/hafner/util/PackageDetectorRunnerTest.java index 4fe143af5..ead29d223 100644 --- a/src/test/java/edu/hm/hafner/util/PackageDetectorRunnerTest.java +++ b/src/test/java/edu/hm/hafner/util/PackageDetectorRunnerTest.java @@ -1,18 +1,18 @@ package edu.hm.hafner.util; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; -import org.junit.jupiter.params.provider.ValueSource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import edu.hm.hafner.util.PackageDetectorFactory.FileSystemFacade; - import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Optional; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; /** * Tests the class {@link PackageDetectorRunner}. @@ -22,9 +22,10 @@ class PackageDetectorRunnerTest extends ResourceTest { @ParameterizedTest(name = "{index} => file={0}, expected package={1}") @CsvSource({ - "MavenJavaTest.txt.java, hudson.plugins.tasks.util", - "ActionBinding.cs, Avaloq.SmartClient.Utilities", - "KotlinTest.txt.kt, edu.hm.kersting"}) + "MavenJavaTest.txt.java, hudson.plugins.tasks.util", + "ActionBinding.cs, Avaloq.SmartClient.Utilities", + "KotlinTest.txt.kt, edu.hm.kersting" + }) void shouldExtractPackageNames(final String fileName, final String expectedPackage) throws IOException { assertThat(detect(fileName)).contains(expectedPackage); } @@ -57,6 +58,7 @@ void shouldHandleException() throws IOException { when(fileSystem.openFile(anyString())).thenThrow(new IOException("Simulated")); assertThat(PackageDetectorFactory.createPackageDetectors(fileSystem) - .detectPackageName("file.java", StandardCharsets.UTF_8)).isEmpty(); + .detectPackageName("file.java", StandardCharsets.UTF_8)) + .isEmpty(); } } diff --git a/src/test/java/edu/hm/hafner/util/PathUtilTest.java b/src/test/java/edu/hm/hafner/util/PathUtilTest.java index 724883551..72bced6ea 100644 --- a/src/test/java/edu/hm/hafner/util/PathUtilTest.java +++ b/src/test/java/edu/hm/hafner/util/PathUtilTest.java @@ -1,16 +1,15 @@ package edu.hm.hafner.util; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; import java.io.IOException; import java.nio.file.LinkOption; import java.nio.file.Path; - -import static org.assertj.core.api.Assertions.*; -import static org.assertj.core.api.Assumptions.*; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; /** * Tests the class {@link PathUtil}. @@ -27,8 +26,7 @@ class PathUtilTest extends ResourceTest { /** * Ensures that illegal file names are processed without problems and the test for existence returns {@code false}. * - * @param fileName - * the file name to check + * @param fileName the file name to check */ @ParameterizedTest(name = "[{index}] Illegal filename = {0}") @ValueSource(strings = {"/does/not/exist", "\0 Null-Byte", "C:/!<>$&/&( \0", "/!<>$&/&( \0"}) @@ -63,18 +61,33 @@ void shouldFindResourceFolder() { var pathUtil = new PathUtil(); assertThat(pathUtil.exists(getResourceAsFile(FILE_NAME).toString())).isTrue(); - assertThat(pathUtil.exists(getResourceAsFile(FILE_NAME).getParent().toString())).isTrue(); - assertThat(pathUtil.exists(FILE_NAME, getResourceAsFile(FILE_NAME).getParent().toString())).isTrue(); - assertThat(pathUtil.exists(getResourceAsFile(FILE_NAME).getRoot().toString())).isTrue(); + assertThat(pathUtil.exists(getResourceAsFile(FILE_NAME).getParent().toString())) + .isTrue(); + assertThat(pathUtil.exists( + FILE_NAME, getResourceAsFile(FILE_NAME).getParent().toString())) + .isTrue(); + assertThat(pathUtil.exists(getResourceAsFile(FILE_NAME).getRoot().toString())) + .isTrue(); } @DisplayName("Should verify valid absolute paths") @ParameterizedTest(name = "[{index}] path={0}") - @ValueSource(strings = {"/", "/tmp", "C:\\", "c:\\", "C:\\Tmp", "C:/tmp/absolute.txt", "file:///project/src/main/java/com/app/ui/model/Activity.kt"}) + @ValueSource( + strings = { + "/", + "/tmp", + "C:\\", + "c:\\", + "C:\\Tmp", + "C:/tmp/absolute.txt", + "file:///project/src/main/java/com/app/ui/model/Activity.kt" + }) void shouldFindAbsolutePaths(final String path) { var pathUtil = new PathUtil(); - assertThat(pathUtil.isAbsolute(path)).as("Show be detected as absolute path").isTrue(); + assertThat(pathUtil.isAbsolute(path)) + .as("Show be detected as absolute path") + .isTrue(); } @Test @@ -112,17 +125,19 @@ void shouldConvertToRelative() { var absolutePath = getResourceAsFile(FILE_NAME); - assertThat(pathUtil.getRelativePath(absolutePath.getParent(), FILE_NAME)).isEqualTo(FILE_NAME); + assertThat(pathUtil.getRelativePath(absolutePath.getParent(), FILE_NAME)) + .isEqualTo(FILE_NAME); assertThat(pathUtil.getRelativePath(FILE_NAME)).isEqualTo(FILE_NAME); - assertThat(pathUtil.getRelativePath(absolutePath.getParent(), NOT_EXISTING_RELATIVE)).isEqualTo( - NOT_EXISTING_RELATIVE); + assertThat(pathUtil.getRelativePath(absolutePath.getParent(), NOT_EXISTING_RELATIVE)) + .isEqualTo(NOT_EXISTING_RELATIVE); - assertThat(pathUtil.getRelativePath(absolutePath.getParent().getParent(), "util/" + FILE_NAME)).isEqualTo( - "util/" + FILE_NAME); + assertThat(pathUtil.getRelativePath(absolutePath.getParent().getParent(), "util/" + FILE_NAME)) + .isEqualTo("util/" + FILE_NAME); - assertThat(pathUtil.getRelativePath(absolutePath.getParent(), absolutePath.toString())).isEqualTo(FILE_NAME); - assertThat(pathUtil.getRelativePath(Path.of(NOT_EXISTING), absolutePath.toString())).isEqualTo( - pathUtil.getAbsolutePath(absolutePath)); + assertThat(pathUtil.getRelativePath(absolutePath.getParent(), absolutePath.toString())) + .isEqualTo(FILE_NAME); + assertThat(pathUtil.getRelativePath(Path.of(NOT_EXISTING), absolutePath.toString())) + .isEqualTo(pathUtil.getAbsolutePath(absolutePath)); assertThat(pathUtil.getRelativePath(Path.of(NOT_EXISTING), FILE_NAME)).isEqualTo(FILE_NAME); assertThat(pathUtil.getRelativePath(NOT_EXISTING, FILE_NAME)).isEqualTo(FILE_NAME); @@ -134,10 +149,10 @@ void shouldConvertNotResolvedToRelative() { var absolutePath = getResourceAsFile(FILE_NAME); - assertThat(pathUtil.getRelativePath(absolutePath.getParent().getParent(), "./util/" + FILE_NAME)).isEqualTo( - "util/" + FILE_NAME); - assertThat(pathUtil.getRelativePath(absolutePath.getParent().getParent(), - "../hafner/util/" + FILE_NAME)).isEqualTo("util/" + FILE_NAME); + assertThat(pathUtil.getRelativePath(absolutePath.getParent().getParent(), "./util/" + FILE_NAME)) + .isEqualTo("util/" + FILE_NAME); + assertThat(pathUtil.getRelativePath(absolutePath.getParent().getParent(), "../hafner/util/" + FILE_NAME)) + .isEqualTo("util/" + FILE_NAME); } @Test @@ -171,7 +186,9 @@ void shouldStayInSymbolicLinks() throws IOException { var real = current.toRealPath(); var realWithSymbolic = current.toRealPath(LinkOption.NOFOLLOW_LINKS); - assumeThat(real).as("Current working directory path is not based on symbolic links").isNotEqualTo(realWithSymbolic); + assumeThat(real) + .as("Current working directory path is not based on symbolic links") + .isNotEqualTo(realWithSymbolic); var fromUtil = new PathUtil().getAbsolutePath(current); var unixStyle = realWithSymbolic.toString().replace('\\', '/'); diff --git a/src/test/java/edu/hm/hafner/util/PrefixLoggerTest.java b/src/test/java/edu/hm/hafner/util/PrefixLoggerTest.java index f12ea5e0b..c7a1b37c7 100644 --- a/src/test/java/edu/hm/hafner/util/PrefixLoggerTest.java +++ b/src/test/java/edu/hm/hafner/util/PrefixLoggerTest.java @@ -1,13 +1,15 @@ package edu.hm.hafner.util; -import org.junit.jupiter.api.Test; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import java.io.PrintStream; import java.util.List; - -import static java.util.Arrays.*; -import static java.util.Collections.*; -import static org.mockito.Mockito.*; +import org.junit.jupiter.api.Test; /** * Tests the class {@link PrefixLogger}. diff --git a/src/test/java/edu/hm/hafner/util/ResourceExtractorTest.java b/src/test/java/edu/hm/hafner/util/ResourceExtractorTest.java index 38a5dab89..9ea3b8d90 100644 --- a/src/test/java/edu/hm/hafner/util/ResourceExtractorTest.java +++ b/src/test/java/edu/hm/hafner/util/ResourceExtractorTest.java @@ -1,5 +1,11 @@ package edu.hm.hafner.util; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import java.io.IOException; import java.io.UncheckedIOException; import java.net.URL; @@ -9,14 +15,10 @@ import java.security.CodeSource; import java.security.ProtectionDomain; import java.util.NoSuchElementException; - import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; - /** * Tests the class {@link ResourceExtractor}. * @@ -49,8 +51,7 @@ void shouldLocateResourcesInJarFile() { void shouldExtractFromFolder(@TempDir final Path targetFolder) { var proxy = new ResourceExtractor(ResourceExtractor.class); - proxy.extract(targetFolder, ASSERTJ_TEMPLATES, JENKINS_FILE, - "edu/hm/hafner/util/ResourceExtractor.class"); + proxy.extract(targetFolder, ASSERTJ_TEMPLATES, JENKINS_FILE, "edu/hm/hafner/util/ResourceExtractor.class"); assertThat(readToString(targetFolder.resolve(ASSERTJ_TEMPLATES))) .contains("has${Property}(${propertyType} ${property_safe})"); @@ -70,19 +71,18 @@ void shouldThrowExceptionIfTargetIsFileInFolder() throws IOException { void shouldThrowExceptionIfFileDoesNotExistInFolder(@TempDir final Path targetFolder) { var proxy = new ResourceExtractor(ResourceExtractor.class); - assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(() -> - proxy.extract(targetFolder, "does-not-exist")); + assertThatExceptionOfType(UncheckedIOException.class) + .isThrownBy(() -> proxy.extract(targetFolder, "does-not-exist")); } @Test void shouldExtractFromJar(@TempDir final Path targetFolder) { var proxy = new ResourceExtractor(StringUtils.class); - proxy.extract(targetFolder, MANIFEST_MF, - "org/apache/commons/lang3/StringUtils.class"); + proxy.extract(targetFolder, MANIFEST_MF, "org/apache/commons/lang3/StringUtils.class"); - assertThat(readToString(targetFolder.resolve(MANIFEST_MF))).contains("Manifest-Version: 1.0", - "Bundle-SymbolicName: org.apache.commons.lang3"); + assertThat(readToString(targetFolder.resolve(MANIFEST_MF))) + .contains("Manifest-Version: 1.0", "Bundle-SymbolicName: org.apache.commons.lang3"); } @Test @@ -97,8 +97,8 @@ void shouldThrowExceptionIfTargetIsFileInJar() throws IOException { void shouldThrowExceptionIfFileDoesNotExistInJar(@TempDir final Path targetFolder) { var proxy = new ResourceExtractor(StringUtils.class); - assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(() -> - proxy.extract(targetFolder, "does-not-exist")); + assertThatExceptionOfType(NoSuchElementException.class) + .isThrownBy(() -> proxy.extract(targetFolder, "does-not-exist")); } @Test @@ -131,8 +131,7 @@ void shouldHandleClassloaderProblems() { private String readToString(final Path output) { try { return new String(Files.readAllBytes(output), StandardCharsets.UTF_8); - } - catch (IOException exception) { + } catch (IOException exception) { throw new UncheckedIOException(exception); } } diff --git a/src/test/java/edu/hm/hafner/util/ResourceTest.java b/src/test/java/edu/hm/hafner/util/ResourceTest.java index da6647d6f..9c27501cd 100644 --- a/src/test/java/edu/hm/hafner/util/ResourceTest.java +++ b/src/test/java/edu/hm/hafner/util/ResourceTest.java @@ -1,11 +1,8 @@ package edu.hm.hafner.util; -import org.apache.commons.io.IOUtils; -import org.apache.commons.io.input.BOMInputStream; -import org.opentest4j.TestAbortedException; +import static org.assertj.core.api.Assumptions.assumeThat; import com.google.errorprone.annotations.MustBeClosed; - import java.io.BufferedReader; import java.io.File; import java.io.IOException; @@ -17,8 +14,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.stream.Stream; - -import static org.assertj.core.api.Assumptions.*; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.BOMInputStream; +import org.opentest4j.TestAbortedException; /** * Base class for tests that need to read resource files from disk. Provides several useful methods that simplify @@ -37,18 +35,15 @@ protected boolean isWindows() { } /** - * Creates an empty file in the default temporary-file directory, using - * the prefix and suffix "test" to generate its name. The resulting {@code - * Path} is associated with the default {@code FileSystem}. + * Creates an empty file in the default temporary-file directory, using the prefix and suffix "test" to generate its + * name. The resulting {@code Path} is associated with the default {@code FileSystem}. * - * @return the path to the newly created file that did not exist before - * this method was invoked + * @return the path to the newly created file that did not exist before this method was invoked */ protected Path createTempFile() { try { return Files.createTempFile("test", ".test"); - } - catch (IOException | IllegalArgumentException | UnsupportedOperationException | SecurityException exception) { + } catch (IOException | IllegalArgumentException | UnsupportedOperationException | SecurityException exception) { throw new AssertionError(exception); } } @@ -57,14 +52,10 @@ protected Path createTempFile() { * Reads all the bytes from a file. The method ensures that the file is closed when all bytes have been read or an * I/O error, or other runtime exception, is thrown. * - *

- * Note that this method is intended for simple cases where it is - * convenient to read all bytes into a byte array. It is not intended for reading in large files. - *

- * - * @param fileName - * name of the desired resource + *

Note that this method is intended for simple cases where it is convenient to read all bytes into a byte array. + * It is not intended for reading in large files. * + * @param fileName name of the desired resource * @return the content represented by a byte array */ protected byte[] readAllBytes(final String fileName) { @@ -74,8 +65,7 @@ protected byte[] readAllBytes(final String fileName) { ensureThatResourceExists(resource, fileName); return IOUtils.toByteArray(resource); - } - catch (IOException e) { + } catch (IOException e) { throw new AssertionError("Can't read resource " + fileName, e); } } @@ -84,21 +74,16 @@ protected byte[] readAllBytes(final String fileName) { * Reads all the bytes from a file. The method ensures that the file is closed when all bytes have been read or an * I/O error, or other runtime exception, is thrown. * - *

- * Note that this method is intended for simple cases where it is - * convenient to read all bytes into a byte array. It is not intended for reading in large files. - *

- * - * @param path - * path of the desired resource + *

Note that this method is intended for simple cases where it is convenient to read all bytes into a byte array. + * It is not intended for reading in large files. * + * @param path path of the desired resource * @return the content represented by a byte array */ protected byte[] readAllBytes(final Path path) { try { return Files.readAllBytes(path); - } - catch (IOException e) { + } catch (IOException e) { throw new AssertionError("Can't read resource " + path, e); } } @@ -113,14 +98,10 @@ private Path getPath(final String name) throws URISyntaxException { * Read all lines from the desired resource as a {@code Stream}, i.e. this method populates lazily as the stream is * consumed. * - *

- * Bytes from the resource are decoded into characters using UTF-8 and the same line terminators as specified by + *

Bytes from the resource are decoded into characters using UTF-8 and the same line terminators as specified by * {@link Files#readAllLines(Path, Charset)} are supported. - *

- * - * @param fileName - * name of the desired resource * + * @param fileName name of the desired resource * @return the content represented as a {@link Stream} of lines */ @MustBeClosed @@ -132,24 +113,18 @@ protected Stream asStream(final String fileName) { * Read all lines from the desired resource as a {@code Stream}, i.e. this method populates lazily as the stream is * consumed. * - *

- * Bytes from the resource are decoded into characters using the specified charset and the same line terminators as - * specified by {@link Files#readAllLines(Path, Charset)} are supported. - *

- * - * @param fileName - * name of the desired resource - * @param charset - * the charset to use for decoding + *

Bytes from the resource are decoded into characters using the specified charset and the same line terminators + * as specified by {@link Files#readAllLines(Path, Charset)} are supported. * + * @param fileName name of the desired resource + * @param charset the charset to use for decoding * @return the content represented as a {@link Stream} of lines */ @MustBeClosed protected Stream asStream(final String fileName, final Charset charset) { try { return Files.lines(getPath(fileName), charset); - } - catch (IOException | URISyntaxException e) { + } catch (IOException | URISyntaxException e) { throw new AssertionError("Can't read resource " + fileName, e); } } @@ -157,9 +132,7 @@ protected Stream asStream(final String fileName, final Charset charset) /** * Finds a resource with the given name and returns an input stream with UTF-8 decoding. * - * @param fileName - * name of the desired resource - * + * @param fileName name of the desired resource * @return the content represented as an {@link InputStream} */ protected InputStream asInputStream(final String fileName) { @@ -188,9 +161,7 @@ protected Class getTestResourceClass() { /** * Finds a resource with the given name and returns the content (decoded with UTF-8) as String. * - * @param fileName - * name of the desired resource - * + * @param fileName name of the desired resource * @return the content represented as {@link String} */ protected String toString(final String fileName) { @@ -200,9 +171,7 @@ protected String toString(final String fileName) { /** * Returns the content of the specified {@link Path} (decoded with UTF-8) as String. * - * @param file - * the desired file - * + * @param file the desired file * @return the content represented as {@link String} */ protected String toString(final Path file) { @@ -216,9 +185,7 @@ private String createString(final byte[] bytes) { /** * Read all lines from the specified text String as a {@code Stream}. * - * @param text - * the text to return as {@link Stream} of lines - * + * @param text the text to return as {@link Stream} of lines * @return the content represented by a byte array */ @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") @@ -227,11 +194,9 @@ protected Stream getTextLinesAsStream(final String text) { } /** - * Returns the {@link Path} of the specified resource. The file name must be relative to the test class. - * - * @param fileName - * the file to read (relative to this {@link ResourceTest} class) + * Returns the {@link Path} of the specified resource. The file name must be relative to the test class. * + * @param fileName the file to read (relative to this {@link ResourceTest} class) * @return an {@link BOMInputStream input stream} using character set UTF-8 * @see #getTestResourceClass() */ @@ -242,8 +207,7 @@ protected Path getResourceAsFile(final String fileName) { ensureThatResourceExists(resource, fileName); return Path.of(resource.toURI()); - } - catch (URISyntaxException e) { + } catch (URISyntaxException e) { throw new AssertionError("Can't open file " + fileName, e); } } diff --git a/src/test/java/edu/hm/hafner/util/SecureXmlParserFactoryTest.java b/src/test/java/edu/hm/hafner/util/SecureXmlParserFactoryTest.java index 12428b2f0..b335d6519 100644 --- a/src/test/java/edu/hm/hafner/util/SecureXmlParserFactoryTest.java +++ b/src/test/java/edu/hm/hafner/util/SecureXmlParserFactoryTest.java @@ -1,5 +1,18 @@ package edu.hm.hafner.util; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import edu.hm.hafner.util.SecureXmlParserFactory.ParsingException; import java.io.Reader; import java.io.StringReader; import java.nio.charset.StandardCharsets; @@ -10,16 +23,10 @@ import javax.xml.stream.XMLStreamException; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerFactory; - import org.junit.jupiter.api.Test; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; -import edu.hm.hafner.util.SecureXmlParserFactory.ParsingException; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; - /** * Tests the class {@link SecureXmlParserFactory}. * @@ -54,7 +61,8 @@ void shouldCreateDocumentBuilder() throws ParserConfigurationException { var brokenDocumentBuilderFactory = createBrokenDocumentBuilderFactory(); when(factory.createDocumentBuilderFactory()).thenReturn(brokenDocumentBuilderFactory); - assertThatIllegalArgumentException().isThrownBy(factory::createDocumentBuilder) + assertThatIllegalArgumentException() + .isThrownBy(factory::createDocumentBuilder) .withMessage("Can't create instance of DocumentBuilder"); } @@ -73,7 +81,8 @@ void shouldCreateSaxParser() throws ParserConfigurationException, SAXException { var brokenSaxParserFactory = createBrokenSaxParserFactory(); when(factory.createSaxParserFactory()).thenReturn(brokenSaxParserFactory); - assertThatIllegalArgumentException().isThrownBy(factory::createSaxParser) + assertThatIllegalArgumentException() + .isThrownBy(factory::createSaxParser) .withMessage("Can't create instance of SAXParser"); } @@ -106,10 +115,11 @@ void shouldParseEmptyDocument() throws SAXException { void shouldFailWithParsingExceptionIfParsingBrokenDocument() throws SAXException { var factory = new SecureXmlParserFactory(); - assertThatExceptionOfType(ParsingException.class).isThrownBy(() -> - factory.parse(createBrokenXmlReader(), StandardCharsets.UTF_8, mock(DefaultHandler.class))); - assertThatExceptionOfType(ParsingException.class).isThrownBy(() -> - factory.readDocument(createBrokenXmlReader(), StandardCharsets.UTF_8)); + assertThatExceptionOfType(ParsingException.class) + .isThrownBy(() -> + factory.parse(createBrokenXmlReader(), StandardCharsets.UTF_8, mock(DefaultHandler.class))); + assertThatExceptionOfType(ParsingException.class) + .isThrownBy(() -> factory.readDocument(createBrokenXmlReader(), StandardCharsets.UTF_8)); } @Test @@ -121,7 +131,8 @@ void shouldCreateXmlStreamReader() throws XMLStreamException { var brokenXmlInputFactory = createBrokenXmlInputFactory(); when(factory.createXmlInputFactory()).thenReturn(brokenXmlInputFactory); - assertThatIllegalArgumentException().isThrownBy(() -> factory.createXmlStreamReader(createEmptyXmlReader())) + assertThatIllegalArgumentException() + .isThrownBy(() -> factory.createXmlStreamReader(createEmptyXmlReader())) .withMessage("Can't create instance of XMLStreamReader"); } @@ -134,14 +145,17 @@ void shouldCreateXmlEventReader() throws XMLStreamException { var brokenXmlInputFactory = createBrokenXmlInputFactory(); when(factory.createXmlInputFactory()).thenReturn(brokenXmlInputFactory); - assertThatIllegalArgumentException().isThrownBy(() -> factory.createXmlEventReader(createEmptyXmlReader())) + assertThatIllegalArgumentException() + .isThrownBy(() -> factory.createXmlEventReader(createEmptyXmlReader())) .withMessage("Can't create instance of XMLEventReader"); } private XMLInputFactory createBrokenXmlInputFactory() throws XMLStreamException { var xmlInputFactory = mock(XMLInputFactory.class); - when(xmlInputFactory.createXMLStreamReader((Reader) any())).thenThrow(new XMLStreamException(EXPECTED_EXCEPTION)); - when(xmlInputFactory.createXMLEventReader((Reader) any())).thenThrow(new XMLStreamException(EXPECTED_EXCEPTION)); + when(xmlInputFactory.createXMLStreamReader((Reader) any())) + .thenThrow(new XMLStreamException(EXPECTED_EXCEPTION)); + when(xmlInputFactory.createXMLEventReader((Reader) any())) + .thenThrow(new XMLStreamException(EXPECTED_EXCEPTION)); return xmlInputFactory; } @@ -162,7 +176,8 @@ void shouldCreateTransformer() throws TransformerConfigurationException { var brokenTransformerFactory = createBrokenTransformerFactory(); when(factory.createTransformerFactory()).thenReturn(brokenTransformerFactory); - assertThatIllegalArgumentException().isThrownBy(factory::createTransformer) + assertThatIllegalArgumentException() + .isThrownBy(factory::createTransformer) .withMessage("Can't create instance of Transformer"); } diff --git a/src/test/java/edu/hm/hafner/util/SerializableTest.java b/src/test/java/edu/hm/hafner/util/SerializableTest.java index 7c824f17b..299335806 100644 --- a/src/test/java/edu/hm/hafner/util/SerializableTest.java +++ b/src/test/java/edu/hm/hafner/util/SerializableTest.java @@ -1,8 +1,6 @@ package edu.hm.hafner.util; -import org.assertj.core.api.ObjectAssert; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -13,16 +11,15 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; - -import static org.assertj.core.api.Assertions.*; +import org.assertj.core.api.ObjectAssert; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; /** * Base class to test the serialization of instances of {@link Serializable}. Note that the instances under test must * override equals so that the test case can check the serialized instances for equality. * - * @param - * concrete type of the {@link Serializable} under test - * + * @param concrete type of the {@link Serializable} under test * @author Ullrich Hafner */ public abstract class SerializableTest extends ResourceTest { @@ -49,8 +46,7 @@ void shouldBeSerializable() { * Resolves the subject under test from an array of bytes and compares the created instance with the original * subject under test. * - * @param serializedInstance - * the byte stream of the serializable + * @param serializedInstance the byte stream of the serializable */ protected void assertThatSerializableCanBeRestoredFrom(final byte... serializedInstance) { assertThatRestoredInstanceEqualsOriginalInstance(createSerializable(), restore(serializedInstance)); @@ -59,14 +55,12 @@ protected void assertThatSerializableCanBeRestoredFrom(final byte... serializedI /** * Asserts that the instance restored from the serialization is equal to the original instance before the * serialization. By default, the {@link ObjectAssert#usingRecursiveComparison() recursive comparison strategy} of - * AssertJ is used to compare these instances. If your subject under test overrides - * {@link Object#equals(Object) equals}, then you should override this method with {@code original.equals(restored)} - * so the customized equals method will be used. + * AssertJ is used to compare these instances. If your subject under test overrides {@link Object#equals(Object) + * equals}, then you should override this method with {@code original.equals(restored)} so the customized equals + * method will be used. * - * @param original - * the instance before the serialization - * @param restored - * the instance restored by the deserialization + * @param original the instance before the serialization + * @param restored the instance restored by the deserialization */ protected void assertThatRestoredInstanceEqualsOriginalInstance(final T original, final T restored) { assertThat(restored).usingRecursiveComparison().isEqualTo(original); @@ -75,9 +69,7 @@ protected void assertThatRestoredInstanceEqualsOriginalInstance(final T original /** * Deserializes the subject under test from an array of bytes. * - * @param serializedInstance - * the byte stream of the serializable - * + * @param serializedInstance the byte stream of the serializable * @return the deserialized instance */ @SuppressWarnings({"unchecked", "BanSerializableRead"}) @@ -85,8 +77,7 @@ protected T restore(final byte[] serializedInstance) { try (var inputStream = new ObjectInputStream(new ByteArrayInputStream(serializedInstance))) { var object = inputStream.readObject(); return (T) object; - } - catch (IOException | ClassNotFoundException e) { + } catch (IOException | ClassNotFoundException e) { throw new AssertionError("Can't resolve instance from byte array", e); } } @@ -97,17 +88,14 @@ protected T restore(final byte[] serializedInstance) { * supertypes are written. Objects referenced by this object are written transitively so that a complete equivalent * graph of objects can be reconstructed by an ObjectInputStream. * - * @param object - * the object to serialize - * + * @param object the object to serialize * @return the object serialization */ protected byte[] toByteArray(final Serializable object) { var out = new ByteArrayOutputStream(); try (var stream = new ObjectOutputStream(out)) { stream.writeObject(object); - } - catch (IOException exception) { + } catch (IOException exception) { throw new IllegalStateException("Can't serialize object " + object, exception); } return out.toByteArray(); @@ -116,11 +104,9 @@ protected byte[] toByteArray(final Serializable object) { /** * Serializes an issue using an {@link ObjectOutputStream } to the file /tmp/serializable.ser. * - * @throws IOException - * if the file could not be created + * @throws IOException if the file could not be created */ protected void createSerializationFile() throws IOException { - Files.write(Path.of("/tmp/serializable.ser"), toByteArray(createSerializable()), - StandardOpenOption.CREATE_NEW); + Files.write(Path.of("/tmp/serializable.ser"), toByteArray(createSerializable()), StandardOpenOption.CREATE_NEW); } } diff --git a/src/test/java/edu/hm/hafner/util/StringComparableTest.java b/src/test/java/edu/hm/hafner/util/StringComparableTest.java index b776e905c..c666dc915 100644 --- a/src/test/java/edu/hm/hafner/util/StringComparableTest.java +++ b/src/test/java/edu/hm/hafner/util/StringComparableTest.java @@ -1,8 +1,8 @@ package edu.hm.hafner.util; /** - * Example class that shows on how to verify that String instances comply with the contract in {@link - * Comparable#compareTo(Object)}. + * Example class that shows on how to verify that String instances comply with the contract in + * {@link Comparable#compareTo(Object)}. * * @author Ullrich Hafner */ diff --git a/src/test/java/edu/hm/hafner/util/StringEqualsTest.java b/src/test/java/edu/hm/hafner/util/StringEqualsTest.java index 5db9b4790..f091c69b8 100644 --- a/src/test/java/edu/hm/hafner/util/StringEqualsTest.java +++ b/src/test/java/edu/hm/hafner/util/StringEqualsTest.java @@ -1,8 +1,8 @@ package edu.hm.hafner.util; /** - * Example class that shows on how to verify that String instances comply with the contract in {@link - * Object#equals(Object)}. + * Example class that shows on how to verify that String instances comply with the contract in + * {@link Object#equals(Object)}. * * @author Ullrich Hafner */ diff --git a/src/test/java/edu/hm/hafner/util/TreeStringBuilderTest.java b/src/test/java/edu/hm/hafner/util/TreeStringBuilderTest.java index 3cbd4ccc8..26bfcb4ed 100644 --- a/src/test/java/edu/hm/hafner/util/TreeStringBuilderTest.java +++ b/src/test/java/edu/hm/hafner/util/TreeStringBuilderTest.java @@ -1,6 +1,7 @@ package edu.hm.hafner.util; -import org.junit.jupiter.api.Test; +import static edu.hm.hafner.util.assertions.Assertions.assertThat; +import static edu.hm.hafner.util.assertions.Assertions.assertThatThrownBy; import java.util.ArrayList; import java.util.List; @@ -8,8 +9,7 @@ import java.util.Random; import nl.jqno.equalsverifier.EqualsVerifier; import nl.jqno.equalsverifier.Warning; - -import static edu.hm.hafner.util.assertions.Assertions.*; +import org.junit.jupiter.api.Test; /** * Tests the class {@link TreeStringBuilder}. @@ -78,9 +78,7 @@ void shouldThrowAssertionErrorIfLabelIsEmpty() { assertThatThrownBy(() -> new TreeString(new TreeString(), "")).isInstanceOf(AssertionError.class); } - /** - * Pseudo random (but deterministic) test. - */ + /** Pseudo random (but deterministic) test. */ @Test void shouldCreateRandomTreeStrings() { String[] dictionary = {"aa", "b", "aba", "ba"};