diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index cd03c2b..73efb29 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -29,18 +29,18 @@ jobs:
with:
persist-credentials: false
- name: Setup Java
- uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
+ uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
with:
distribution: temurin
java-version: 21
- name: Initialize CodeQL
- uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
+ uses: github/codeql-action/init@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
with:
languages: ${{ matrix.language }}
queries: security-and-quality
- name: Build for CodeQL
run: mvn -B verify --file pom.xml
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
+ uses: github/codeql-action/analyze@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
with:
category: "/language:${{ matrix.language }}"
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 70ef064..eb8c139 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -19,7 +19,7 @@ jobs:
with:
persist-credentials: false
- name: Set up JDK
- uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
+ uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
with:
distribution: temurin
java-version: 21
@@ -49,7 +49,7 @@ jobs:
with:
persist-credentials: false
- name: Set up JDK
- uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
+ uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
with:
distribution: temurin
java-version: 21
@@ -60,6 +60,9 @@ jobs:
verify:
name: Maven Verify (JDK ${{ matrix.java }})
runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ id-token: write
strategy:
fail-fast: false
matrix:
@@ -71,7 +74,7 @@ jobs:
with:
persist-credentials: false
- name: Set up JDK
- uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
+ uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
with:
distribution: temurin
java-version: ${{ matrix.java }}
@@ -99,5 +102,6 @@ jobs:
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
files: target/site/jacoco/jacoco.xml
- fail_ci_if_error: false
+ use_oidc: true
+ fail_ci_if_error: true
verbose: true
diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml
index a5e2ade..9ddc3a9 100644
--- a/.github/workflows/quality.yml
+++ b/.github/workflows/quality.yml
@@ -19,7 +19,7 @@ jobs:
with:
persist-credentials: false
- name: Set up JDK
- uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
+ uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
with:
distribution: temurin
java-version: 21
@@ -44,3 +44,27 @@ jobs:
name: spotbugs-report
path: target/spotbugsXml.xml
if-no-files-found: ignore
+
+ sonarcloud:
+ name: SonarCloud analysis
+ if: github.actor != 'dependabot[bot]'
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+ - name: Set up JDK
+ uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
+ with:
+ distribution: temurin
+ java-version: 21
+ cache: maven
+ - name: Build and analyze
+ env:
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+ run: >-
+ mvn -B verify sonar:sonar
+ -Dsonar.qualitygate.wait=true
+ -Dsonar.qualitygate.timeout=300
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
index 3e7222c..cbb1823 100644
--- a/.github/workflows/scorecard.yml
+++ b/.github/workflows/scorecard.yml
@@ -9,8 +9,6 @@ on:
- cron: "23 3 * * 1"
workflow_dispatch:
-permissions: read-all
-
jobs:
scorecard:
name: OSSF Scorecards
@@ -36,7 +34,7 @@ jobs:
results_format: sarif
publish_results: true
- name: Upload Scorecard SARIF
- uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
+ uses: github/codeql-action/upload-sarif@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
with:
sarif_file: scorecard-results.sarif
- name: Upload Scorecard artifact
diff --git a/README.en.md b/README.en.md
index 046316b..1f24f83 100644
--- a/README.en.md
+++ b/README.en.md
@@ -7,6 +7,7 @@ Curated Java reference implementations for deterministic algorithms and stateful
[](https://github.com/krotname/JavaAlgorithmsShowcase/actions/workflows/maven.yml)
[](https://github.com/krotname/JavaAlgorithmsShowcase/actions/workflows/quality.yml)
[](https://github.com/krotname/JavaAlgorithmsShowcase/actions/workflows/codeql-analysis.yml)
+[](https://sonarcloud.io/summary/new_code?id=krotname_JavaAlgorithmsShowcase)
[](https://app.codecov.io/gh/krotname/JavaAlgorithmsShowcase)
[](https://securityscorecards.dev/viewer/?uri=github.com/krotname/JavaAlgorithmsShowcase)
[](https://www.bestpractices.dev/projects/13151)
@@ -21,8 +22,8 @@ Curated Java reference implementations for deterministic algorithms and stateful
| Component | Engineering problem | What it demonstrates |
| --- | --- | --- |
-| [Transaction validation](src/main/java/transactions) | Ordered, stateful operations; cascading invalidation; balance and overflow boundaries | Deterministic state transitions, immutable results, integration scenarios |
-| [Algorithm utilities](src/main/java/algorithms) | Boundary conditions, complexity, stream and file I/O | Deterministic APIs, explicit complexity tradeoffs, edge cases, and property-based invariants |
+| [Transaction validation](src/main/java/transactions) | Ordered, stateful operations; cascading invalidation; balance and overflow boundaries | Deterministic state transitions, immutable results, integration and property-based tests |
+| [Algorithm utilities](src/main/java/algorithms) | Boundary conditions, complexity, stream and file I/O | Deterministic APIs, explicit complexity tradeoffs, and edge cases |
| [Parsing / validation](src/main/java/common/SafeParse.java) | Malformed numeric and text input | Explicit contracts, preserved exception causality, unit tests, and static analysis |
This is an engineering demonstration repository, not a commercial product. Implementations are selected to make code quality, test strategy, and the development process directly reviewable.
diff --git a/README.md b/README.md
index 7528efe..dd7dcd7 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,7 @@ Curated Java reference implementations for deterministic algorithms and stateful
[](https://github.com/krotname/JavaAlgorithmsShowcase/actions/workflows/maven.yml)
[](https://github.com/krotname/JavaAlgorithmsShowcase/actions/workflows/quality.yml)
[](https://github.com/krotname/JavaAlgorithmsShowcase/actions/workflows/codeql-analysis.yml)
+[](https://sonarcloud.io/summary/new_code?id=krotname_JavaAlgorithmsShowcase)
[](https://app.codecov.io/gh/krotname/JavaAlgorithmsShowcase)
[](https://securityscorecards.dev/viewer/?uri=github.com/krotname/JavaAlgorithmsShowcase)
[](https://www.bestpractices.dev/projects/13151)
@@ -21,8 +22,8 @@ Curated Java reference implementations for deterministic algorithms and stateful
| Компонент | Инженерная задача | Что демонстрирует |
| --- | --- | --- |
-| [Transaction validation](src/main/java/transactions) | Порядок и состояние операций, каскадная невалидность, баланс и переполнение | Детерминированные переходы состояния, неизменяемый результат, интеграционные сценарии |
-| [Algorithm utilities](src/main/java/algorithms) | Граничные условия, сложность, поточный и файловый ввод-вывод | Детерминированные API, явные компромиссы по сложности, edge cases и property-based инварианты |
+| [Transaction validation](src/main/java/transactions) | Порядок и состояние операций, каскадная невалидность, баланс и переполнение | Детерминированные переходы состояния, неизменяемый результат, интеграционные и property-based тесты |
+| [Algorithm utilities](src/main/java/algorithms) | Граничные условия, сложность, поточный и файловый ввод-вывод | Детерминированные API, явные компромиссы по сложности и edge cases |
| [Parsing / validation](src/main/java/common/SafeParse.java) | Некорректный числовой и текстовый ввод | Явные контракты, сохранение причины исключения, unit-тесты и статический анализ |
Это демонстрационный инженерный репозиторий, а не коммерческий продукт. Реализации отобраны так, чтобы на ревью были видны качество кода, тестовая стратегия и воспроизводимый процесс разработки.
diff --git a/pom.xml b/pom.xml
index f409930..3ef4590 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,13 +20,18 @@
0.8.15
3.6.0
3.28.0
- 4.9.8.5
+ 4.10.3.0
+ 5.7.0.6970
UTF-8
- 6.1.1
+ 6.1.2
3.27.7
1.10.1
- 6.1.1
+ 6.1.2
1.18.46
+ https://sonarcloud.io
+ krotname
+ krotname_JavaAlgorithmsShowcase
+ ${project.build.directory}/site/jacoco/jacoco.xml
0.70
0.70
0.70
@@ -233,6 +238,12 @@
+
+
+ org.sonarsource.scanner.maven
+ sonar-maven-plugin
+ ${sonar.maven.version}
+
diff --git a/src/main/java/interview/CallbackScheduler.java b/src/main/java/interview/CallbackScheduler.java
index 5c8bd36..ad94f31 100644
--- a/src/main/java/interview/CallbackScheduler.java
+++ b/src/main/java/interview/CallbackScheduler.java
@@ -14,7 +14,7 @@
* Класс CallbackSchedulerImpl планирует исполнение Runnable в заданный момент времени.
* Задачи делегируются ScheduledExecutorService, чтобы не держать отдельный ручной worker-loop.
*/
-public class CallbackScheduler implements AutoCloseable {
+public final class CallbackScheduler implements AutoCloseable {
private final ScheduledExecutorService scheduler;
diff --git a/src/main/java/kyu6/TribonacciSequence.java b/src/main/java/kyu6/TribonacciSequence.java
index 90d1868..a8749d1 100644
--- a/src/main/java/kyu6/TribonacciSequence.java
+++ b/src/main/java/kyu6/TribonacciSequence.java
@@ -4,19 +4,21 @@
public class TribonacciSequence {
+ private static final int SIGNATURE_SIZE = 3;
+
//6 https://www.codewars.com/kata/556deca17c58da83c00002db
public static double[] tribonacci(double[] s, int n) {
- if (s == null || s.length != 3) {
+ if (s == null || s.length != SIGNATURE_SIZE) {
throw new IllegalArgumentException("signature must contain exactly three values");
}
if (n < 0) {
throw new IllegalArgumentException("sequence length must not be negative");
}
double[] result = new double[n];
- int signatureLength = Math.min(s.length, n);
+ int signatureLength = Math.min(SIGNATURE_SIZE, n);
System.arraycopy(s, 0, result, 0, signatureLength);
- for (int i = s.length; i < n; i++) {
+ for (int i = SIGNATURE_SIZE; i < n; i++) {
result[i] = result[i - 1] + result[i - 2] + result[i - 3];
}
return result;
diff --git a/src/main/java/transactions/ValidationResult.java b/src/main/java/transactions/ValidationResult.java
index 95019a1..79c44df 100644
--- a/src/main/java/transactions/ValidationResult.java
+++ b/src/main/java/transactions/ValidationResult.java
@@ -1,18 +1,19 @@
package transactions;
-import lombok.Getter;
-
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
-@Getter
-final class ValidationResult {
- private final List transactions;
-
- public ValidationResult(List transactions) {
- this.transactions = List.copyOf(transactions);
+record ValidationResult(List transactions) {
+ ValidationResult {
+ transactions = List.copyOf(transactions);
}
public static ValidationResult empty() {
return new ValidationResult(List.of());
}
+
+ public List getTransactions() {
+ return Collections.unmodifiableList(new ArrayList<>(transactions));
+ }
}
diff --git a/src/test/java/quality/PropertySuite.java b/src/test/java/quality/PropertySuite.java
index 5540edf..67c9b15 100644
--- a/src/test/java/quality/PropertySuite.java
+++ b/src/test/java/quality/PropertySuite.java
@@ -11,8 +11,8 @@
* to additional property-oriented classes when needed.
*/
@Suite
-@SelectPackages({"kyu6", "kyu7"})
-@IncludeClassNamePatterns(".*(toCamelCaseTest|SumOddNumbersPropertyTest|ValidatePinPropertyTest)")
+@SelectPackages({"kyu6", "kyu7", "transactions"})
+@IncludeClassNamePatterns(".*(toCamelCaseTest|SumOddNumbersPropertyTest|ValidatePinPropertyTest|InMemoryValidationServicePropertyTest)")
@IncludeTags("property")
public final class PropertySuite {
private PropertySuite() {
diff --git a/src/test/java/transactions/InMemoryValidationServicePropertyTest.java b/src/test/java/transactions/InMemoryValidationServicePropertyTest.java
new file mode 100644
index 0000000..d90d805
--- /dev/null
+++ b/src/test/java/transactions/InMemoryValidationServicePropertyTest.java
@@ -0,0 +1,104 @@
+package transactions;
+
+import net.jqwik.api.Arbitraries;
+import net.jqwik.api.Arbitrary;
+import net.jqwik.api.Combinators;
+import net.jqwik.api.ForAll;
+import net.jqwik.api.Property;
+import net.jqwik.api.Provide;
+import net.jqwik.api.Tag;
+import net.jqwik.api.constraints.LongRange;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@Tag("property")
+class InMemoryValidationServicePropertyTest {
+ private final ValidationService service = ValidationService.getInstance();
+
+ @Property(tries = 250)
+ void validationIsIndependentOfInputOrder(
+ @ForAll("transactionBatches") List transactions,
+ @ForAll @LongRange(min = 0, max = 1_000_000) long balance) {
+ List reversed = new ArrayList<>(transactions);
+ Collections.reverse(reversed);
+
+ List directResult = service.validate(transactions, balance).getTransactions();
+ List reversedResult = service.validate(reversed, balance).getTransactions();
+
+ assertEquals(directResult, reversedResult);
+ assertEquals(transactions.size(), directResult.size());
+ }
+
+ @Property(tries = 250)
+ void everyOrderHasOneConsistentStatus(
+ @ForAll("transactionBatches") List transactions,
+ @ForAll @LongRange(min = 0, max = 1_000_000) long balance) {
+ List statuses = service.validate(transactions, balance).getTransactions();
+ Map statusByOrder = new HashMap<>();
+
+ for (int index = 0; index < statuses.size(); index++) {
+ TransactionStatus current = statuses.get(index);
+ if (index > 0) {
+ assertTrue(statuses.get(index - 1).transaction().id() < current.transaction().id());
+ }
+ Boolean existing = statusByOrder.putIfAbsent(current.transaction().orderId(), current.status());
+ if (existing != null) {
+ assertEquals(existing, current.status());
+ }
+ }
+ }
+
+ @Property(tries = 150)
+ void nonOverflowingWinsAreAlwaysAccepted(
+ @ForAll("winOnlyBatches") List transactions,
+ @ForAll @LongRange(min = 0, max = 1_000_000) long balance) {
+ List statuses = service.validate(transactions, balance).getTransactions();
+
+ assertEquals(transactions.size(), statuses.size());
+ assertTrue(statuses.stream().allMatch(TransactionStatus::status));
+ }
+
+ @Provide
+ Arbitrary> transactionBatches() {
+ Arbitrary seed = Combinators.combine(
+ Arbitraries.longs().between(0, 10),
+ Arbitraries.longs().between(0, 10_000),
+ Arbitraries.of(TransactionType.values())
+ ).as(TransactionSeed::new);
+
+ return seed.list().ofMaxSize(30)
+ .map(InMemoryValidationServicePropertyTest::toTransactions);
+ }
+
+ @Provide
+ Arbitrary> winOnlyBatches() {
+ return Arbitraries.longs().between(0, 10_000)
+ .list().ofMaxSize(30)
+ .map(amounts -> {
+ List transactions = new ArrayList<>(amounts.size());
+ for (int index = 0; index < amounts.size(); index++) {
+ transactions.add(new Transaction(index + 1L, index % 7L, amounts.get(index), TransactionType.WIN));
+ }
+ return transactions;
+ });
+ }
+
+ private static List toTransactions(List seeds) {
+ List transactions = new ArrayList<>(seeds.size());
+ for (int index = 0; index < seeds.size(); index++) {
+ TransactionSeed seed = seeds.get(index);
+ transactions.add(new Transaction(index + 1L, seed.orderId(), seed.amount(), seed.type()));
+ }
+ return transactions;
+ }
+
+ private record TransactionSeed(long orderId, long amount, TransactionType type) {
+ }
+}