From 5b7d6d4ff039bb171a6913a7e4319bba92d598ea Mon Sep 17 00:00:00 2001
From: Joseph Ottinger
Date: Fri, 8 May 2026 16:31:34 -0400
Subject: [PATCH 1/7] Updates module dependencies
* Main focus is on bytebuddy, BUT
* Also updates other dependencies to update CVE issues
---
pom.xml | 34 +++++++++++++++++++++++-----------
1 file changed, 23 insertions(+), 11 deletions(-)
diff --git a/pom.xml b/pom.xml
index f1dd0317..902defaa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -46,27 +46,28 @@
21
- 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
From 2e32eb5d94388fb5b6ebedb5b1e243996e099de5 Mon Sep 17 00:00:00 2001
From: Joseph Ottinger
Date: Fri, 8 May 2026 17:54:05 -0400
Subject: [PATCH 2/7] Updating benchmarks dependencies for CI/CD
---
benchmarks/pom.xml | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
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
From af1f82af7053e61a2532976ad3266c2474d27d50 Mon Sep 17 00:00:00 2001
From: Joseph Ottinger
Date: Fri, 8 May 2026 17:07:04 -0400
Subject: [PATCH 3/7] Updates code to conform to java 17
* Does not fully address submodules, as they're out of scope and should be backwards compatible anyway
* Migrates pattern-matched switch to alternatives
* Migrates new List access methods to alternatives
* Changes PreviewCompiler in src/test/java to use Maven-specified compiler version
---
pom.xml | 2 +-
.../PdfShapeClipBeginRenderHandler.java | 16 +++--
.../document/layout/TextFlowSupport.java | 6 +-
.../definitions/ShapeContainerDefinition.java | 15 ++--
.../document/templates/components/Module.java | 29 +++++---
.../templates/cv/presets/BlueBanner.java | 52 ++++++--------
.../templates/cv/presets/BoxedSections.java | 64 ++++++++---------
.../cv/presets/CenteredHeadline.java | 52 ++++++--------
.../templates/cv/presets/ClassicSerif.java | 23 +++---
.../templates/cv/presets/CompactMono.java | 23 +++---
.../templates/cv/presets/EditorialBlue.java | 71 +++++++++----------
.../cv/presets/EngineeringResume.java | 20 +++---
.../templates/cv/presets/Executive.java | 40 +++++------
.../templates/cv/presets/MonogramSidebar.java | 29 ++++----
.../templates/cv/presets/NordicClean.java | 20 +++---
.../document/templates/cv/presets/Panel.java | 40 +++++------
.../templates/cv/presets/SidebarPortrait.java | 51 +++++++------
.../templates/cv/presets/TimelineMinimal.java | 23 +++---
.../com/demcha/compose/engine/font/Font.java | 6 +-
.../layout/container/ContainerAligner.java | 10 ++-
.../compose/devtool/GraphComposeDevTool.java | 23 +++---
.../compose/devtool/PreviewCompiler.java | 50 ++++++++++++-
.../compose/devtool/RecursivePathWatcher.java | 6 +-
.../document/api/DocumentListNodeTest.java | 18 ++---
.../document/api/DocumentSessionTest.java | 55 +++++++-------
.../PdfFixedLayoutBackendFeaturesTest.java | 4 +-
.../document/dsl/AddLinkShortcutTest.java | 6 +-
.../document/dsl/ListBuilderNestedTest.java | 14 ++--
.../table/TableCellComposedContentTest.java | 2 +-
.../SessionTemplateComposeTargetTest.java | 10 +--
...gineComposerHarnessLayoutSnapshotTest.java | 2 +-
.../handlers/PdfLinkRenderHandlerTest.java | 4 +-
.../PdfTableRowRenderHandlerTest.java | 8 +--
.../engine/assembly/TableBuilderTest.java | 26 +++----
.../layout/LayoutSnapshotExtractorTest.java | 4 +-
.../testing/visual/TableRowSpanDemoTest.java | 6 +-
36 files changed, 435 insertions(+), 395 deletions(-)
diff --git a/pom.xml b/pom.xml
index 902defaa..484a2cd1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,7 +43,7 @@
UTF-8
- 21
+ 17
0.64.8
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)
From 11d48d1fe69efa4945f32b386a0754ee89f5daa8 Mon Sep 17 00:00:00 2001
From: DemchaAV
Date: Sat, 9 May 2026 09:02:34 +0100
Subject: [PATCH 4/7] v1.7 prep: complete the Java 17 baseline migration
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Builds on @jottinger's three commits cherry-picked above:
- 5b7d6d4f Updates module dependencies (=#11 base)
- 2e32eb5d Updating benchmarks dependencies (=#11 follow-up)
- af1f82af Updates code to conform to java 17 (=#12)
Follow-up gaps closed by this commit:
- examples/pom.xml — flip 21 → 17 (only
the root and benchmarks POMs were flipped upstream).
- examples/.../WeeklyScheduleRenderer.java — rewrite the two
switch-with-deconstruction blocks (DayShift / Half) plus the
StatusFill instanceof-with-record-pattern check as Java
17–compatible instanceof if-else chains. Records still expose
status() / shift() / lunch() / dinner() so call sites are a
one-line refactor each.
- benchmarks/.../BenchmarkMedianTool.java — 9× List.getFirst() →
.get(0) (List.getFirst is JEP 431, Java 21).
- benchmarks/.../ComparativeBenchmark.java — Thread.threadId() →
Thread.getId() (threadId is Java 19+).
- .github/workflows/ci.yml — actions/setup-java java-version
'21' → '17' across all five jobs.
- README.md — Java-21 badge → Java-17+.
- CHANGELOG.md — new "## v1.7.0 — Planned" entry detailing the
Java 17 baseline, dependency refresh, and Maven Central
distribution plan; credits @jottinger upfront.
Local verification on this worktree:
- ./mvnw -B -ntp clean verify -pl . → 785 / 0 / 0 / 0
- ./mvnw -B -ntp -f examples/pom.xml clean compile → BUILD SUCCESS
- ./mvnw -B -ntp -f benchmarks/pom.xml clean compile → BUILD SUCCESS
Note: local Maven runs on Java 21 with -release 17, which catches
compile-time API drift but not runtime-only Java 21 hooks. The
draft PR's CI run on Temurin JDK 17 is the truth source for that.
Co-Authored-By: Joseph Ottinger
---
.github/workflows/ci.yml | 20 ++++-----
CHANGELOG.md | 38 ++++++++++++++++
README.md | 2 +-
.../demcha/compose/BenchmarkMedianTool.java | 18 ++++----
.../demcha/compose/ComparativeBenchmark.java | 4 +-
examples/pom.xml | 2 +-
.../support/WeeklyScheduleRenderer.java | 45 ++++++++++---------
7 files changed, 86 insertions(+), 43 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a8764b82..7a988da6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -26,11 +26,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
@@ -51,11 +51,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: Build and run tests
@@ -76,11 +76,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 +114,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 +153,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..7886f741 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,44 @@
All notable changes to GraphCompose are documented here. Versions
follow semantic versioning; release dates are ISO 8601.
+## v1.7.0 — Planned
+
+The "broad reach" release. Drops the Java 21 baseline in favour of
+Java 17+ to widen enterprise adoption without losing any author-
+facing surface. 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`.
+ Author-facing API is unchanged; the engine source drops
+ Java 21–only constructs (switch-with-type-patterns, `List.getFirst()`)
+ 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`.
+
+### Distribution
+
+- **Maven Central** is the planned primary distribution channel
+ ([#7](https://github.com/DemchaAV/GraphCompose/issues/7)). JitPack
+ stays as a documented fallback.
+
+### Non-goals
+
+- No public-API breakage on the engine surface.
+- No Templates v3 work — Templates v2 remains the canonical author
+ surface.
+
+---
+
## 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 @@
-
+
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) {
From 2b1f64f1c2a783314b7f78d6199c302e4bc0ee55 Mon Sep 17 00:00:00 2001
From: DemchaAV
Date: Sat, 9 May 2026 09:09:58 +0100
Subject: [PATCH 5/7] ci: run on PRs to any base + on develop pushes (so v1.7
prep PR triggers)
---
.github/workflows/ci.yml | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7a988da6..7730abd0 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'
From eda2169568964989479b077cdd80c842c3892523 Mon Sep 17 00:00:00 2001
From: DemchaAV
Date: Sat, 9 May 2026 09:30:08 +0100
Subject: [PATCH 6/7] docs(changelog): reframe Java 17 baseline as v1.6.1
(patch), not v1.7
SemVer-correct framing: this PR adds no public API, doesn't break
v1.6.0 callers, and is mostly compatibility expansion + dependency
hygiene. That's a patch, not a minor. Maven Central distribution
(the only true minor-level change in the prior framing) stays
queued for the actual v1.7.0 alongside the JMH migration.
---
CHANGELOG.md | 43 ++++++++++++++++++++++---------------------
1 file changed, 22 insertions(+), 21 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7886f741..a2b0d10d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,22 +3,28 @@
All notable changes to GraphCompose are documented here. Versions
follow semantic versioning; release dates are ISO 8601.
-## v1.7.0 — Planned
-
-The "broad reach" release. Drops the Java 21 baseline in favour of
-Java 17+ to widen enterprise adoption without losing any author-
-facing surface. Co-developed with external contributor
-[@jottinger](https://github.com/jottinger) ([#8](https://github.com/DemchaAV/GraphCompose/issues/8),
+## 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`
+- **Java 17 baseline.** `` flips from `21`
to `17` across `pom.xml`, `examples/pom.xml`, and `benchmarks/pom.xml`.
- Author-facing API is unchanged; the engine source drops
- Java 21–only constructs (switch-with-type-patterns, `List.getFirst()`)
- in favour of Java 17–compatible forms. CI runs against
- Temurin JDK 17.
+ 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`,
@@ -27,17 +33,12 @@ facing surface. Co-developed with external contributor
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`.
-### Distribution
-
-- **Maven Central** is the planned primary distribution channel
- ([#7](https://github.com/DemchaAV/GraphCompose/issues/7)). JitPack
- stays as a documented fallback.
-
-### Non-goals
+### Looking ahead
-- No public-API breakage on the engine surface.
-- No Templates v3 work — Templates v2 remains the canonical author
- surface.
+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.
---
From bcb089cf6b9a5b8d74336f8f62ad174a80dd4ed1 Mon Sep 17 00:00:00 2001
From: DemchaAV
Date: Sat, 9 May 2026 09:42:32 +0100
Subject: [PATCH 7/7] ci: matrix Build+test across JDK 17 / 21 / 25
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Each push and PR now runs the full canonical test suite (~819
tests, mvnw verify) against three JDKs in parallel:
- JDK 17 — the baseline target (compiler.release=17). Lowest
common denominator; what enterprise stacks deploy on.
- JDK 21 — the previous baseline. Validates that bytecode-17
still runs cleanly on the post-Java-21 sequenced-collections
JVM after the getFirst()/getLast()/threadId() rewrites.
- JDK 25 — the current LTS. Validates the byte-buddy 1.18.7 +
mockito 5.23 dependency bumps that #10 motivated; without
those, mockito breaks on the JDK 25 access rules.
fail-fast: false so a regression on one JDK doesn't mask the
status of the others. Javadoc generation runs only on JDK 17 —
its output is JVM-independent, no need to triple-bake.
The other jobs (Architecture+Documentation Guards, Examples
Generation Smoke Test, Performance Smoke Check, Weekly Benchmark
Diff) stay single-JDK on 17 — they're fast enough that adding
parallel JDKs adds no signal, and benchmark numbers across JVMs
would be noisy comparisons rather than a regression gate.
---
.github/workflows/ci.yml | 20 +++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7730abd0..ee85d40a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -42,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
@@ -53,17 +63,21 @@ jobs:
- name: Check out repository
uses: actions/checkout@v4
- - name: Set up Temurin JDK 17
+ - name: Set up Temurin JDK ${{ matrix.java }}
uses: actions/setup-java@v4
with:
distribution: temurin
- java-version: '17'
+ 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: