diff --git a/.config/checkstyle/checkstyle.xml b/.config/checkstyle/checkstyle.xml index a0d7f17..463a629 100644 --- a/.config/checkstyle/checkstyle.xml +++ b/.config/checkstyle/checkstyle.xml @@ -52,6 +52,7 @@ + @@ -68,6 +69,16 @@ + + + + + + + + + + @@ -85,7 +96,7 @@ - + @@ -93,7 +104,6 @@ - @@ -118,11 +128,18 @@ - + + + + + + + + diff --git a/.config/pmd/java/ruleset.xml b/.config/pmd/java/ruleset.xml new file mode 100644 index 0000000..e96576b --- /dev/null +++ b/.config/pmd/java/ruleset.xml @@ -0,0 +1,1125 @@ + + + + + This ruleset checks the code for discouraged programming constructs. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Usually all cases where `StringBuilder` (or the outdated `StringBuffer`) is used are either due to confusing (legacy) logic or in situations where it may be easily replaced by a simpler string concatenation. + +Solution: +* Do not use `StringBuffer` because it's thread-safe and usually this is not needed +* If `StringBuilder` is only used in a simple method (like `toString`) and is effectively inlined: Use a simpler string concatenation (`"a" + x + "b"`). This will be [optimized by the Java compiler internally](https://docs.oracle.com/javase/specs/jls/se25/html/jls-15.html#jls-15.18.1). +* In all other cases: + * Check what is happening and if it makes ANY sense! If for example a CSV file is built here consider using a proper library instead! + * Abstract the Strings into a DTO, join them together using a collection (or `StringJoiner`) or use Java's Streaming API instead + + 3 + + + + + + + + + + + +Calling setters of `java.lang.System` usually indicates bad design and likely causes unexpected behavior. +For example, it may break when multiple Threads are working with the same value. +It may also overwrite user defined options or properties. + +Try to pass the value only to the place where it's really needed and use it there accordingly. + + 3 + + + + + + + + + + + +Using a `@PostConstruct` method is usually only done when field injection is used and initialization needs to be performed after that. + +It's better to do this directly in the constructor with constructor injection, so that all logic will be encapsulated there. +This also makes using the bean in environments where JavaEE is not present - for example in tests - a lot easier, as forgetting to call the `@PostConstruct` method is no longer possible. + + 3 + + + + + + + + + + + +`@PreDestroy` should be replaced by implementing `AutoCloseable` and overwriting the `close` method instead. + +This also makes using the bean in environments where JavaEE is not present - for example in tests - a lot easier, as forgetting to call the `@PreDestroy` method is no much more difficult. + + 3 + + + + + + + + + + + +Trying to manually manage threads usually gets quickly out of control and may result in various problems like uncontrollable spawning of threads. +Threads can also not be cancelled properly. + +Use managed Thread services like `ExecutorService` and `CompletableFuture` instead. + + 3 + + + + + + + + + + + +ZipEntry name should be sanitized. +Unsanitized names may contain '..' which can result in path traversal ("ZipSlip"). + +You can suppress this warning when you properly sanitized the name. + + 4 + + + + + + + + + + + +Nearly every known usage of (Java) Object Deserialization has resulted in [a security vulnerability](https://cloud.google.com/blog/topics/threat-intelligence/hunting-deserialization-exploits?hl=en). +Vulnerabilities are so common that there are [dedicated projects for exploit payload generation](https://github.com/frohoff/ysoserial). + +Java Object Serialization may also fail to deserialize properly when the underlying classes are changed. +This can result in unexpected crashes when outdated data is deserialized. + +Use proven data interchange formats like JSON instead. + + 2 + + + + + + + + + + + + + +Do not use native HTML! Use Vaadin layouts and components to create required structure. +If you are 100% sure that you escaped the value properly and you have no better options you can suppress this. + + 2 + + + + + + + + + + + + +`List` allows duplicates while a `Set` does not. +A `Set` also prevents duplicates when the ORM reads multiple identical rows from the database (e.g. when using JOIN). + + 2 + + + + + + + + + + + + + + + +java.text.NumberFormat: DecimalFormat and ChoiceFormat are thread-unsafe. + +Solution: Create a new local one when needed in a method. + + 1 + + + + + + + + + + + + + + +A regular expression is compiled implicitly on every invocation. +Problem: This can be (CPU) expensive, depending on the length of the regular expression. + +Solution: Compile the regex pattern only once and assign it to a private static final Pattern field. +java.util.Pattern objects are thread-safe, so they can be shared among threads. + + 2 + + + + 5 and +(matches(@Image, '[\.\$\|\(\)\[\]\{\}\^\?\*\+\\]+'))) +or +self::VariableAccess and @Name=ancestor::ClassBody[1]/FieldDeclaration/VariableDeclarator[StringLiteral[string-length(@Image) > 5 and +(matches(@Image, '[\.\$\|\(\)\[\]\{\}\^\?\*\+\\]+'))] or not(StringLiteral)]/VariableId/@Name] +]]> + + + + + + + + + + + +The default constructor of ByteArrayOutputStream creates a 32 bytes initial capacity and for StringWriter 16 chars. +Such a small buffer as capacity usually needs several expensive expansions. + +Solution: Explicitly declared the buffer size so that an expansion is not needed in most cases. +Typically much larger than 32, e.g. 4096. + + 2 + + + + + + + + + + + + + + +The time to find element is O(n); n = the number of enum values. +This identical processing is executed for every call. +Considered problematic when `n > 3`. + +Solution: Use a static field-to-enum-value Map. Access time is O(1), provided the hashCode is well-defined. +Implement a fromString method to provide the reverse conversion by using the map. + + 3 + + + + 3]//MethodDeclaration/Block + //MethodCall[pmd-java:matchesSig('java.util.stream.Stream#findFirst()') or pmd-java:matchesSig('java.util.stream.Stream#findAny()')] + [//MethodCall[pmd-java:matchesSig('java.util.stream.Stream#of(_)') or pmd-java:matchesSig('java.util.Arrays#stream(_)')] + [ArgumentList/MethodCall[pmd-java:matchesSig('_#values()')]]] +]]> + + + + + fromString(String name) { + return Stream.of(values()).filter(v -> v.toString().equals(name)).findAny(); // bad: iterates for every call, O(n) access time + } +} + +Usage: `Fruit f = Fruit.fromString("banana");` + +// GOOD +public enum Fruit { + APPLE("apple"), + ORANGE("orange"), + BANANA("banana"), + KIWI("kiwi"); + + private static final Map nameToValue = + Stream.of(values()).collect(toMap(Object::toString, v -> v)); + private final String name; + + Fruit(String name) { this.name = name; } + @Override public String toString() { return name; } + public static Optional fromString(String name) { + return Optional.ofNullable(nameToValue.get(name)); // good, get from Map, O(1) access time + } +} +]]> + + + + + +A regular expression is compiled on every invocation. +Problem: this can be expensive, depending on the length of the regular expression. + +Solution: Usually a pattern is a literal, not dynamic and can be compiled only once. Assign it to a private static field. +java.util.Pattern objects are thread-safe so they can be shared among threads. + + 2 + + + + + + + + + + + + + + + +Recreating a DateTimeFormatter is relatively expensive. + +Solution: Java 8+ java.time.DateTimeFormatter is thread-safe and can be shared among threads. +Create the formatter from a pattern only once, to initialize a static final field. + + 2 + + + + + + + + + + + +Creating a security provider is expensive because of loading of algorithms and other classes. +Additionally, it uses synchronized which leads to lock contention when used with multiple threads. + +Solution: This only needs to happen once in the JVM lifetime, because once loaded the provider is typically available from the Security class. +Create the security provider only once: Only in case when it's not yet available from the Security class. + + 2 + + + + + + + + + + + + + + +Reflection is relatively expensive. + +Solution: Avoid reflection. Use the non-reflective, explicit way like generation by IDE. + + 2 + + + + + + + + + + + + + + +java.util.SimpleDateFormat is thread-unsafe. +The usual solution is to create a new one when needed in a method. +Creating SimpleDateFormat is relatively expensive. + +Solution: Use java.time.DateTimeFormatter. These classes are immutable, thus thread-safe and can be made static. + + 2 + + + + + + + + + + + + + + +Blocking calls, for instance remote calls, may exhaust the common pool for some time thereby blocking all other use of the common pool. +In addition, nested use of the common pool can lead to deadlock. Do not use the common pool for blocking calls. +The parallelStream() call uses the common pool. + +Solution: Use a dedicated thread pool with enough threads to get proper parallelism. +The number of threads in the common pool is equal to the number of CPUs and meant to utilize all of them. +It assumes CPU-intensive non-blocking processing of in-memory data. + +See also: [_Be Aware of ForkJoinPool#commonPool()_](https://dzone.com/articles/be-aware-of-forkjoinpoolcommonpool) + + 2 + + + + + + + + + list = new ArrayList(); + final ForkJoinPool myFjPool = new ForkJoinPool(10); + final ExecutorService myExePool = Executors.newFixedThreadPool(10); + + void bad1() { + list.parallelStream().forEach(elem -> storeDataRemoteCall(elem)); // bad + } + + void good1() { + CompletableFuture[] futures = list.stream().map(elem -> CompletableFuture.supplyAsync(() -> storeDataRemoteCall(elem), myExePool)) + .toArray(CompletableFuture[]::new); + CompletableFuture.allOf(futures).get(10, TimeUnit.MILLISECONDS)); + } + + void good2() throws ExecutionException, InterruptedException { + myFjPool.submit(() -> + list.parallelStream().forEach(elem -> storeDataRemoteCall(elem)) + ).get(); + } + + String storeDataRemoteCall(String elem) { + // do remote call, blocking. We don't use the returned value. + RestTemplate tmpl; + return ""; + } +} +]]> + + + + + +CompletableFuture.supplyAsync/runAsync is typically used for remote calls. +By default it uses the common pool. +The number of threads in the common pool is equal to the number of CPU's, which is suitable for in-memory processing. +For I/O, however, this number is typically not suitable because most time is spent waiting for the response and not in CPU. +The common pool must not be used for blocking calls. + +Solution: A separate, properly sized pool of threads (an Executor) should be used for the async calls. + +See also: [_Be Aware of ForkJoinPool#commonPool()_](https://dzone.com/articles/be-aware-of-forkjoinpoolcommonpool) + + 2 + + + + + + + + +>[] futures = accounts.stream() + .map(account -> CompletableFuture.supplyAsync(() -> isAccountBlocked(account))) // bad + .toArray(CompletableFuture[]::new); + } + + void good() { + CompletableFuture>[] futures = accounts.stream() + .map(account -> CompletableFuture.supplyAsync(() -> isAccountBlocked(account), asyncPool)) // good + .toArray(CompletableFuture[]::new); + } +} +]]> + + + + + +`take()` stalls indefinitely in case of hanging threads and consumes a thread. + +Solution: use `poll()` with a timeout value and handle the timeout. + + 2 + + + + + + + + + void collectAllCollectionReplyFromThreads(CompletionService> completionService) { + try { + Future> futureLocal = completionService.take(); // bad + Future> futuresGood = completionService.poll(3, TimeUnit.SECONDS); // good + responseCollector.addAll(futuresGood.get(10, TimeUnit.SECONDS)); // good + } catch (InterruptedException | ExecutionException e) { + LOGGER.error("Error in Thread : {}", e); + } catch (TimeoutException e) { + LOGGER.error("Timeout in Thread : {}", e); + } +} +]]> + + + + + +Stalls indefinitely in case of stalled Callable(s) and consumes threads. + +Solution: Provide a timeout to the invokeAll/invokeAny method and handle the timeout. + + 2 + + + + + + + + +> executeTasksBad(Collection> tasks, ExecutorService executor) throws Exception { + return executor.invokeAll(tasks); // bad, no timeout + } + private List> executeTasksGood(Collection> tasks, ExecutorService executor) throws Exception { + return executor.invokeAll(tasks, OUR_TIMEOUT_IN_MILLIS, TimeUnit.MILLISECONDS); // good + } +} +]]> + + + + + +Stalls indefinitely in case of hanging threads and consumes a thread. + +Solution: Provide a timeout value and handle the timeout. + + 2 + + + + + + + + + complFuture) throws Exception { + return complFuture.get(); // bad +} + +public static String good(CompletableFuture complFuture) throws Exception { + return complFuture.get(10, TimeUnit.SECONDS); // good +} +]]> + + + + + + +Apache HttpClient with its connection pool and timeouts should be setup once and then used for many requests. +It is quite expensive to create and can only provide the benefits of pooling when reused in all requests for that connection. + +Solution: Create/build HttpClient with proper connection pooling and timeouts once, and then use it for requests. + + 3 + + + + + + + + + connectBad(Object req) { + HttpEntity requestEntity = new HttpEntity<>(req); + + HttpClient httpClient = HttpClientBuilder.create().setMaxConnPerRoute(10).build(); // bad + return remoteCall(httpClient, requestEntity); + } +} +]]> + + + + + +Problem: Gson creation is relatively expensive. A JMH benchmark shows a 24x improvement reusing one instance. + +Solution: Since Gson objects are thread-safe after creation, they can be shared between threads. +So reuse created instances from a static field. +Pay attention to use thread-safe (custom) adapters and serializers. + + 3 + + + + + + + + + + + + diff --git a/.gitattributes b/.gitattributes index 0f9f33e..8ac8027 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,3 +3,7 @@ # Force sh files to have LF *.sh text eol=lf + +# Force MVN Wrapper Linux files LF +mvnw text eol=lf +maven-wrapper.properties text eol=lf diff --git a/.github/.lycheeignore b/.github/.lycheeignore index 972ca61..dc88a07 100644 --- a/.github/.lycheeignore +++ b/.github/.lycheeignore @@ -1,2 +1,3 @@ # Ignorefile for broken link check localhost +mvnrepository.com diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 1df4f97..3e09a9e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,6 +1,7 @@ name: 🐞 Bug description: Create a bug report for something that is broken labels: [bug] +type: bug body: - type: markdown attributes: @@ -32,6 +33,15 @@ body: validations: required: true + - type: textarea + id: description + attributes: + label: Description of the problem + description: | + Describe as exactly as possible what is not working. + validations: + required: true + - type: textarea id: steps-to-reproduce attributes: @@ -46,20 +56,6 @@ body: validations: required: true - - type: textarea - id: expected-behavior - attributes: - label: Expected behavior - description: | - Tell us what you expect to happen. - - - type: textarea - id: actual-behavior - attributes: - label: Actual behavior - description: | - Tell us what happens with the steps given above. - - type: textarea id: additional-information attributes: diff --git a/.github/ISSUE_TEMPLATE/enhancement.yml b/.github/ISSUE_TEMPLATE/enhancement.yml index 6b13535..cd1d38c 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.yml +++ b/.github/ISSUE_TEMPLATE/enhancement.yml @@ -1,6 +1,7 @@ name: ✨ Feature/Enhancement description: Suggest a new feature or enhancement labels: [enhancement] +type: feature body: - type: markdown attributes: diff --git a/.github/workflows/broken-links.yml b/.github/workflows/broken-links.yml index 96af798..2675c8b 100644 --- a/.github/workflows/broken-links.yml +++ b/.github/workflows/broken-links.yml @@ -11,31 +11,34 @@ permissions: jobs: link-checker: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - run: mv .github/.lycheeignore .lycheeignore - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@v1 + uses: lycheeverse/lychee-action@a8c4c7cb88f0c7386610c35eb25108e448569cb0 # v2 + with: + fail: false # Don't fail on broken links, create an issue instead - name: Find already existing issue id: find-issue run: | - echo "number=$(gh issue list -l 'bug' -l 'automated' -L 1 -S 'in:title \"Link Checker Report\"' -s 'open' --json 'number' --jq '.[].number')" >> $GITHUB_OUTPUT + echo "number=$(gh issue list -l 'bug' -l 'automated' -L 1 -S 'in:title "Link Checker Report"' -s 'open' --json 'number' --jq '.[].number')" >> $GITHUB_OUTPUT env: GH_TOKEN: ${{ github.token }} - + - name: Close issue if everything is fine - if: env.lychee_exit_code == 0 && steps.find-issue.outputs.number != '' + if: steps.lychee.outputs.exit_code == 0 && steps.find-issue.outputs.number != '' run: gh issue close -r 'not planned' ${{ steps.find-issue.outputs.number }} env: GH_TOKEN: ${{ github.token }} - name: Create Issue From File - if: env.lychee_exit_code != 0 - uses: peter-evans/create-issue-from-file@v5 + if: steps.lychee.outputs.exit_code != 0 + uses: peter-evans/create-issue-from-file@fca9117c27cdc29c6c4db3b86c48e4115a786710 # v6 with: issue-number: ${{ steps.find-issue.outputs.number }} title: Link Checker Report diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml new file mode 100644 index 0000000..401a845 --- /dev/null +++ b/.github/workflows/check-build.yml @@ -0,0 +1,160 @@ +name: Check Build + +on: + workflow_dispatch: + push: + branches: [ develop ] + paths-ignore: + - '**.md' + - '.config/**' + - '.github/**' + - '.idea/**' + - 'assets/**' + pull_request: + branches: [ develop ] + paths-ignore: + - '**.md' + - '.config/**' + - '.github/**' + - '.idea/**' + - 'assets/**' + +env: + PRIMARY_MAVEN_MODULE: ${{ github.event.repository.name }} + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + matrix: + java: [17, 21, 25] + distribution: [temurin] + steps: + - uses: actions/checkout@v6 + + - name: Set up JDK + uses: actions/setup-java@v5 + with: + distribution: ${{ matrix.distribution }} + java-version: ${{ matrix.java }} + + - name: Cache Maven + uses: actions/cache@v5 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-mvn-build-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-mvn-build- + + - name: Build with Maven + run: ./mvnw -B clean package + + - name: Check for uncommited changes + run: | + if [[ "$(git status --porcelain)" != "" ]]; then + echo ---------------------------------------- + echo git status + echo ---------------------------------------- + git status + echo ---------------------------------------- + echo git diff + echo ---------------------------------------- + git diff + echo ---------------------------------------- + echo Troubleshooting + echo ---------------------------------------- + echo "::error::Unstaged changes detected. Locally try running: git clean -ffdx && ./mvnw -B clean package" + exit 1 + fi + + - name: Upload files + uses: actions/upload-artifact@v6 + with: + name: files-java-${{ matrix.java }} + path: ${{ env.PRIMARY_MAVEN_MODULE }}/target/${{ env.PRIMARY_MAVEN_MODULE }}-*.jar + if-no-files-found: error + + checkstyle: + runs-on: ubuntu-latest + if: ${{ github.event_name != 'pull_request' || !startsWith(github.head_ref, 'renovate/') }} + timeout-minutes: 15 + strategy: + matrix: + java: [21] + distribution: [temurin] + steps: + - uses: actions/checkout@v6 + + - name: Set up JDK + uses: actions/setup-java@v5 + with: + distribution: ${{ matrix.distribution }} + java-version: ${{ matrix.java }} + + - name: Cache Maven + uses: actions/cache@v5 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-mvn-checkstyle-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-mvn-checkstyle- + + - name: CheckStyle Cache + uses: actions/cache@v5 + with: + path: '**/target/checkstyle-cachefile' + key: ${{ runner.os }}-checkstyle-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-checkstyle- + + - name: Run Checkstyle + run: ./mvnw -B checkstyle:check -P checkstyle -T2C + + pmd: + runs-on: ubuntu-latest + if: ${{ github.event_name != 'pull_request' || !startsWith(github.head_ref, 'renovate/') }} + timeout-minutes: 15 + strategy: + matrix: + java: [17] + distribution: [temurin] + steps: + - uses: actions/checkout@v6 + + - name: Set up JDK + uses: actions/setup-java@v5 + with: + distribution: ${{ matrix.distribution }} + java-version: ${{ matrix.java }} + + - name: Cache Maven + uses: actions/cache@v5 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-mvn-pmd-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-mvn-pmd- + + - name: PMD Cache + uses: actions/cache@v5 + with: + path: '**/target/pmd/pmd.cache' + key: ${{ runner.os }}-pmd-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-pmd- + + - name: Run PMD + run: ./mvnw -B test pmd:aggregate-pmd-no-fork pmd:check -P pmd -DskipTests -T2C + + - name: Run CPD (Copy Paste Detector) + run: ./mvnw -B pmd:aggregate-cpd pmd:cpd-check -P pmd -DskipTests -T2C + + - name: Upload report + if: always() + uses: actions/upload-artifact@v6 + with: + name: pmd-report + if-no-files-found: ignore + path: | + target/reports/** diff --git a/.github/workflows/checkBuild.yml b/.github/workflows/checkBuild.yml deleted file mode 100644 index 9ca290c..0000000 --- a/.github/workflows/checkBuild.yml +++ /dev/null @@ -1,92 +0,0 @@ -name: Check Build - -on: - workflow_dispatch: - push: - branches: [ develop ] - paths-ignore: - - '**.md' - - '.config/**' - - '.github/**' - - '.idea/**' - - 'assets/**' - pull_request: - branches: [ develop ] - paths-ignore: - - '**.md' - - '.config/**' - - '.github/**' - - '.idea/**' - - 'assets/**' - -env: - PRIMARY_MAVEN_MODULE: ${{ github.event.repository.name }} - -jobs: - build: - runs-on: ubuntu-latest - - strategy: - matrix: - java: [17, 21] - distribution: [temurin] - - steps: - - uses: actions/checkout@v4 - - - name: Set up JDK - uses: actions/setup-java@v4 - with: - distribution: ${{ matrix.distribution }} - java-version: ${{ matrix.java }} - cache: 'maven' - - - name: Build with Maven - run: ./mvnw -B clean package - - - name: Check for uncommited changes - run: | - if [[ "$(git status --porcelain)" != "" ]]; then - echo ---------------------------------------- - echo git status - echo ---------------------------------------- - git status - echo ---------------------------------------- - echo git diff - echo ---------------------------------------- - git diff - echo ---------------------------------------- - echo Troubleshooting - echo ---------------------------------------- - echo "::error::Unstaged changes detected. Locally try running: git clean -ffdx && ./mvnw -B clean package" - exit 1 - fi - - - name: Upload files - uses: actions/upload-artifact@v4 - with: - name: files-java-${{ matrix.java }} - path: ${{ env.PRIMARY_MAVEN_MODULE }}/target/${{ env.PRIMARY_MAVEN_MODULE }}-*.jar - if-no-files-found: error - - code-style: - runs-on: ubuntu-latest - if: ${{ github.event_name != 'pull_request' || !startsWith(github.head_ref, 'renovate/') }} - - strategy: - matrix: - java: [17] - distribution: [temurin] - - steps: - - uses: actions/checkout@v4 - - - name: Set up JDK - uses: actions/setup-java@v4 - with: - distribution: ${{ matrix.distribution }} - java-version: ${{ matrix.java }} - cache: 'maven' - - - name: Run Checkstyle - run: ./mvnw -B checkstyle:check -P checkstyle -T2C diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a348722..ed1e999 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,21 +11,32 @@ permissions: contents: write pull-requests: write +# DO NOT RESTORE CACHE for critical release steps to prevent a (extremely unlikely) scenario +# where a supply chain attack could be achieved due to poisoned cache jobs: - check_code: # Validates the code + check-code: runs-on: ubuntu-latest + timeout-minutes: 30 steps: - - uses: actions/checkout@v4 - + - uses: actions/checkout@v6 + - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' - cache: 'maven' - + + # Try to reuse existing cache from check-build + - name: Try restore Maven Cache + uses: actions/cache/restore@v5 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-mvn-build-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-mvn-build- + - name: Build with Maven - run: ./mvnw -B clean package + run: ./mvnw -B clean package -T2C - name: Check for uncommited changes run: | @@ -45,30 +56,23 @@ jobs: exit 1 fi - prepare_release: + prepare-release: runs-on: ubuntu-latest - needs: [check_code] + needs: [check-code] + timeout-minutes: 10 outputs: - upload_url: ${{ steps.create_release.outputs.upload_url }} + upload_url: ${{ steps.create-release.outputs.upload_url }} steps: - - uses: actions/checkout@v4 - + - uses: actions/checkout@v6 + - name: Configure Git run: | git config --global user.email "actions@github.com" git config --global user.name "GitHub Actions" - + - name: Un-SNAP - run: | - mvnwPath=$(readlink -f ./mvnw) - modules=("") # root - modules+=($(grep -ozP '(?<=module>)[^<]+' 'pom.xml' | tr -d '\0')) - for i in "${modules[@]}" - do - echo "Processing $i/pom.xml" - (cd "$i" && $mvnwPath -B versions:set -DremoveSnapshot -DgenerateBackupPoms=false) - done - + run: ./mvnw -B versions:set -DremoveSnapshot -DprocessAllModules -DgenerateBackupPoms=false + - name: Get version id: version run: | @@ -76,7 +80,7 @@ jobs: echo "release=$version" >> $GITHUB_OUTPUT echo "releasenumber=${version//[!0-9]/}" >> $GITHUB_OUTPUT working-directory: ${{ env.PRIMARY_MAVEN_MODULE }} - + - name: Commit and Push run: | git add -A @@ -84,41 +88,42 @@ jobs: git push origin git tag v${{ steps.version.outputs.release }} git push origin --tags - + - name: Create Release - id: create_release - uses: shogo82148/actions-create-release@v1 + id: create-release + uses: shogo82148/actions-create-release@559c27ce7eb834825e2b55927c64f6d1bd1db716 # v1 with: tag_name: v${{ steps.version.outputs.release }} release_name: v${{ steps.version.outputs.release }} commitish: master body: | - ## [Changelog](https://github.com/xdev-software/${{ env.PRIMARY_MAVEN_MODULE }}/blob/develop/CHANGELOG.md#${{ steps.version.outputs.releasenumber }}) - See [Changelog#v${{ steps.version.outputs.release }}](https://github.com/xdev-software/${{ env.PRIMARY_MAVEN_MODULE }}/blob/develop/CHANGELOG.md#${{ steps.version.outputs.releasenumber }}) for more information. + ## [Changelog](https://github.com/${{ github.repository }}/blob/develop/CHANGELOG.md#${{ steps.version.outputs.releasenumber }}) + See [Changelog#v${{ steps.version.outputs.release }}](https://github.com/${{ github.repository }}/blob/develop/CHANGELOG.md#${{ steps.version.outputs.releasenumber }}) for more information. ## Installation Download the javaagent from the assets section below and [attach it to your java application](https://github.com/xdev-software/thread-origin-agent#usage). - publish_assets: + publish-assets: runs-on: ubuntu-latest - needs: [prepare_release] + needs: [prepare-release] + timeout-minutes: 60 steps: - - uses: actions/checkout@v4 - + - uses: actions/checkout@v6 + - name: Init Git and pull run: | git config --global user.email "actions@github.com" git config --global user.name "GitHub Actions" git pull - + - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' - name: Build - run: ../mvnw -B clean package + run: ../mvnw -B clean package -DskipTests working-directory: ${{ env.PRIMARY_MAVEN_MODULE }} - name: Upload Assets @@ -129,10 +134,11 @@ jobs: publish-pages: runs-on: ubuntu-latest - needs: [prepare_release] + needs: [prepare-release] + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 - + - uses: actions/checkout@v6 + - name: Init Git and pull run: | git config --global user.email "actions@github.com" @@ -140,28 +146,38 @@ jobs: git pull - name: Setup - Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' - cache: 'maven' + + # Try to reuse existing cache from check-build + - name: Try restore Maven Cache + uses: actions/cache/restore@v5 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-mvn-build-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-mvn-build- - name: Build site - run: ../mvnw -B site + run: ../mvnw -B compile site -DskipTests -T2C working-directory: ${{ env.PRIMARY_MAVEN_MODULE }} - name: Deploy to Github pages - uses: peaceiris/actions-gh-pages@v4 + uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./${{ env.PRIMARY_MAVEN_MODULE }}/target/site + force_orphan: true - after_release: + after-release: runs-on: ubuntu-latest - needs: [publish_assets] + needs: [publish-assets] + timeout-minutes: 10 steps: - - uses: actions/checkout@v4 - + - uses: actions/checkout@v6 + - name: Init Git and pull run: | git config --global user.email "actions@github.com" @@ -169,22 +185,14 @@ jobs: git pull - name: Inc Version and SNAP - run: | - mvnwPath=$(readlink -f ./mvnw) - modules=("") # root - modules+=($(grep -ozP '(?<=module>)[^<]+' 'pom.xml' | tr -d '\0')) - for i in "${modules[@]}" - do - echo "Processing $i/pom.xml" - (cd "$i" && $mvnwPath -B build-helper:parse-version versions:set -DnewVersion=\${parsedVersion.majorVersion}.\${parsedVersion.minorVersion}.\${parsedVersion.nextIncrementalVersion} -DgenerateBackupPoms=false -DnextSnapshot=true) - done + run: ./mvnw -B versions:set -DnextSnapshot -DprocessAllModules -DgenerateBackupPoms=false - name: Git Commit and Push run: | git add -A git commit -m "Preparing for next development iteration" git push origin - + - name: pull-request env: GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index ff880f0..6471ce7 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -14,11 +14,12 @@ permissions: jobs: labels: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: sparse-checkout: .github/labels.yml - - uses: EndBug/label-sync@v2 + - uses: EndBug/label-sync@52074158190acb45f3077f9099fea818aa43f97a # v2 with: config-file: .github/labels.yml diff --git a/.github/workflows/update-from-template.yml b/.github/workflows/update-from-template.yml index 41a1bcd..f447710 100644 --- a/.github/workflows/update-from-template.yml +++ b/.github/workflows/update-from-template.yml @@ -1,9 +1,10 @@ name: Update from Template # This workflow keeps the repo up to date with changes from the template repo (REMOTE_URL) -# It duplicates the REMOTE_BRANCH (into UPDATE_BRANCH) and tries to merge it into the +# It duplicates the REMOTE_BRANCH (into UPDATE_BRANCH) and tries to merge it into # this repos default branch (which is checked out here) # Note that this requires a PAT (Personal Access Token) - at best from a servicing account +# PAT permissions: read:discussion, read:org, repo, workflow # Also note that you should have at least once merged the template repo into the current repo manually # otherwise a "refusing to merge unrelated histories" error might occur. @@ -11,10 +12,16 @@ on: schedule: - cron: '55 2 * * 1' workflow_dispatch: + inputs: + no_automatic_merge: + type: boolean + description: 'No automatic merge' + default: false env: UPDATE_BRANCH: update-from-template - REMOTE_URL: https://github.com/xdev-software/java-template.git + UPDATE_BRANCH_MERGED: update-from-template-merged + REMOTE_URL: https://github.com/xdev-software/standard-maven-template.git REMOTE_BRANCH: master permissions: @@ -24,23 +31,26 @@ permissions: jobs: update: runs-on: ubuntu-latest - + timeout-minutes: 60 + outputs: + update_branch_merged_commit: ${{ steps.manage-branches.outputs.update_branch_merged_commit }} + create_update_branch_merged_pr: ${{ steps.manage-branches.outputs.create_update_branch_merged_pr }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: # Required because otherwise there are always changes detected when executing diff/rev-list fetch-depth: 0 # If no PAT is used the following error occurs on a push: # refusing to allow a GitHub App to create or update workflow `.github/workflows/xxx.yml` without `workflows` permission token: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }} - + - name: Init Git run: | - git config --global user.email "actions@github.com" - git config --global user.name "GitHub Actions" + git config --global user.email "111048771+xdev-gh-bot@users.noreply.github.com" + git config --global user.name "XDEV Bot" - - name: Main workflow - id: main + - name: Manage branches + id: manage-branches run: | echo "Adding remote template-repo" git remote add template ${{ env.REMOTE_URL }} @@ -48,8 +58,9 @@ jobs: echo "Fetching remote template repo" git fetch template - echo "Deleting local branch that will contain the updates - if present" + echo "Deleting local branches that will contain the updates - if present" git branch -D ${{ env.UPDATE_BRANCH }} || true + git branch -D ${{ env.UPDATE_BRANCH_MERGED }} || true echo "Checking if the remote template repo has new commits" git rev-list ..template/${{ env.REMOTE_BRANCH }} @@ -57,10 +68,12 @@ jobs: if [ $(git rev-list --count ..template/${{ env.REMOTE_BRANCH }}) -eq 0 ]; then echo "There are no commits new commits on the template repo" - echo "Deleting origin branch that contains the updates - if present" + echo "Deleting origin branch(es) that contain the updates - if present" git push -f origin --delete ${{ env.UPDATE_BRANCH }} || true + git push -f origin --delete ${{ env.UPDATE_BRANCH_MERGED }} || true - echo "abort=1" >> $GITHUB_OUTPUT + echo "create_update_branch_pr=0" >> $GITHUB_OUTPUT + echo "create_update_branch_merged_pr=0" >> $GITHUB_OUTPUT exit 0 fi @@ -73,21 +86,235 @@ jobs: echo "Pushing update branch" git push -f -u origin ${{ env.UPDATE_BRANCH }} - echo "Getting current branch" - current_branch=$(git branch --show-current) - echo "Current branch is $current_branch" - echo "current_branch=$current_branch" >> $GITHUB_OUTPUT + echo "Getting base branch" + base_branch=$(git branch --show-current) + echo "Base branch is $base_branch" + echo "base_branch=$base_branch" >> $GITHUB_OUTPUT - echo "abort=0" >> $GITHUB_OUTPUT + echo "Trying to create auto-merged branch ${{ env.UPDATE_BRANCH_MERGED }}" + git branch ${{ env.UPDATE_BRANCH_MERGED }} ${{ env.UPDATE_BRANCH }} + git checkout ${{ env.UPDATE_BRANCH_MERGED }} - - name: pull-request - if: steps.main.outputs.abort == 0 + echo "Merging branch $base_branch into ${{ env.UPDATE_BRANCH_MERGED }}" + git merge $base_branch && merge_exit_code=$? || merge_exit_code=$? + if [ $merge_exit_code -ne 0 ]; then + echo "Auto merge failed! Manual merge required" + echo "::notice ::Auto merge failed - Manual merge required" + + echo "Cleaning up failed merge" + git merge --abort + git checkout $base_branch + git branch -D ${{ env.UPDATE_BRANCH_MERGED }} || true + + echo "Deleting auto-merge branch - if present" + git push -f origin --delete ${{ env.UPDATE_BRANCH_MERGED }} || true + + echo "create_update_branch_pr=1" >> $GITHUB_OUTPUT + echo "create_update_branch_merged_pr=0" >> $GITHUB_OUTPUT + exit 0 + fi + + echo "Post processing: Trying to automatically fill in template variables" + find . -type f \ + -not -path "./.git/**" \ + -not -path "./.github/workflows/update-from-template.yml" -print0 \ + | xargs -0 sed -i "s/template-placeholder/${GITHUB_REPOSITORY#*/}/g" + + git status + git add --all + + if [[ "$(git status --porcelain)" != "" ]]; then + echo "Filled in template; Committing" + + git commit -m "Fill in template" + fi + + echo "Pushing auto-merged branch" + git push -f -u origin ${{ env.UPDATE_BRANCH_MERGED }} + + echo "update_branch_merged_commit=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + + echo "Restoring base branch $base_branch" + git checkout $base_branch + + echo "create_update_branch_pr=0" >> $GITHUB_OUTPUT + echo "create_update_branch_merged_pr=1" >> $GITHUB_OUTPUT + echo "try_close_update_branch_pr=1" >> $GITHUB_OUTPUT + + - name: Create/Update PR update_branch + if: steps.manage-branches.outputs.create_update_branch_pr == 1 env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }} run: | gh_pr_up() { gh pr create -H "${{ env.UPDATE_BRANCH }}" "$@" || (git checkout "${{ env.UPDATE_BRANCH }}" && gh pr edit "$@") } - gh_pr_up -B "${{ steps.main.outputs.current_branch }}" \ + gh_pr_up -B "${{ steps.manage-branches.outputs.base_branch }}" \ --title "Update from template" \ --body "An automated PR to sync changes from the template into this repo" + + # Ensure that only a single PR is open (otherwise confusion and spam) + - name: Close PR update_branch + if: steps.manage-branches.outputs.try_close_update_branch_pr == 1 + env: + GH_TOKEN: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }} + run: | + gh pr close "${{ env.UPDATE_BRANCH }}" || true + + - name: Create/Update PR update_branch_merged + if: steps.manage-branches.outputs.create_update_branch_merged_pr == 1 + env: + GH_TOKEN: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }} + run: | + gh_pr_up() { + gh pr create -H "${{ env.UPDATE_BRANCH_MERGED }}" "$@" || (git checkout "${{ env.UPDATE_BRANCH_MERGED }}" && gh pr edit "$@") + } + gh_pr_up -B "${{ steps.manage-branches.outputs.base_branch }}" \ + --title "Update from template (auto-merged)" \ + --body "An automated PR to sync changes from the template into this repo" + + # Wait a moment so that checks of PR have higher prio than following job + sleep 3 + + # Split into two jobs to help with executor starvation + auto-merge: + needs: [update] + if: needs.update.outputs.create_update_branch_merged_pr == 1 + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v6 + with: + # Required because otherwise there are always changes detected when executing diff/rev-list + fetch-depth: 0 + # If no PAT is used the following error occurs on a push: + # refusing to allow a GitHub App to create or update workflow `.github/workflows/xxx.yml` without `workflows` permission + token: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }} + + - name: Init Git + run: | + git config --global user.email "111048771+xdev-gh-bot@users.noreply.github.com" + git config --global user.name "XDEV Bot" + + - name: Checking if auto-merge for PR update_branch_merged can be done + id: auto-merge-check + env: + GH_TOKEN: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }} + run: | + not_failed_conclusion="skipped|neutral|success" + not_relevant_app_slug="dependabot|github-pages|sonarqubecloud" + + echo "Waiting for checks to start..." + sleep 40s + + for i in {1..20}; do + echo "Checking if PR can be auto-merged. Try: $i" + + echo "Checking if update-branch-merged exists" + git fetch + if [[ $(git ls-remote --heads origin refs/heads/${{ env.UPDATE_BRANCH_MERGED }}) ]]; then + echo "Branch still exists; Continuing..." + else + echo "Branch origin/${{ env.UPDATE_BRANCH_MERGED }} is missing" + exit 0 + fi + + echo "Fetching checks" + cs_response=$(curl -sL \ + --fail-with-body \ + --connect-timeout 60 \ + --max-time 120 \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/${{ github.repository }}/commits/${{ needs.update.outputs.update_branch_merged_commit }}/check-suites) + + cs_data=$(echo $cs_response | jq '.check_suites[] | { conclusion: .conclusion, slug: .app.slug, check_runs_url: .check_runs_url }') + echo $cs_data + + if [[ -z "$cs_data" ]]; then + echo "No check suite data - Assuming that there are no checks to run" + + echo "perform=1" >> $GITHUB_OUTPUT + exit 0 + fi + + cs_failed=$(echo $cs_data | jq --arg x "$not_failed_conclusion" 'select ((.conclusion == null or (.conclusion | test($x))) | not)') + if [[ -z "$cs_failed" ]]; then + echo "No check failed so far; Checking if relevant checks are still running" + + cs_relevant_still_running=$(echo $cs_data | jq --arg x "$not_relevant_app_slug" 'select (.conclusion == null and (.slug | test($x) | not))') + if [[ -z $cs_relevant_still_running ]]; then + echo "All relevant checks finished - PR can be merged" + + echo "perform=1" >> $GITHUB_OUTPUT + exit 0 + else + echo "Relevant checks are still running" + echo $cs_relevant_still_running + fi + else + echo "Detected failed check" + echo $cs_failed + + echo "perform=0" >> $GITHUB_OUTPUT + exit 0 + fi + + echo "Waiting before next run..." + sleep 30s + done + + echo "Timed out - Assuming executor starvation - Forcing merge" + echo "perform=1" >> $GITHUB_OUTPUT + + - name: Auto-merge update_branch_merged + if: steps.auto-merge-check.outputs.perform == 1 + run: | + echo "Getting base branch" + base_branch=$(git branch --show-current) + echo "Base branch is $base_branch" + + echo "Fetching..." + git fetch + if [[ $(git rev-parse origin/${{ env.UPDATE_BRANCH_MERGED }}) ]]; then + echo "Branch still exists; Continuing..." + else + echo "Branch origin/${{ env.UPDATE_BRANCH_MERGED }} is missing" + exit 0 + fi + + expected_commit="${{ needs.update.outputs.update_branch_merged_commit }}" + actual_commit=$(git rev-parse origin/${{ env.UPDATE_BRANCH_MERGED }}) + if [[ "$expected_commit" != "$actual_commit" ]]; then + echo "Branch ${{ env.UPDATE_BRANCH_MERGED }} contains unexpected commit $actual_commit" + echo "Expected: $expected_commit" + + exit 0 + fi + + echo "Ensuring that current branch $base_branch is up-to-date" + git pull + + echo "Merging origin/${{ env.UPDATE_BRANCH_MERGED }} into $base_branch" + git merge origin/${{ env.UPDATE_BRANCH_MERGED }} && merge_exit_code=$? || merge_exit_code=$? + if [ $merge_exit_code -ne 0 ]; then + echo "Unexpected merge failure $merge_exit_code - Requires manual resolution" + + exit 0 + fi + + if [[ "${{ inputs.no_automatic_merge }}" == "true" ]]; then + echo "Exiting due no_automatic_merge" + + exit 0 + fi + + echo "Pushing" + git push + + echo "Cleaning up" + git branch -D ${{ env.UPDATE_BRANCH }} || true + git branch -D ${{ env.UPDATE_BRANCH_MERGED }} || true + git push -f origin --delete ${{ env.UPDATE_BRANCH }} || true + git push -f origin --delete ${{ env.UPDATE_BRANCH_MERGED }} || true diff --git a/.gitignore b/.gitignore index d0e81bf..eb4294a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,12 @@ # Maven target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties dependency-reduced-pom.xml -buildNumber.properties -.mvn/timing.properties -# https://github.com/takari/maven-wrapper#usage-without-binary-jar + +# Maven Wrapper .mvn/wrapper/maven-wrapper.jar +# Maven Flatten Plugin +.flattened-pom.xml # Compiled class file *.class @@ -18,20 +14,12 @@ buildNumber.properties # Log file *.log -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - # Package/Binary Files don't belong into a git repo *.jar *.war -*.nar *.ear *.zip *.tar.gz -*.rar *.dll *.exe *.bin @@ -39,32 +27,11 @@ buildNumber.properties # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* - -# bin / compiled stuff -target/ - - -# JRebel -**/resources/rebel.xml -**/resources/rebel-remote.xml - -# eclispe stuff for root -/.settings/ -/.classpath -/.project - - -# eclispe stuff for modules -/*/.metadata/ -/*/.apt_generated_tests/ -/*/.settings/ -/*/.classpath -/*/.project -/*/RemoteSystemsTempFiles/ - -#custom -.flattened-pom.xml -.tern-project +# Eclipse +.metadata +.settings +.classpath +.project # == IntelliJ == *.iml @@ -76,6 +43,9 @@ target/ .idea/* !.idea/saveactions_settings.xml !.idea/checkstyle-idea.xml +!.idea/externalDependencies.xml +!.idea/pmd-x.xml +!.idea/PMDPlugin.xml !.idea/inspectionProfiles/ .idea/inspectionProfiles/* diff --git a/.idea/PMDPlugin.xml b/.idea/PMDPlugin.xml new file mode 100644 index 0000000..0936e51 --- /dev/null +++ b/.idea/PMDPlugin.xml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml index eb3fcd8..a751c41 100644 --- a/.idea/checkstyle-idea.xml +++ b/.idea/checkstyle-idea.xml @@ -1,7 +1,7 @@ - 10.15.0 + 13.0.0 JavaOnlyWithTests true true diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 19681fa..21e0aff 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -96,4 +96,4 @@ - + \ No newline at end of file diff --git a/.idea/externalDependencies.xml b/.idea/externalDependencies.xml new file mode 100644 index 0000000..78be5b8 --- /dev/null +++ b/.idea/externalDependencies.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/pmd-x.xml b/.idea/pmd-x.xml new file mode 100644 index 0000000..260e454 --- /dev/null +++ b/.idea/pmd-x.xml @@ -0,0 +1,27 @@ + + + + false + true + true + SUPPORTED_ONLY_WITH_TESTS + + + + \ No newline at end of file diff --git a/.idea/saveactions_settings.xml b/.idea/saveactions_settings.xml index 71a42c4..12a4f04 100644 --- a/.idea/saveactions_settings.xml +++ b/.idea/saveactions_settings.xml @@ -4,11 +4,12 @@ diff --git a/renovate.json5 b/renovate.json5 index 11a77b2..e626363 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -1,4 +1,24 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "rebaseWhen": "behind-base-branch" + "rebaseWhen": "behind-base-branch", + "packageRules": [ + { + "description": "Ignore project internal dependencies", + "packagePattern": "^software.xdev:thread-origin-agent", + "datasources": [ + "maven" + ], + "enabled": false + }, + { + "description": "Group net.sourceforge.pmd", + "matchPackagePatterns": [ + "^net.sourceforge.pmd" + ], + "datasources": [ + "maven" + ], + "groupName": "net.sourceforge.pmd" + } + ] } diff --git a/thread-origin-agent/pom.xml b/thread-origin-agent/pom.xml index 23a1a2a..bbcf172 100644 --- a/thread-origin-agent/pom.xml +++ b/thread-origin-agent/pom.xml @@ -35,7 +35,7 @@ - Apache License, Version 2.0 + Apache-2.0 https://www.apache.org/licenses/LICENSE-2.0.txt repo @@ -49,30 +49,6 @@ UTF-8 - - - - central - https://repo.maven.apache.org/maven2 - - false - - - - - - - - central - https://repo.maven.apache.org/maven2 - - false - - - - org.javassist @@ -87,12 +63,12 @@ org.apache.maven.plugins maven-site-plugin - 4.0.0-M14 + 4.0.0-M16 org.apache.maven.plugins maven-project-info-reports-plugin - 3.5.0 + 3.9.0 @@ -100,7 +76,7 @@ com.mycila license-maven-plugin - 4.5 + 5.0.0 ${project.organization.url} @@ -129,7 +105,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.13.0 + 3.14.1 ${maven.compiler.release} @@ -141,7 +117,7 @@ org.apache.maven.plugins maven-assembly-plugin - 3.7.1 + 3.8.0 jar-with-dependencies @@ -171,16 +147,17 @@ org.apache.maven.plugins maven-checkstyle-plugin - 3.3.1 + 3.6.0 com.puppycrawl.tools checkstyle - 10.16.0 + 13.0.0 ../.config/checkstyle/checkstyle.xml + true @@ -193,5 +170,47 @@ + + pmd + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.28.0 + + true + true + true + + ../.config/pmd/java/ruleset.xml + + + + + net.sourceforge.pmd + pmd-core + 7.20.0 + + + net.sourceforge.pmd + pmd-java + 7.20.0 + + + + + + + + + + org.apache.maven.plugins + maven-jxr-plugin + 3.6.0 + + + + diff --git a/thread-origin-agent/src/main/java/software/xdev/tools/threadoriginagent/ThreadOriginTransformer.java b/thread-origin-agent/src/main/java/software/xdev/tools/threadoriginagent/ThreadOriginTransformer.java index b513400..aeaf55c 100644 --- a/thread-origin-agent/src/main/java/software/xdev/tools/threadoriginagent/ThreadOriginTransformer.java +++ b/thread-origin-agent/src/main/java/software/xdev/tools/threadoriginagent/ThreadOriginTransformer.java @@ -130,6 +130,7 @@ public static void premain(final String agentArgument, final Instrumentation ins log("Retransformed loaded classes; " + success + "x successful, " + unmodifiable + "x unmodifiable"); } + @SuppressWarnings("PMD.CognitiveComplexity") @Override public byte[] transform( final ClassLoader loader, @@ -239,7 +240,7 @@ public void edit(final MethodCall m) throws CannotCompileException void replaceThread(final MethodCall m, final String methodName, final CtClass declaringClass) throws CannotCompileException { - if(methodName.equals("start")) + if("start".equals(methodName)) { m.replace("{ " + "System.out.println(\"[TOA] Detected " @@ -251,7 +252,7 @@ void replaceThread(final MethodCall m, final String methodName, final CtClass de + PROCEED + "} "); } - else if(LOG_THREAD_JOINS && methodName.equals("join")) + else if(LOG_THREAD_JOINS && "join".equals(methodName)) { m.replace("{ " + "System.out.println(\"[TOA] Detected " @@ -264,7 +265,7 @@ else if(LOG_THREAD_JOINS && methodName.equals("join")) } } - @SuppressWarnings("java:S106") + @SuppressWarnings({"java:S106", "PMD.SystemPrintln"}) private static void log(final String message) { System.out.println("[TOA] " + message);