diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8764b82..ee85d40a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,9 +4,11 @@ on: push: branches: - main + - develop pull_request: - branches: - - main + # Run CI for any incoming PR regardless of the base branch so + # feature branches targeting `develop` (e.g. v1.7 prep) also + # exercise the full pipeline. workflow_dispatch: schedule: - cron: '0 5 * * 1' @@ -26,11 +28,11 @@ jobs: - name: Check out repository uses: actions/checkout@v4 - - name: Set up Temurin JDK 21 + - name: Set up Temurin JDK 17 uses: actions/setup-java@v4 with: distribution: temurin - java-version: '21' + java-version: '17' cache: maven - name: Run guards @@ -40,10 +42,20 @@ jobs: test build-and-test: - name: Build and run tests + name: Build and run tests (JDK ${{ matrix.java }}) if: github.event_name != 'schedule' needs: architecture-and-documentation-guards runs-on: ubuntu-latest + strategy: + # Don't cancel sibling matrix jobs on the first failure — a + # regression on one JDK is informative even if other JDKs are + # still running. + fail-fast: false + matrix: + # 17 = baseline, 21 = previous baseline still supported, + # 25 = current LTS, validates the byte-buddy / Mockito 5.23 + # bumps that #10 motivated. + java: ['17', '21', '25'] env: JAVA_TOOL_OPTIONS: -Djava.awt.headless=true @@ -51,17 +63,21 @@ jobs: - name: Check out repository uses: actions/checkout@v4 - - name: Set up Temurin JDK 21 + - name: Set up Temurin JDK ${{ matrix.java }} uses: actions/setup-java@v4 with: distribution: temurin - java-version: '21' + java-version: ${{ matrix.java }} cache: maven - name: Build and run tests run: ./mvnw -B -ntp clean verify - name: Generate Javadoc + # Run only on the baseline JDK — Javadoc output is identical + # across JVMs and one pass is enough to catch broken @link + # references. + if: matrix.java == '17' run: ./mvnw -B -ntp javadoc:javadoc examples-generation: @@ -76,11 +92,11 @@ jobs: - name: Check out repository uses: actions/checkout@v4 - - name: Set up Temurin JDK 21 + - name: Set up Temurin JDK 17 uses: actions/setup-java@v4 with: distribution: temurin - java-version: '21' + java-version: '17' cache: maven - name: Install root artifact @@ -114,11 +130,11 @@ jobs: - name: Check out repository uses: actions/checkout@v4 - - name: Set up Temurin JDK 21 + - name: Set up Temurin JDK 17 uses: actions/setup-java@v4 with: distribution: temurin - java-version: '21' + java-version: '17' cache: maven - name: Install root artifact @@ -153,11 +169,11 @@ jobs: - name: Check out repository uses: actions/checkout@v4 - - name: Set up Temurin JDK 21 + - name: Set up Temurin JDK 17 uses: actions/setup-java@v4 with: distribution: temurin - java-version: '21' + java-version: '17' cache: maven - name: Restore benchmark history cache diff --git a/CHANGELOG.md b/CHANGELOG.md index 05a6f7ea..a2b0d10d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,45 @@ All notable changes to GraphCompose are documented here. Versions follow semantic versioning; release dates are ISO 8601. +## v1.6.1 — Planned + +Maintenance + compatibility patch. Drops the Java 21 source/target +baseline to **Java 17+** so the library can ship into older +enterprise stacks without a fork, and refreshes test/build +dependencies. **No public API change** — engine, DSL, themes, +templates, and backend records all stay source-compatible with +v1.6.0; existing v1.6.0 callers compile and behave unchanged. + +Co-developed with external contributor +[@jottinger](https://github.com/jottinger) +([#8](https://github.com/DemchaAV/GraphCompose/issues/8), +[#10](https://github.com/DemchaAV/GraphCompose/issues/10)). + +### Toolchain + +- **Java 17 baseline.** `` flips from `21` + to `17` across `pom.xml`, `examples/pom.xml`, and `benchmarks/pom.xml`. + Engine source loses the Java 21–only constructs + (switch-with-type-patterns, switch-with-deconstruction, + `List.getFirst()`, `Thread.threadId()`) in favour of Java 17 + –compatible forms. CI runs against Temurin JDK 17. +- **Dependency refresh + CVE pass.** Bumps Jackson `2.20.1 → 2.21.3`, + Logback `1.5.18 → 1.5.32`, Lombok `1.18.38 → 1.18.46`, POI + `5.4.0 → 5.5.1`, SnakeYAML `2.4 → 2.6`, AssertJ `3.27.3 → 3.27.6`, + JUnit `5.12.2 → 5.14.4`, Mockito `5.20.0 → 5.23.0`. Adds explicit + ByteBuddy `1.18.7` so Mockito works on the Java 25+ access rules. + Maven plugin bumps: `maven-compiler-plugin 3.13 → 3.15`, + `maven-surefire-plugin 3.2.5 → 3.5.5`, `exec-maven-plugin 3.5 → 3.6.2`. + +### Looking ahead + +Maven Central distribution +([#7](https://github.com/DemchaAV/GraphCompose/issues/7)) remains +on the **v1.7.0** roadmap alongside the JMH benchmark migration; +v1.6.1 stays on JitPack as a maintenance release. + +--- + ## v1.6.0 — 2026-05-07 The "expressive" release. Closes the remaining canonical-vs-legacy diff --git a/README.md b/README.md index 4b5bb7d5..3de46b4c 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ CI Latest release JitPack - Java 21 + Java 17+ PDFBox 3.0 MIT License

diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index ecfa446b..5ab64df7 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -17,11 +17,11 @@ 1.6.0 - 21 + 17 - 5.12.2 - 3.27.3 - 1.5.18 + 5.14.4 + 3.27.6 + 1.5.32 1.0.10 5.5.13.3 @@ -116,7 +116,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.13.0 + 3.15.0 ${maven.compiler.release} @@ -124,12 +124,12 @@ org.apache.maven.plugins maven-surefire-plugin - 3.2.5 + 3.5.5 org.codehaus.mojo exec-maven-plugin - 3.5.0 + 3.6.2 diff --git a/benchmarks/src/main/java/com/demcha/compose/BenchmarkMedianTool.java b/benchmarks/src/main/java/com/demcha/compose/BenchmarkMedianTool.java index 63fda6a9..5eb78664 100644 --- a/benchmarks/src/main/java/com/demcha/compose/BenchmarkMedianTool.java +++ b/benchmarks/src/main/java/com/demcha/compose/BenchmarkMedianTool.java @@ -42,7 +42,7 @@ private void run(Input input) throws Exception { reportFiles.add(new ReportFile(reportPath, JSON.readTree(Files.readAllBytes(reportPath)))); } - SuiteType suiteType = detectSuiteType(reportFiles.getFirst().report()); + SuiteType suiteType = detectSuiteType(reportFiles.get(0).report()); for (ReportFile reportFile : reportFiles) { SuiteType currentType = detectSuiteType(reportFile.report()); if (currentType != suiteType) { @@ -58,7 +58,7 @@ private void run(Input input) throws Exception { } private void aggregateCurrentSpeed(List reportFiles) throws Exception { - JsonNode firstReport = reportFiles.getFirst().report(); + JsonNode firstReport = reportFiles.get(0).report(); String profile = firstReport.path("profile").asText("full"); for (ReportFile reportFile : reportFiles) { String currentProfile = reportFile.report().path("profile").asText("full"); @@ -135,7 +135,7 @@ private void aggregateCurrentSpeed(List reportFiles) throws Exceptio } private List aggregateCurrentSpeedLatency(List reportFiles) { - List firstRows = iterable(reportFiles.getFirst().report().path("latency")); + List firstRows = iterable(reportFiles.get(0).report().path("latency")); Map firstByScenario = indexBy(firstRows, "scenario"); for (ReportFile reportFile : reportFiles) { @@ -150,7 +150,7 @@ private List aggregateCurrentSpeedLatency(List rows = reportFiles.stream() .map(reportFile -> indexBy(iterable(reportFile.report().path("latency")), "scenario").get(scenario)) .toList(); - JsonNode exemplar = rows.getFirst(); + JsonNode exemplar = rows.get(0); return new CurrentSpeedLatencyMedianRow( scenario, exemplar.path("description").asText(""), @@ -166,7 +166,7 @@ private List aggregateCurrentSpeedLatency(List aggregateCurrentSpeedThroughput(List reportFiles) { - List firstRows = iterable(reportFiles.getFirst().report().path("throughput")); + List firstRows = iterable(reportFiles.get(0).report().path("throughput")); Map firstByScenario = indexThroughput(firstRows); for (ReportFile reportFile : reportFiles) { @@ -181,7 +181,7 @@ private List aggregateCurrentSpeedThroughput(Li List rows = reportFiles.stream() .map(reportFile -> indexThroughput(iterable(reportFile.report().path("throughput"))).get(key)) .toList(); - JsonNode exemplar = rows.getFirst(); + JsonNode exemplar = rows.get(0); return new CurrentSpeedThroughputMedianRow( exemplar.path("scenario").asText(), exemplar.path("threads").asInt(), @@ -196,7 +196,7 @@ private void aggregateComparative(List reportFiles) throws Exception int warmupIterations = requireIntConsistency(reportFiles, "warmupIterations"); int measurementIterations = requireIntConsistency(reportFiles, "measurementIterations"); - List firstRows = iterable(reportFiles.getFirst().report().path("libraries")); + List firstRows = iterable(reportFiles.get(0).report().path("libraries")); Map firstByLibrary = indexBy(firstRows, "library"); for (ReportFile reportFile : reportFiles) { Map currentByLibrary = indexBy(iterable(reportFile.report().path("libraries")), "library"); @@ -246,7 +246,7 @@ private void aggregateComparative(List reportFiles) throws Exception } private static int requireIntConsistency(List reportFiles, String fieldName) { - int expected = reportFiles.getFirst().report().path(fieldName).asInt(); + int expected = reportFiles.get(0).report().path(fieldName).asInt(); for (ReportFile reportFile : reportFiles) { int current = reportFile.report().path(fieldName).asInt(); if (current != expected) { @@ -259,7 +259,7 @@ private static int requireIntConsistency(List reportFiles, String fi } private static List requireIntegerArrayConsistency(List reportFiles, String fieldName) { - List expected = iterable(reportFiles.getFirst().report().path(fieldName)).stream() + List expected = iterable(reportFiles.get(0).report().path(fieldName)).stream() .map(JsonNode::asInt) .toList(); for (ReportFile reportFile : reportFiles) { diff --git a/benchmarks/src/main/java/com/demcha/compose/ComparativeBenchmark.java b/benchmarks/src/main/java/com/demcha/compose/ComparativeBenchmark.java index f0eb2373..76cd87c7 100644 --- a/benchmarks/src/main/java/com/demcha/compose/ComparativeBenchmark.java +++ b/benchmarks/src/main/java/com/demcha/compose/ComparativeBenchmark.java @@ -95,14 +95,14 @@ private static ComparativeRow runBenchmark(String name, BenchmarkTask task) thro for (int i = 0; i < MEASUREMENT_ITERATIONS; i++) { System.gc(); // Форсируем сборку мусора перед каждым замером для чистоты аллокации - long startBytes = bean.getThreadAllocatedBytes(Thread.currentThread().threadId()); + long startBytes = bean.getThreadAllocatedBytes(Thread.currentThread().getId()); long startTime = System.nanoTime(); // Выполняем задачу и получаем байты PDF byte[] pdfBytes = task.runAndGetBytes(); long endTime = System.nanoTime(); - long endBytes = bean.getThreadAllocatedBytes(Thread.currentThread().threadId()); + long endBytes = bean.getThreadAllocatedBytes(Thread.currentThread().getId()); totalTimeNs += (endTime - startTime); totalAllocatedBytes += (endBytes - startBytes); diff --git a/examples/pom.xml b/examples/pom.xml index b3f76104..02231b42 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -14,7 +14,7 @@ ${project.version} 1.5.18 - 21 + 17 5.12.2 3.27.3 diff --git a/examples/src/main/java/com/demcha/examples/support/WeeklyScheduleRenderer.java b/examples/src/main/java/com/demcha/examples/support/WeeklyScheduleRenderer.java index 5b80f37a..67184dd8 100644 --- a/examples/src/main/java/com/demcha/examples/support/WeeklyScheduleRenderer.java +++ b/examples/src/main/java/com/demcha/examples/support/WeeklyScheduleRenderer.java @@ -604,27 +604,27 @@ private static int totalColumnsCovered(List cells) { * cells is always 4. */ private static List dayCells(DayShift dayShift, Theme theme) { - if (dayShift == null) { + if (dayShift == null || dayShift instanceof DayShift.EmptyDay) { return List.of(emptyDayCell(theme)); } - return switch (dayShift) { - case DayShift.EmptyDay e -> - List.of(emptyDayCell(theme)); - case DayShift.FullStatus(ShiftStatus s) -> - List.of(fullStatusCell(s, theme)); - case DayShift.CrossMeal(Shift shift) -> - shiftAcrossDay(shift, theme); - case DayShift.Halves(Half lunch, Half dinner) -> - mergedHalfCells(lunch, dinner, theme); - }; + if (dayShift instanceof DayShift.FullStatus fs) { + return List.of(fullStatusCell(fs.status(), theme)); + } + if (dayShift instanceof DayShift.CrossMeal cm) { + return shiftAcrossDay(cm.shift(), theme); + } + if (dayShift instanceof DayShift.Halves h) { + return mergedHalfCells(h.lunch(), h.dinner(), theme); + } + throw new IllegalStateException("Unhandled DayShift variant: " + dayShift); } private static List mergedHalfCells(Half lunch, Half dinner, Theme theme) { // Both halves on the same status fill → merge to colSpan(4). - if (lunch instanceof Half.StatusFill(ShiftStatus ls) - && dinner instanceof Half.StatusFill(ShiftStatus ds) - && ls == ds) { - return List.of(fullStatusCell(ls, theme)); + if (lunch instanceof Half.StatusFill ls + && dinner instanceof Half.StatusFill ds + && ls.status() == ds.status()) { + return List.of(fullStatusCell(ls.status(), theme)); } List cells = new ArrayList<>(); cells.addAll(halfCells(lunch, theme)); @@ -633,11 +633,16 @@ private static List mergedHalfCells(Half lunch, Half dinner, } private static List halfCells(Half half, Theme theme) { - return switch (half) { - case Half.Empty e -> List.of(emptyHalfCell(theme)); - case Half.StatusFill(ShiftStatus s) -> List.of(statusHalfCell(s, theme)); - case Half.Working(Shift shift) -> shiftHalfCells(shift, theme); - }; + if (half instanceof Half.Empty) { + return List.of(emptyHalfCell(theme)); + } + if (half instanceof Half.StatusFill sf) { + return List.of(statusHalfCell(sf.status(), theme)); + } + if (half instanceof Half.Working w) { + return shiftHalfCells(w.shift(), theme); + } + throw new IllegalStateException("Unhandled Half variant: " + half); } private static DocumentTableCell emptyDayCell(Theme theme) { diff --git a/pom.xml b/pom.xml index f1dd0317..484a2cd1 100644 --- a/pom.xml +++ b/pom.xml @@ -43,30 +43,31 @@ UTF-8 - 21 + 17 - 3.27.3 0.64.8 - 2.20.1 + 2.21.3 2.2.20 - 1.5.18 - 1.18.38 + 1.5.32 + 1.18.46 3.0.7 - 5.4.0 + 5.5.1 21.0.2 2.0.17 - 2.4 + 2.6 3.5.3 - 5.12.2 - 5.20.0 + 3.27.6 + 5.14.4 + 5.23.0 + 1.18.7 - 3.13.0 - 3.8.0 - 3.2.5 + 3.15.0 + 3.12.0 + 3.5.5 @@ -208,6 +209,17 @@ mockito-core ${mockito.version} test + + + net.bytebuddy + byte-buddy + + + + + net.bytebuddy + byte-buddy + ${byteBuddy.version} org.mockito diff --git a/src/main/java/com/demcha/compose/document/backend/fixed/pdf/handlers/PdfShapeClipBeginRenderHandler.java b/src/main/java/com/demcha/compose/document/backend/fixed/pdf/handlers/PdfShapeClipBeginRenderHandler.java index f8f8c6da..6e42ad3e 100644 --- a/src/main/java/com/demcha/compose/document/backend/fixed/pdf/handlers/PdfShapeClipBeginRenderHandler.java +++ b/src/main/java/com/demcha/compose/document/backend/fixed/pdf/handlers/PdfShapeClipBeginRenderHandler.java @@ -69,12 +69,18 @@ public void render(PlacedFragment fragment, if (policy == ClipPolicy.CLIP_BOUNDS) { stream.addRect(x, y, width, height); } else { // CLIP_PATH - switch (outline) { - case ShapeOutline.Ellipse ignored -> addEllipsePath(stream, x, y, width, height); - case ShapeOutline.RoundedRectangle r -> addRoundedRectanglePath( - stream, x, y, width, height, + /* + * This is code that the java 21 switch pattern matching was designed to avoid. + */ + if (outline instanceof ShapeOutline.Ellipse) { + addEllipsePath(stream, x, y, width, height); + } else if (outline instanceof ShapeOutline.RoundedRectangle r) { + addRoundedRectanglePath(stream, x, y, width, height, (float) Math.min(r.cornerRadius(), Math.min(width, height) / 2.0f)); - case ShapeOutline.Rectangle ignored -> stream.addRect(x, y, width, height); + } else if (outline instanceof ShapeOutline.Rectangle) { + stream.addRect(x, y, width, height); + } else { + throw new IllegalStateException("Unknown outline: " + outline); } } // clip() pushes the current path onto the clipping path; the path diff --git a/src/main/java/com/demcha/compose/document/layout/TextFlowSupport.java b/src/main/java/com/demcha/compose/document/layout/TextFlowSupport.java index 98a81372..94744552 100644 --- a/src/main/java/com/demcha/compose/document/layout/TextFlowSupport.java +++ b/src/main/java/com/demcha/compose/document/layout/TextFlowSupport.java @@ -238,7 +238,7 @@ public static PreparedSplitResult splitList(PreparedNode pre return new PreparedSplitResult<>(head, tail); } - PreparedListItemLayout firstItem = layout.items().getFirst(); + PreparedListItemLayout firstItem = layout.items().get(0); PreparedParagraphLayout itemLayout = firstItem.paragraphLayout(); int maxLines = maxLinesThatFit( itemLayout.visualLines(), @@ -796,7 +796,7 @@ private static List wrapParagraph(List logicalLines, result.add(currentPrefix + chunks.get(index)); currentPrefix = continuationPrefix; } - currentLine = currentPrefix + chunks.getLast(); + currentLine = currentPrefix + chunks.get(chunks.size() - 1); hasContent = true; } @@ -1451,7 +1451,7 @@ private static int maxLinesThatFit(List lines, double lineGap, do if (lines.isEmpty()) { return 0; } - if (availableHeight + EPS < lines.getFirst().lineHeight()) { + if (availableHeight + EPS < lines.get(0).lineHeight()) { return 0; } diff --git a/src/main/java/com/demcha/compose/document/layout/definitions/ShapeContainerDefinition.java b/src/main/java/com/demcha/compose/document/layout/definitions/ShapeContainerDefinition.java index 16bbd65d..c6c45441 100644 --- a/src/main/java/com/demcha/compose/document/layout/definitions/ShapeContainerDefinition.java +++ b/src/main/java/com/demcha/compose/document/layout/definitions/ShapeContainerDefinition.java @@ -105,8 +105,9 @@ public List emitFragments(PreparedNode prepa } Color awtFill = node.fillColor() == null ? null : node.fillColor().color(); Stroke stroke = toStroke(node.stroke()); - LayoutFragment outlineFragment = switch (outline) { - case ShapeOutline.Ellipse ignored -> new LayoutFragment( + LayoutFragment outlineFragment; + if (outline instanceof ShapeOutline.Ellipse) { + outlineFragment = new LayoutFragment( placement.path(), 0, padLeft, @@ -114,7 +115,8 @@ public List emitFragments(PreparedNode prepa width, height, new EllipseFragmentPayload(awtFill, stroke, null, null)); - case ShapeOutline.Rectangle ignored -> new LayoutFragment( + } else if (outline instanceof ShapeOutline.Rectangle) { + outlineFragment = new LayoutFragment( placement.path(), 0, padLeft, @@ -122,7 +124,8 @@ public List emitFragments(PreparedNode prepa width, height, new ShapeFragmentPayload(awtFill, stroke, 0.0, null, null, null)); - case ShapeOutline.RoundedRectangle r -> new LayoutFragment( + } else if (outline instanceof ShapeOutline.RoundedRectangle r) { + outlineFragment = new LayoutFragment( placement.path(), 0, padLeft, @@ -130,7 +133,9 @@ public List emitFragments(PreparedNode prepa width, height, new ShapeFragmentPayload(awtFill, stroke, r.cornerRadius(), null, null, null)); - }; + } else { + throw new IllegalStateException("Unsupported shape outline: " + outline); + } List opening = new ArrayList<>(4); boolean hasTransform = !node.transform().isIdentity(); diff --git a/src/main/java/com/demcha/compose/document/templates/components/Module.java b/src/main/java/com/demcha/compose/document/templates/components/Module.java index 4ec37118..45eb161c 100644 --- a/src/main/java/com/demcha/compose/document/templates/components/Module.java +++ b/src/main/java/com/demcha/compose/document/templates/components/Module.java @@ -158,16 +158,25 @@ private DocumentTextStyle bodyStyle(BusinessTheme theme) { } private DocumentNode renderBody(BusinessTheme theme, Spacing spacing) { - return switch (body) { - case ParagraphBlock p -> renderParagraph(p, theme, spacing); - case BulletListBlock b -> renderList(b.items(), ListMarker.bullet(), - "bullet", theme, spacing); - case NumberedListBlock n -> renderList(n.items(), ListMarker.custom("1."), - "numbered", theme, spacing); - case MultiParagraphBlock m -> renderMultiParagraph(m, theme, spacing); - case IndentedBlock i -> renderIndented(i, theme, spacing); - case KeyValueBlock k -> renderKeyValue(k, theme, spacing); - }; + if (body instanceof ParagraphBlock p) { + return renderParagraph(p, theme, spacing); + } + if (body instanceof BulletListBlock b) { + return renderList(b.items(), ListMarker.bullet(), "bullet", theme, spacing); + } + if (body instanceof NumberedListBlock n) { + return renderList(n.items(), ListMarker.custom("1."), "numbered", theme, spacing); + } + if (body instanceof MultiParagraphBlock m) { + return renderMultiParagraph(m, theme, spacing); + } + if (body instanceof IndentedBlock i) { + return renderIndented(i, theme, spacing); + } + if (body instanceof KeyValueBlock k) { + return renderKeyValue(k, theme, spacing); + } + throw new IllegalStateException("Unsupported module body: " + body); } private ParagraphNode renderParagraph(ParagraphBlock block, BusinessTheme theme, Spacing spacing) { diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/BlueBanner.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/BlueBanner.java index e83f22ac..25b4ec96 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/presets/BlueBanner.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/BlueBanner.java @@ -215,39 +215,33 @@ private void addModuleBody(SectionBuilder section, CvModule module) { } private void renderBody(SectionBuilder section, Block body) { - switch (body) { - case ParagraphBlock p -> renderParagraph(section, p.text()); - case MultiParagraphBlock m -> { - for (String line : m.paragraphs()) { - WorkEntry entry = parseWorkEntry(line); - if (entry != null) { - renderWorkEntry(section, entry); - } else { - renderParagraph(section, line); - } + if (body instanceof ParagraphBlock p) { + renderParagraph(section, p.text()); + } else if (body instanceof MultiParagraphBlock m) { + for (String line : m.paragraphs()) { + WorkEntry entry = parseWorkEntry(line); + if (entry != null) { + renderWorkEntry(section, entry); + } else { + renderParagraph(section, line); } } - case BulletListBlock b -> renderBulletList(section, b.items()); - case NumberedListBlock n -> { - for (String item : n.items()) { - renderParagraph(section, item); - } - } - case IndentedBlock i -> { - for (IndentedBlock.Item item : i.items()) { - String inline = (item.title().isBlank() ? "" : item.title()) - + (item.title().isBlank() || item.body().isBlank() ? "" : " - ") - + (item.body().isBlank() ? "" : item.body()); - renderParagraph(section, inline); - } + } else if (body instanceof BulletListBlock b) { + renderBulletList(section, b.items()); + } else if (body instanceof NumberedListBlock n) { + for (String item : n.items()) { + renderParagraph(section, item); } - case KeyValueBlock kv -> { - for (KeyValueBlock.Entry entry : kv.entries()) { - renderKeyValueEntry(section, entry); - } + } else if (body instanceof IndentedBlock i) { + for (IndentedBlock.Item item : i.items()) { + String inline = (item.title().isBlank() ? "" : item.title()) + + (item.title().isBlank() || item.body().isBlank() ? "" : " - ") + + (item.body().isBlank() ? "" : item.body()); + renderParagraph(section, inline); } - default -> { - // ignore other block kinds + } else if (body instanceof KeyValueBlock kv) { + for (KeyValueBlock.Entry entry : kv.entries()) { + renderKeyValueEntry(section, entry); } } } diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/BoxedSections.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/BoxedSections.java index 39b45e3c..dc8d8e6f 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/presets/BoxedSections.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/BoxedSections.java @@ -189,48 +189,40 @@ private void addModuleBody(SectionBuilder section, CvModule module) { } private void renderBody(SectionBuilder section, Block body) { - switch (body) { - case ParagraphBlock p -> renderParagraph(section, p.text()); - case MultiParagraphBlock m -> { - for (String line : m.paragraphs()) { - WorkEntry entry = parseWorkEntry(line); - if (entry != null) { - renderWorkEntry(section, entry); - } else { - renderParagraph(section, line); - } + if (body instanceof ParagraphBlock p) { + renderParagraph(section, p.text()); + } else if (body instanceof MultiParagraphBlock m) { + for (String line : m.paragraphs()) { + WorkEntry entry = parseWorkEntry(line); + if (entry != null) { + renderWorkEntry(section, entry); + } else { + renderParagraph(section, line); } } - case BulletListBlock b -> { - for (String item : b.items()) { - WorkEntry entry = parseWorkEntry(item); - if (entry != null) { - renderWorkEntry(section, entry); - } else { - renderBulletItem(section, item); - } + } else if (body instanceof BulletListBlock b) { + for (String item : b.items()) { + WorkEntry entry = parseWorkEntry(item); + if (entry != null) { + renderWorkEntry(section, entry); + } else { + renderBulletItem(section, item); } } - case NumberedListBlock n -> { - for (String item : n.items()) { - renderParagraph(section, item); - } - } - case IndentedBlock i -> { - for (IndentedBlock.Item item : i.items()) { - String inline = (item.title().isBlank() ? "" : item.title()) - + (item.title().isBlank() || item.body().isBlank() ? "" : " - ") - + (item.body().isBlank() ? "" : item.body()); - renderParagraph(section, inline); - } + } else if (body instanceof NumberedListBlock n) { + for (String item : n.items()) { + renderParagraph(section, item); } - case KeyValueBlock kv -> { - for (KeyValueBlock.Entry entry : kv.entries()) { - renderParagraph(section, entry.key() + ": " + entry.value()); - } + } else if (body instanceof IndentedBlock i) { + for (IndentedBlock.Item item : i.items()) { + String inline = (item.title().isBlank() ? "" : item.title()) + + (item.title().isBlank() || item.body().isBlank() ? "" : " - ") + + (item.body().isBlank() ? "" : item.body()); + renderParagraph(section, inline); } - default -> { - // ignore other block kinds + } else if (body instanceof KeyValueBlock kv) { + for (KeyValueBlock.Entry entry : kv.entries()) { + renderParagraph(section, entry.key() + ": " + entry.value()); } } } diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/CenteredHeadline.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/CenteredHeadline.java index f2bd57da..e7b7ca2d 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/presets/CenteredHeadline.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/CenteredHeadline.java @@ -216,39 +216,33 @@ private void renderBody(SectionBuilder section, CvModule module) { section.spacing(4) .padding(DocumentInsets.zero()); Block body = module.body(); - switch (body) { - case ParagraphBlock p -> renderParagraph(section, p.text()); - case MultiParagraphBlock m -> { - for (String line : m.paragraphs()) { - WorkEntry entry = parseWorkEntry(line); - if (entry != null) { - renderWorkEntry(section, entry); - } else { - renderParagraph(section, line); - } + if (body instanceof ParagraphBlock p) { + renderParagraph(section, p.text()); + } else if (body instanceof MultiParagraphBlock m) { + for (String line : m.paragraphs()) { + WorkEntry entry = parseWorkEntry(line); + if (entry != null) { + renderWorkEntry(section, entry); + } else { + renderParagraph(section, line); } } - case BulletListBlock b -> renderBulletList(section, b.items()); - case NumberedListBlock n -> { - for (String item : n.items()) { - renderParagraph(section, item); - } - } - case IndentedBlock i -> { - for (IndentedBlock.Item item : i.items()) { - String inline = (item.title().isBlank() ? "" : item.title()) - + (item.title().isBlank() || item.body().isBlank() ? "" : " - ") - + (item.body().isBlank() ? "" : item.body()); - renderParagraph(section, inline); - } + } else if (body instanceof BulletListBlock b) { + renderBulletList(section, b.items()); + } else if (body instanceof NumberedListBlock n) { + for (String item : n.items()) { + renderParagraph(section, item); } - case KeyValueBlock kv -> { - for (KeyValueBlock.Entry entry : kv.entries()) { - renderKeyValueEntry(section, entry); - } + } else if (body instanceof IndentedBlock i) { + for (IndentedBlock.Item item : i.items()) { + String inline = (item.title().isBlank() ? "" : item.title()) + + (item.title().isBlank() || item.body().isBlank() ? "" : " - ") + + (item.body().isBlank() ? "" : item.body()); + renderParagraph(section, inline); } - default -> { - // ignore other block kinds + } else if (body instanceof KeyValueBlock kv) { + for (KeyValueBlock.Entry entry : kv.entries()) { + renderKeyValueEntry(section, entry); } } } diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/ClassicSerif.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/ClassicSerif.java index b3a82725..e1868dc6 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/presets/ClassicSerif.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/ClassicSerif.java @@ -349,12 +349,16 @@ private static List moduleLines(CvModule module) { } List lines = new ArrayList<>(); Block body = module.body(); - switch (body) { - case ParagraphBlock p -> addLines(lines, p.text()); - case MultiParagraphBlock m -> m.paragraphs().forEach(line -> addLines(lines, line)); - case BulletListBlock b -> b.items().forEach(item -> addLines(lines, item)); - case NumberedListBlock n -> n.items().forEach(item -> addLines(lines, item)); - case IndentedBlock i -> i.items().forEach(item -> { + if (body instanceof ParagraphBlock p) { + addLines(lines, p.text()); + } else if (body instanceof MultiParagraphBlock m) { + m.paragraphs().forEach(line -> addLines(lines, line)); + } else if (body instanceof BulletListBlock b) { + b.items().forEach(item -> addLines(lines, item)); + } else if (body instanceof NumberedListBlock n) { + n.items().forEach(item -> addLines(lines, item)); + } else if (body instanceof IndentedBlock i) { + i.items().forEach(item -> { String title = safe(item.title()); String bodyText = safe(item.body()); if (title.isBlank() && bodyText.isBlank()) { @@ -368,11 +372,8 @@ private static List moduleLines(CvModule module) { lines.add(title + " | " + bodyText); } }); - case KeyValueBlock kv -> kv.entries().forEach(entry -> - addLines(lines, entry.key() + ": " + entry.value())); - default -> { - // ignore other block kinds - } + } else if (body instanceof KeyValueBlock kv) { + kv.entries().forEach(entry -> addLines(lines, entry.key() + ": " + entry.value())); } return List.copyOf(lines); } diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/CompactMono.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/CompactMono.java index 90137ad7..c29d9204 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/presets/CompactMono.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/CompactMono.java @@ -299,12 +299,16 @@ private static List moduleLines(CvModule module) { } List lines = new ArrayList<>(); Block body = module.body(); - switch (body) { - case ParagraphBlock p -> addLines(lines, p.text()); - case MultiParagraphBlock m -> m.paragraphs().forEach(line -> addLines(lines, line)); - case BulletListBlock b -> b.items().forEach(item -> addLines(lines, item)); - case NumberedListBlock n -> n.items().forEach(item -> addLines(lines, item)); - case IndentedBlock i -> i.items().forEach(item -> { + if (body instanceof ParagraphBlock p) { + addLines(lines, p.text()); + } else if (body instanceof MultiParagraphBlock m) { + m.paragraphs().forEach(line -> addLines(lines, line)); + } else if (body instanceof BulletListBlock b) { + b.items().forEach(item -> addLines(lines, item)); + } else if (body instanceof NumberedListBlock n) { + n.items().forEach(item -> addLines(lines, item)); + } else if (body instanceof IndentedBlock i) { + i.items().forEach(item -> { String title = safe(item.title()); String bodyText = safe(item.body()); if (title.isBlank() && bodyText.isBlank()) { @@ -318,11 +322,8 @@ private static List moduleLines(CvModule module) { lines.add(title + " | " + bodyText); } }); - case KeyValueBlock kv -> kv.entries().forEach(entry -> - addLines(lines, entry.key() + ": " + entry.value())); - default -> { - // ignore other block kinds - } + } else if (body instanceof KeyValueBlock kv) { + kv.entries().forEach(entry -> addLines(lines, entry.key() + ": " + entry.value())); } return List.copyOf(lines); } diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/EditorialBlue.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/EditorialBlue.java index f7328a6f..3666fc88 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/presets/EditorialBlue.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/EditorialBlue.java @@ -549,38 +549,30 @@ private void sectionHeader(PageFlowBuilder pageFlow, String name, String title, } private void renderBody(SectionBuilder section, Block body) { - switch (body) { - case ParagraphBlock p -> renderParagraph(section, p.text()); - case MultiParagraphBlock m -> { - for (String line : m.paragraphs()) { - renderParagraph(section, line); - } - } - case BulletListBlock b -> { - for (String item : b.items()) { - renderParagraph(section, item); - } + if (body instanceof ParagraphBlock p) { + renderParagraph(section, p.text()); + } else if (body instanceof MultiParagraphBlock m) { + for (String line : m.paragraphs()) { + renderParagraph(section, line); } - case NumberedListBlock n -> { - for (String item : n.items()) { - renderParagraph(section, item); - } + } else if (body instanceof BulletListBlock b) { + for (String item : b.items()) { + renderParagraph(section, item); } - case IndentedBlock i -> { - for (IndentedBlock.Item item : i.items()) { - String inline = (item.title().isBlank() ? "" : item.title()) - + (item.title().isBlank() || item.body().isBlank() ? "" : " - ") - + (item.body().isBlank() ? "" : item.body()); - renderParagraph(section, inline); - } + } else if (body instanceof NumberedListBlock n) { + for (String item : n.items()) { + renderParagraph(section, item); } - case KeyValueBlock kv -> { - for (KeyValueBlock.Entry entry : kv.entries()) { - renderKeyValueEntry(section, entry); - } + } else if (body instanceof IndentedBlock i) { + for (IndentedBlock.Item item : i.items()) { + String inline = (item.title().isBlank() ? "" : item.title()) + + (item.title().isBlank() || item.body().isBlank() ? "" : " - ") + + (item.body().isBlank() ? "" : item.body()); + renderParagraph(section, inline); } - default -> { - // ignore other block kinds + } else if (body instanceof KeyValueBlock kv) { + for (KeyValueBlock.Entry entry : kv.entries()) { + renderKeyValueEntry(section, entry); } } } @@ -668,12 +660,16 @@ private static List moduleLines(CvModule module) { } List lines = new ArrayList<>(); Block body = module.body(); - switch (body) { - case ParagraphBlock p -> addLines(lines, p.text()); - case MultiParagraphBlock m -> m.paragraphs().forEach(line -> addLines(lines, line)); - case BulletListBlock b -> b.items().forEach(item -> addLines(lines, item)); - case NumberedListBlock n -> n.items().forEach(item -> addLines(lines, item)); - case IndentedBlock i -> i.items().forEach(item -> { + if (body instanceof ParagraphBlock p) { + addLines(lines, p.text()); + } else if (body instanceof MultiParagraphBlock m) { + m.paragraphs().forEach(line -> addLines(lines, line)); + } else if (body instanceof BulletListBlock b) { + b.items().forEach(item -> addLines(lines, item)); + } else if (body instanceof NumberedListBlock n) { + n.items().forEach(item -> addLines(lines, item)); + } else if (body instanceof IndentedBlock i) { + i.items().forEach(item -> { String title = safe(item.title()); String bodyText = safe(item.body()); if (title.isBlank() && bodyText.isBlank()) { @@ -687,11 +683,8 @@ private static List moduleLines(CvModule module) { lines.add(title + " | " + bodyText); } }); - case KeyValueBlock kv -> kv.entries().forEach(entry -> - addLines(lines, entry.key() + ": " + entry.value())); - default -> { - // ignore other block kinds - } + } else if (body instanceof KeyValueBlock kv) { + kv.entries().forEach(entry -> addLines(lines, entry.key() + ": " + entry.value())); } return List.copyOf(lines); } diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/EngineeringResume.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/EngineeringResume.java index 64df179a..85409b27 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/presets/EngineeringResume.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/EngineeringResume.java @@ -444,11 +444,14 @@ private static List moduleLines(CvModule module) { } List lines = new ArrayList<>(); Block body = module.body(); - switch (body) { - case ParagraphBlock p -> addLines(lines, p.text()); - case MultiParagraphBlock m -> m.paragraphs().forEach(line -> addLines(lines, line)); - case BulletListBlock b -> b.items().forEach(item -> addLines(lines, item)); - case IndentedBlock i -> i.items().forEach(item -> { + if (body instanceof ParagraphBlock p) { + addLines(lines, p.text()); + } else if (body instanceof MultiParagraphBlock m) { + m.paragraphs().forEach(line -> addLines(lines, line)); + } else if (body instanceof BulletListBlock b) { + b.items().forEach(item -> addLines(lines, item)); + } else if (body instanceof IndentedBlock i) { + i.items().forEach(item -> { String title = safe(item.title()); String bodyText = safe(item.body()); if (title.isBlank() && bodyText.isBlank()) { @@ -462,11 +465,8 @@ private static List moduleLines(CvModule module) { lines.add(title + " | " + bodyText); } }); - case KeyValueBlock kv -> kv.entries().forEach(entry -> - addLines(lines, entry.key() + ": " + entry.value())); - default -> { - // ignore other block kinds - } + } else if (body instanceof KeyValueBlock kv) { + kv.entries().forEach(entry -> addLines(lines, entry.key() + ": " + entry.value())); } return List.copyOf(lines); } diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/Executive.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/Executive.java index a34cc558..9772f811 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/presets/Executive.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/Executive.java @@ -189,30 +189,26 @@ private void addModule(PageFlowBuilder flow, CvModule module) { } private void renderBody(SectionBuilder section, Block body) { - switch (body) { - case ParagraphBlock p -> renderParagraph(section, p.text()); - case MultiParagraphBlock m -> { - for (String line : m.paragraphs()) { - renderParagraph(section, line); - } + if (body instanceof ParagraphBlock p) { + renderParagraph(section, p.text()); + } else if (body instanceof MultiParagraphBlock m) { + for (String line : m.paragraphs()) { + renderParagraph(section, line); } - case BulletListBlock b -> renderBulletList(section, b.items()); - case NumberedListBlock n -> renderNumberedList(section, n.items()); - case IndentedBlock i -> { - for (IndentedBlock.Item item : i.items()) { - String inline = (item.title().isBlank() ? "" : item.title()) - + (item.title().isBlank() || item.body().isBlank() ? "" : " - ") - + (item.body().isBlank() ? "" : item.body()); - renderParagraph(section, inline); - } + } else if (body instanceof BulletListBlock b) { + renderBulletList(section, b.items()); + } else if (body instanceof NumberedListBlock n) { + renderNumberedList(section, n.items()); + } else if (body instanceof IndentedBlock i) { + for (IndentedBlock.Item item : i.items()) { + String inline = (item.title().isBlank() ? "" : item.title()) + + (item.title().isBlank() || item.body().isBlank() ? "" : " - ") + + (item.body().isBlank() ? "" : item.body()); + renderParagraph(section, inline); } - case KeyValueBlock kv -> { - for (KeyValueBlock.Entry entry : kv.entries()) { - renderParagraph(section, entry.key() + ": " + entry.value()); - } - } - default -> { - // ignore other block kinds + } else if (body instanceof KeyValueBlock kv) { + for (KeyValueBlock.Entry entry : kv.entries()) { + renderParagraph(section, entry.key() + ": " + entry.value()); } } } diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/MonogramSidebar.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/MonogramSidebar.java index 79a2cd50..de330c28 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/presets/MonogramSidebar.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/MonogramSidebar.java @@ -497,32 +497,34 @@ private static List moduleItems(CvModule module) { } List result = new ArrayList<>(); Block body = module.body(); - switch (body) { - case ParagraphBlock p -> { - String t = safe(p.text()).trim(); - if (!t.isBlank()) { - result.add(t); - } + if (body instanceof ParagraphBlock p) { + String t = safe(p.text()).trim(); + if (!t.isBlank()) { + result.add(t); } - case MultiParagraphBlock m -> m.paragraphs().forEach(line -> { + } else if (body instanceof MultiParagraphBlock m) { + m.paragraphs().forEach(line -> { String t = safe(line).trim(); if (!t.isBlank()) { result.add(t); } }); - case BulletListBlock b -> b.items().forEach(item -> { + } else if (body instanceof BulletListBlock b) { + b.items().forEach(item -> { String t = safe(item).trim(); if (!t.isBlank()) { result.add(t); } }); - case NumberedListBlock n -> n.items().forEach(item -> { + } else if (body instanceof NumberedListBlock n) { + n.items().forEach(item -> { String t = safe(item).trim(); if (!t.isBlank()) { result.add(t); } }); - case IndentedBlock i -> i.items().forEach(item -> { + } else if (body instanceof IndentedBlock i) { + i.items().forEach(item -> { String title = safe(item.title()); String bodyText = safe(item.body()); if (title.isBlank() && bodyText.isBlank()) { @@ -536,11 +538,8 @@ private static List moduleItems(CvModule module) { result.add(title + " | " + bodyText); } }); - case KeyValueBlock kv -> kv.entries().forEach(entry -> - result.add(entry.key() + ": " + entry.value())); - default -> { - // ignore other block kinds - } + } else if (body instanceof KeyValueBlock kv) { + kv.entries().forEach(entry -> result.add(entry.key() + ": " + entry.value())); } return result; } diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/NordicClean.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/NordicClean.java index be144b8b..7f05ec61 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/presets/NordicClean.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/NordicClean.java @@ -428,11 +428,14 @@ private static List moduleLines(CvModule module) { } List lines = new ArrayList<>(); Block body = module.body(); - switch (body) { - case ParagraphBlock p -> addLines(lines, p.text()); - case MultiParagraphBlock m -> m.paragraphs().forEach(line -> addLines(lines, line)); - case BulletListBlock b -> b.items().forEach(item -> addLines(lines, item)); - case IndentedBlock i -> i.items().forEach(item -> { + if (body instanceof ParagraphBlock p) { + addLines(lines, p.text()); + } else if (body instanceof MultiParagraphBlock m) { + m.paragraphs().forEach(line -> addLines(lines, line)); + } else if (body instanceof BulletListBlock b) { + b.items().forEach(item -> addLines(lines, item)); + } else if (body instanceof IndentedBlock i) { + i.items().forEach(item -> { String title = safe(item.title()); String bodyText = safe(item.body()); if (title.isBlank() && bodyText.isBlank()) { @@ -446,11 +449,8 @@ private static List moduleLines(CvModule module) { lines.add(title + " | " + bodyText); } }); - case KeyValueBlock kv -> kv.entries().forEach(entry -> - addLines(lines, entry.key() + ": " + entry.value())); - default -> { - // other block kinds intentionally not surfaced here. - } + } else if (body instanceof KeyValueBlock kv) { + kv.entries().forEach(entry -> addLines(lines, entry.key() + ": " + entry.value())); } return List.copyOf(lines); } diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/Panel.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/Panel.java index b0d68c32..8e1cfd17 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/presets/Panel.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/Panel.java @@ -260,30 +260,26 @@ private void renderPanel(SectionBuilder section, String title, CvModule module) } private void renderBody(SectionBuilder section, Block body) { - switch (body) { - case ParagraphBlock p -> renderParagraph(section, p.text()); - case MultiParagraphBlock m -> { - for (String line : m.paragraphs()) { - renderParagraph(section, line); - } + if (body instanceof ParagraphBlock p) { + renderParagraph(section, p.text()); + } else if (body instanceof MultiParagraphBlock m) { + for (String line : m.paragraphs()) { + renderParagraph(section, line); } - case BulletListBlock b -> renderBulletList(section, b.items()); - case NumberedListBlock n -> renderBulletList(section, n.items()); - case IndentedBlock i -> { - for (IndentedBlock.Item item : i.items()) { - String inline = (item.title().isBlank() ? "" : item.title()) - + (item.title().isBlank() || item.body().isBlank() ? "" : " - ") - + (item.body().isBlank() ? "" : item.body()); - renderParagraph(section, inline); - } + } else if (body instanceof BulletListBlock b) { + renderBulletList(section, b.items()); + } else if (body instanceof NumberedListBlock n) { + renderBulletList(section, n.items()); + } else if (body instanceof IndentedBlock i) { + for (IndentedBlock.Item item : i.items()) { + String inline = (item.title().isBlank() ? "" : item.title()) + + (item.title().isBlank() || item.body().isBlank() ? "" : " - ") + + (item.body().isBlank() ? "" : item.body()); + renderParagraph(section, inline); } - case KeyValueBlock kv -> { - for (KeyValueBlock.Entry entry : kv.entries()) { - renderKeyValueEntry(section, entry); - } - } - default -> { - // ignore other block kinds + } else if (body instanceof KeyValueBlock kv) { + for (KeyValueBlock.Entry entry : kv.entries()) { + renderKeyValueEntry(section, entry); } } } diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/SidebarPortrait.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/SidebarPortrait.java index a417f453..97ae0345 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/presets/SidebarPortrait.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/SidebarPortrait.java @@ -498,20 +498,18 @@ private static List moduleParagraphs(CvModule module) { } List result = new ArrayList<>(); Block body = module.body(); - switch (body) { - case ParagraphBlock p -> result.add(safe(p.text()).trim()); - case MultiParagraphBlock m -> { - for (String line : m.paragraphs()) { - String t = safe(line).trim(); - if (!t.isBlank()) { - result.add(t); - } + if (body instanceof ParagraphBlock p) { + result.add(safe(p.text()).trim()); + } else if (body instanceof MultiParagraphBlock m) { + for (String line : m.paragraphs()) { + String t = safe(line).trim(); + if (!t.isBlank()) { + result.add(t); } } - default -> { - for (String item : moduleItems(module)) { - result.add(item); - } + } else { + for (String item : moduleItems(module)) { + result.add(item); } } return result; @@ -523,32 +521,34 @@ private static List moduleItems(CvModule module) { } List result = new ArrayList<>(); Block body = module.body(); - switch (body) { - case ParagraphBlock p -> { - String t = safe(p.text()).trim(); - if (!t.isBlank()) { - result.add(t); - } + if (body instanceof ParagraphBlock p) { + String t = safe(p.text()).trim(); + if (!t.isBlank()) { + result.add(t); } - case MultiParagraphBlock m -> m.paragraphs().forEach(line -> { + } else if (body instanceof MultiParagraphBlock m) { + m.paragraphs().forEach(line -> { String t = safe(line).trim(); if (!t.isBlank()) { result.add(t); } }); - case BulletListBlock b -> b.items().forEach(item -> { + } else if (body instanceof BulletListBlock b) { + b.items().forEach(item -> { String t = safe(item).trim(); if (!t.isBlank()) { result.add(t); } }); - case NumberedListBlock n -> n.items().forEach(item -> { + } else if (body instanceof NumberedListBlock n) { + n.items().forEach(item -> { String t = safe(item).trim(); if (!t.isBlank()) { result.add(t); } }); - case IndentedBlock i -> i.items().forEach(item -> { + } else if (body instanceof IndentedBlock i) { + i.items().forEach(item -> { String title = safe(item.title()); String bodyText = safe(item.body()); if (title.isBlank() && bodyText.isBlank()) { @@ -562,11 +562,8 @@ private static List moduleItems(CvModule module) { result.add(title + " | " + bodyText); } }); - case KeyValueBlock kv -> kv.entries().forEach(entry -> - result.add(entry.key() + ": " + entry.value())); - default -> { - // ignore other block kinds - } + } else if (body instanceof KeyValueBlock kv) { + kv.entries().forEach(entry -> result.add(entry.key() + ": " + entry.value())); } return result; } diff --git a/src/main/java/com/demcha/compose/document/templates/cv/presets/TimelineMinimal.java b/src/main/java/com/demcha/compose/document/templates/cv/presets/TimelineMinimal.java index 5068cd93..d714efcf 100644 --- a/src/main/java/com/demcha/compose/document/templates/cv/presets/TimelineMinimal.java +++ b/src/main/java/com/demcha/compose/document/templates/cv/presets/TimelineMinimal.java @@ -426,12 +426,16 @@ private static List moduleLines(CvModule module) { } List lines = new ArrayList<>(); Block body = module.body(); - switch (body) { - case ParagraphBlock p -> addLines(lines, p.text()); - case MultiParagraphBlock m -> m.paragraphs().forEach(line -> addLines(lines, line)); - case BulletListBlock b -> b.items().forEach(item -> addLines(lines, item)); - case NumberedListBlock n -> n.items().forEach(item -> addLines(lines, item)); - case IndentedBlock i -> i.items().forEach(item -> { + if (body instanceof ParagraphBlock p) { + addLines(lines, p.text()); + } else if (body instanceof MultiParagraphBlock m) { + m.paragraphs().forEach(line -> addLines(lines, line)); + } else if (body instanceof BulletListBlock b) { + b.items().forEach(item -> addLines(lines, item)); + } else if (body instanceof NumberedListBlock n) { + n.items().forEach(item -> addLines(lines, item)); + } else if (body instanceof IndentedBlock i) { + i.items().forEach(item -> { String title = safe(item.title()); String bodyText = safe(item.body()); if (title.isBlank() && bodyText.isBlank()) { @@ -445,11 +449,8 @@ private static List moduleLines(CvModule module) { lines.add(title + " | " + bodyText); } }); - case KeyValueBlock kv -> kv.entries().forEach(entry -> - addLines(lines, entry.key() + ": " + entry.value())); - default -> { - // ignore other block kinds - } + } else if (body instanceof KeyValueBlock kv) { + kv.entries().forEach(entry -> addLines(lines, entry.key() + ": " + entry.value())); } return List.copyOf(lines); } diff --git a/src/main/java/com/demcha/compose/engine/font/Font.java b/src/main/java/com/demcha/compose/engine/font/Font.java index 00e00b12..d6e87b8d 100644 --- a/src/main/java/com/demcha/compose/engine/font/Font.java +++ b/src/main/java/com/demcha/compose/engine/font/Font.java @@ -20,13 +20,16 @@ public interface Font { T strikethrough(); default T fontType(TextDecoration textDecoration) { + if (textDecoration == null) { + return defaultFont(); + } return switch (textDecoration) { case BOLD -> bold(); case ITALIC -> italic(); case UNDERLINE -> underline(); case BOLD_ITALIC -> boldItalic(); case STRIKETHROUGH -> strikethrough(); - case null, default -> defaultFont(); + default -> defaultFont(); }; } @@ -47,4 +50,3 @@ default T fontType(TextDecoration textDecoration) { ContentSize getTightBounds(String text, TextStyle style); } - diff --git a/src/main/java/com/demcha/compose/engine/layout/container/ContainerAligner.java b/src/main/java/com/demcha/compose/engine/layout/container/ContainerAligner.java index 52fcb8e9..cbd77e55 100644 --- a/src/main/java/com/demcha/compose/engine/layout/container/ContainerAligner.java +++ b/src/main/java/com/demcha/compose/engine/layout/container/ContainerAligner.java @@ -17,6 +17,7 @@ import com.demcha.compose.engine.core.EntityManager; import lombok.extern.slf4j.Slf4j; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -113,9 +114,11 @@ public void alignChildren(Entity parent, EntityManager entityManager) { * The reverse logic is handled by the ReverseLayoutStrategy. */ protected List getOrderedChildren(Entity parent, EntityManager entityManager) { - return parent.getChildren().stream() + List children = parent.getChildren().stream() .map(id -> entityManager.getEntity(id).orElseThrow()) - .collect(Collectors.toList()).reversed(); + .collect(Collectors.toCollection(ArrayList::new)); + java.util.Collections.reverse(children); + return children; } protected abstract void updateChildPosition(Entity child, Axes axes); @@ -242,7 +245,8 @@ public void alignChildren(Entity parent, EntityManager entityManager) { Align align = parent.getComponent(Align.class).orElseThrow(); List children = parent.getChildren().stream() .map(id -> entityManager.getEntity(id).orElseThrow()) - .collect(Collectors.toList()).reversed(); + .collect(Collectors.toCollection(ArrayList::new)); + java.util.Collections.reverse(children); double main = 0; double resolvedWidth = parent.getComponent(ContentSize.class) diff --git a/src/test/java/com/demcha/compose/devtool/GraphComposeDevTool.java b/src/test/java/com/demcha/compose/devtool/GraphComposeDevTool.java index 813f403d..3e63044e 100644 --- a/src/test/java/com/demcha/compose/devtool/GraphComposeDevTool.java +++ b/src/test/java/com/demcha/compose/devtool/GraphComposeDevTool.java @@ -38,6 +38,8 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; /** @@ -51,20 +53,16 @@ public final class GraphComposeDevTool extends Application { private static final long FILE_DEBOUNCE_MILLIS = 250; private static final List CHILD_FIRST_PREFIXES = List.of("com.demcha."); private static final List PARENT_FIRST_PREFIXES = List.of("com.demcha.compose.devtool."); + private static final AtomicInteger REFRESH_THREAD_SEQUENCE = new AtomicInteger(); + private static final AtomicInteger DEBOUNCE_THREAD_SEQUENCE = new AtomicInteger(); private final DevToolWorkspace workspace = DevToolWorkspace.currentProject(); private final PreviewCompiler compiler = new PreviewCompiler(); private final float previewScale = PreviewScaleResolver.fromSystemProperties(); private final ExecutorService refreshExecutor = Executors.newSingleThreadExecutor( - Thread.ofPlatform() - .daemon() - .name("graphcompose-devtool-refresh-", 0) - .factory()); + daemonThreadFactory("graphcompose-devtool-refresh-", REFRESH_THREAD_SEQUENCE)); private final ScheduledExecutorService debounceExecutor = Executors.newSingleThreadScheduledExecutor( - Thread.ofPlatform() - .daemon() - .name("graphcompose-devtool-debounce-", 0) - .factory()); + daemonThreadFactory("graphcompose-devtool-debounce-", DEBOUNCE_THREAD_SEQUENCE)); private final AtomicReference pendingRequest = new AtomicReference<>(); private final AtomicReference> pendingDebounce = new AtomicReference<>(); private final AtomicReference currentPreview = new AtomicReference<>(); @@ -90,6 +88,15 @@ public static void main(String[] args) { Application.launch(GraphComposeDevTool.class, args); } + private static ThreadFactory daemonThreadFactory(String prefix, AtomicInteger sequence) { + return runnable -> { + Thread thread = new Thread(runnable); + thread.setDaemon(true); + thread.setName(prefix + sequence.getAndIncrement()); + return thread; + }; + } + @Override public void start(Stage stage) { var root = new BorderPane(); diff --git a/src/test/java/com/demcha/compose/devtool/PreviewCompiler.java b/src/test/java/com/demcha/compose/devtool/PreviewCompiler.java index 16842f68..8f256512 100644 --- a/src/test/java/com/demcha/compose/devtool/PreviewCompiler.java +++ b/src/test/java/com/demcha/compose/devtool/PreviewCompiler.java @@ -27,6 +27,8 @@ public final class PreviewCompiler { private static final Pattern LOMBOK_VERSION_PATTERN = Pattern.compile("([^<]+)"); + private static final Pattern MAVEN_COMPILER_RELEASE_PATTERN = + Pattern.compile("([^<]+)"); private volatile List cachedCompilerClasspath; private volatile Optional cachedLombokJar; @@ -55,6 +57,13 @@ public CompilationResult compile(DevToolWorkspace workspace, long revision) { return CompilationResult.failure("No Java sources found in watched source roots.", 0, 0); } + int compilerRelease; + try { + compilerRelease = compilerRelease(workspace); + } catch (IllegalStateException ex) { + return CompilationResult.failure(ex.getMessage(), 0, sourceFiles.size()); + } + Path outputDir = workspace.compiledOutputRoot().resolve("rev-" + revision); long startedAt = System.nanoTime(); deleteDirectoryQuietly(outputDir); @@ -84,8 +93,10 @@ public CompilationResult compile(DevToolWorkspace workspace, long revision) { var compilerClasspath = compilerClasspath(workspace); var options = new ArrayList(); + // Keep preview compilation aligned with the Maven project's declared + // target release instead of whatever newer JDK happens to run tests. options.add("--release"); - options.add("21"); + options.add(Integer.toString(compilerRelease)); options.add("-encoding"); options.add("UTF-8"); options.add("-classpath"); @@ -138,6 +149,20 @@ static void deleteDirectoryQuietly(Path path) { } } + private int compilerRelease(DevToolWorkspace workspace) { + int runtimeRelease = Runtime.version().feature(); + int configuredRelease = readCompilerReleaseFromPom(workspace.projectRoot()) + .or(this::readCompilerReleaseFromCurrentProjectPom) + .orElse(runtimeRelease); + + if (configuredRelease > runtimeRelease) { + throw new IllegalStateException( + "Preview compiler requires JDK %d or newer, but tests are running on JDK %d." + .formatted(configuredRelease, runtimeRelease)); + } + return configuredRelease; + } + private List compilerClasspath(DevToolWorkspace workspace) { List existing = cachedCompilerClasspath; if (existing != null) { @@ -187,7 +212,20 @@ private Optional findLombokJar(DevToolWorkspace workspace) { .or(() -> findLatestInstalledLombokJar()); } + private Optional readCompilerReleaseFromCurrentProjectPom() { + return readCompilerReleaseFromPom(Path.of("").toAbsolutePath().normalize()); + } + + private Optional readCompilerReleaseFromPom(Path projectRoot) { + return readPomProperty(projectRoot, MAVEN_COMPILER_RELEASE_PATTERN) + .flatMap(this::parseReleaseValue); + } + private Optional readLombokVersionFromPom(Path projectRoot) { + return readPomProperty(projectRoot, LOMBOK_VERSION_PATTERN); + } + + private Optional readPomProperty(Path projectRoot, Pattern pattern) { Path pom = projectRoot.resolve("pom.xml"); if (Files.notExists(pom)) { return Optional.empty(); @@ -195,7 +233,7 @@ private Optional readLombokVersionFromPom(Path projectRoot) { try { String content = Files.readString(pom, StandardCharsets.UTF_8); - Matcher matcher = LOMBOK_VERSION_PATTERN.matcher(content); + Matcher matcher = pattern.matcher(content); if (matcher.find()) { return Optional.of(matcher.group(1).trim()); } @@ -206,6 +244,14 @@ private Optional readLombokVersionFromPom(Path projectRoot) { return Optional.empty(); } + private Optional parseReleaseValue(String value) { + try { + return Optional.of(Integer.parseInt(value)); + } catch (NumberFormatException ignored) { + return Optional.empty(); + } + } + private Optional resolveLombokJar(String version) { Path jar = Path.of(System.getProperty("user.home"), ".m2", "repository", "org", "projectlombok", "lombok", version, "lombok-%s.jar".formatted(version)); diff --git a/src/test/java/com/demcha/compose/devtool/RecursivePathWatcher.java b/src/test/java/com/demcha/compose/devtool/RecursivePathWatcher.java index a6ad982e..28f9a8bc 100644 --- a/src/test/java/com/demcha/compose/devtool/RecursivePathWatcher.java +++ b/src/test/java/com/demcha/compose/devtool/RecursivePathWatcher.java @@ -43,10 +43,8 @@ public void start() throws IOException { } running = true; - thread = Thread.ofPlatform() - .daemon() - .name("graphcompose-devtool-watch", 0) - .unstarted(this::processLoop); + thread = new Thread(this::processLoop, "graphcompose-devtool-watch"); + thread.setDaemon(true); thread.start(); } diff --git a/src/test/java/com/demcha/compose/document/api/DocumentListNodeTest.java b/src/test/java/com/demcha/compose/document/api/DocumentListNodeTest.java index f17248a2..766c6628 100644 --- a/src/test/java/com/demcha/compose/document/api/DocumentListNodeTest.java +++ b/src/test/java/com/demcha/compose/document/api/DocumentListNodeTest.java @@ -38,7 +38,7 @@ void listShortcutShouldBuildBulletListWithoutManualParagraphLoop() throws Except .build(); assertThat(root.children()).hasSize(1); - assertThat(root.children().getFirst()).isInstanceOf(ListNode.class); + assertThat(root.children().get(0)).isInstanceOf(ListNode.class); List payloads = paragraphPayloads(session.layoutGraph()); assertThat(payloads).hasSize(3); @@ -108,13 +108,13 @@ void wrappedListItemShouldIndentContinuationWithoutRepeatingMarker() throws Exce .build(); List payloads = paragraphPayloads(session.layoutGraph()); - List firstItemLines = payloads.getFirst().lines().stream() + List firstItemLines = payloads.get(0).lines().stream() .map(ParagraphLine::text) .toList(); assertThat(firstItemLines).hasSizeGreaterThan(1); assertThat(countOccurrences(String.join("\n", firstItemLines), "\u2022")).isEqualTo(1); - assertThat(payloads.get(1).lines().getFirst().text()).isEqualTo("\u2022 Tail"); + assertThat(payloads.get(1).lines().get(0).text()).isEqualTo("\u2022 Tail"); } } @@ -137,15 +137,15 @@ void markerlessListShouldIndentWrappedContinuationWithoutIndentingEveryItem() th .build(); List payloads = paragraphPayloads(session.layoutGraph()); - List firstItemLines = payloads.getFirst().lines().stream() + List firstItemLines = payloads.get(0).lines().stream() .map(ParagraphLine::text) .toList(); assertThat(payloads).hasSize(2); assertThat(firstItemLines).hasSizeGreaterThan(1); - assertThat(firstItemLines.getFirst()).startsWith("Alpha"); + assertThat(firstItemLines.get(0)).startsWith("Alpha"); assertThat(firstItemLines.get(1)).startsWith(" "); - assertThat(payloads.get(1).lines().getFirst().text()).startsWith("Beta"); + assertThat(payloads.get(1).lines().get(0).text()).startsWith("Beta"); } } @@ -238,9 +238,9 @@ void listFragmentsShouldCarryPaddingThroughParagraphPayload() throws Exception { assertThat(fragment.width()).isCloseTo(listNode.placementWidth(), within(0.01)); }); - ParagraphFragmentPayload first = paragraphPayload(fragments.getFirst()); + ParagraphFragmentPayload first = paragraphPayload(fragments.get(0)); ParagraphFragmentPayload middle = paragraphPayload(fragments.get(1)); - ParagraphFragmentPayload last = paragraphPayload(fragments.getLast()); + ParagraphFragmentPayload last = paragraphPayload(fragments.get(fragments.size() - 1)); assertPadding(first.padding(), 3, 5, 0, 11); assertPadding(middle.padding(), 0, 5, 0, 11); @@ -270,7 +270,7 @@ private static void assertPadding(Padding padding, double top, double right, dou private static List firstLineTexts(List payloads) { return payloads.stream() - .map(payload -> payload.lines().getFirst().text()) + .map(payload -> payload.lines().get(0).text()) .toList(); } diff --git a/src/test/java/com/demcha/compose/document/api/DocumentSessionTest.java b/src/test/java/com/demcha/compose/document/api/DocumentSessionTest.java index be870c06..e829b0fd 100644 --- a/src/test/java/com/demcha/compose/document/api/DocumentSessionTest.java +++ b/src/test/java/com/demcha/compose/document/api/DocumentSessionTest.java @@ -129,8 +129,8 @@ void pageFlowShortcutShouldAttachRootWithoutDslFacade() throws Exception { assertThat(session.roots()).containsExactly(root); assertThat(root.children()).hasSize(1); - assertThat(root.children().getFirst()).isInstanceOf(ParagraphNode.class); - assertThat(((ParagraphNode) root.children().getFirst()).text()).isEqualTo("Shortcut paragraph"); + assertThat(root.children().get(0)).isInstanceOf(ParagraphNode.class); + assertThat(((ParagraphNode) root.children().get(0)).text()).isEqualTo("Shortcut paragraph"); assertThat(session.layoutGraph().totalPages()).isEqualTo(1); } } @@ -148,13 +148,13 @@ void composeShortcutShouldBatchDslCallsWithoutManualBuilderLifecycle() throws Ex .addText("Composed paragraph"))); assertThat(session.roots()).hasSize(1); - assertThat(session.roots().getFirst()).isInstanceOf(ContainerNode.class); + assertThat(session.roots().get(0)).isInstanceOf(ContainerNode.class); - ContainerNode root = (ContainerNode) session.roots().getFirst(); + ContainerNode root = (ContainerNode) session.roots().get(0); assertThat(root.name()).isEqualTo("ComposeShortcut"); assertThat(root.children()).hasSize(1); - assertThat(root.children().getFirst()).isInstanceOf(ParagraphNode.class); - assertThat(((ParagraphNode) root.children().getFirst()).text()).isEqualTo("Composed paragraph"); + assertThat(root.children().get(0)).isInstanceOf(ParagraphNode.class); + assertThat(((ParagraphNode) root.children().get(0)).text()).isEqualTo("Composed paragraph"); assertThat(session.layoutGraph().totalPages()).isEqualTo(1); } } @@ -175,13 +175,13 @@ void namedSectionShortcutShouldAvoidRepeatingSectionNameInsideNestedBuilder() th .build(); assertThat(root.children()).hasSize(1); - assertThat(root.children().getFirst()).isInstanceOf(SectionNode.class); + assertThat(root.children().get(0)).isInstanceOf(SectionNode.class); - SectionNode section = (SectionNode) root.children().getFirst(); + SectionNode section = (SectionNode) root.children().get(0); assertThat(section.name()).isEqualTo("Profile"); assertThat(section.children()).hasSize(1); - assertThat(section.children().getFirst()).isInstanceOf(ParagraphNode.class); - assertThat(((ParagraphNode) section.children().getFirst()).text()).isEqualTo("Senior platform engineer"); + assertThat(section.children().get(0)).isInstanceOf(ParagraphNode.class); + assertThat(((ParagraphNode) section.children().get(0)).text()).isEqualTo("Senior platform engineer"); assertThat(session.layoutGraph().totalPages()).isEqualTo(1); } } @@ -210,14 +210,14 @@ void styledSectionShouldEmitDecorationBeforeChildFragments() throws Exception { LayoutGraph graph = session.layoutGraph(); assertThat(graph.fragments()).hasSize(2); - assertThat(graph.fragments().getFirst().path()).contains("InfoCard[0]"); - assertThat(graph.fragments().getFirst().payload()) + assertThat(graph.fragments().get(0).path()).contains("InfoCard[0]"); + assertThat(graph.fragments().get(0).payload()) .isInstanceOf(ShapeFragmentPayload.class); assertThat(graph.fragments().get(1).payload()) .isInstanceOf(ParagraphFragmentPayload.class); ShapeFragmentPayload payload = - (ShapeFragmentPayload) graph.fragments().getFirst().payload(); + (ShapeFragmentPayload) graph.fragments().get(0).payload(); assertThat(payload.fillColor()).isEqualTo(fill.color()); assertThat(payload.stroke().strokeColor().color()).isEqualTo(DocumentColor.ROYAL_BLUE.color()); assertThat(payload.stroke().width()).isEqualTo(0.8); @@ -338,13 +338,13 @@ void moduleShortcutShouldBuildTitledSemanticBlocks() throws Exception { .build(); assertThat(root.children()).hasSize(1); - assertThat(root.children().getFirst()).isInstanceOf(SectionNode.class); + assertThat(root.children().get(0)).isInstanceOf(SectionNode.class); - SectionNode module = (SectionNode) root.children().getFirst(); + SectionNode module = (SectionNode) root.children().get(0); assertThat(module.name()).isEqualTo("TechnicalSkills"); assertThat(module.children()).hasSize(5); - assertThat(module.children().getFirst()).isInstanceOf(ParagraphNode.class); - assertThat(((ParagraphNode) module.children().getFirst()).text()).isEqualTo("Technical Skills"); + assertThat(module.children().get(0)).isInstanceOf(ParagraphNode.class); + assertThat(((ParagraphNode) module.children().get(0)).text()).isEqualTo("Technical Skills"); assertThat(module.children().get(1)).isInstanceOf(ParagraphNode.class); assertThat(module.children().get(2)).isInstanceOf(ListNode.class); assertThat(module.children().get(3)).isInstanceOf(ListNode.class); @@ -416,11 +416,11 @@ void tableConvenienceMethodsShouldBuildHeaderAndBulkRows() throws Exception { .build(); assertThat(root.children()).hasSize(1); - assertThat(root.children().getFirst()).isInstanceOf(TableNode.class); + assertThat(root.children().get(0)).isInstanceOf(TableNode.class); - TableNode table = (TableNode) root.children().getFirst(); + TableNode table = (TableNode) root.children().get(0); assertThat(table.rows()).hasSize(3); - assertThat(table.rows().getFirst()) + assertThat(table.rows().get(0)) .extracting(DocumentTableCell::lines) .containsExactly(List.of("Role"), List.of("Owner"), List.of("Status")); assertThat(table.rows().get(1)) @@ -467,16 +467,16 @@ void paragraphShouldSplitAcrossPagesAndPdfShouldMatchPageCount() throws Exceptio LayoutGraph graph = session.layoutGraph(); assertThat(graph.totalPages()).isGreaterThan(1); assertThat(graph.nodes()).hasSize(1); - assertThat(graph.nodes().getFirst().startPage()).isEqualTo(0); - assertThat(graph.nodes().getFirst().endPage()).isGreaterThan(0); + assertThat(graph.nodes().get(0).startPage()).isEqualTo(0); + assertThat(graph.nodes().get(0).endPage()).isGreaterThan(0); assertThat(graph.fragments()).extracting(PlacedFragment::pageIndex).doesNotHaveDuplicates(); assertThat(graph.fragments()).allSatisfy(fragment -> assertThat(fragment.payload()).isInstanceOf(ParagraphFragmentPayload.class)); ParagraphFragmentPayload firstPayload = - (ParagraphFragmentPayload) graph.fragments().getFirst().payload(); + (ParagraphFragmentPayload) graph.fragments().get(0).payload(); ParagraphFragmentPayload lastPayload = - (ParagraphFragmentPayload) graph.fragments().getLast().payload(); + (ParagraphFragmentPayload) graph.fragments().get(graph.fragments().size() - 1).payload(); assertThat(firstPayload.padding().top()).isEqualTo(4.0); assertThat(firstPayload.padding().bottom()).isEqualTo(0.0); assertThat(lastPayload.padding().top()).isEqualTo(0.0); @@ -558,7 +558,7 @@ void paragraphShouldPreserveExplicitNewlinesInPreparedFragments() throws Excepti assertThat(graph.fragments()).hasSize(1); ParagraphFragmentPayload payload = - (ParagraphFragmentPayload) graph.fragments().getFirst().payload(); + (ParagraphFragmentPayload) graph.fragments().get(0).payload(); assertThat(payload.lines()) .extracting(ParagraphLine::text) .containsExactly("First line", "", "Second line"); @@ -832,7 +832,7 @@ void guideLinesShouldRenderLegacyMarginPaddingAndBoxColors() throws Exception { try (PDDocument document = Loader.loadPDF(pdfBytes)) { BufferedImage image = new PDFRenderer(document).renderImageWithDPI(0, RENDER_DPI); - PlacedFragment fragment = graph.fragments().getFirst(); + PlacedFragment fragment = graph.fragments().get(0); assertThat(hasColorNear( image, @@ -884,7 +884,7 @@ void sessionGuideLinesShouldApplyToConveniencePdfOutputWithoutInvalidatingLayout try (PDDocument document = Loader.loadPDF(pdfBytes)) { BufferedImage image = new PDFRenderer(document).renderImageWithDPI(0, RENDER_DPI); - PlacedFragment fragment = graph.fragments().getFirst(); + PlacedFragment fragment = graph.fragments().get(0); assertThat(hasColorNear( image, @@ -1113,4 +1113,3 @@ private boolean isCloseColor(Color actual, Color expected, int tolerancePerChann && Math.abs(actual.getBlue() - expected.getBlue()) <= tolerancePerChannel; } } - diff --git a/src/test/java/com/demcha/compose/document/backend/fixed/pdf/PdfFixedLayoutBackendFeaturesTest.java b/src/test/java/com/demcha/compose/document/backend/fixed/pdf/PdfFixedLayoutBackendFeaturesTest.java index 473ff84d..b9d581cf 100644 --- a/src/test/java/com/demcha/compose/document/backend/fixed/pdf/PdfFixedLayoutBackendFeaturesTest.java +++ b/src/test/java/com/demcha/compose/document/backend/fixed/pdf/PdfFixedLayoutBackendFeaturesTest.java @@ -128,8 +128,8 @@ void shouldRenderCanonicalPdfChromeAndSemanticAnnotations() throws Exception { assertThat(firstChild.getTitle()).isEqualTo("Overview"); assertThat(document.getPage(0).getAnnotations()).isNotEmpty(); - assertThat(document.getPage(0).getAnnotations().getFirst()).isInstanceOf(PDAnnotationLink.class); - PDAnnotationLink annotation = (PDAnnotationLink) document.getPage(0).getAnnotations().getFirst(); + assertThat(document.getPage(0).getAnnotations().get(0)).isInstanceOf(PDAnnotationLink.class); + PDAnnotationLink annotation = (PDAnnotationLink) document.getPage(0).getAnnotations().get(0); assertThat(annotation.getAction()).isInstanceOf(PDActionURI.class); assertThat(((PDActionURI) annotation.getAction()).getURI()).isEqualTo("https://example.com/docs"); diff --git a/src/test/java/com/demcha/compose/document/dsl/AddLinkShortcutTest.java b/src/test/java/com/demcha/compose/document/dsl/AddLinkShortcutTest.java index 8fac1f28..97a1a912 100644 --- a/src/test/java/com/demcha/compose/document/dsl/AddLinkShortcutTest.java +++ b/src/test/java/com/demcha/compose/document/dsl/AddLinkShortcutTest.java @@ -34,8 +34,8 @@ void addLinkRendersClickableUriAndVisibleText() throws Exception { try (PDDocument document = Loader.loadPDF(pdfBytes)) { assertThat(document.getPage(0).getAnnotations()).isNotEmpty(); - assertThat(document.getPage(0).getAnnotations().getFirst()).isInstanceOf(PDAnnotationLink.class); - PDAnnotationLink link = (PDAnnotationLink) document.getPage(0).getAnnotations().getFirst(); + assertThat(document.getPage(0).getAnnotations().get(0)).isInstanceOf(PDAnnotationLink.class); + PDAnnotationLink link = (PDAnnotationLink) document.getPage(0).getAnnotations().get(0); assertThat(link.getAction()).isInstanceOf(PDActionURI.class); assertThat(((PDActionURI) link.getAction()).getURI()).isEqualTo("https://example.com/docs"); assertThat(new PDFTextStripper().getText(document)).contains("Documentation"); @@ -60,7 +60,7 @@ void addLinkAcceptsDocumentLinkOptions() throws Exception { } try (PDDocument document = Loader.loadPDF(pdfBytes)) { - PDAnnotationLink link = (PDAnnotationLink) document.getPage(0).getAnnotations().getFirst(); + PDAnnotationLink link = (PDAnnotationLink) document.getPage(0).getAnnotations().get(0); assertThat(((PDActionURI) link.getAction()).getURI()).isEqualTo("mailto:author@example.dev"); } } diff --git a/src/test/java/com/demcha/compose/document/dsl/ListBuilderNestedTest.java b/src/test/java/com/demcha/compose/document/dsl/ListBuilderNestedTest.java index 3236a1e0..9fb39e01 100644 --- a/src/test/java/com/demcha/compose/document/dsl/ListBuilderNestedTest.java +++ b/src/test/java/com/demcha/compose/document/dsl/ListBuilderNestedTest.java @@ -63,7 +63,7 @@ void nestedAddItemPromotesListToNestedRepresentationAndClearsFlatItems() { assertThat(node.items()).isEmpty(); assertThat(node.nestedItems()).hasSize(1); - ListItem parent = node.nestedItems().getFirst(); + ListItem parent = node.nestedItems().get(0); assertThat(parent.label()).isEqualTo("Parent"); assertThat(parent.children()).extracting(ListItem::label) .containsExactly("Child A", "Child B"); @@ -200,11 +200,11 @@ void markerOverridesAreBakedIntoNodeNestedItemsAtBuildTime() { .build(); assertThat(node.nestedItems()).hasSize(1); - ListItem top = node.nestedItems().getFirst(); + ListItem top = node.nestedItems().get(0); assertThat(top.marker()).isNotNull(); assertThat(top.marker().value()).isEqualTo("- "); assertThat(top.children()).hasSize(1); - assertThat(top.children().getFirst().marker().value()).isEqualTo("→ "); + assertThat(top.children().get(0).marker().value()).isEqualTo("→ "); } @Test @@ -251,10 +251,10 @@ void pageFlowBuilderWiresNestedListIntoContainerHierarchy() throws Exception { .build(); assertThat(root.children()).hasSize(1); - assertThat(root.children().getFirst()).isInstanceOf(ListNode.class); - ListNode listNode = (ListNode) root.children().getFirst(); + assertThat(root.children().get(0)).isInstanceOf(ListNode.class); + ListNode listNode = (ListNode) root.children().get(0); assertThat(listNode.nestedItems()).hasSize(1); - assertThat(listNode.nestedItems().getFirst().children()) + assertThat(listNode.nestedItems().get(0).children()) .extracting(ListItem::label) .containsExactly("a1"); } @@ -285,7 +285,7 @@ private static List firstLineTexts(DocumentSession session) { .map(PlacedFragment::payload) .filter(ParagraphFragmentPayload.class::isInstance) .map(ParagraphFragmentPayload.class::cast) - .map(payload -> payload.lines().getFirst().text()) + .map(payload -> payload.lines().get(0).text()) .toList(); } } diff --git a/src/test/java/com/demcha/compose/document/table/TableCellComposedContentTest.java b/src/test/java/com/demcha/compose/document/table/TableCellComposedContentTest.java index 313fa154..3283ab91 100644 --- a/src/test/java/com/demcha/compose/document/table/TableCellComposedContentTest.java +++ b/src/test/java/com/demcha/compose/document/table/TableCellComposedContentTest.java @@ -74,7 +74,7 @@ void composedCellEmitsChildParagraphFragmentInsideTableLayout() throws Exception assertThat(paragraphFragments).hasSize(1); ParagraphFragmentPayload childPayload = (ParagraphFragmentPayload) - paragraphFragments.getFirst().payload(); + paragraphFragments.get(0).payload(); // The paragraph wraps within the cell's inner width, so the // full sentence is split across two visual lines. Combine // them before checking the rendered text. diff --git a/src/test/java/com/demcha/compose/document/templates/support/common/SessionTemplateComposeTargetTest.java b/src/test/java/com/demcha/compose/document/templates/support/common/SessionTemplateComposeTargetTest.java index 2b2d6e0b..53687372 100644 --- a/src/test/java/com/demcha/compose/document/templates/support/common/SessionTemplateComposeTargetTest.java +++ b/src/test/java/com/demcha/compose/document/templates/support/common/SessionTemplateComposeTargetTest.java @@ -63,14 +63,14 @@ void addModuleShouldAttachSectionNodeInsteadOfFlatteningBlocks() throws Exceptio target.finishDocument(); assertThat(session.roots()).hasSize(1); - DocumentNode rootNode = session.roots().getFirst(); + DocumentNode rootNode = session.roots().get(0); assertThat(rootNode).isInstanceOf(ContainerNode.class); ContainerNode root = (ContainerNode) rootNode; assertThat(root.children()).hasSize(1); - assertThat(root.children().getFirst()).isInstanceOf(SectionNode.class); + assertThat(root.children().get(0)).isInstanceOf(SectionNode.class); - SectionNode module = (SectionNode) root.children().getFirst(); + SectionNode module = (SectionNode) root.children().get(0); assertThat(module.name()).isEqualTo("TechnicalSkills"); assertThat(module.children()).hasSize(3); assertThat(module.children().get(0)).isInstanceOf(ParagraphNode.class); @@ -111,8 +111,8 @@ void customBlocksInsideModuleShouldRenderIntoTheModuleSection() throws Exception Margin.zero())))))); target.finishDocument(); - ContainerNode root = (ContainerNode) session.roots().getFirst(); - SectionNode module = (SectionNode) root.children().getFirst(); + ContainerNode root = (ContainerNode) session.roots().get(0); + SectionNode module = (SectionNode) root.children().get(0); assertThat(module.children()).hasSize(2); assertThat(module.children().get(1)).isInstanceOf(ParagraphNode.class); diff --git a/src/test/java/com/demcha/compose/engine/core/EngineComposerHarnessLayoutSnapshotTest.java b/src/test/java/com/demcha/compose/engine/core/EngineComposerHarnessLayoutSnapshotTest.java index af57f151..5056fd28 100644 --- a/src/test/java/com/demcha/compose/engine/core/EngineComposerHarnessLayoutSnapshotTest.java +++ b/src/test/java/com/demcha/compose/engine/core/EngineComposerHarnessLayoutSnapshotTest.java @@ -155,7 +155,7 @@ private com.demcha.compose.engine.components.content.text.LineTextData firstLine BlockTextData textData = blockEntity.getComponent(BlockTextData.class) .orElseThrow(() -> new AssertionError("Missing BlockTextData for " + entityName)); - return textData.lines().getFirst(); + return textData.lines().get(0); } private EntityManager entityManager(EngineComposerHarness composer) throws Exception { diff --git a/src/test/java/com/demcha/compose/engine/render/pdf/handlers/PdfLinkRenderHandlerTest.java b/src/test/java/com/demcha/compose/engine/render/pdf/handlers/PdfLinkRenderHandlerTest.java index 95ef4c24..85c330ee 100644 --- a/src/test/java/com/demcha/compose/engine/render/pdf/handlers/PdfLinkRenderHandlerTest.java +++ b/src/test/java/com/demcha/compose/engine/render/pdf/handlers/PdfLinkRenderHandlerTest.java @@ -42,9 +42,9 @@ void shouldCreatePageAndAttachUriAnnotation() throws Exception { assertThat(rendered).isTrue(); assertThat(document.getNumberOfPages()).isEqualTo(1); assertThat(document.getPage(0).getAnnotations()).hasSize(1); - assertThat(document.getPage(0).getAnnotations().getFirst()).isInstanceOf(PDAnnotationLink.class); + assertThat(document.getPage(0).getAnnotations().get(0)).isInstanceOf(PDAnnotationLink.class); - PDAnnotationLink annotation = (PDAnnotationLink) document.getPage(0).getAnnotations().getFirst(); + PDAnnotationLink annotation = (PDAnnotationLink) document.getPage(0).getAnnotations().get(0); assertThat(annotation.getAction()).isInstanceOf(PDActionURI.class); assertThat(((PDActionURI) annotation.getAction()).getURI()).isEqualTo("https://example.com/profile"); assertThat(annotation.getRectangle().getWidth()).isEqualTo(128.0f); diff --git a/src/test/java/com/demcha/compose/engine/render/pdf/handlers/PdfTableRowRenderHandlerTest.java b/src/test/java/com/demcha/compose/engine/render/pdf/handlers/PdfTableRowRenderHandlerTest.java index 520cd0f6..4c402eeb 100644 --- a/src/test/java/com/demcha/compose/engine/render/pdf/handlers/PdfTableRowRenderHandlerTest.java +++ b/src/test/java/com/demcha/compose/engine/render/pdf/handlers/PdfTableRowRenderHandlerTest.java @@ -141,9 +141,9 @@ void shouldResolveTopAndMiddleAnchorsForMultilineContent() { assertThat(topLines).hasSize(2); assertThat(middleLines).hasSize(2); - assertThat(topLines.getFirst().baselineY()).isGreaterThan(middleLines.getFirst().baselineY()); - assertThat(topLines.getFirst().baselineY() - topLines.get(1).baselineY()).isEqualTo(lineHeight); - assertThat(middleLines.getFirst().baselineY() - middleLines.get(1).baselineY()).isEqualTo(lineHeight); + assertThat(topLines.get(0).baselineY()).isGreaterThan(middleLines.get(0).baselineY()); + assertThat(topLines.get(0).baselineY() - topLines.get(1).baselineY()).isEqualTo(lineHeight); + assertThat(middleLines.get(0).baselineY() - middleLines.get(1).baselineY()).isEqualTo(lineHeight); } @Test @@ -171,6 +171,6 @@ void shouldApplyCellLineSpacingWhenResolvingMultilineContent() { double lineHeight = font.getLineHeight(cell.style().textStyle()); assertThat(lines).hasSize(2); - assertThat(lines.getFirst().baselineY() - lines.get(1).baselineY()).isEqualTo(lineHeight + lineSpacing); + assertThat(lines.get(0).baselineY() - lines.get(1).baselineY()).isEqualTo(lineHeight + lineSpacing); } } diff --git a/src/test/java/com/demcha/compose/testsupport/engine/assembly/TableBuilderTest.java b/src/test/java/com/demcha/compose/testsupport/engine/assembly/TableBuilderTest.java index f07eb149..33d0efbe 100644 --- a/src/test/java/com/demcha/compose/testsupport/engine/assembly/TableBuilderTest.java +++ b/src/test/java/com/demcha/compose/testsupport/engine/assembly/TableBuilderTest.java @@ -41,8 +41,8 @@ void shouldComputeFixedAndAutoColumnWidths() throws Exception { TableRowData rowData = firstRow.getComponent(TableRowData.class).orElseThrow(); assertThat(layoutData.columnWidths()).hasSize(2); - assertThat(layoutData.columnWidths().getFirst()).isEqualTo(120.0); - assertThat(rowData.cells().getFirst().width()).isEqualTo(120.0); + assertThat(layoutData.columnWidths().get(0)).isEqualTo(120.0); + assertThat(rowData.cells().get(0).width()).isEqualTo(120.0); assertThat(rowData.cells().get(1).width()).isEqualTo(layoutData.columnWidths().get(1)); assertThat(layoutData.finalWidth()).isEqualTo(layoutData.columnWidths().stream().mapToDouble(Double::doubleValue).sum()); } @@ -119,13 +119,13 @@ void shouldApplyStylePrecedenceDefaultThenColumnThenRow() throws Exception { .getComponent(TableRowData.class) .orElseThrow() .cells() - .getFirst(); + .get(0); TableResolvedCell secondRowFirstCell = child(table, 1) .getComponent(TableRowData.class) .orElseThrow() .cells() - .getFirst(); + .get(0); assertThat(firstRowFirstCell.style().fillColor()).isEqualTo(ComponentColor.RED); assertThat(firstRowFirstCell.style().padding()).isEqualTo(Padding.of(8)); @@ -181,7 +181,7 @@ void shouldApplyCellOverrideAfterDefaultColumnAndRowStyles() throws Exception { .getComponent(TableRowData.class) .orElseThrow() .cells() - .getFirst(); + .get(0); assertThat(firstCell.style().fillColor()).isEqualTo(ComponentColor.GREEN); assertThat(firstCell.style().padding()).isEqualTo(Padding.of(8)); @@ -203,7 +203,7 @@ void shouldPreserveStringRowApiAsSingleLineCellSpec() throws Exception { .getComponent(TableRowData.class) .orElseThrow() .cells() - .getFirst(); + .get(0); assertThat(firstCell.lines()).containsExactly("Alpha"); } @@ -228,13 +228,13 @@ void shouldMeasureMultilineCellsUsingLongestLineAndLineCount() throws Exception .getComponent(TableRowData.class) .orElseThrow() .cells() - .getFirst(); + .get(0); TableResolvedCell singleLineCell = child(longestLineTable, 0) .getComponent(TableRowData.class) .orElseThrow() .cells() - .getFirst(); + .get(0); double paddingVertical = TableCellLayoutStyle.DEFAULT.padding().vertical(); double expectedMultilineHeight = (2 * (singleLineCell.height() - paddingVertical)) + paddingVertical; @@ -271,12 +271,12 @@ void shouldIncludeCellLineSpacingInMultilineCellHeight() throws Exception { .getComponent(TableRowData.class) .orElseThrow() .cells() - .getFirst(); + .get(0); TableResolvedCell singleLineCell = child(singleLineTable, 0) .getComponent(TableRowData.class) .orElseThrow() .cells() - .getFirst(); + .get(0); assertThat(multilineCell.height()).isEqualTo((2 * singleLineCell.height()) + 2.5); } @@ -332,7 +332,7 @@ void shouldCreateStableRowAndCellNamesFromTableName() throws Exception { .build(); Entity row = child(table, 0); - TableResolvedCell cell = row.getComponent(TableRowData.class).orElseThrow().cells().getFirst(); + TableResolvedCell cell = row.getComponent(TableRowData.class).orElseThrow().cells().get(0); assertThat(row.getComponent(EntityName.class)).hasValue(new EntityName("Orders__row_0")); assertThat(cell.name()).isEqualTo("Orders__row_0__cell_0"); @@ -353,7 +353,7 @@ void shouldAssignBordersToCurrentCellsSoTrailingPageLinesStayVisible() throws Ex .getComponent(TableRowData.class) .orElseThrow() .cells() - .getFirst(); + .get(0); TableResolvedCell firstRowSecondCell = child(table, 0) .getComponent(TableRowData.class) @@ -419,7 +419,7 @@ void shouldLetCellOwnTopBoundaryWhenPreviousRowStrokeIsInvisible() throws Except .getComponent(TableRowData.class) .orElseThrow() .cells() - .getFirst(); + .get(0); assertThat(secondRowCell.borderSides()).containsExactlyInAnyOrder(Side.TOP, Side.LEFT, Side.RIGHT, Side.BOTTOM); assertThat(secondRowCell.fillInsets().top()).isEqualTo(1.0); diff --git a/src/test/java/com/demcha/testing/layout/LayoutSnapshotExtractorTest.java b/src/test/java/com/demcha/testing/layout/LayoutSnapshotExtractorTest.java index 2ab5114f..23c0b528 100644 --- a/src/test/java/com/demcha/testing/layout/LayoutSnapshotExtractorTest.java +++ b/src/test/java/com/demcha/testing/layout/LayoutSnapshotExtractorTest.java @@ -162,8 +162,8 @@ void shouldCaptureTotalPagesFromPlacementSpan() { assertThat(snapshot.totalPages()).isEqualTo(3); assertThat(snapshot.nodes()).hasSize(1); - assertThat(snapshot.nodes().getFirst().startPage()).isEqualTo(2); - assertThat(snapshot.nodes().getFirst().endPage()).isEqualTo(0); + assertThat(snapshot.nodes().get(0).startPage()).isEqualTo(2); + assertThat(snapshot.nodes().get(0).endPage()).isEqualTo(0); } @Test diff --git a/src/test/java/com/demcha/testing/visual/TableRowSpanDemoTest.java b/src/test/java/com/demcha/testing/visual/TableRowSpanDemoTest.java index ece28e44..716b0522 100644 --- a/src/test/java/com/demcha/testing/visual/TableRowSpanDemoTest.java +++ b/src/test/java/com/demcha/testing/visual/TableRowSpanDemoTest.java @@ -138,7 +138,7 @@ void middleColumnRowSpanDoesNotExposePageBackgroundAtCellJoin() throws Exception BufferedImage image = PdfVisualRegression.standard() .renderScale(1.0f) .renderPages(pdf) - .getFirst(); + .get(0); List rowFragments = graph.fragments().stream() .filter(fragment -> fragment.payload() instanceof TableRowFragmentPayload) @@ -146,7 +146,7 @@ void middleColumnRowSpanDoesNotExposePageBackgroundAtCellJoin() throws Exception PlacedFragment secondRow = rowFragments.get(1); TableRowFragmentPayload secondRowPayload = (TableRowFragmentPayload) secondRow.payload(); - TableResolvedCell leftCell = secondRowPayload.cells().getFirst(); + TableResolvedCell leftCell = secondRowPayload.cells().get(0); double rowTop = secondRow.y() + secondRow.height(); double leftCellRight = secondRow.x() + leftCell.x() + leftCell.width(); @@ -189,7 +189,7 @@ void middleColumnRowSpanDoesNotDrawSeparatorsOutsideTableEdge() throws Exception BufferedImage image = PdfVisualRegression.standard() .renderScale(1.0f) .renderPages(pdf) - .getFirst(); + .get(0); List rowFragments = graph.fragments().stream() .filter(fragment -> fragment.payload() instanceof TableRowFragmentPayload)