From 5519a6d9a50a05eb51256d964e7904ac2a1477c3 Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Thu, 12 Feb 2026 18:49:45 +0100 Subject: [PATCH 01/22] use JDK 17 in CI/CD --- .github/workflows/build.yml | 4 ++-- .github/workflows/release.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 03a79ab..c76377f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,10 +31,10 @@ jobs: timezoneMacos: "Europe/Berlin" timezoneWindows: "W. Europe Standard Time" - - name: Set up JDK 21 + - name: Set up JDK 17 uses: actions/setup-java@v5.2.0 with: - java-version: '21' + java-version: '17' distribution: 'temurin' cache: 'gradle' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5c59197..ab5e93c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,10 +30,10 @@ jobs: echo "gradle.properties criado:" cat gradle.properties - - name: Set up JDK 21 + - name: Set up JDK 17 uses: actions/setup-java@v5.2.0 with: - java-version: '21' + java-version: '17' distribution: 'temurin' cache: 'gradle' From 78de567beb3c033193c701dc6ab6df46d832f87e Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Thu, 12 Feb 2026 18:50:12 +0100 Subject: [PATCH 02/22] remove leading space in .gitattributes --- .gitattributes => .gitattributes | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .gitattributes => .gitattributes (100%) diff --git a/ .gitattributes b/.gitattributes similarity index 100% rename from .gitattributes rename to .gitattributes From fe951bfb49a93d2d73a4cc8456248494f9804301 Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Thu, 12 Feb 2026 18:51:13 +0100 Subject: [PATCH 03/22] remove .idea folder --- .idea/.gitignore | 8 -------- .idea/.name | 1 - .idea/aws.xml | 11 ----------- .idea/dictionaries/project.xml | 7 ------- .idea/gradle.xml | 17 ----------------- .idea/misc.xml | 10 ---------- .idea/sonarlint.xml | 11 ----------- .idea/vcs.xml | 6 ------ 8 files changed, 71 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/.name delete mode 100644 .idea/aws.xml delete mode 100644 .idea/dictionaries/project.xml delete mode 100644 .idea/gradle.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/sonarlint.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 5c147d9..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -JToon \ No newline at end of file diff --git a/.idea/aws.xml b/.idea/aws.xml deleted file mode 100644 index b63b642..0000000 --- a/.idea/aws.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml deleted file mode 100644 index 9923ee7..0000000 --- a/.idea/dictionaries/project.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - toon - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 2a65317..0000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 59f0034..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/sonarlint.xml b/.idea/sonarlint.xml deleted file mode 100644 index c78dfb6..0000000 --- a/.idea/sonarlint.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 281a6999a9e3b7d27730671a19d810c91d5d036c Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Thu, 12 Feb 2026 18:57:21 +0100 Subject: [PATCH 04/22] install spotbugs --- build.gradle | 37 +++++++++++++++++++++++++++++++++++++ spotbugs-exclude.xml | 27 +++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 spotbugs-exclude.xml diff --git a/build.gradle b/build.gradle index 03323a5..09f227f 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,7 @@ plugins { id 'maven-publish' id 'signing' id 'jacoco' + id 'com.github.spotbugs' version '6.0.15' } group = 'dev.toonformat' @@ -35,9 +36,44 @@ jacoco { reportsDirectory = layout.buildDirectory.dir('customJacocoReportDir') } +spotbugs { + toolVersion = '4.8.6' + excludeFilter = file('spotbugs-exclude.xml') + effort = "max" + reportLevel = "low" + reportsDir = layout.buildDirectory.dir('spotbugs') +} + +tasks.spotbugsMain { + reports { + html { + required = true + outputLocation = file("${spotbugs.reportsDir.get()}/spotbugs-main.html") + } + xml { + required = true + outputLocation = file("${spotbugs.reportsDir.get()}/spotbugs-main.xml") + } + } +} + +tasks.spotbugsTest { + reports { + html { + required = true + outputLocation = file("${spotbugs.reportsDir.get()}/spotbugs-test.html") + } + xml { + required = true + outputLocation = file("${spotbugs.reportsDir.get()}/spotbugs-test.xml") + } + } +} + dependencies { implementation 'tools.jackson.core:jackson-databind:3.0.4' implementation 'tools.jackson.module:jackson-module-afterburner:3.0.4' + compileOnly 'com.github.spotbugs:spotbugs-annotations:4.8.6' testImplementation platform('org.junit:junit-bom:6.0.2') testImplementation 'org.junit.jupiter:junit-jupiter' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' @@ -79,6 +115,7 @@ jacocoTestCoverageVerification { } } check.dependsOn jacocoTestReport +check.dependsOn spotbugsMain tasks.register('generateJavadoc', Javadoc) { description = 'Generates Javadoc HTML documentation in the docs/javadoc folder' diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml new file mode 100644 index 0000000..a8c7876 --- /dev/null +++ b/spotbugs-exclude.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + From 35cddf63e61d277c3ca16019a0a5870822a6b7c3 Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Thu, 12 Feb 2026 19:33:14 +0100 Subject: [PATCH 05/22] adding pmd --- build.gradle | 26 +++++++++ pmd-rules-test.xml | 133 +++++++++++++++++++++++++++++++++++++++++++++ pmd-rules.xml | 100 ++++++++++++++++++++++++++++++++++ 3 files changed, 259 insertions(+) create mode 100644 pmd-rules-test.xml create mode 100644 pmd-rules.xml diff --git a/build.gradle b/build.gradle index 09f227f..f74416d 100644 --- a/build.gradle +++ b/build.gradle @@ -4,6 +4,7 @@ plugins { id 'signing' id 'jacoco' id 'com.github.spotbugs' version '6.0.15' + id 'pmd' } group = 'dev.toonformat' @@ -70,6 +71,30 @@ tasks.spotbugsTest { } } +pmd { + toolVersion = '7.0.0' + ruleSetFiles = files('pmd-rules.xml') + ruleSets = [] // Disable default rulesets, use custom file only + consoleOutput = true + ignoreFailures = true +} + +tasks.pmdMain { + reports { + html.required = true + xml.required = true + } +} + +tasks.pmdTest { + ruleSetFiles = files('pmd-rules-test.xml') + ignoreFailures = true + reports { + html.required = true + xml.required = true + } +} + dependencies { implementation 'tools.jackson.core:jackson-databind:3.0.4' implementation 'tools.jackson.module:jackson-module-afterburner:3.0.4' @@ -116,6 +141,7 @@ jacocoTestCoverageVerification { } check.dependsOn jacocoTestReport check.dependsOn spotbugsMain +check.dependsOn pmdMain tasks.register('generateJavadoc', Javadoc) { description = 'Generates Javadoc HTML documentation in the docs/javadoc folder' diff --git a/pmd-rules-test.xml b/pmd-rules-test.xml new file mode 100644 index 0000000..19628b4 --- /dev/null +++ b/pmd-rules-test.xml @@ -0,0 +1,133 @@ + + + + + PMD ruleset for JToon project + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pmd-rules.xml b/pmd-rules.xml new file mode 100644 index 0000000..4c41fed --- /dev/null +++ b/pmd-rules.xml @@ -0,0 +1,100 @@ + + + + + PMD ruleset for JToon project + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 71f8a6c6c893fbb89aacb695f01e26bc203548cf Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Thu, 12 Feb 2026 19:33:38 +0100 Subject: [PATCH 06/22] running PMD and fix the warnnings --- checkstyle.xml | 84 +++++++++++++++++++ spotbugs-exclude.xml | 28 +++++++ .../jtoon/decoder/DecodeContext.java | 2 +- .../jtoon/decoder/DecodeHelper.java | 2 +- .../toonformat/jtoon/decoder/KeyDecoder.java | 27 +++--- .../jtoon/decoder/ListItemDecoder.java | 2 +- .../jtoon/decoder/ObjectDecoder.java | 9 +- .../jtoon/decoder/ValueDecoder.java | 2 +- .../jtoon/encoder/ArrayEncoder.java | 2 +- .../dev/toonformat/jtoon/encoder/Flatten.java | 4 +- .../jtoon/encoder/HeaderFormatter.java | 5 +- .../jtoon/encoder/ObjectEncoder.java | 42 +++++----- .../jtoon/normalizer/JsonNormalizer.java | 3 +- .../jtoon/util/ObjectMapperSingleton.java | 8 +- .../jtoon/util/StringValidator.java | 12 +-- 15 files changed, 172 insertions(+), 60 deletions(-) create mode 100644 checkstyle.xml diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 0000000..a691216 --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index a8c7876..78214ec 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -24,4 +24,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/dev/toonformat/jtoon/decoder/DecodeContext.java b/src/main/java/dev/toonformat/jtoon/decoder/DecodeContext.java index f319efe..dbb0b70 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/DecodeContext.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/DecodeContext.java @@ -23,7 +23,7 @@ public class DecodeContext { /** * Current line being decoded. */ - protected int currentLine = 0; + protected int currentLine; /** * Default constructor diff --git a/src/main/java/dev/toonformat/jtoon/decoder/DecodeHelper.java b/src/main/java/dev/toonformat/jtoon/decoder/DecodeHelper.java index f0db743..2a75b90 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/DecodeHelper.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/DecodeHelper.java @@ -84,7 +84,7 @@ private static int computeLeadingSpaces(String line, DecodeContext context) { * @return true or false depending on if the line is blank or not */ static boolean isBlankLine(String line) { - return line.trim().isEmpty(); + return line.isBlank(); } /** diff --git a/src/main/java/dev/toonformat/jtoon/decoder/KeyDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/KeyDecoder.java index 8d90be5..20151f6 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/KeyDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/KeyDecoder.java @@ -56,22 +56,23 @@ static void processKeyedArrayLine(Map result, String content, St */ static void expandPathIntoMap(Map current, String dottedKey, Object value, DecodeContext context) { String[] segments = dottedKey.split("\\."); + Map currentMap = current; // Navigate/create nested structure for (int i = 0; i < segments.length - 1; i++) { String segment = segments[i]; - Object existing = current.get(segment); + Object existing = currentMap.get(segment); if (existing == null) { // Create a new nested object Map nested = new LinkedHashMap<>(); - current.put(segment, nested); - current = nested; + currentMap.put(segment, nested); + currentMap = nested; } else if (existing instanceof Map) { // Use existing nested object @SuppressWarnings("unchecked") Map existingMap = (Map) existing; - current = existingMap; + currentMap = existingMap; } else { // Conflict: existing is not a Map if (context.options.strict()) { @@ -81,22 +82,24 @@ static void expandPathIntoMap(Map current, String dottedKey, Obj } // LWW: overwrite with new nested object Map nested = new LinkedHashMap<>(); - current.put(segment, nested); - current = nested; + currentMap.put(segment, nested); + currentMap = nested; } } // Set the final value String finalSegment = segments[segments.length - 1]; - Object existing = current.get(finalSegment); + Object existing = currentMap.get(finalSegment); DecodeHelper.checkFinalValueConflict(finalSegment, existing, value, context); // LWW: last write wins (always overwrite in non-strict, or if types match in // strict) - current.put(finalSegment, value); + currentMap.put(finalSegment, value); } + + /** * Processes a key-value line (e.g., "key: value"). * @@ -191,7 +194,7 @@ private static Object parseKeyValue(String value, int depth, DecodeContext conte } else { // If the value is empty, create an empty object; otherwise parse as primitive Object parsedValue; - if (value.trim().isEmpty()) { + if (value.isBlank()) { parsedValue = new LinkedHashMap<>(); } else { parsedValue = PrimitiveDecoder.parse(value); @@ -202,7 +205,7 @@ private static Object parseKeyValue(String value, int depth, DecodeContext conte } else { // If the value is empty, create an empty object; otherwise parse as primitive Object parsedValue; - if (value.trim().isEmpty()) { + if (value.isBlank()) { parsedValue = new LinkedHashMap<>(); } else { parsedValue = PrimitiveDecoder.parse(value); @@ -267,7 +270,7 @@ static Object parseKeyedArrayValue(Matcher keyedArray, String content, int depth String key = StringEscaper.unescape(originalKey); String arrayHeader = content.substring(keyedArray.group(1).length()); - var arrayValue = ArrayDecoder.parseArray(arrayHeader, depth, context); + List arrayValue = ArrayDecoder.parseArray(arrayHeader, depth, context); Map obj = new LinkedHashMap<>(); // Handle path expansion for array keys @@ -308,7 +311,7 @@ static boolean parseKeyedArrayField(String fieldContent, Map ite // For nested arrays in list items, default to comma delimiter if not specified Delimiter nestedArrayDelimiter = ArrayDecoder.extractDelimiterFromHeader(arrayHeader, context); - var arrayValue = ArrayDecoder.parseArrayWithDelimiter(arrayHeader, depth + 2, nestedArrayDelimiter, context); + List arrayValue = ArrayDecoder.parseArrayWithDelimiter(arrayHeader, depth + 2, nestedArrayDelimiter, context); // Handle path expansion for array keys if (shouldExpandKey(originalKey, context)) { diff --git a/src/main/java/dev/toonformat/jtoon/decoder/ListItemDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/ListItemDecoder.java index 2d5c118..1433081 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/ListItemDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/ListItemDecoder.java @@ -122,7 +122,7 @@ public static Object parseListItem(String content, int depth, DecodeContext cont Object parsedValue; // If no next line exists, handle a simple case if (context.currentLine >= context.lines.length) { - parsedValue = value.trim().isEmpty() ? new LinkedHashMap<>() : PrimitiveDecoder.parse(value); + parsedValue = value.isBlank() ? new LinkedHashMap<>() : PrimitiveDecoder.parse(value); } else { // List item is at depth + 1, so pass depth + 1 to parseObjectItemValue parsedValue = ObjectDecoder.parseObjectItemValue(value, depth + 1, context); diff --git a/src/main/java/dev/toonformat/jtoon/decoder/ObjectDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/ObjectDecoder.java index c1f23a1..430dbb0 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/ObjectDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/ObjectDecoder.java @@ -3,6 +3,7 @@ import dev.toonformat.jtoon.util.StringEscaper; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.regex.Matcher; @@ -124,7 +125,7 @@ private static void processRootKeyedArrayLine(Map objectMap, Str String key = StringEscaper.unescape(originalKey); String arrayHeader = content.substring(originalKey.length()); - var arrayValue = ArrayDecoder.parseArray(arrayHeader, depth, context); + List arrayValue = ArrayDecoder.parseArray(arrayHeader, depth, context); // Handle path expansion for array keys if (KeyDecoder.shouldExpandKey(originalKeyTrimmed, context)) { @@ -174,7 +175,7 @@ static Object parseFieldValue(String fieldValue, int fieldDepth, DecodeContext c return parseNestedObject(fieldDepth, context); } else { // If the value is empty, create an empty object; otherwise parse as primitive - if (fieldValue.trim().isEmpty()) { + if (fieldValue.isBlank()) { context.currentLine++; return new LinkedHashMap<>(); } else { @@ -184,7 +185,7 @@ static Object parseFieldValue(String fieldValue, int fieldDepth, DecodeContext c } } else { // If the value is empty, create an empty object; otherwise parse as primitive - if (fieldValue.trim().isEmpty()) { + if (fieldValue.isBlank()) { context.currentLine++; return new LinkedHashMap<>(); } else { @@ -205,7 +206,7 @@ static Object parseFieldValue(String fieldValue, int fieldDepth, DecodeContext c * @return the parsed value (Map, List, or primitive) */ static Object parseObjectItemValue(String value, int depth, DecodeContext context) { - boolean isEmpty = value.trim().isEmpty(); + boolean isEmpty = value.isBlank(); // Find the next non-blank line and its depth Integer nextDepth = DecodeHelper.findNextNonBlankLineDepth(context); diff --git a/src/main/java/dev/toonformat/jtoon/decoder/ValueDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/ValueDecoder.java index 129b3c6..a3cc59c 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/ValueDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/ValueDecoder.java @@ -48,7 +48,7 @@ private ValueDecoder() { * invalid */ public static Object decode(String toon, DecodeOptions options) { - if (toon == null || toon.trim().isEmpty()) { + if (toon == null || toon.isBlank()) { return new LinkedHashMap<>(); } diff --git a/src/main/java/dev/toonformat/jtoon/encoder/ArrayEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/ArrayEncoder.java index 1e3404f..3a2abe9 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/ArrayEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/ArrayEncoder.java @@ -60,7 +60,7 @@ public static void encodeArray(String key, ArrayNode value, LineWriter writer, i // Array of objects if (isArrayOfObjects(value)) { - var header = TabularArrayEncoder.detectTabularHeader(value); + List header = TabularArrayEncoder.detectTabularHeader(value); if (!header.isEmpty()) { TabularArrayEncoder.encodeArrayOfObjectsAsTabular(key, value, header, writer, depth, options); } else { diff --git a/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java b/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java index fc8bc7a..82e7b4a 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java @@ -17,12 +17,12 @@ */ public final class Flatten { + private static final Pattern SAFE_IDENTIFIER = Pattern.compile("(?i)^[A-Z_]\\w*$"); + private Flatten() { throw new UnsupportedOperationException("Utility class cannot be instantiated"); } - private static final Pattern SAFE_IDENTIFIER = Pattern.compile("(?i)^[A-Z_]\\w*$"); - /** * Represents the result of a key-folding operation. * diff --git a/src/main/java/dev/toonformat/jtoon/encoder/HeaderFormatter.java b/src/main/java/dev/toonformat/jtoon/encoder/HeaderFormatter.java index 164e3d6..68a1635 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/HeaderFormatter.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/HeaderFormatter.java @@ -96,7 +96,7 @@ private static void appendArrayLength( } private static void appendDelimiterIfNotDefault(StringBuilder header, String delimiter) { - if (!delimiter.equals(COMMA)) { + if (!COMMA.equals(delimiter)) { header.append(delimiter); } } @@ -110,8 +110,7 @@ private static void appendFieldsIfPresent( } header.append(OPEN_BRACE); - String quotedFields = formatFields(fields, delimiter); - header.append(quotedFields); + header.append(formatFields(fields, delimiter)); header.append(CLOSE_BRACE); } diff --git a/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java index fabf737..7b4a30f 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java @@ -76,15 +76,15 @@ public static void encodeObject(ObjectNode value, LineWriter writer, int depth, * @param blockedKeys contains only keys that have undergone a successful flattening */ public static void encodeKeyValuePair(String key, - JsonNode value, - LineWriter writer, - int depth, - EncodeOptions options, - Set siblings, - Set rootLiteralKeys, - String pathPrefix, - Integer flattenDepth, - Set blockedKeys + JsonNode value, + LineWriter writer, + int depth, + EncodeOptions options, + Set siblings, + Set rootLiteralKeys, + String pathPrefix, + Integer flattenDepth, + Set blockedKeys ) { if (key == null) { return; @@ -93,33 +93,34 @@ public static void encodeKeyValuePair(String key, String currentPath = pathPrefix != null ? pathPrefix + DOT + key : key; int effectiveFlattenDepth = flattenDepth != null && flattenDepth > 0 ? flattenDepth : options.flattenDepth(); int remainingDepth = effectiveFlattenDepth - depth; + EncodeOptions currentOptions = options; // Attempt key folding when enabled - if (KeyFolding.SAFE.equals(options.flatten()) + if (KeyFolding.SAFE.equals(currentOptions.flatten()) && !siblings.isEmpty() && remainingDepth > 0 && blockedKeys != null && !blockedKeys.contains(key)) { Flatten.FoldResult foldResult = Flatten.tryFoldKeyChain(key, value, siblings, rootLiteralKeys, pathPrefix, remainingDepth); if (foldResult != null) { - options = flatten(key, foldResult, writer, depth, options, rootLiteralKeys, pathPrefix, blockedKeys, remainingDepth); - if (options == null) { + currentOptions = flatten(key, foldResult, writer, depth, currentOptions, rootLiteralKeys, pathPrefix, blockedKeys, remainingDepth); + if (currentOptions == null) { return; } } } if (value.isValueNode()) { - writer.push(depth, encodedKey + COLON + SPACE + PrimitiveEncoder.encodePrimitive(value, options.delimiter().toString())); + writer.push(depth, encodedKey + COLON + SPACE + PrimitiveEncoder.encodePrimitive(value, currentOptions.delimiter().toString())); } if (value.isArray()) { - ArrayEncoder.encodeArray(key, (ArrayNode) value, writer, depth, options); + ArrayEncoder.encodeArray(key, (ArrayNode) value, writer, depth, currentOptions); } if (value.isObject()) { ObjectNode objValue = (ObjectNode) value; writer.push(depth, encodedKey + COLON); if (!objValue.isEmpty()) { - encodeObject(objValue, writer, depth + 1, options, rootLiteralKeys, currentPath, effectiveFlattenDepth, blockedKeys); + encodeObject(objValue, writer, depth + 1, currentOptions, rootLiteralKeys, currentPath, effectiveFlattenDepth, blockedKeys); } } } @@ -141,6 +142,7 @@ public static void encodeKeyValuePair(String key, private static EncodeOptions flatten(String key, Flatten.FoldResult foldResult, LineWriter writer, int depth, EncodeOptions options, Set rootLiteralKeys, String pathPrefix, Set blockedKeys, int remainingDepth) { String foldedKey = foldResult.foldedKey(); + EncodeOptions currentOptions = options; // prevent second folding pass blockedKeys.add(key); @@ -151,13 +153,13 @@ private static EncodeOptions flatten(String key, Flatten.FoldResult foldResult, // Case 1: Fully folded to a leaf value if (remainder == null) { - handleFullyFoldedLeaf(foldResult, writer, depth, options, encodedFoldedKey); + handleFullyFoldedLeaf(foldResult, writer, depth, currentOptions, encodedFoldedKey); return null; } // Case 2: Partially folded with a tail object if (remainder.isObject()) { - writer.push(depth, indentedLine(depth, encodedFoldedKey + COLON, options.indent())); + writer.push(depth, indentedLine(depth, encodedFoldedKey + COLON, currentOptions.indent())); String foldedPath = pathPrefix != null ? String.join(DOT, pathPrefix, foldedKey) : foldedKey; int newRemainingDepth = remainingDepth - foldResult.segmentCount(); @@ -166,14 +168,14 @@ private static EncodeOptions flatten(String key, Flatten.FoldResult foldResult, // Pass "-1" if remainingDepth is exhausted and set the encoding in the option to false. // to encode normally without flattening newRemainingDepth = -1; - options = new EncodeOptions(options.indent(), options.delimiter(), options.lengthMarker(), KeyFolding.OFF, options.flattenDepth()); + currentOptions = new EncodeOptions(currentOptions.indent(), currentOptions.delimiter(), currentOptions.lengthMarker(), KeyFolding.OFF, currentOptions.flattenDepth()); } - encodeObject((ObjectNode) remainder, writer, depth + 1, options, rootLiteralKeys, foldedPath, newRemainingDepth, blockedKeys); + encodeObject((ObjectNode) remainder, writer, depth + 1, currentOptions, rootLiteralKeys, foldedPath, newRemainingDepth, blockedKeys); return null; } - return options; + return currentOptions; } private static void handleFullyFoldedLeaf(Flatten.FoldResult foldResult, LineWriter writer, int depth, EncodeOptions options, String encodedFoldedKey) { diff --git a/src/main/java/dev/toonformat/jtoon/normalizer/JsonNormalizer.java b/src/main/java/dev/toonformat/jtoon/normalizer/JsonNormalizer.java index 52581f5..1efaabd 100644 --- a/src/main/java/dev/toonformat/jtoon/normalizer/JsonNormalizer.java +++ b/src/main/java/dev/toonformat/jtoon/normalizer/JsonNormalizer.java @@ -1,7 +1,6 @@ package dev.toonformat.jtoon.normalizer; import dev.toonformat.jtoon.util.ObjectMapperSingleton; -import tools.jackson.core.JacksonException; import tools.jackson.databind.JsonNode; import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.node.ArrayNode; @@ -73,7 +72,7 @@ private JsonNormalizer() { * @throws IllegalArgumentException if the input is blank or not valid JSON */ public static JsonNode parse(String json) { - if (json == null || json.trim().isEmpty()) { + if (json == null || json.isBlank()) { throw new IllegalArgumentException("Invalid JSON"); } try { diff --git a/src/main/java/dev/toonformat/jtoon/util/ObjectMapperSingleton.java b/src/main/java/dev/toonformat/jtoon/util/ObjectMapperSingleton.java index 8d24086..7f2c3f3 100644 --- a/src/main/java/dev/toonformat/jtoon/util/ObjectMapperSingleton.java +++ b/src/main/java/dev/toonformat/jtoon/util/ObjectMapperSingleton.java @@ -15,7 +15,7 @@ public final class ObjectMapperSingleton { /** * Holds the singleton ObjectMapper. */ - private static volatile ObjectMapper INSTANCE; + private static volatile ObjectMapper instance; private ObjectMapperSingleton() { throw new UnsupportedOperationException("Utility class cannot be instantiated"); @@ -27,12 +27,12 @@ private ObjectMapperSingleton() { * @return ObjectMapper */ public static ObjectMapper getInstance() { - ObjectMapper result = INSTANCE; + ObjectMapper result = instance; if (result == null) { synchronized (ObjectMapperSingleton.class) { - result = INSTANCE; + result = instance; if (result == null) { - INSTANCE = result = JsonMapper.builder() + instance = result = JsonMapper.builder() .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(JsonInclude.Include.ALWAYS)) .addModule(new AfterburnerModule()) // Speeds up Jackson by 20–40% in most real-world cases .defaultTimeZone(TimeZone.getTimeZone("UTC")) // set a default timezone for dates diff --git a/src/main/java/dev/toonformat/jtoon/util/StringValidator.java b/src/main/java/dev/toonformat/jtoon/util/StringValidator.java index 9ecee67..a4c2251 100644 --- a/src/main/java/dev/toonformat/jtoon/util/StringValidator.java +++ b/src/main/java/dev/toonformat/jtoon/util/StringValidator.java @@ -64,11 +64,7 @@ public static boolean isSafeUnquoted(String value, String delimiter) { return false; } - if (containsDelimiter(value, delimiter)) { - return false; - } - - return !startsWithListMarker(value); + return !containsDelimiter(value, delimiter) && !startsWithListMarker(value); } /** @@ -90,9 +86,9 @@ private static boolean isPaddedWithWhitespace(String value) { } private static boolean looksLikeKeyword(String value) { - return value.equals(TRUE_LITERAL) - || value.equals(FALSE_LITERAL) - || value.equals(NULL_LITERAL); + return TRUE_LITERAL.equals(value) + || FALSE_LITERAL.equals(value) + || NULL_LITERAL.equals(value); } private static boolean looksLikeNumber(String value) { From feeec03b9b9749c4a62b21ff6265d9859ecf855e Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Thu, 12 Feb 2026 19:41:26 +0100 Subject: [PATCH 07/22] install checkstyle --- build.gradle | 20 ++++++++++++++++++++ checkstyle.xml | 13 ++++--------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index f74416d..2c6a93b 100644 --- a/build.gradle +++ b/build.gradle @@ -5,6 +5,7 @@ plugins { id 'jacoco' id 'com.github.spotbugs' version '6.0.15' id 'pmd' + id 'checkstyle' } group = 'dev.toonformat' @@ -95,6 +96,24 @@ tasks.pmdTest { } } +checkstyle { + toolVersion = '10.12.5' + configFile = file('checkstyle.xml') + showViolations = true + ignoreFailures = true +} + +tasks.checkstyleMain { + reports { + xml.required = true + html.required = true + } +} + +tasks.checkstyleTest { + enabled = false +} + dependencies { implementation 'tools.jackson.core:jackson-databind:3.0.4' implementation 'tools.jackson.module:jackson-module-afterburner:3.0.4' @@ -142,6 +161,7 @@ jacocoTestCoverageVerification { check.dependsOn jacocoTestReport check.dependsOn spotbugsMain check.dependsOn pmdMain +check.dependsOn checkstyleMain tasks.register('generateJavadoc', Javadoc) { description = 'Generates Javadoc HTML documentation in the docs/javadoc folder' diff --git a/checkstyle.xml b/checkstyle.xml index a691216..3785d24 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -22,7 +22,6 @@ - @@ -37,8 +36,8 @@ - - + + @@ -62,14 +61,10 @@ - - - - - + @@ -81,4 +76,4 @@ - \ No newline at end of file + From a8a266a9399431da4a6af6492eccb953f5c52ab1 Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Thu, 12 Feb 2026 19:47:15 +0100 Subject: [PATCH 08/22] run checkstyle and fix warnings --- .../toonformat/jtoon/decoder/KeyDecoder.java | 82 ++++++++++--------- .../jtoon/decoder/ListItemDecoder.java | 42 +++++----- .../jtoon/decoder/TabularArrayDecoder.java | 17 ++-- .../jtoon/decoder/ValueDecoder.java | 24 +++--- .../jtoon/encoder/ArrayEncoder.java | 49 ++++++----- .../dev/toonformat/jtoon/encoder/Flatten.java | 8 +- .../jtoon/encoder/ListItemEncoder.java | 34 ++++---- .../jtoon/encoder/ObjectEncoder.java | 69 +++++++++------- 8 files changed, 169 insertions(+), 156 deletions(-) diff --git a/src/main/java/dev/toonformat/jtoon/decoder/KeyDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/KeyDecoder.java index 20151f6..8ce8f22 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/KeyDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/KeyDecoder.java @@ -3,12 +3,10 @@ import dev.toonformat.jtoon.Delimiter; import dev.toonformat.jtoon.PathExpansion; import dev.toonformat.jtoon.util.StringEscaper; - import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; - import static dev.toonformat.jtoon.util.Constants.DOT; import static dev.toonformat.jtoon.util.Headers.KEYED_ARRAY_PATTERN; @@ -32,9 +30,9 @@ private KeyDecoder() { */ static void processKeyedArrayLine(Map result, String content, String originalKey, int parentDepth, DecodeContext context) { - String key = StringEscaper.unescape(originalKey); - String arrayHeader = content.substring(originalKey.length()); - List arrayValue = ArrayDecoder.parseArray(arrayHeader, parentDepth + 1, context); + final String key = StringEscaper.unescape(originalKey); + final String arrayHeader = content.substring(originalKey.length()); + final List arrayValue = ArrayDecoder.parseArray(arrayHeader, parentDepth + 1, context); // Handle path expansion for array keys if (shouldExpandKey(originalKey, context)) { @@ -54,24 +52,25 @@ static void processKeyedArrayLine(Map result, String content, St * @param value value * @param context decode an object to deal with lines, delimiter and options */ - static void expandPathIntoMap(Map current, String dottedKey, Object value, DecodeContext context) { - String[] segments = dottedKey.split("\\."); + static void expandPathIntoMap(Map current, String dottedKey, Object value, + DecodeContext context) { + final String[] segments = dottedKey.split("\\."); Map currentMap = current; // Navigate/create nested structure for (int i = 0; i < segments.length - 1; i++) { - String segment = segments[i]; - Object existing = currentMap.get(segment); + final String segment = segments[i]; + final Object existing = currentMap.get(segment); if (existing == null) { // Create a new nested object - Map nested = new LinkedHashMap<>(); + final Map nested = new LinkedHashMap<>(); currentMap.put(segment, nested); currentMap = nested; } else if (existing instanceof Map) { // Use existing nested object @SuppressWarnings("unchecked") - Map existingMap = (Map) existing; + final Map existingMap = (Map) existing; currentMap = existingMap; } else { // Conflict: existing is not a Map @@ -81,15 +80,15 @@ static void expandPathIntoMap(Map current, String dottedKey, Obj segment, existing.getClass().getSimpleName())); } // LWW: overwrite with new nested object - Map nested = new LinkedHashMap<>(); + final Map nested = new LinkedHashMap<>(); currentMap.put(segment, nested); currentMap = nested; } } // Set the final value - String finalSegment = segments[segments.length - 1]; - Object existing = currentMap.get(finalSegment); + final String finalSegment = segments[segments.length - 1]; + final Object existing = currentMap.get(finalSegment); DecodeHelper.checkFinalValueConflict(finalSegment, existing, value, context); @@ -109,11 +108,11 @@ static void expandPathIntoMap(Map current, String dottedKey, Obj * @param context decode an object to deal with lines, delimiter and options */ static void processKeyValueLine(Map result, String content, int depth, DecodeContext context) { - int colonIdx = DecodeHelper.findUnquotedColon(content); + final int colonIdx = DecodeHelper.findUnquotedColon(content); if (colonIdx > 0) { - String key = content.substring(0, colonIdx).trim(); - String value = content.substring(colonIdx + 1).trim(); + final String key = content.substring(0, colonIdx).trim(); + final String value = content.substring(colonIdx + 1).trim(); parseKeyValuePairIntoMap(result, key, value, depth, context); } else { // No colon found in key-value context - this is an error @@ -136,9 +135,9 @@ static void processKeyValueLine(Map result, String content, int */ static void parseKeyValuePairIntoMap(Map map, String key, String value, int depth, DecodeContext context) { - String unescapedKey = StringEscaper.unescape(key); + final String unescapedKey = StringEscaper.unescape(key); - Object parsedValue = parseKeyValue(value, depth, context); + final Object parsedValue = parseKeyValue(value, depth, context); putKeyValueIntoMap(map, key, unescapedKey, parsedValue, context); } @@ -186,14 +185,14 @@ static boolean shouldExpandKey(String key, DecodeContext context) { private static Object parseKeyValue(String value, int depth, DecodeContext context) { // Check if the next line is nested (deeper indentation) if (context.currentLine + 1 < context.lines.length) { - int nextDepth = DecodeHelper.getDepth(context.lines[context.currentLine + 1], context); + final int nextDepth = DecodeHelper.getDepth(context.lines[context.currentLine + 1], context); if (nextDepth > depth) { context.currentLine++; // parseNestedObject manages the currentLine, so we don't increment here return ObjectDecoder.parseNestedObject(depth, context); } else { // If the value is empty, create an empty object; otherwise parse as primitive - Object parsedValue; + final Object parsedValue; if (value.isBlank()) { parsedValue = new LinkedHashMap<>(); } else { @@ -204,7 +203,7 @@ private static Object parseKeyValue(String value, int depth, DecodeContext conte } } else { // If the value is empty, create an empty object; otherwise parse as primitive - Object parsedValue; + final Object parsedValue; if (value.isBlank()) { parsedValue = new LinkedHashMap<>(); } else { @@ -247,7 +246,7 @@ private static void putKeyValueIntoMap(Map map, String originalK */ static Object parseKeyValuePair(String key, String value, int depth, boolean parseRootFields, DecodeContext context) { - Map obj = new LinkedHashMap<>(); + final Map obj = new LinkedHashMap<>(); parseKeyValuePairIntoMap(obj, key, value, depth, context); if (parseRootFields) { @@ -266,12 +265,12 @@ static Object parseKeyValuePair(String key, String value, int depth, boolean par * @return parsed keyed array value */ static Object parseKeyedArrayValue(Matcher keyedArray, String content, int depth, DecodeContext context) { - String originalKey = keyedArray.group(1).trim(); - String key = StringEscaper.unescape(originalKey); - String arrayHeader = content.substring(keyedArray.group(1).length()); + final String originalKey = keyedArray.group(1).trim(); + final String key = StringEscaper.unescape(originalKey); + final String arrayHeader = content.substring(keyedArray.group(1).length()); - List arrayValue = ArrayDecoder.parseArray(arrayHeader, depth, context); - Map obj = new LinkedHashMap<>(); + final List arrayValue = ArrayDecoder.parseArray(arrayHeader, depth, context); + final Map obj = new LinkedHashMap<>(); // Handle path expansion for array keys if (shouldExpandKey(originalKey, context)) { @@ -299,19 +298,21 @@ static Object parseKeyedArrayValue(Matcher keyedArray, String content, int depth * @param context decode an object to deal with lines, delimiter and options * @return true if the field was processed as a keyed array, false otherwise */ - static boolean parseKeyedArrayField(String fieldContent, Map item, int depth, DecodeContext context) { - Matcher keyedArray = KEYED_ARRAY_PATTERN.matcher(fieldContent); + static boolean parseKeyedArrayField(String fieldContent, Map item, int depth, + DecodeContext context) { + final Matcher keyedArray = KEYED_ARRAY_PATTERN.matcher(fieldContent); if (!keyedArray.matches()) { return false; } - String originalKey = keyedArray.group(1).trim(); - String key = StringEscaper.unescape(originalKey); - String arrayHeader = fieldContent.substring(keyedArray.group(1).length()); + final String originalKey = keyedArray.group(1).trim(); + final String key = StringEscaper.unescape(originalKey); + final String arrayHeader = fieldContent.substring(keyedArray.group(1).length()); // For nested arrays in list items, default to comma delimiter if not specified - Delimiter nestedArrayDelimiter = ArrayDecoder.extractDelimiterFromHeader(arrayHeader, context); - List arrayValue = ArrayDecoder.parseArrayWithDelimiter(arrayHeader, depth + 2, nestedArrayDelimiter, context); + final Delimiter nestedArrayDelimiter = ArrayDecoder.extractDelimiterFromHeader(arrayHeader, context); + final List arrayValue = ArrayDecoder.parseArrayWithDelimiter(arrayHeader, depth + 2, + nestedArrayDelimiter, context); // Handle path expansion for array keys if (shouldExpandKey(originalKey, context)) { @@ -333,16 +334,17 @@ static boolean parseKeyedArrayField(String fieldContent, Map ite * @param context decode an object to deal with lines, delimiter and options * @return true if the field was processed as a key-value pair, false otherwise */ - static boolean parseKeyValueField(String fieldContent, Map item, int depth, DecodeContext context) { - int colonIdx = DecodeHelper.findUnquotedColon(fieldContent); + static boolean parseKeyValueField(String fieldContent, Map item, int depth, + DecodeContext context) { + final int colonIdx = DecodeHelper.findUnquotedColon(fieldContent); if (colonIdx <= 0) { return false; } - String fieldKey = StringEscaper.unescape(fieldContent.substring(0, colonIdx).trim()); - String fieldValue = fieldContent.substring(colonIdx + 1).trim(); + final String fieldKey = StringEscaper.unescape(fieldContent.substring(0, colonIdx).trim()); + final String fieldValue = fieldContent.substring(colonIdx + 1).trim(); - Object parsedValue = ObjectDecoder.parseFieldValue(fieldValue, depth + 2, context); + final Object parsedValue = ObjectDecoder.parseFieldValue(fieldValue, depth + 2, context); // Handle path expansion if (shouldExpandKey(fieldKey, context)) { diff --git a/src/main/java/dev/toonformat/jtoon/decoder/ListItemDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/ListItemDecoder.java index 1433081..45ac4e4 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/ListItemDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/ListItemDecoder.java @@ -17,7 +17,9 @@ */ public final class ListItemDecoder { - private ListItemDecoder() {throw new UnsupportedOperationException("Utility class cannot be instantiated");} + private ListItemDecoder() { + throw new UnsupportedOperationException("Utility class cannot be instantiated"); + } /** * Processes a single list array item if it matches the expected depth. @@ -31,7 +33,7 @@ public final class ListItemDecoder { public static void processListArrayItem(String line, int lineDepth, int depth, List result, DecodeContext context) { if (lineDepth == depth + 1) { - String content = line.substring((depth + 1) * context.options.indent()); + final String content = line.substring((depth + 1) * context.options.indent()); if (content.startsWith(LIST_ITEM_MARKER)) { result.add(parseListItem(content, depth, context)); @@ -54,7 +56,7 @@ public static void processListArrayItem(String line, int lineDepth, int depth, */ public static Object parseListItem(String content, int depth, DecodeContext context) { // Handle empty item: just "-" or "- " - String itemContent; + final String itemContent; if (content.length() > 2) { itemContent = content.substring(2).trim(); } else { @@ -70,7 +72,7 @@ public static Object parseListItem(String content, int depth, DecodeContext cont // Check for standalone array (e.g., "[2]: 1,2") if (itemContent.startsWith(OPEN_BRACKET)) { // For nested arrays in list items, default to comma delimiter if not specified - Delimiter nestedArrayDelimiter = ArrayDecoder.extractDelimiterFromHeader(itemContent, context); + final Delimiter nestedArrayDelimiter = ArrayDecoder.extractDelimiterFromHeader(itemContent, context); // parseArrayWithDelimiter handles currentLine increment internally // For inline arrays, it increments. For multi-line arrays, parseListArray // handles it. @@ -81,17 +83,19 @@ public static Object parseListItem(String content, int depth, DecodeContext cont } // Check for keyed array pattern (e.g., "tags[3]: a,b,c" or "data[2]{id}: ...") - Matcher keyedArray = KEYED_ARRAY_PATTERN.matcher(itemContent); + final Matcher keyedArray = KEYED_ARRAY_PATTERN.matcher(itemContent); if (keyedArray.matches()) { - String originalKey = keyedArray.group(1).trim(); - String key = StringEscaper.unescape(originalKey); - String arrayHeader = itemContent.substring(keyedArray.group(1).length()); + final String originalKey = keyedArray.group(1).trim(); + final String key = StringEscaper.unescape(originalKey); + final String arrayHeader = itemContent.substring(keyedArray.group(1).length()); // For nested arrays in list items, default to comma delimiter if not specified - Delimiter nestedArrayDelimiter = ArrayDecoder.extractDelimiterFromHeader(arrayHeader, context); - List arrayValue = ArrayDecoder.parseArrayWithDelimiter(arrayHeader, depth + 2, nestedArrayDelimiter, context); + final Delimiter nestedArrayDelimiter = ArrayDecoder.extractDelimiterFromHeader(arrayHeader, context); + final List arrayValue = ArrayDecoder.parseArrayWithDelimiter( + arrayHeader, depth + 2, nestedArrayDelimiter, context + ); - Map item = new LinkedHashMap<>(); + final Map item = new LinkedHashMap<>(); item.put(key, arrayValue); // parseArrayWithDelimiter manages currentLine correctly: @@ -104,7 +108,7 @@ public static Object parseListItem(String content, int depth, DecodeContext cont return item; } - int colonIdx = DecodeHelper.findUnquotedColon(itemContent); + final int colonIdx = DecodeHelper.findUnquotedColon(itemContent); // Simple scalar: - value if (colonIdx <= 0) { @@ -113,13 +117,13 @@ public static Object parseListItem(String content, int depth, DecodeContext cont } // Object item: - key: value - String key = StringEscaper.unescape(itemContent.substring(0, colonIdx).trim()); - String value = itemContent.substring(colonIdx + 1).trim(); + final String key = StringEscaper.unescape(itemContent.substring(0, colonIdx).trim()); + final String value = itemContent.substring(colonIdx + 1).trim(); context.currentLine++; - Map item = new LinkedHashMap<>(); - Object parsedValue; + final Map item = new LinkedHashMap<>(); + final Object parsedValue; // If no next line exists, handle a simple case if (context.currentLine >= context.lines.length) { parsedValue = value.isBlank() ? new LinkedHashMap<>() : PrimitiveDecoder.parse(value); @@ -142,15 +146,15 @@ public static Object parseListItem(String content, int depth, DecodeContext cont */ private static void parseListItemFields(Map item, int depth, DecodeContext context) { while (context.currentLine < context.lines.length) { - String line = context.lines[context.currentLine]; - int lineDepth = DecodeHelper.getDepth(line, context); + final String line = context.lines[context.currentLine]; + final int lineDepth = DecodeHelper.getDepth(line, context); if (lineDepth < depth + 2) { return; } if (lineDepth == depth + 2) { - String fieldContent = line.substring((depth + 2) * context.options.indent()); + final String fieldContent = line.substring((depth + 2) * context.options.indent()); // Try to parse as a keyed array first, then as a key-value pair boolean wasParsed = KeyDecoder.parseKeyedArrayField(fieldContent, item, depth, context); diff --git a/src/main/java/dev/toonformat/jtoon/decoder/TabularArrayDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/TabularArrayDecoder.java index 60a3dc3..7a0827d 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/TabularArrayDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/TabularArrayDecoder.java @@ -34,22 +34,23 @@ private TabularArrayDecoder() { * @param context decode an object to deal with lines, delimiter and options * @return tabular array converted to JSON format */ - public static List parseTabularArray(String header, int depth, Delimiter arrayDelimiter, DecodeContext context) { - Matcher matcher = TABULAR_HEADER_PATTERN.matcher(header); + public static List parseTabularArray(String header, int depth, Delimiter arrayDelimiter, + DecodeContext context) { + final Matcher matcher = TABULAR_HEADER_PATTERN.matcher(header); if (!matcher.find()) { return Collections.emptyList(); } - String keysStr = matcher.group(4); - List keys = parseTabularKeys(keysStr, arrayDelimiter, context); + final String keysStr = matcher.group(4); + final List keys = parseTabularKeys(keysStr, arrayDelimiter, context); - List result = new ArrayList<>(); + final List result = new ArrayList<>(); context.currentLine++; // Determine the expected row depth dynamically from the first non-blank line int expectedRowDepth = depth + 1; if (context.currentLine < context.lines.length) { - int nextNonBlankLine = DecodeHelper.findNextNonBlankLine(context.currentLine, context); + final int nextNonBlankLine = DecodeHelper.findNextNonBlankLine(context.currentLine, context); if (nextNonBlankLine < context.lines.length) { expectedRowDepth = DecodeHelper.getDepth(context.lines[nextNonBlankLine], context); } @@ -80,8 +81,8 @@ private static List parseTabularKeys(String keysStr, Delimiter arrayDeli validateKeysDelimiter(keysStr, arrayDelimiter); } - List result = new ArrayList<>(); - List rawValues = ArrayDecoder.parseDelimitedValues(keysStr, arrayDelimiter); + final List result = new ArrayList<>(); + final List rawValues = ArrayDecoder.parseDelimitedValues(keysStr, arrayDelimiter); for (String key : rawValues) { result.add(StringEscaper.unescape(key)); } diff --git a/src/main/java/dev/toonformat/jtoon/decoder/ValueDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/ValueDecoder.java index a3cc59c..2e30d60 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/ValueDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/ValueDecoder.java @@ -3,10 +3,8 @@ import dev.toonformat.jtoon.DecodeOptions; import dev.toonformat.jtoon.util.ObjectMapperSingleton; import tools.jackson.databind.ObjectMapper; - import java.util.LinkedHashMap; import java.util.regex.Matcher; - import static dev.toonformat.jtoon.util.Constants.NULL_LITERAL; import static dev.toonformat.jtoon.util.Constants.OPEN_BRACKET; import static dev.toonformat.jtoon.util.Headers.KEYED_ARRAY_PATTERN; @@ -53,14 +51,16 @@ public static Object decode(String toon, DecodeOptions options) { } // Special case: if input is exactly "null", return null - String trimmed = toon.trim(); + final String trimmed = toon.trim(); if (NULL_LITERAL.equals(trimmed)) { return null; } // Don't trim leading whitespace - we need it for indentation validation // Only trim trailing whitespace to avoid issues with empty lines at the end - String processed = Character.isWhitespace(toon.charAt(toon.length() - 1)) ? toon.stripTrailing() : toon; + final String processed = Character.isWhitespace(toon.charAt(toon.length() - 1)) + ? toon.stripTrailing() + : toon; //set an own decode context final DecodeContext context = new DecodeContext(); @@ -68,9 +68,9 @@ public static Object decode(String toon, DecodeOptions options) { context.options = options; context.delimiter = options.delimiter(); - int lineIndex = context.currentLine; - String line = context.lines[lineIndex]; - int depth = DecodeHelper.getDepth(line, context); + final int lineIndex = context.currentLine; + final String line = context.lines[lineIndex]; + final int depth = DecodeHelper.getDepth(line, context); if (depth > 0) { if (context.options.strict()) { @@ -85,15 +85,15 @@ public static Object decode(String toon, DecodeOptions options) { } // Handle keyed arrays: items[2]{id,name}: - Matcher keyedArray = KEYED_ARRAY_PATTERN.matcher(line); + final Matcher keyedArray = KEYED_ARRAY_PATTERN.matcher(line); if (keyedArray.matches()) { return KeyDecoder.parseKeyedArrayValue(keyedArray, line, depth, context); } // Handle key-value pairs: name: Ada - int colonIdx = DecodeHelper.findUnquotedColon(line); + final int colonIdx = DecodeHelper.findUnquotedColon(line); if (colonIdx > 0) { - String key = line.substring(0, colonIdx).trim(); - String value = line.substring(colonIdx + 1).trim(); + final String key = line.substring(0, colonIdx).trim(); + final String value = line.substring(colonIdx + 1).trim(); return KeyDecoder.parseKeyValuePair(key, value, depth, depth == 0, context); } @@ -118,7 +118,7 @@ public static Object decode(String toon, DecodeOptions options) { */ public static String decodeToJson(String toon, DecodeOptions options) { try { - Object decoded = decode(toon, options); + final Object decoded = decode(toon, options); return MAPPER.writeValueAsString(decoded); } catch (Exception e) { throw new IllegalArgumentException("Failed to convert decoded value to JSON: " + e.getMessage(), e); diff --git a/src/main/java/dev/toonformat/jtoon/encoder/ArrayEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/ArrayEncoder.java index 3a2abe9..0adf509 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/ArrayEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/ArrayEncoder.java @@ -4,11 +4,9 @@ import tools.jackson.databind.JsonNode; import tools.jackson.databind.node.ArrayNode; import tools.jackson.databind.node.ObjectNode; - import java.util.ArrayList; import java.util.List; import java.util.stream.StreamSupport; - import static dev.toonformat.jtoon.util.Constants.LIST_ITEM_PREFIX; import static dev.toonformat.jtoon.util.Constants.SPACE; @@ -34,7 +32,7 @@ private ArrayEncoder() { */ public static void encodeArray(String key, ArrayNode value, LineWriter writer, int depth, EncodeOptions options) { if (value.isEmpty()) { - String header = PrimitiveEncoder.formatHeader(0, key, null, options.delimiter().toString(), + final String header = PrimitiveEncoder.formatHeader(0, key, null, options.delimiter().toString(), options.lengthMarker()); writer.push(depth, header); return; @@ -48,7 +46,7 @@ public static void encodeArray(String key, ArrayNode value, LineWriter writer, i // Array of arrays (all primitives) if (isArrayOfArrays(value)) { - boolean allPrimitiveArrays = StreamSupport.stream(value.spliterator(), false) + final boolean allPrimitiveArrays = StreamSupport.stream(value.spliterator(), false) .filter(JsonNode::isArray) .allMatch(ArrayEncoder::isArrayOfPrimitives); @@ -60,7 +58,7 @@ public static void encodeArray(String key, ArrayNode value, LineWriter writer, i // Array of objects if (isArrayOfObjects(value)) { - List header = TabularArrayEncoder.detectTabularHeader(value); + final List header = TabularArrayEncoder.detectTabularHeader(value); if (!header.isEmpty()) { TabularArrayEncoder.encodeArrayOfObjectsAsTabular(key, value, header, writer, depth, options); } else { @@ -128,11 +126,12 @@ public static boolean isArrayOfObjects(JsonNode array) { } /** - * Encodes a primitive array inline: key[N]: v1,v2,v3 + * Encodes a primitive array inline: key[N]: v1,v2,v3. */ private static void encodeInlinePrimitiveArray(String prefix, ArrayNode values, LineWriter writer, int depth, EncodeOptions options) { - String formatted = formatInlineArray(values, options.delimiter().toString(), prefix, options.lengthMarker()); + final String formatted = formatInlineArray(values, options.delimiter().toString(), prefix, + options.lengthMarker()); writer.push(depth, formatted); } @@ -146,11 +145,11 @@ private static void encodeInlinePrimitiveArray(String prefix, ArrayNode values, * @return the formatted inline array string */ public static String formatInlineArray(ArrayNode values, String delimiter, String prefix, boolean lengthMarker) { - List valueList = new ArrayList<>(); + final List valueList = new ArrayList<>(); values.forEach(valueList::add); - String header = PrimitiveEncoder.formatHeader(values.size(), prefix, null, delimiter, lengthMarker); - String joinedValue = PrimitiveEncoder.joinEncodedValues(valueList, delimiter); + final String header = PrimitiveEncoder.formatHeader(values.size(), prefix, null, delimiter, lengthMarker); + final String joinedValue = PrimitiveEncoder.joinEncodedValues(valueList, delimiter); // Only add space if there are values if (values.isEmpty()) { @@ -164,14 +163,14 @@ public static String formatInlineArray(ArrayNode values, String delimiter, Strin */ private static void encodeArrayOfArraysAsListItems(String prefix, ArrayNode values, LineWriter writer, int depth, EncodeOptions options) { - String header = PrimitiveEncoder.formatHeader(values.size(), prefix, null, options.delimiter().toString(), - options.lengthMarker()); + final String header = PrimitiveEncoder.formatHeader(values.size(), prefix, null, + options.delimiter().toString(), options.lengthMarker()); writer.push(depth, header); for (JsonNode arr : values) { if (arr.isArray() && isArrayOfPrimitives(arr)) { - String inline = formatInlineArray((ArrayNode) arr, options.delimiter().toString(), null, - options.lengthMarker()); + final String inline = formatInlineArray((ArrayNode) arr, options.delimiter().toString(), null, + options.lengthMarker()); writer.push(depth + 1, LIST_ITEM_PREFIX + inline); } } @@ -185,8 +184,8 @@ private static void encodeMixedArrayAsListItems(String prefix, LineWriter writer, int depth, EncodeOptions options) { - String header = PrimitiveEncoder.formatHeader(items.size(), prefix, null, options.delimiter().toString(), - options.lengthMarker()); + final String header = PrimitiveEncoder.formatHeader(items.size(), prefix, null, + options.delimiter().toString(), options.lengthMarker()); writer.push(depth, header); for (JsonNode item : items) { @@ -197,23 +196,23 @@ private static void encodeMixedArrayAsListItems(String prefix, } else if (item.isArray()) { // Direct array as list item if (isArrayOfPrimitives(item)) { - String inline = formatInlineArray((ArrayNode) item, options.delimiter().toString(), null, - options.lengthMarker()); + final String inline = formatInlineArray((ArrayNode) item, options.delimiter().toString(), null, + options.lengthMarker()); writer.push(depth + 1, LIST_ITEM_PREFIX + inline); } if (isArrayOfObjects(item)) { - ArrayNode arrayItems = (ArrayNode) item; - String nestedHeader = PrimitiveEncoder.formatHeader(arrayItems.size(), null, null, - options.delimiter().toString(), options.lengthMarker()); + final ArrayNode arrayItems = (ArrayNode) item; + final String nestedHeader = PrimitiveEncoder.formatHeader(arrayItems.size(), null, null, + options.delimiter().toString(), + options.lengthMarker()); writer.push(depth + 1, LIST_ITEM_PREFIX + nestedHeader); - arrayItems.elements() - .forEach(e -> ListItemEncoder.encodeObjectAsListItem((ObjectNode) e, writer, depth + 2, options)); + arrayItems.elements().forEach(e -> ListItemEncoder.encodeObjectAsListItem((ObjectNode) e, writer, + depth + 2, options)); } } else if (item.isObject()) { // Object as list item - delegate to ListItemEncoder - ListItemEncoder.encodeObjectAsListItem((ObjectNode) item, writer, - depth + 1, options); + ListItemEncoder.encodeObjectAsListItem((ObjectNode) item, writer, depth + 1, options); } } } diff --git a/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java b/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java index 82e7b4a..b6aa66f 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java @@ -2,14 +2,12 @@ import tools.jackson.databind.JsonNode; import tools.jackson.databind.node.ObjectNode; - import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; - import static dev.toonformat.jtoon.util.Constants.DOT; /** @@ -38,7 +36,7 @@ public record FoldResult(String foldedKey, } /** - * Represents the result of the Collect segments of the single-key chain + * Represents the result of the Collect segments of the single-key chain. * * @param segments collected single-key object * @param tail the tail node (if any) @@ -77,7 +75,7 @@ public static FoldResult tryFoldKeyChain(String key, } // start chain from absolute key - String absKey = (pathPrefix == null) ? key : String.join(DOT, pathPrefix, key); + final String absKey = (pathPrefix == null) ? key : String.join(DOT, pathPrefix, key); // Collect segments of the single-key chain final ChainResult chain = collectSingleKeyChain(absKey, value, remainingDepth); @@ -99,7 +97,7 @@ public static FoldResult tryFoldKeyChain(String key, } // Build folded key - String foldedKey = String.join(DOT, chain.segments); + final String foldedKey = String.join(DOT, chain.segments); // Detect collisions with sibling keys if (siblings.contains(foldedKey)) { diff --git a/src/main/java/dev/toonformat/jtoon/encoder/ListItemEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/ListItemEncoder.java index 2424f05..26e6fe5 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/ListItemEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/ListItemEncoder.java @@ -4,12 +4,10 @@ import tools.jackson.databind.JsonNode; import tools.jackson.databind.node.ArrayNode; import tools.jackson.databind.node.ObjectNode; - import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; - import static dev.toonformat.jtoon.util.Constants.LIST_ITEM_MARKER; import static dev.toonformat.jtoon.util.Constants.COLON; import static dev.toonformat.jtoon.util.Constants.SPACE; @@ -38,7 +36,7 @@ private ListItemEncoder() { * @param options Encoding options */ public static void encodeObjectAsListItem(ObjectNode obj, LineWriter writer, int depth, EncodeOptions options) { - List keys = new ArrayList<>(obj.propertyNames()); + final List keys = new ArrayList<>(obj.propertyNames()); if (keys.isEmpty()) { writer.push(depth, LIST_ITEM_MARKER); @@ -46,14 +44,15 @@ public static void encodeObjectAsListItem(ObjectNode obj, LineWriter writer, int } // First key-value on the same line as "- " - String firstKey = keys.get(0); - JsonNode firstValue = obj.get(firstKey); + final String firstKey = keys.get(0); + final JsonNode firstValue = obj.get(firstKey); encodeFirstKeyValue(firstKey, firstValue, writer, depth, options); // Remaining keys on indented lines for (int i = 1; i < keys.size(); i++) { - String key = keys.get(i); - ObjectEncoder.encodeKeyValuePair(key, obj.get(key), writer, depth + 1, options, new HashSet<>(keys), Set.of(), null, null, new HashSet<>()); + final String key = keys.get(i); + ObjectEncoder.encodeKeyValuePair(key, obj.get(key), writer, depth + 1, options, new HashSet<>(keys), + Set.of(), null, null, new HashSet<>()); } } @@ -63,7 +62,7 @@ public static void encodeObjectAsListItem(ObjectNode obj, LineWriter writer, int */ private static void encodeFirstKeyValue(String key, JsonNode value, LineWriter writer, int depth, EncodeOptions options) { - String encodedKey = PrimitiveEncoder.encodeKey(key); + final String encodedKey = PrimitiveEncoder.encodeKey(key); if (value.isValueNode()) { encodeFirstValueAsPrimitive(encodedKey, value, writer, depth, options); @@ -93,17 +92,18 @@ private static void encodeFirstValueAsArray(String key, String encodedKey, Array private static void encodeFirstArrayAsPrimitives(String key, ArrayNode arrayValue, LineWriter writer, int depth, EncodeOptions options) { - String formatted = ArrayEncoder.formatInlineArray(arrayValue, options.delimiter().toString(), key, - options.lengthMarker()); + final String formatted = ArrayEncoder.formatInlineArray(arrayValue, options.delimiter().toString(), key, + options.lengthMarker()); writer.push(depth, LIST_ITEM_PREFIX + formatted); } private static void encodeFirstArrayAsObjects(String key, String encodedKey, ArrayNode arrayValue, LineWriter writer, int depth, EncodeOptions options) { - List header = TabularArrayEncoder.detectTabularHeader(arrayValue); + final List header = TabularArrayEncoder.detectTabularHeader(arrayValue); if (!header.isEmpty()) { - String headerStr = PrimitiveEncoder.formatHeader(arrayValue.size(), key, header, - options.delimiter().toString(), options.lengthMarker()); + final String headerStr = PrimitiveEncoder.formatHeader(arrayValue.size(), key, header, + options.delimiter().toString(), + options.lengthMarker()); writer.push(depth, LIST_ITEM_PREFIX + headerStr); // Write just the rows, header was already written above TabularArrayEncoder.writeTabularRows(arrayValue, header, writer, depth + 2, options); @@ -118,8 +118,8 @@ private static void encodeFirstArrayAsObjects(String key, String encodedKey, Arr } } - private static void encodeFirstArrayAsComplex(String encodedKey, ArrayNode arrayValue, LineWriter writer, int depth, - EncodeOptions options) { + private static void encodeFirstArrayAsComplex(String encodedKey, ArrayNode arrayValue, LineWriter writer, + int depth, EncodeOptions options) { writer.push(depth, LIST_ITEM_PREFIX + encodedKey + OPEN_BRACKET + arrayValue.size() + CLOSE_BRACKET + COLON); for (JsonNode item : arrayValue) { @@ -127,8 +127,8 @@ private static void encodeFirstArrayAsComplex(String encodedKey, ArrayNode array writer.push(depth + 2, LIST_ITEM_PREFIX + PrimitiveEncoder.encodePrimitive(item, options.delimiter().toString())); } else if (item.isArray() && ArrayEncoder.isArrayOfPrimitives(item)) { - String inline = ArrayEncoder.formatInlineArray((ArrayNode) item, options.delimiter().toString(), null, - options.lengthMarker()); + final String inline = ArrayEncoder.formatInlineArray((ArrayNode) item, options.delimiter().toString(), + null, options.lengthMarker()); writer.push(depth + 2, LIST_ITEM_PREFIX + inline); } else if (item.isObject()) { encodeObjectAsListItem((ObjectNode) item, writer, depth + 2, options); diff --git a/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java index 7b4a30f..fea2885 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java @@ -5,13 +5,11 @@ import tools.jackson.databind.JsonNode; import tools.jackson.databind.node.ArrayNode; import tools.jackson.databind.node.ObjectNode; - import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; - import static dev.toonformat.jtoon.util.Constants.DOT; import static dev.toonformat.jtoon.util.Constants.COLON; import static dev.toonformat.jtoon.util.Constants.SPACE; @@ -38,8 +36,10 @@ private ObjectEncoder() { * @param remainingDepth optional override for the remaining depth * @param blockedKeys contains only keys that have undergone a successful flattening */ - public static void encodeObject(ObjectNode value, LineWriter writer, int depth, EncodeOptions options, Set rootLiteralKeys, String pathPrefix, Integer remainingDepth, Set blockedKeys) { - List> fields = value.properties().stream().toList(); + public static void encodeObject(ObjectNode value, LineWriter writer, int depth, EncodeOptions options, + Set rootLiteralKeys, String pathPrefix, Integer remainingDepth, + Set blockedKeys) { + final List> fields = value.properties().stream().toList(); // At root level (depth 0), collect all literal dotted keys for collision checking if (depth == 0 && rootLiteralKeys != null) { @@ -49,15 +49,16 @@ public static void encodeObject(ObjectNode value, LineWriter writer, int depth, .map(Map.Entry::getKey) .forEach(rootLiteralKeys::add); } - int effectiveFlattenDepth = remainingDepth != null ? remainingDepth : options.flattenDepth(); + final int effectiveFlattenDepth = remainingDepth != null ? remainingDepth : options.flattenDepth(); //the siblings collision do not need the absolute path - Set siblings = fields.stream() + final Set siblings = fields.stream() .map(Map.Entry::getKey) .collect(Collectors.toCollection(LinkedHashSet::new)); for (Map.Entry entry : fields) { - encodeKeyValuePair(entry.getKey(), entry.getValue(), writer, depth, options, siblings, rootLiteralKeys, pathPrefix, effectiveFlattenDepth, blockedKeys); + encodeKeyValuePair(entry.getKey(), entry.getValue(), writer, depth, options, siblings, rootLiteralKeys, + pathPrefix, effectiveFlattenDepth, blockedKeys); } } @@ -89,10 +90,10 @@ public static void encodeKeyValuePair(String key, if (key == null) { return; } - String encodedKey = PrimitiveEncoder.encodeKey(key); - String currentPath = pathPrefix != null ? pathPrefix + DOT + key : key; - int effectiveFlattenDepth = flattenDepth != null && flattenDepth > 0 ? flattenDepth : options.flattenDepth(); - int remainingDepth = effectiveFlattenDepth - depth; + final String encodedKey = PrimitiveEncoder.encodeKey(key); + final String currentPath = pathPrefix != null ? pathPrefix + DOT + key : key; + final int effectiveFlattenDepth = flattenDepth != null && flattenDepth > 0 ? flattenDepth : options.flattenDepth(); + final int remainingDepth = effectiveFlattenDepth - depth; EncodeOptions currentOptions = options; // Attempt key folding when enabled @@ -101,9 +102,11 @@ public static void encodeKeyValuePair(String key, && remainingDepth > 0 && blockedKeys != null && !blockedKeys.contains(key)) { - Flatten.FoldResult foldResult = Flatten.tryFoldKeyChain(key, value, siblings, rootLiteralKeys, pathPrefix, remainingDepth); + final Flatten.FoldResult foldResult = Flatten.tryFoldKeyChain(key, value, siblings, rootLiteralKeys, + pathPrefix, remainingDepth); if (foldResult != null) { - currentOptions = flatten(key, foldResult, writer, depth, currentOptions, rootLiteralKeys, pathPrefix, blockedKeys, remainingDepth); + currentOptions = flatten(key, foldResult, writer, depth, currentOptions, rootLiteralKeys, pathPrefix, + blockedKeys, remainingDepth); if (currentOptions == null) { return; } @@ -111,16 +114,18 @@ public static void encodeKeyValuePair(String key, } if (value.isValueNode()) { - writer.push(depth, encodedKey + COLON + SPACE + PrimitiveEncoder.encodePrimitive(value, currentOptions.delimiter().toString())); + writer.push(depth, encodedKey + COLON + SPACE + + PrimitiveEncoder.encodePrimitive(value, currentOptions.delimiter().toString())); } if (value.isArray()) { ArrayEncoder.encodeArray(key, (ArrayNode) value, writer, depth, currentOptions); } if (value.isObject()) { - ObjectNode objValue = (ObjectNode) value; + final ObjectNode objValue = (ObjectNode) value; writer.push(depth, encodedKey + COLON); if (!objValue.isEmpty()) { - encodeObject(objValue, writer, depth + 1, currentOptions, rootLiteralKeys, currentPath, effectiveFlattenDepth, blockedKeys); + encodeObject(objValue, writer, depth + 1, currentOptions, rootLiteralKeys, currentPath, + effectiveFlattenDepth, blockedKeys); } } } @@ -139,17 +144,18 @@ public static void encodeKeyValuePair(String key, * @param remainingDepth the depth that remind to the limit * @return EncodeOptions changes for Case 2 */ - private static EncodeOptions flatten(String key, Flatten.FoldResult foldResult, LineWriter writer, int depth, EncodeOptions options, Set rootLiteralKeys, String pathPrefix, Set blockedKeys, - int remainingDepth) { - String foldedKey = foldResult.foldedKey(); + private static EncodeOptions flatten(String key, Flatten.FoldResult foldResult, LineWriter writer, int depth, + EncodeOptions options, Set rootLiteralKeys, String pathPrefix, + Set blockedKeys, int remainingDepth) { + final String foldedKey = foldResult.foldedKey(); EncodeOptions currentOptions = options; // prevent second folding pass blockedKeys.add(key); blockedKeys.add(foldedKey); - String encodedFoldedKey = PrimitiveEncoder.encodeKey(foldedKey); - JsonNode remainder = foldResult.remainder(); + final String encodedFoldedKey = PrimitiveEncoder.encodeKey(foldedKey); + final JsonNode remainder = foldResult.remainder(); // Case 1: Fully folded to a leaf value if (remainder == null) { @@ -161,32 +167,36 @@ private static EncodeOptions flatten(String key, Flatten.FoldResult foldResult, if (remainder.isObject()) { writer.push(depth, indentedLine(depth, encodedFoldedKey + COLON, currentOptions.indent())); - String foldedPath = pathPrefix != null ? String.join(DOT, pathPrefix, foldedKey) : foldedKey; + final String foldedPath = pathPrefix != null ? String.join(DOT, pathPrefix, foldedKey) : foldedKey; int newRemainingDepth = remainingDepth - foldResult.segmentCount(); if (newRemainingDepth <= 0) { // Pass "-1" if remainingDepth is exhausted and set the encoding in the option to false. // to encode normally without flattening newRemainingDepth = -1; - currentOptions = new EncodeOptions(currentOptions.indent(), currentOptions.delimiter(), currentOptions.lengthMarker(), KeyFolding.OFF, currentOptions.flattenDepth()); + currentOptions = new EncodeOptions(currentOptions.indent(), currentOptions.delimiter(), + currentOptions.lengthMarker(), KeyFolding.OFF, + currentOptions.flattenDepth()); } - encodeObject((ObjectNode) remainder, writer, depth + 1, currentOptions, rootLiteralKeys, foldedPath, newRemainingDepth, blockedKeys); + encodeObject((ObjectNode) remainder, writer, depth + 1, currentOptions, rootLiteralKeys, foldedPath, + newRemainingDepth, blockedKeys); return null; } return currentOptions; } - private static void handleFullyFoldedLeaf(Flatten.FoldResult foldResult, LineWriter writer, int depth, EncodeOptions options, String encodedFoldedKey) { - JsonNode leaf = foldResult.leafValue(); + private static void handleFullyFoldedLeaf(Flatten.FoldResult foldResult, LineWriter writer, int depth, + EncodeOptions options, String encodedFoldedKey) { + final JsonNode leaf = foldResult.leafValue(); // Primitive if (leaf.isValueNode()) { writer.push(depth, indentedLine(depth, - encodedFoldedKey + COLON + SPACE + - PrimitiveEncoder.encodePrimitive(leaf, options.delimiter().toString()), + encodedFoldedKey + COLON + SPACE + + PrimitiveEncoder.encodePrimitive(leaf, options.delimiter().toString()), options.indent())); return; } @@ -201,8 +211,7 @@ private static void handleFullyFoldedLeaf(Flatten.FoldResult foldResult, LineWri if (leaf.isObject()) { writer.push(depth, indentedLine(depth, encodedFoldedKey + COLON, options.indent())); if (!leaf.isEmpty()) { - encodeObject((ObjectNode) leaf, writer, depth + 1, options, - null, null, null, null); + encodeObject((ObjectNode) leaf, writer, depth + 1, options, null, null, null, null); } } } From ce9fd32e757492cdc22c1096bd60a99a4a0b1711 Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Thu, 12 Feb 2026 20:03:44 +0100 Subject: [PATCH 09/22] cleanup warnings --- .../dev/toonformat/jtoon/DecodeOptions.java | 2 +- .../java/dev/toonformat/jtoon/Delimiter.java | 10 +-- .../dev/toonformat/jtoon/EncodeOptions.java | 12 ++-- src/main/java/dev/toonformat/jtoon/JToon.java | 4 +- .../java/dev/toonformat/jtoon/KeyFolding.java | 2 +- .../jtoon/decoder/ArrayDecoder.java | 69 ++++++++++--------- .../jtoon/decoder/DecodeContext.java | 4 +- .../jtoon/decoder/DecodeHelper.java | 14 ++-- .../toonformat/jtoon/decoder/KeyDecoder.java | 2 +- .../jtoon/decoder/ListItemDecoder.java | 2 - .../jtoon/decoder/ObjectDecoder.java | 45 ++++++------ .../jtoon/decoder/PrimitiveDecoder.java | 5 +- .../jtoon/decoder/TabularArrayDecoder.java | 42 +++++------ .../dev/toonformat/jtoon/encoder/Flatten.java | 10 +-- .../jtoon/encoder/HeaderFormatter.java | 5 +- .../toonformat/jtoon/encoder/LineWriter.java | 3 +- .../jtoon/encoder/ObjectEncoder.java | 4 +- .../jtoon/encoder/PrimitiveEncoder.java | 8 +-- .../jtoon/encoder/TabularArrayEncoder.java | 23 +++---- .../jtoon/encoder/ValueEncoder.java | 5 +- .../jtoon/normalizer/JsonNormalizer.java | 11 ++- .../dev/toonformat/jtoon/util/Headers.java | 8 +-- .../jtoon/util/ObjectMapperSingleton.java | 1 - .../toonformat/jtoon/util/StringEscaper.java | 4 +- .../jtoon/util/StringValidator.java | 5 +- 25 files changed, 146 insertions(+), 154 deletions(-) diff --git a/src/main/java/dev/toonformat/jtoon/DecodeOptions.java b/src/main/java/dev/toonformat/jtoon/DecodeOptions.java index 8163247..6f085a0 100644 --- a/src/main/java/dev/toonformat/jtoon/DecodeOptions.java +++ b/src/main/java/dev/toonformat/jtoon/DecodeOptions.java @@ -18,7 +18,7 @@ public record DecodeOptions( boolean strict, PathExpansion expandPaths) { /** - * Default decoding options: 2 spaces indent, comma delimiter, strict validation, path expansion off + * Default decoding options: 2 spaces indent, comma delimiter, strict validation, path expansion off. */ public static final DecodeOptions DEFAULT = new DecodeOptions(2, Delimiter.COMMA, true, PathExpansion.OFF); diff --git a/src/main/java/dev/toonformat/jtoon/Delimiter.java b/src/main/java/dev/toonformat/jtoon/Delimiter.java index 4e5ebe0..e5bb5ae 100644 --- a/src/main/java/dev/toonformat/jtoon/Delimiter.java +++ b/src/main/java/dev/toonformat/jtoon/Delimiter.java @@ -5,24 +5,24 @@ */ public enum Delimiter { /** - * Comma delimiter (,) - default option + * Comma delimiter (,) - default option. */ COMMA(","), /** - * Tab delimiter (\t) + * Tab delimiter (\t). */ TAB("\t"), /** - * Pipe delimiter (|) + * Pipe delimiter (|). */ PIPE("|"); private final String value; - Delimiter(String value) { - this.value = value; + Delimiter(String delimiterValue) { + this.value = delimiterValue; } /** diff --git a/src/main/java/dev/toonformat/jtoon/EncodeOptions.java b/src/main/java/dev/toonformat/jtoon/EncodeOptions.java index 43ae51d..1e92a2d 100644 --- a/src/main/java/dev/toonformat/jtoon/EncodeOptions.java +++ b/src/main/java/dev/toonformat/jtoon/EncodeOptions.java @@ -21,9 +21,10 @@ public record EncodeOptions( KeyFolding flatten, int flattenDepth) { /** - * Default encoding options: 2 spaces indent, comma delimiter, no length marker + * Default encoding options: 2 spaces indent, comma delimiter, no length marker. */ - public static final EncodeOptions DEFAULT = new EncodeOptions(2, Delimiter.COMMA, false, KeyFolding.OFF, Integer.MAX_VALUE); + public static final EncodeOptions DEFAULT = new EncodeOptions( + 2, Delimiter.COMMA, false, KeyFolding.OFF, Integer.MAX_VALUE); /** * Creates EncodeOptions with default values. @@ -73,12 +74,13 @@ public static EncodeOptions withLengthMarker(boolean lengthMarker) { * @return a new EncodeOptions instance with the flatten setting */ public static EncodeOptions withFlatten(boolean flatten) { - return new EncodeOptions(2, Delimiter.COMMA, false, flatten ? KeyFolding.SAFE : KeyFolding.OFF, Integer.MAX_VALUE); + return new EncodeOptions(2, Delimiter.COMMA, false, + flatten ? KeyFolding.SAFE : KeyFolding.OFF, Integer.MAX_VALUE); } /** - * Creates EncodeOptions with custom flatten flag and the depth of to flatten the nested objects, using default indent and - * delimiter. + * Creates EncodeOptions with custom flatten flag and the depth of to flatten + * the nested objects, using default indent and delimiter. * * @param flattenDepth optional maximum depth to flatten nested objects. * @return a new EncodeOptions instance with the flatten setting and the depth of to flatten the nested objects. diff --git a/src/main/java/dev/toonformat/jtoon/JToon.java b/src/main/java/dev/toonformat/jtoon/JToon.java index 518de7f..09bc59b 100644 --- a/src/main/java/dev/toonformat/jtoon/JToon.java +++ b/src/main/java/dev/toonformat/jtoon/JToon.java @@ -43,7 +43,7 @@ public static String encode(Object input) { * @return The JToon-formatted string */ public static String encode(Object input, EncodeOptions options) { - JsonNode normalizedValue = JsonNormalizer.normalize(input); + final JsonNode normalizedValue = JsonNormalizer.normalize(input); return ValueEncoder.encodeValue(normalizedValue, options); } @@ -79,7 +79,7 @@ public static String encodeJson(String json) { * @throws IllegalArgumentException if the input is not valid JSON */ public static String encodeJson(String json, EncodeOptions options) { - JsonNode parsed = JsonNormalizer.parse(json); + final JsonNode parsed = JsonNormalizer.parse(json); return ValueEncoder.encodeValue(parsed, options); } diff --git a/src/main/java/dev/toonformat/jtoon/KeyFolding.java b/src/main/java/dev/toonformat/jtoon/KeyFolding.java index 3189cea..23d8f45 100644 --- a/src/main/java/dev/toonformat/jtoon/KeyFolding.java +++ b/src/main/java/dev/toonformat/jtoon/KeyFolding.java @@ -12,7 +12,7 @@ public enum KeyFolding { SAFE, /** - * Off mode: default + * Off mode: default. */ OFF } diff --git a/src/main/java/dev/toonformat/jtoon/decoder/ArrayDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/ArrayDecoder.java index 8d992df..1b4193e 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/ArrayDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/ArrayDecoder.java @@ -1,12 +1,10 @@ package dev.toonformat.jtoon.decoder; import dev.toonformat.jtoon.Delimiter; - import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.regex.Matcher; - import static dev.toonformat.jtoon.util.Constants.BACKSLASH; import static dev.toonformat.jtoon.util.Constants.COLON; import static dev.toonformat.jtoon.util.Constants.DOUBLE_QUOTE; @@ -19,6 +17,8 @@ */ public final class ArrayDecoder { + private static final int DELIMITER_GROUP_INDEX = 3; + private ArrayDecoder() { throw new UnsupportedOperationException("Utility class cannot be instantiated"); } @@ -33,7 +33,7 @@ private ArrayDecoder() { * @return parsed array with delimiter */ static List parseArray(String header, int depth, DecodeContext context) { - Delimiter arrayDelimiter = extractDelimiterFromHeader(header, context); + final Delimiter arrayDelimiter = extractDelimiterFromHeader(header, context); return parseArrayWithDelimiter(header, depth, arrayDelimiter, context); } @@ -47,9 +47,9 @@ static List parseArray(String header, int depth, DecodeContext context) * @return extracted delimiter from header */ static Delimiter extractDelimiterFromHeader(String header, DecodeContext context) { - Matcher matcher = ARRAY_HEADER_PATTERN.matcher(header); + final Matcher matcher = ARRAY_HEADER_PATTERN.matcher(header); if (matcher.find()) { - String delimiter = matcher.group(3); + final String delimiter = matcher.group(DELIMITER_GROUP_INDEX); if (delimiter != null) { if (Delimiter.TAB.toString().equals(delimiter)) { return Delimiter.TAB; @@ -74,23 +74,24 @@ static Delimiter extractDelimiterFromHeader(String header, DecodeContext context * @param context decode an object to deal with lines, delimiter and options * @return parsed array */ - static List parseArrayWithDelimiter(String header, int depth, Delimiter arrayDelimiter, DecodeContext context) { - Matcher tabularMatcher = TABULAR_HEADER_PATTERN.matcher(header); - Matcher arrayMatcher = ARRAY_HEADER_PATTERN.matcher(header); + static List parseArrayWithDelimiter(String header, int depth, Delimiter arrayDelimiter, + DecodeContext context) { + final Matcher tabularMatcher = TABULAR_HEADER_PATTERN.matcher(header); + final Matcher arrayMatcher = ARRAY_HEADER_PATTERN.matcher(header); if (tabularMatcher.find()) { return TabularArrayDecoder.parseTabularArray(header, depth, arrayDelimiter, context); } if (arrayMatcher.find()) { - int headerEndIdx = arrayMatcher.end(); - String afterHeader = header.substring(headerEndIdx).trim(); + final int headerEndIdx = arrayMatcher.end(); + final String afterHeader = header.substring(headerEndIdx).trim(); if (afterHeader.startsWith(COLON)) { - String inlineContent = afterHeader.substring(1).trim(); + final String inlineContent = afterHeader.substring(1).trim(); if (!inlineContent.isEmpty()) { - List result = parseArrayValues(inlineContent, arrayDelimiter); + final List result = parseArrayValues(inlineContent, arrayDelimiter); validateArrayLength(header, result.size()); context.currentLine++; return result; @@ -99,9 +100,9 @@ static List parseArrayWithDelimiter(String header, int depth, Delimiter context.currentLine++; if (context.currentLine < context.lines.length) { - String nextLine = context.lines[context.currentLine]; - int nextDepth = DecodeHelper.getDepth(nextLine, context); - String nextContent = nextLine.substring(nextDepth * context.options.indent()); + final String nextLine = context.lines[context.currentLine]; + final int nextDepth = DecodeHelper.getDepth(nextLine, context); + final String nextContent = nextLine.substring(nextDepth * context.options.indent()); if (nextDepth <= depth) { // The next line is not a child of this array, @@ -115,12 +116,12 @@ static List parseArrayWithDelimiter(String header, int depth, Delimiter return parseListArray(depth, header, context); } else { context.currentLine++; - List result = parseArrayValues(nextContent, arrayDelimiter); + final List result = parseArrayValues(nextContent, arrayDelimiter); validateArrayLength(header, result.size()); return result; } } - List empty = new ArrayList<>(); + final List empty = new ArrayList<>(); validateArrayLength(header, 0); return empty; } @@ -138,7 +139,7 @@ static List parseArrayWithDelimiter(String header, int depth, Delimiter * @param actualLength actual length */ static void validateArrayLength(String header, int actualLength) { - Integer declaredLength = extractLengthFromHeader(header); + final Integer declaredLength = extractLengthFromHeader(header); if (declaredLength != null && declaredLength != actualLength) { throw new IllegalArgumentException( String.format("Array length mismatch: declared %d, found %d", declaredLength, actualLength)); @@ -153,7 +154,7 @@ static void validateArrayLength(String header, int actualLength) { * @return extracted length from header */ private static Integer extractLengthFromHeader(String header) { - Matcher matcher = ARRAY_HEADER_PATTERN.matcher(header); + final Matcher matcher = ARRAY_HEADER_PATTERN.matcher(header); if (matcher.find()) { return Integer.parseInt(matcher.group(2)); } @@ -168,9 +169,9 @@ private static Integer extractLengthFromHeader(String header) { * @return parsed array values */ static List parseArrayValues(String values, Delimiter arrayDelimiter) { - List result = new ArrayList<>(); - List rawValues = parseDelimitedValues(values, arrayDelimiter); - for (String value : rawValues) { + final List result = new ArrayList<>(); + final List rawValues = parseDelimitedValues(values, arrayDelimiter); + for (final String value : rawValues) { result.add(PrimitiveDecoder.parse(value)); } return result; @@ -185,15 +186,15 @@ static List parseArrayValues(String values, Delimiter arrayDelimiter) { * @return parsed delimited values */ static List parseDelimitedValues(String input, Delimiter arrayDelimiter) { - List result = new ArrayList<>(); - StringBuilder stringBuilder = new StringBuilder(); + final List result = new ArrayList<>(); + final StringBuilder stringBuilder = new StringBuilder(); boolean inQuotes = false; boolean escaped = false; - char delimiterChar = arrayDelimiter.toString().charAt(0); + final char delimiterChar = arrayDelimiter.toString().charAt(0); int i = 0; while (i < input.length()) { - char currentChar = input.charAt(i); + final char currentChar = input.charAt(i); if (escaped) { stringBuilder.append(currentChar); @@ -209,9 +210,9 @@ static List parseDelimitedValues(String input, Delimiter arrayDelimiter) i++; } else if (currentChar == delimiterChar && !inQuotes) { // Found delimiter - add stringBuilder value (trimmed) and reset - String value = stringBuilder.toString().trim(); + final String value = stringBuilder.toString().trim(); result.add(value); - stringBuilder = new StringBuilder(); + stringBuilder.setLength(0); // Skip whitespace after delimiter do { i++; @@ -235,19 +236,19 @@ static List parseDelimitedValues(String input, Delimiter arrayDelimiter) * Example: items[2]:\n - item1\n - item2 */ private static List parseListArray(int depth, String header, DecodeContext context) { - List result = new ArrayList<>(); + final List result = new ArrayList<>(); context.currentLine++; boolean shouldContinue = true; while (shouldContinue && context.currentLine < context.lines.length) { - String line = context.lines[context.currentLine]; + final String line = context.lines[context.currentLine]; if (DecodeHelper.isBlankLine(line)) { if (handleBlankLineInListArray(depth, context)) { shouldContinue = false; } } else { - int lineDepth = DecodeHelper.getDepth(line, context); + final int lineDepth = DecodeHelper.getDepth(line, context); if (shouldTerminateListArray(lineDepth, depth, line, context)) { shouldContinue = false; } else { @@ -271,13 +272,13 @@ private static List parseListArray(int depth, String header, DecodeConte * @return true if an array should terminate, false if a line should be skipped */ private static boolean handleBlankLineInListArray(int depth, DecodeContext context) { - int nextNonBlankLine = DecodeHelper.findNextNonBlankLine(context.currentLine + 1, context); + final int nextNonBlankLine = DecodeHelper.findNextNonBlankLine(context.currentLine + 1, context); if (nextNonBlankLine >= context.lines.length) { return true; // EOF - terminate array } - int nextDepth = DecodeHelper.getDepth(context.lines[nextNonBlankLine], context); + final int nextDepth = DecodeHelper.getDepth(context.lines[nextNonBlankLine], context); if (nextDepth <= depth) { return true; // Blank line is outside array - terminate } @@ -305,7 +306,7 @@ private static boolean shouldTerminateListArray(int lineDepth, int depth, String } // Also terminate if line is at expected depth but doesn't start with "-" if (lineDepth == depth + 1) { - String content = line.substring((depth + 1) * context.options.indent()); + final String content = line.substring((depth + 1) * context.options.indent()); return !content.startsWith("-"); // Not an array item - terminate } return false; diff --git a/src/main/java/dev/toonformat/jtoon/decoder/DecodeContext.java b/src/main/java/dev/toonformat/jtoon/decoder/DecodeContext.java index dbb0b70..2447915 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/DecodeContext.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/DecodeContext.java @@ -4,7 +4,7 @@ import dev.toonformat.jtoon.Delimiter; /** - * Deals with the main attributes used to decode TOON to JSON format + * Deals with the main attributes used to decode TOON to JSON format. */ public class DecodeContext { @@ -26,7 +26,7 @@ public class DecodeContext { protected int currentLine; /** - * Default constructor + * Default constructor. */ public DecodeContext() { } diff --git a/src/main/java/dev/toonformat/jtoon/decoder/DecodeHelper.java b/src/main/java/dev/toonformat/jtoon/decoder/DecodeHelper.java index 2a75b90..f15a59d 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/DecodeHelper.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/DecodeHelper.java @@ -1,10 +1,8 @@ package dev.toonformat.jtoon.decoder; import dev.toonformat.jtoon.Delimiter; - import java.util.List; import java.util.Map; - import static dev.toonformat.jtoon.util.Constants.BACKSLASH; import static dev.toonformat.jtoon.util.Constants.DOUBLE_QUOTE; import static dev.toonformat.jtoon.util.Constants.SPACE; @@ -45,13 +43,13 @@ public static int getDepth(String line, DecodeContext context) { * @return amount of leading spaces */ private static int computeLeadingSpaces(String line, DecodeContext context) { - int indentSize = context.options.indent(); + final int indentSize = context.options.indent(); int leadingSpaces = 0; int i = 0; - int lengthOfLine = line.length(); + final int lengthOfLine = line.length(); while (i < lengthOfLine) { - char c = line.charAt(i); + final char c = line.charAt(i); if (c == SPACE.charAt(0)) { leadingSpaces++; } else if (c == Delimiter.TAB.getValue()) { @@ -99,7 +97,7 @@ static int findUnquotedColon(String content) { boolean escaped = false; for (int i = 0; i < content.length(); i++) { - char c = content.charAt(i); + final char c = content.charAt(i); if (escaped) { escaped = false; @@ -169,7 +167,7 @@ static void checkPathExpansionConflict(Map map, String key, Obje return; } - Object existing = map.get(key); + final Object existing = map.get(key); checkFinalValueConflict(key, existing, value, context); } @@ -204,7 +202,7 @@ static void validateNoMultiplePrimitivesAtRoot(DecodeContext context) { lineIndex++; } if (lineIndex < context.lines.length) { - int nextDepth = getDepth(context.lines[lineIndex], context); + final int nextDepth = getDepth(context.lines[lineIndex], context); if (nextDepth == 0) { throw new IllegalArgumentException( "Multiple primitives at root depth in strict mode at line " + (lineIndex + 1)); diff --git a/src/main/java/dev/toonformat/jtoon/decoder/KeyDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/KeyDecoder.java index 8ce8f22..4dde16c 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/KeyDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/KeyDecoder.java @@ -165,7 +165,7 @@ static boolean shouldExpandKey(String key, DecodeContext context) { // Valid identifier: starts with a letter or underscore, followed by letters, // digits, underscores // Each segment must match this pattern - String[] segments = key.split("\\."); + final String[] segments = key.split("\\."); for (String segment : segments) { if (!segment.matches("^[a-zA-Z_]\\w*$")) { return false; diff --git a/src/main/java/dev/toonformat/jtoon/decoder/ListItemDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/ListItemDecoder.java index 45ac4e4..58fadf7 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/ListItemDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/ListItemDecoder.java @@ -2,12 +2,10 @@ import dev.toonformat.jtoon.Delimiter; import dev.toonformat.jtoon.util.StringEscaper; - import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; - import static dev.toonformat.jtoon.util.Constants.LIST_ITEM_MARKER; import static dev.toonformat.jtoon.util.Constants.OPEN_BRACKET; import static dev.toonformat.jtoon.util.Headers.KEYED_ARRAY_PATTERN; diff --git a/src/main/java/dev/toonformat/jtoon/decoder/ObjectDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/ObjectDecoder.java index 430dbb0..e147d44 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/ObjectDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/ObjectDecoder.java @@ -1,12 +1,10 @@ package dev.toonformat.jtoon.decoder; import dev.toonformat.jtoon.util.StringEscaper; - import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; - import static dev.toonformat.jtoon.util.Headers.KEYED_ARRAY_PATTERN; /** @@ -26,10 +24,10 @@ private ObjectDecoder() { * @return parsed nested object */ static Map parseNestedObject(int parentDepth, DecodeContext context) { - Map result = new LinkedHashMap<>(); + final Map result = new LinkedHashMap<>(); while (context.currentLine < context.lines.length) { - String line = context.lines[context.currentLine]; + final String line = context.lines[context.currentLine]; // Skip blank lines if (DecodeHelper.isBlankLine(line)) { @@ -37,7 +35,7 @@ static Map parseNestedObject(int parentDepth, DecodeContext cont continue; } - int depth = DecodeHelper.getDepth(line, context); + final int depth = DecodeHelper.getDepth(line, context); if (depth <= parentDepth) { return result; @@ -58,9 +56,10 @@ static Map parseNestedObject(int parentDepth, DecodeContext cont * Returns true if the line was processed, false if it was a blank line that was * skipped. */ - private static void processDirectChildLine(Map result, String line, int parentDepth, int depth, DecodeContext context) { - String content = line.substring((parentDepth + 1) * context.options.indent()); - Matcher keyedArray = KEYED_ARRAY_PATTERN.matcher(content); + private static void processDirectChildLine(Map result, String line, int parentDepth, int depth, + DecodeContext context) { + final String content = line.substring((parentDepth + 1) * context.options.indent()); + final Matcher keyedArray = KEYED_ARRAY_PATTERN.matcher(content); if (keyedArray.find()) { KeyDecoder.processKeyedArrayLine(result, content, keyedArray.group(1), parentDepth, context); @@ -78,8 +77,8 @@ private static void processDirectChildLine(Map result, String li */ static void parseRootObjectFields(Map obj, int depth, DecodeContext context) { while (context.currentLine < context.lines.length) { - String line = context.lines[context.currentLine]; - int lineDepth = DecodeHelper.getDepth(line, context); + final String line = context.lines[context.currentLine]; + final int lineDepth = DecodeHelper.getDepth(line, context); if (lineDepth != depth) { return; @@ -91,16 +90,16 @@ static void parseRootObjectFields(Map obj, int depth, DecodeCont continue; } - String content = line.substring(depth * context.options.indent()); + final String content = line.substring(depth * context.options.indent()); - Matcher keyedArray = KEYED_ARRAY_PATTERN.matcher(content); + final Matcher keyedArray = KEYED_ARRAY_PATTERN.matcher(content); if (keyedArray.matches()) { processRootKeyedArrayLine(obj, content, keyedArray.group(1), depth, context); } else { - int colonIdx = DecodeHelper.findUnquotedColon(content); + final int colonIdx = DecodeHelper.findUnquotedColon(content); if (colonIdx > 0) { - String key = content.substring(0, colonIdx).trim(); - String value = content.substring(colonIdx + 1).trim(); + final String key = content.substring(0, colonIdx).trim(); + final String value = content.substring(colonIdx + 1).trim(); KeyDecoder.parseKeyValuePairIntoMap(obj, key, value, depth, context); } else { @@ -121,11 +120,11 @@ static void parseRootObjectFields(Map obj, int depth, DecodeCont */ private static void processRootKeyedArrayLine(Map objectMap, String content, String originalKey, int depth, DecodeContext context) { - String originalKeyTrimmed = originalKey.trim(); - String key = StringEscaper.unescape(originalKey); - String arrayHeader = content.substring(originalKey.length()); + final String originalKeyTrimmed = originalKey.trim(); + final String key = StringEscaper.unescape(originalKey); + final String arrayHeader = content.substring(originalKey.length()); - List arrayValue = ArrayDecoder.parseArray(arrayHeader, depth, context); + final List arrayValue = ArrayDecoder.parseArray(arrayHeader, depth, context); // Handle path expansion for array keys if (KeyDecoder.shouldExpandKey(originalKeyTrimmed, context)) { @@ -146,7 +145,7 @@ private static void processRootKeyedArrayLine(Map objectMap, Str * @return the parsed scalar value */ static Object parseBareScalarValue(String content, int depth, DecodeContext context) { - Object result = PrimitiveDecoder.parse(content); + final Object result = PrimitiveDecoder.parse(content); context.currentLine++; // In strict mode, check if there are more primitives at the root level @@ -168,7 +167,7 @@ static Object parseBareScalarValue(String content, int depth, DecodeContext cont static Object parseFieldValue(String fieldValue, int fieldDepth, DecodeContext context) { // Check if the next line is nested if (context.currentLine + 1 < context.lines.length) { - int nextDepth = DecodeHelper.getDepth(context.lines[context.currentLine + 1], context); + final int nextDepth = DecodeHelper.getDepth(context.lines[context.currentLine + 1], context); if (nextDepth > fieldDepth) { context.currentLine++; // parseNestedObject manages the currentLine, so we don't increment here @@ -206,10 +205,10 @@ static Object parseFieldValue(String fieldValue, int fieldDepth, DecodeContext c * @return the parsed value (Map, List, or primitive) */ static Object parseObjectItemValue(String value, int depth, DecodeContext context) { - boolean isEmpty = value.isBlank(); + final boolean isEmpty = value.isBlank(); // Find the next non-blank line and its depth - Integer nextDepth = DecodeHelper.findNextNonBlankLineDepth(context); + final Integer nextDepth = DecodeHelper.findNextNonBlankLineDepth(context); if (nextDepth == null) { // No non-blank line found - create an empty object return new LinkedHashMap<>(); diff --git a/src/main/java/dev/toonformat/jtoon/decoder/PrimitiveDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/PrimitiveDecoder.java index e88aa55..5ffb466 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/PrimitiveDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/PrimitiveDecoder.java @@ -1,7 +1,6 @@ package dev.toonformat.jtoon.decoder; import dev.toonformat.jtoon.util.StringEscaper; - import static dev.toonformat.jtoon.util.Constants.DOT; import static dev.toonformat.jtoon.util.Constants.NULL_LITERAL; import static dev.toonformat.jtoon.util.Constants.TRUE_LITERAL; @@ -75,7 +74,7 @@ static Object parse(String value) { } // Check for leading zeros (treat as string, except for "0", "-0", "0.0", etc.) - String trimmed = value.trim(); + final String trimmed = value.trim(); if (trimmed.length() > 1 && trimmed.matches("^-?0+[0-7].*")) { return value; } @@ -84,7 +83,7 @@ static Object parse(String value) { try { // Check if it contains exponent notation or decimal point if (value.contains(DOT) || value.contains("e") || value.contains("E")) { - double parsed = Double.parseDouble(value); + final double parsed = Double.parseDouble(value); // Handle negative zero - Java doesn't distinguish, but spec says it should be 0 if (parsed == 0.0) { return 0L; diff --git a/src/main/java/dev/toonformat/jtoon/decoder/TabularArrayDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/TabularArrayDecoder.java index 7a0827d..87b1406 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/TabularArrayDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/TabularArrayDecoder.java @@ -2,14 +2,12 @@ import dev.toonformat.jtoon.Delimiter; import dev.toonformat.jtoon.util.StringEscaper; - import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; - import static dev.toonformat.jtoon.util.Constants.BACKSLASH; import static dev.toonformat.jtoon.util.Constants.DOUBLE_QUOTE; import static dev.toonformat.jtoon.util.Headers.TABULAR_HEADER_PATTERN; @@ -83,7 +81,7 @@ private static List parseTabularKeys(String keysStr, Delimiter arrayDeli final List result = new ArrayList<>(); final List rawValues = ArrayDecoder.parseDelimitedValues(keysStr, arrayDelimiter); - for (String key : rawValues) { + for (final String key : rawValues) { result.add(StringEscaper.unescape(key)); } return result; @@ -96,12 +94,12 @@ private static List parseTabularKeys(String keysStr, Delimiter arrayDeli * @param expectedDelimiter the expected delimiter used in the array */ private static void validateKeysDelimiter(String keysStr, Delimiter expectedDelimiter) { - char expectedChar = expectedDelimiter.toString().charAt(0); + final char expectedChar = expectedDelimiter.toString().charAt(0); boolean inQuotes = false; boolean escaped = false; for (int i = 0; i < keysStr.length(); i++) { - char c = keysStr.charAt(i); + final char c = keysStr.charAt(i); if (escaped) { escaped = false; } else if (c == BACKSLASH) { @@ -148,13 +146,13 @@ private static void checkDelimiterMismatch(char expectedChar, char actualChar) { */ private static boolean processTabularArrayLine(int expectedRowDepth, List keys, Delimiter arrayDelimiter, List result, DecodeContext context) { - String line = context.lines[context.currentLine]; + final String line = context.lines[context.currentLine]; if (DecodeHelper.isBlankLine(line)) { return !handleBlankLineInTabularArray(expectedRowDepth, context); } - int lineDepth = DecodeHelper.getDepth(line, context); + final int lineDepth = DecodeHelper.getDepth(line, context); if (shouldTerminateTabularArray(line, lineDepth, expectedRowDepth, context)) { return false; } @@ -173,12 +171,12 @@ private static boolean processTabularArrayLine(int expectedRowDepth, List 0) { return true; // Key-value pair at the same depth-terminate an array } @@ -220,8 +219,8 @@ private static boolean shouldTerminateTabularArray(String line, int lineDepth, i // Check for a key-value pair at the expected row depth if (lineDepth == expectedRowDepth) { - String rowContent = line.substring(expectedRowDepth * context.options.indent()); - int colonIdx = DecodeHelper.findUnquotedColon(rowContent); + final String rowContent = line.substring(expectedRowDepth * context.options.indent()); + final int colonIdx = DecodeHelper.findUnquotedColon(rowContent); return colonIdx > 0; // Key-value pair at the same depth as rows - terminate an array } @@ -243,8 +242,8 @@ private static boolean shouldTerminateTabularArray(String line, int lineDepth, i private static boolean processTabularRow(String line, int lineDepth, int expectedRowDepth, List keys, Delimiter arrayDelimiter, List result, DecodeContext context) { if (lineDepth == expectedRowDepth) { - String rowContent = line.substring(expectedRowDepth * context.options.indent()); - Map row = parseTabularRow(rowContent, keys, arrayDelimiter, context); + final String rowContent = line.substring(expectedRowDepth * context.options.indent()); + final Map row = parseTabularRow(rowContent, keys, arrayDelimiter, context); result.add(row); return true; } else if (lineDepth > expectedRowDepth) { @@ -265,9 +264,10 @@ private static boolean processTabularRow(String line, int lineDepth, int expecte * @param context decode an object to deal with lines, delimiter and options * @return a Map containing the parsed row values */ - private static Map parseTabularRow(String rowContent, List keys, Delimiter arrayDelimiter, DecodeContext context) { - Map row = new LinkedHashMap<>(); - List values = ArrayDecoder.parseArrayValues(rowContent, arrayDelimiter); + private static Map parseTabularRow(String rowContent, List keys, + Delimiter arrayDelimiter, DecodeContext context) { + final Map row = new LinkedHashMap<>(); + final List values = ArrayDecoder.parseArrayValues(rowContent, arrayDelimiter); // Validate value count matches key count if (context.options.strict() && values.size() != keys.size()) { diff --git a/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java b/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java index b6aa66f..60d6e25 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java @@ -105,7 +105,7 @@ public static FoldResult tryFoldKeyChain(String key, } // Compute absolute dotted path - String absolutePath = + final String absolutePath = (pathPrefix != null && !pathPrefix.isEmpty()) ? String.join(DOT, pathPrefix, foldedKey) : foldedKey; @@ -125,7 +125,7 @@ public static FoldResult tryFoldKeyChain(String key, /** * Traverses nested single-key {@link ObjectNode} values, collecting the - * sequence of keys until one of the following occurs: + * sequence of keys until one of the following occurs. * - A non-object value is encountered * - An object with zero or more than one key is encountered * - An empty object is encountered (treated as a leaf) @@ -138,7 +138,7 @@ public static FoldResult tryFoldKeyChain(String key, */ private static ChainResult collectSingleKeyChain(String startKey, JsonNode startValue, int maxDepth) { // normalize absolute key to its local segment - String localStartKey = startKey.contains(DOT) + final String localStartKey = startKey.contains(DOT) ? startKey.substring(startKey.lastIndexOf(DOT.charAt(0)) + 1) : startKey; @@ -151,14 +151,14 @@ private static ChainResult collectSingleKeyChain(String startKey, JsonNode start while (currentValue.isObject() && depthCounter < maxDepth) { final ObjectNode obj = (ObjectNode) currentValue; - Iterator> it = obj.properties().iterator(); + final Iterator> it = obj.properties().iterator(); // empty object leaf if (!it.hasNext()) { return new ChainResult(segments, null, currentValue); } - Map.Entry entry = it.next(); + final Map.Entry entry = it.next(); // >1 field, this is a tail object if (it.hasNext()) { diff --git a/src/main/java/dev/toonformat/jtoon/encoder/HeaderFormatter.java b/src/main/java/dev/toonformat/jtoon/encoder/HeaderFormatter.java index 68a1635..dfb83b1 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/HeaderFormatter.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/HeaderFormatter.java @@ -1,7 +1,6 @@ package dev.toonformat.jtoon.encoder; import java.util.List; - import static dev.toonformat.jtoon.util.Constants.COLON; import static dev.toonformat.jtoon.util.Constants.OPEN_BRACKET; import static dev.toonformat.jtoon.util.Constants.COMMA; @@ -43,7 +42,7 @@ public record HeaderConfig( * @return Formatted header string */ public static String format(HeaderConfig config) { - StringBuilder header = new StringBuilder(); + final StringBuilder header = new StringBuilder(); appendKeyIfPresent(header, config.key()); appendArrayLength(header, config.length(), config.delimiter(), config.lengthMarker()); @@ -69,7 +68,7 @@ public static String format( List fields, String delimiter, boolean lengthMarker) { - HeaderConfig config = new HeaderConfig(length, key, fields, delimiter, lengthMarker); + final HeaderConfig config = new HeaderConfig(length, key, fields, delimiter, lengthMarker); return format(config); } diff --git a/src/main/java/dev/toonformat/jtoon/encoder/LineWriter.java b/src/main/java/dev/toonformat/jtoon/encoder/LineWriter.java index 59850b5..64dc296 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/LineWriter.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/LineWriter.java @@ -2,7 +2,6 @@ import java.util.ArrayList; import java.util.List; - import static dev.toonformat.jtoon.util.Constants.SPACE; /** @@ -28,7 +27,7 @@ public LineWriter(int indentSize) { * @param content Line content to add */ public void push(int depth, String content) { - String indent = indentationString.repeat(depth); + final String indent = indentationString.repeat(depth); lines.add(indent + content); } diff --git a/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java index fea2885..63df414 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java @@ -92,7 +92,9 @@ public static void encodeKeyValuePair(String key, } final String encodedKey = PrimitiveEncoder.encodeKey(key); final String currentPath = pathPrefix != null ? pathPrefix + DOT + key : key; - final int effectiveFlattenDepth = flattenDepth != null && flattenDepth > 0 ? flattenDepth : options.flattenDepth(); + final int effectiveFlattenDepth = flattenDepth != null && flattenDepth > 0 + ? flattenDepth + : options.flattenDepth(); final int remainingDepth = effectiveFlattenDepth - depth; EncodeOptions currentOptions = options; diff --git a/src/main/java/dev/toonformat/jtoon/encoder/PrimitiveEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/PrimitiveEncoder.java index 8675bd2..78aab20 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/PrimitiveEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/PrimitiveEncoder.java @@ -3,11 +3,9 @@ import dev.toonformat.jtoon.util.StringEscaper; import dev.toonformat.jtoon.util.StringValidator; import tools.jackson.databind.JsonNode; - import java.math.BigDecimal; import java.util.List; import java.util.Objects; - import static dev.toonformat.jtoon.util.Constants.NULL_LITERAL; import static dev.toonformat.jtoon.util.Constants.DOUBLE_QUOTE; @@ -47,9 +45,9 @@ private static String encodeNumber(JsonNode value) { return value.asString(); } - double doubleValue = value.asDouble(); - BigDecimal decimal = BigDecimal.valueOf(doubleValue); - String plainString = decimal.toPlainString(); + final double doubleValue = value.asDouble(); + final BigDecimal decimal = BigDecimal.valueOf(doubleValue); + final String plainString = decimal.toPlainString(); return stripTrailingZeros(plainString); } diff --git a/src/main/java/dev/toonformat/jtoon/encoder/TabularArrayEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/TabularArrayEncoder.java index 0a9552f..8cd24a8 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/TabularArrayEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/TabularArrayEncoder.java @@ -4,7 +4,6 @@ import tools.jackson.databind.JsonNode; import tools.jackson.databind.node.ArrayNode; import tools.jackson.databind.node.ObjectNode; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -31,13 +30,13 @@ public static List detectTabularHeader(ArrayNode rows) { return Collections.emptyList(); } - JsonNode firstRow = rows.get(0); + final JsonNode firstRow = rows.get(0); if (!firstRow.isObject()) { return Collections.emptyList(); } - ObjectNode firstObj = (ObjectNode) firstRow; - List firstKeys = new ArrayList<>(firstObj.propertyNames()); + final ObjectNode firstObj = (ObjectNode) firstRow; + final List firstKeys = new ArrayList<>(firstObj.propertyNames()); if (firstKeys.isEmpty()) { return Collections.emptyList(); @@ -59,8 +58,8 @@ private static boolean isTabularArray(ArrayNode rows, List header) { return false; } - ObjectNode obj = (ObjectNode) row; - List keys = new ArrayList<>(obj.propertyNames()); + final ObjectNode obj = (ObjectNode) row; + final List keys = new ArrayList<>(obj.propertyNames()); // All objects must have the same keys (but order can differ) if (keys.size() != header.size()) { @@ -92,9 +91,9 @@ private static boolean isTabularArray(ArrayNode rows, List header) { * @param options Encoding options */ public static void encodeArrayOfObjectsAsTabular(String prefix, ArrayNode rows, List header, - LineWriter writer, int depth, EncodeOptions options) { - String headerStr = PrimitiveEncoder.formatHeader(rows.size(), prefix, header, options.delimiter().toString(), - options.lengthMarker()); + LineWriter writer, int depth, EncodeOptions options) { + final String headerStr = PrimitiveEncoder.formatHeader(rows.size(), prefix, header, + options.delimiter().toString(), options.lengthMarker()); writer.push(depth, headerStr); writeTabularRows(rows, header, writer, depth + 1, options); @@ -117,12 +116,12 @@ public static void writeTabularRows(ArrayNode rows, List header, LineWri if (!row.isObject()) { continue; } - ObjectNode obj = (ObjectNode) row; - List values = new ArrayList<>(); + final ObjectNode obj = (ObjectNode) row; + final List values = new ArrayList<>(); for (String key : header) { values.add(obj.get(key)); } - String joinedValue = PrimitiveEncoder.joinEncodedValues(values, options.delimiter().toString()); + final String joinedValue = PrimitiveEncoder.joinEncodedValues(values, options.delimiter().toString()); writer.push(depth, joinedValue); } } diff --git a/src/main/java/dev/toonformat/jtoon/encoder/ValueEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/ValueEncoder.java index 697c89f..b3960a4 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/ValueEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/ValueEncoder.java @@ -4,7 +4,6 @@ import tools.jackson.databind.JsonNode; import tools.jackson.databind.node.ArrayNode; import tools.jackson.databind.node.ObjectNode; - import java.util.HashSet; import java.util.Set; @@ -32,12 +31,12 @@ public static String encodeValue(JsonNode value, EncodeOptions options) { } // Complex values need a LineWriter for indentation - LineWriter writer = new LineWriter(options.indent()); + final LineWriter writer = new LineWriter(options.indent()); if (value.isArray()) { ArrayEncoder.encodeArray(null, (ArrayNode) value, writer, 0, options); } else if (value.isObject()) { - Set jsonNodes = new HashSet<>(value.propertyNames()); + final Set jsonNodes = new HashSet<>(value.propertyNames()); ObjectEncoder.encodeObject((ObjectNode) value, writer, 0, options, jsonNodes, null, null, new HashSet<>()); } diff --git a/src/main/java/dev/toonformat/jtoon/normalizer/JsonNormalizer.java b/src/main/java/dev/toonformat/jtoon/normalizer/JsonNormalizer.java index 1efaabd..d10b443 100644 --- a/src/main/java/dev/toonformat/jtoon/normalizer/JsonNormalizer.java +++ b/src/main/java/dev/toonformat/jtoon/normalizer/JsonNormalizer.java @@ -14,7 +14,6 @@ import tools.jackson.databind.node.ObjectNode; import tools.jackson.databind.node.ShortNode; import tools.jackson.databind.node.StringNode; - import java.math.BigDecimal; import java.math.BigInteger; import java.time.Instant; @@ -174,7 +173,7 @@ private static Optional tryConvertToLong(Double value) { if (value > Long.MAX_VALUE || value < Long.MIN_VALUE) { return Optional.empty(); } - long longVal = value.longValue(); + final long longVal = value.longValue(); return Optional.of(LongNode.valueOf(longVal)); } @@ -196,7 +195,7 @@ private static JsonNode tryNormalizeBigNumber(Object value) { * Normalizes BigInteger, converting to long if within range. */ private static JsonNode normalizeBigInteger(BigInteger value) { - boolean fitsInLong = value.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0 + final boolean fitsInLong = value.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0 && value.compareTo(BigInteger.valueOf(Long.MIN_VALUE)) >= 0; return fitsInLong ? LongNode.valueOf(value.longValue()) @@ -260,7 +259,7 @@ private static JsonNode tryNormalizeCollection(Object value) { * Normalizes a Collection to an ArrayNode. */ private static ArrayNode normalizeCollection(Collection collection) { - ArrayNode arrayNode = MAPPER.createArrayNode(); + final ArrayNode arrayNode = MAPPER.createArrayNode(); collection.forEach(item -> arrayNode.add(normalize(item))); return arrayNode; } @@ -269,7 +268,7 @@ private static ArrayNode normalizeCollection(Collection collection) { * Normalizes a Map to an ObjectNode. */ private static ObjectNode normalizeMap(Map map) { - ObjectNode objectNode = MAPPER.createObjectNode(); + final ObjectNode objectNode = MAPPER.createObjectNode(); map.forEach((key, value) -> objectNode.set(String.valueOf(key), normalize(value))); return objectNode; } @@ -317,7 +316,7 @@ private static JsonNode normalizeArray(Object array) { * Builds an ArrayNode using a functional approach. */ private static ArrayNode buildArrayNode(int length, IntFunction mapper) { - ArrayNode arrayNode = MAPPER.createArrayNode(); + final ArrayNode arrayNode = MAPPER.createArrayNode(); for (int i = 0; i < length; i++) { arrayNode.add(mapper.apply(i)); } diff --git a/src/main/java/dev/toonformat/jtoon/util/Headers.java b/src/main/java/dev/toonformat/jtoon/util/Headers.java index dadf18f..db5b4cb 100644 --- a/src/main/java/dev/toonformat/jtoon/util/Headers.java +++ b/src/main/java/dev/toonformat/jtoon/util/Headers.java @@ -3,25 +3,25 @@ import java.util.regex.Pattern; /** - * Patterns in form of regex that must be followed in order to decode arrays, tabular, keyed arrays + * Patterns in form of regex that must be followed in order to decode arrays, tabular, keyed arrays. */ public class Headers { /** - * Matches standalone array headers: [3], [#2], [3\t], [2|] + * Matches standalone array headers: [3], [#2], [3\t], [2|]. * Group 1: optional # marker, Group 2: digits, Group 3: optional delimiter */ public static final Pattern ARRAY_HEADER_PATTERN = Pattern.compile("^\\[(#?)(\\d+)([\\t|])?]"); /** - * Matches tabular array headers with field names: [2]{id,name,role}: + * Matches tabular array headers with field names: [2]{id,name,role}:. * Group 1: optional # marker, Group 2: digits, Group 3: optional delimiter, * Group 4: field spec */ public static final Pattern TABULAR_HEADER_PATTERN = Pattern.compile("^\\[(#?)(\\d+)([\\t|])?]\\{(.+)}:"); /** - * Matches keyed array headers: items[2]{id,name}: or tags[3]: or data[4]{id}: + * Matches keyed array headers: items[2]{id,name}: or tags[3]: or data[4]{id}:. * Captures: group(1)=key, group(2)=#marker, group(3)=delimiter, * group(4)=optional field spec */ diff --git a/src/main/java/dev/toonformat/jtoon/util/ObjectMapperSingleton.java b/src/main/java/dev/toonformat/jtoon/util/ObjectMapperSingleton.java index 7f2c3f3..d7c39bc 100644 --- a/src/main/java/dev/toonformat/jtoon/util/ObjectMapperSingleton.java +++ b/src/main/java/dev/toonformat/jtoon/util/ObjectMapperSingleton.java @@ -5,7 +5,6 @@ import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.json.JsonMapper; import tools.jackson.module.afterburner.AfterburnerModule; - import java.util.TimeZone; /** diff --git a/src/main/java/dev/toonformat/jtoon/util/StringEscaper.java b/src/main/java/dev/toonformat/jtoon/util/StringEscaper.java index de49057..795bdc7 100644 --- a/src/main/java/dev/toonformat/jtoon/util/StringEscaper.java +++ b/src/main/java/dev/toonformat/jtoon/util/StringEscaper.java @@ -44,7 +44,7 @@ public static void validateString(String value) { // Check for invalid escape sequences in quoted strings if (value.startsWith("\"") && value.endsWith("\"")) { - String unquoted = value.substring(1, value.length() - 1); + final String unquoted = value.substring(1, value.length() - 1); boolean escaped = false; for (char c : unquoted.toCharArray()) { @@ -90,7 +90,7 @@ public static String unescape(String value) { unquoted = value.substring(1, value.length() - 1); } - StringBuilder result = new StringBuilder(); + final StringBuilder result = new StringBuilder(); boolean escaped = false; for (char c : unquoted.toCharArray()) { diff --git a/src/main/java/dev/toonformat/jtoon/util/StringValidator.java b/src/main/java/dev/toonformat/jtoon/util/StringValidator.java index a4c2251..d441665 100644 --- a/src/main/java/dev/toonformat/jtoon/util/StringValidator.java +++ b/src/main/java/dev/toonformat/jtoon/util/StringValidator.java @@ -1,7 +1,6 @@ package dev.toonformat.jtoon.util; import java.util.regex.Pattern; - import static dev.toonformat.jtoon.util.Constants.*; /** @@ -92,7 +91,9 @@ private static boolean looksLikeKeyword(String value) { } private static boolean looksLikeNumber(String value) { - return OCTAL_PATTERN.matcher(value).matches() || LEADING_ZERO_PATTERN.matcher(value).matches() || NUMERIC_PATTERN.matcher(value).matches(); + return OCTAL_PATTERN.matcher(value).matches() + || LEADING_ZERO_PATTERN.matcher(value).matches() + || NUMERIC_PATTERN.matcher(value).matches(); } private static boolean containsColon(String value) { From 536b8c2e001e0735ceb203141e5f2f26fd0210e7 Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Thu, 12 Feb 2026 20:15:23 +0100 Subject: [PATCH 10/22] adding pitest --- build.gradle | 11 +++++++++++ settings.gradle | 14 ++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/build.gradle b/build.gradle index 2c6a93b..fc613fd 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,7 @@ plugins { id 'com.github.spotbugs' version '6.0.15' id 'pmd' id 'checkstyle' + id 'info.solidsoft.pitest' version '1.19.0-rc.3' } group = 'dev.toonformat' @@ -38,6 +39,15 @@ jacoco { reportsDirectory = layout.buildDirectory.dir('customJacocoReportDir') } +pitest { + pitestVersion = '1.17.4' + targetClasses = ['dev.toonformat.jtoon.*'] + targetTests = ['dev.toonformat.jtoon.*'] + outputFormats = ['XML', 'HTML'] + mutationThreshold = 70 + coverageThreshold = 70 +} + spotbugs { toolVersion = '4.8.6' excludeFilter = file('spotbugs-exclude.xml') @@ -122,6 +132,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testImplementation 'org.awaitility:awaitility:4.2.1' + testImplementation 'org.pitest:pitest-junit5-plugin:1.2.3' } test { diff --git a/settings.gradle b/settings.gradle index 7e825da..4dc8f38 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,15 @@ +pluginManagement { + repositories { + gradlePluginPortal() + mavenCentral() + } + resolutionStrategy { + eachPlugin { + if (requested.id.id == 'info.solidsoft.pitest') { + useModule("info.solidsoft.gradle.pitest:gradle-pitest-plugin:${requested.version}") + } + } + } +} + rootProject.name = 'JToon' \ No newline at end of file From f177c3cf1fe5f28f48651ce6cf876ae9997a5e59 Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Thu, 12 Feb 2026 20:18:39 +0100 Subject: [PATCH 11/22] update versions numbers --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index fc613fd..dc5773a 100644 --- a/build.gradle +++ b/build.gradle @@ -49,7 +49,7 @@ pitest { } spotbugs { - toolVersion = '4.8.6' + toolVersion = '4.9.8' excludeFilter = file('spotbugs-exclude.xml') effort = "max" reportLevel = "low" @@ -127,11 +127,11 @@ tasks.checkstyleTest { dependencies { implementation 'tools.jackson.core:jackson-databind:3.0.4' implementation 'tools.jackson.module:jackson-module-afterburner:3.0.4' - compileOnly 'com.github.spotbugs:spotbugs-annotations:4.8.6' + compileOnly 'com.github.spotbugs:spotbugs-annotations:4.9.8' testImplementation platform('org.junit:junit-bom:6.0.2') testImplementation 'org.junit.jupiter:junit-jupiter' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - testImplementation 'org.awaitility:awaitility:4.2.1' + testImplementation 'org.awaitility:awaitility:4.3.0' testImplementation 'org.pitest:pitest-junit5-plugin:1.2.3' } From 4994a4dd5420e96e2c8f73b5eee94aaf65c87d60 Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Thu, 12 Feb 2026 20:46:44 +0100 Subject: [PATCH 12/22] adding JHM --- build.gradle | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/build.gradle b/build.gradle index dc5773a..64b2a07 100644 --- a/build.gradle +++ b/build.gradle @@ -133,6 +133,9 @@ dependencies { testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testImplementation 'org.awaitility:awaitility:4.3.0' testImplementation 'org.pitest:pitest-junit5-plugin:1.2.3' + testImplementation 'org.openjdk.jmh:jmh-core:1.37' + testImplementation 'org.openjdk.jmh:jmh-generator-annprocess:1.37' + testAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.37' } test { @@ -194,3 +197,14 @@ tasks.register('specsValidation', Test) { include '**/ConformanceTest.class' } +tasks.register('jmh', JavaExec) { + group = 'verification' + description = 'Run JMH benchmarks' + classpath = configurations.testRuntimeClasspath + sourceSets.test.runtimeClasspath + mainClass = 'dev.toonformat.jtoon.JToonBenchmark' + workingDir = projectDir + doFirst { + file('build/jmh-results').mkdirs() + } +} + From 5caf07becd9eceeaa3f3a067680e92c889e8d214 Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Thu, 12 Feb 2026 20:47:01 +0100 Subject: [PATCH 13/22] adding test --- .../dev/toonformat/jtoon/JToonBenchmark.java | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 src/test/java/dev/toonformat/jtoon/JToonBenchmark.java diff --git a/src/test/java/dev/toonformat/jtoon/JToonBenchmark.java b/src/test/java/dev/toonformat/jtoon/JToonBenchmark.java new file mode 100644 index 0000000..b430061 --- /dev/null +++ b/src/test/java/dev/toonformat/jtoon/JToonBenchmark.java @@ -0,0 +1,88 @@ +package dev.toonformat.jtoon; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@State(Scope.Thread) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 2) +@Fork(2) +public class JToonBenchmark { + + @Param({"10", "100", "1000"}) + private int size; + + private Map testObject; + private String toonString; + private String jsonString; + + @Setup + public void setup() { + testObject = new HashMap<>(); + for (int i = 0; i < size; i++) { + Map nested = new HashMap<>(); + nested.put("id", i); + nested.put("name", "item_" + i); + nested.put("value", Math.random() * 1000); + nested.put("active", i % 2 == 0); + testObject.put("key_" + i, nested); + } + toonString = JToon.encode(testObject); + jsonString = "{\"name\":\"test\",\"value\":42,\"items\":[" + + String.join(",", java.util.Collections.nCopies(10, "{\"id\":1,\"name\":\"test\"}")) + + "],\"nested\":{\"a\":1,\"b\":2,\"c\":3}}"; + } + + @Benchmark + public String encodeObject() { + return JToon.encode(testObject); + } + + @Benchmark + public String encodeJson() { + return JToon.encodeJson(jsonString); + } + + @Benchmark + public Object decodeToon() { + return JToon.decode(toonString); + } + + @Benchmark + public String decodeToonToJson() { + return JToon.decodeToJson(toonString); + } + + @Benchmark + public Object decodeJson() { + return JToon.decode(toonString); + } + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .include(JToonBenchmark.class.getSimpleName()) + .result("build/jmh-results/results.json") + .build(); + + new Runner(opt).run(); + } +} From f67531f51619c2146a398a36fe440c139154eedc Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Thu, 12 Feb 2026 20:59:08 +0100 Subject: [PATCH 14/22] update pmd rules and spotbugs rules --- pmd-rules-test.xml | 12 ------------ spotbugs-exclude.xml | 5 +++++ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/pmd-rules-test.xml b/pmd-rules-test.xml index 19628b4..380b587 100644 --- a/pmd-rules-test.xml +++ b/pmd-rules-test.xml @@ -10,11 +10,7 @@ - - - - @@ -30,8 +26,6 @@ - - @@ -50,7 +44,6 @@ - @@ -61,8 +54,6 @@ - - @@ -70,7 +61,6 @@ - @@ -88,12 +78,10 @@ - - diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index 78214ec..ba794cc 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -6,6 +6,11 @@ + + + + + From 9c8be1771e001e6c8b39d971d3299d7a2a61e207 Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Thu, 12 Feb 2026 21:04:14 +0100 Subject: [PATCH 15/22] fix javaDoc --- src/main/java/dev/toonformat/jtoon/Delimiter.java | 5 +++++ src/main/java/dev/toonformat/jtoon/util/Headers.java | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/src/main/java/dev/toonformat/jtoon/Delimiter.java b/src/main/java/dev/toonformat/jtoon/Delimiter.java index e5bb5ae..072b759 100644 --- a/src/main/java/dev/toonformat/jtoon/Delimiter.java +++ b/src/main/java/dev/toonformat/jtoon/Delimiter.java @@ -34,6 +34,11 @@ public String toString() { return value; } + + /** + * Returns the character representation of this delimiter. + * @return the character value of this delimiter + */ public char getValue() { return value.charAt(0); } diff --git a/src/main/java/dev/toonformat/jtoon/util/Headers.java b/src/main/java/dev/toonformat/jtoon/util/Headers.java index db5b4cb..7783d0d 100644 --- a/src/main/java/dev/toonformat/jtoon/util/Headers.java +++ b/src/main/java/dev/toonformat/jtoon/util/Headers.java @@ -7,6 +7,10 @@ */ public class Headers { + private Headers() { + throw new UnsupportedOperationException("Utility class cannot be instantiated"); + } + /** * Matches standalone array headers: [3], [#2], [3\t], [2|]. * Group 1: optional # marker, Group 2: digits, Group 3: optional delimiter From 0012704c9181e17dc2ea4a70fe55d2414e423519 Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Thu, 12 Feb 2026 21:20:53 +0100 Subject: [PATCH 16/22] clean up rules --- build.gradle | 2 ++ pmd-rules-test.xml | 6 ++++++ src/main/java/dev/toonformat/jtoon/util/Headers.java | 10 +++++----- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 64b2a07..ed82cd5 100644 --- a/build.gradle +++ b/build.gradle @@ -80,6 +80,7 @@ tasks.spotbugsTest { outputLocation = file("${spotbugs.reportsDir.get()}/spotbugs-test.xml") } } + ignoreFailures = true } pmd { @@ -122,6 +123,7 @@ tasks.checkstyleMain { tasks.checkstyleTest { enabled = false + ignoreFailures = true } dependencies { diff --git a/pmd-rules-test.xml b/pmd-rules-test.xml index 380b587..362a0c8 100644 --- a/pmd-rules-test.xml +++ b/pmd-rules-test.xml @@ -16,6 +16,7 @@ + @@ -47,6 +48,7 @@ + @@ -105,6 +107,10 @@ + + + + diff --git a/src/main/java/dev/toonformat/jtoon/util/Headers.java b/src/main/java/dev/toonformat/jtoon/util/Headers.java index 7783d0d..b2b6d16 100644 --- a/src/main/java/dev/toonformat/jtoon/util/Headers.java +++ b/src/main/java/dev/toonformat/jtoon/util/Headers.java @@ -5,11 +5,7 @@ /** * Patterns in form of regex that must be followed in order to decode arrays, tabular, keyed arrays. */ -public class Headers { - - private Headers() { - throw new UnsupportedOperationException("Utility class cannot be instantiated"); - } +public final class Headers { /** * Matches standalone array headers: [3], [#2], [3\t], [2|]. @@ -31,4 +27,8 @@ private Headers() { */ public static final Pattern KEYED_ARRAY_PATTERN = Pattern.compile("^(.+?)\\[(#?)\\d+([\\t|])?](\\{[^}]+})?:.*$"); + private Headers() { + throw new UnsupportedOperationException("Utility class cannot be instantiated"); + } + } From ebfdf2a274422319f355e7b11345313409bc56a8 Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Thu, 12 Feb 2026 21:28:54 +0100 Subject: [PATCH 17/22] remove warnings for pmd test --- pmd-rules-test.xml | 1 - src/test/java/dev/toonformat/jtoon/TestPojos.java | 2 +- .../dev/toonformat/jtoon/conformance/ConformanceTest.java | 2 ++ .../java/dev/toonformat/jtoon/encoder/ArrayEncoderTest.java | 4 ++-- .../dev/toonformat/jtoon/encoder/ListItemEncoderTest.java | 4 ++-- .../java/dev/toonformat/jtoon/encoder/ObjectEncoderTest.java | 2 +- .../dev/toonformat/jtoon/encoder/TabularArrayEncoderTest.java | 4 ++-- .../java/dev/toonformat/jtoon/encoder/ValueEncoderTest.java | 2 +- .../dev/toonformat/jtoon/normalizer/JsonNormalizerTest.java | 2 +- 9 files changed, 12 insertions(+), 11 deletions(-) diff --git a/pmd-rules-test.xml b/pmd-rules-test.xml index 362a0c8..1f138b2 100644 --- a/pmd-rules-test.xml +++ b/pmd-rules-test.xml @@ -110,7 +110,6 @@ - diff --git a/src/test/java/dev/toonformat/jtoon/TestPojos.java b/src/test/java/dev/toonformat/jtoon/TestPojos.java index ac76d32..bc9ac3d 100644 --- a/src/test/java/dev/toonformat/jtoon/TestPojos.java +++ b/src/test/java/dev/toonformat/jtoon/TestPojos.java @@ -191,7 +191,7 @@ public CustomHotelInfoLlmRerankDTOSerializer(Class t) { } @Override - public void serialize(HotelInfoLlmRerankDTO value, JsonGenerator jsonGenerator, SerializationContext provider) throws JacksonException { + public void serialize(HotelInfoLlmRerankDTO value, JsonGenerator jsonGenerator, SerializationContext provider) { jsonGenerator.writeString(value.hotelId); } } diff --git a/src/test/java/dev/toonformat/jtoon/conformance/ConformanceTest.java b/src/test/java/dev/toonformat/jtoon/conformance/ConformanceTest.java index 6155904..d3c90c9 100644 --- a/src/test/java/dev/toonformat/jtoon/conformance/ConformanceTest.java +++ b/src/test/java/dev/toonformat/jtoon/conformance/ConformanceTest.java @@ -48,6 +48,7 @@ private Stream loadTestFixtures(File directory) { .map(this::parseFixture); } + @SuppressWarnings("PMD.AvoidThrowingRawExceptionTypes") private EncodeTestFile parseFixture(File file) { try { EncodeTestFixture fixture = mapper.readValue(file, EncodeTestFixture.class); @@ -131,6 +132,7 @@ private Stream loadTestFixtures(File directory) { .map(this::parseFixture); } + @SuppressWarnings("PMD.AvoidThrowingRawExceptionTypes") private DecodeTestFile parseFixture(File file) { try { var fixture = mapper.readValue(file, DecodeTestFixture.class); diff --git a/src/test/java/dev/toonformat/jtoon/encoder/ArrayEncoderTest.java b/src/test/java/dev/toonformat/jtoon/encoder/ArrayEncoderTest.java index 67bc058..ac3d84c 100644 --- a/src/test/java/dev/toonformat/jtoon/encoder/ArrayEncoderTest.java +++ b/src/test/java/dev/toonformat/jtoon/encoder/ArrayEncoderTest.java @@ -19,8 +19,8 @@ class ArrayEncoderTest { - private final ObjectMapper MAPPER = new ObjectMapper(); - private final JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance; + private static final ObjectMapper MAPPER = new ObjectMapper(); + private static final JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance; @Test void isArrayOfPrimitivesTestWithObjectNode() { diff --git a/src/test/java/dev/toonformat/jtoon/encoder/ListItemEncoderTest.java b/src/test/java/dev/toonformat/jtoon/encoder/ListItemEncoderTest.java index 00f7586..891dbd3 100644 --- a/src/test/java/dev/toonformat/jtoon/encoder/ListItemEncoderTest.java +++ b/src/test/java/dev/toonformat/jtoon/encoder/ListItemEncoderTest.java @@ -16,8 +16,8 @@ import static org.junit.jupiter.api.Assertions.*; class ListItemEncoderTest { - private final JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance; - private final EncodeOptions options = EncodeOptions.DEFAULT; + private static final JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance; + private static final EncodeOptions options = EncodeOptions.DEFAULT; @Test @DisplayName("throws unsupported Operation Exception for calling the constructor") diff --git a/src/test/java/dev/toonformat/jtoon/encoder/ObjectEncoderTest.java b/src/test/java/dev/toonformat/jtoon/encoder/ObjectEncoderTest.java index f19634f..af91bcf 100644 --- a/src/test/java/dev/toonformat/jtoon/encoder/ObjectEncoderTest.java +++ b/src/test/java/dev/toonformat/jtoon/encoder/ObjectEncoderTest.java @@ -33,7 +33,7 @@ class ObjectEncoderTest { private static final ObjectMapper MAPPER = new ObjectMapper(); - private final JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance; + private static final JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance; @Test void givenSimpleObject_whenEncoding_thenOutputsCorrectLines() { diff --git a/src/test/java/dev/toonformat/jtoon/encoder/TabularArrayEncoderTest.java b/src/test/java/dev/toonformat/jtoon/encoder/TabularArrayEncoderTest.java index 8b9d5e2..4d1f0f6 100644 --- a/src/test/java/dev/toonformat/jtoon/encoder/TabularArrayEncoderTest.java +++ b/src/test/java/dev/toonformat/jtoon/encoder/TabularArrayEncoderTest.java @@ -15,8 +15,8 @@ class TabularArrayEncoderTest { - private final JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance; - private final EncodeOptions options = EncodeOptions.DEFAULT; + private static final JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance; + private static final EncodeOptions options = EncodeOptions.DEFAULT; @Test @DisplayName("throws unsupported Operation Exception for calling the constructor") diff --git a/src/test/java/dev/toonformat/jtoon/encoder/ValueEncoderTest.java b/src/test/java/dev/toonformat/jtoon/encoder/ValueEncoderTest.java index e9495f8..6a96e3c 100644 --- a/src/test/java/dev/toonformat/jtoon/encoder/ValueEncoderTest.java +++ b/src/test/java/dev/toonformat/jtoon/encoder/ValueEncoderTest.java @@ -14,7 +14,7 @@ class ValueEncoderTest { - private final JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance; + private static final JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance; @Test @DisplayName("throws unsupported Operation Exception for calling the constructor") diff --git a/src/test/java/dev/toonformat/jtoon/normalizer/JsonNormalizerTest.java b/src/test/java/dev/toonformat/jtoon/normalizer/JsonNormalizerTest.java index 376cd25..0c5bcfa 100644 --- a/src/test/java/dev/toonformat/jtoon/normalizer/JsonNormalizerTest.java +++ b/src/test/java/dev/toonformat/jtoon/normalizer/JsonNormalizerTest.java @@ -1876,7 +1876,7 @@ void NormalizeArray_thenNullNode() throws Exception { class NormalizePojo { class ExplodingPojo { public String getValue() { - throw new RuntimeException("Boom"); + throw new IllegalStateException("Boom"); } } From e802e921d2ccc38f768c0ad1dc3b140dbfdfebc9 Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Thu, 12 Feb 2026 22:04:35 +0100 Subject: [PATCH 18/22] adding final to public methods calls --- build.gradle | 4 +- checkstyle.xml | 6 +- .../dev/toonformat/jtoon/DecodeOptions.java | 6 +- .../java/dev/toonformat/jtoon/Delimiter.java | 2 +- .../dev/toonformat/jtoon/EncodeOptions.java | 10 +-- src/main/java/dev/toonformat/jtoon/JToon.java | 16 ++-- .../jtoon/decoder/ArrayDecoder.java | 22 +++--- .../jtoon/decoder/DecodeHelper.java | 18 ++--- .../toonformat/jtoon/decoder/KeyDecoder.java | 36 ++++----- .../jtoon/decoder/ListItemDecoder.java | 8 +- .../jtoon/decoder/ObjectDecoder.java | 18 ++--- .../jtoon/decoder/PrimitiveDecoder.java | 2 +- .../jtoon/decoder/TabularArrayDecoder.java | 28 +++---- .../jtoon/decoder/ValueDecoder.java | 4 +- .../jtoon/encoder/ArrayEncoder.java | 28 +++---- .../dev/toonformat/jtoon/encoder/Flatten.java | 14 ++-- .../jtoon/encoder/HeaderFormatter.java | 32 ++++---- .../toonformat/jtoon/encoder/LineWriter.java | 50 ++++++++++--- .../jtoon/encoder/ListItemEncoder.java | 30 ++++---- .../jtoon/encoder/ObjectEncoder.java | 38 +++++----- .../jtoon/encoder/PrimitiveEncoder.java | 73 ++++++++++++------- .../jtoon/encoder/TabularArrayEncoder.java | 12 +-- .../jtoon/encoder/ValueEncoder.java | 2 +- .../jtoon/normalizer/JsonNormalizer.java | 38 +++++----- .../toonformat/jtoon/util/StringEscaper.java | 10 +-- .../jtoon/util/StringValidator.java | 24 +++--- 26 files changed, 291 insertions(+), 240 deletions(-) diff --git a/build.gradle b/build.gradle index ed82cd5..65b2fe1 100644 --- a/build.gradle +++ b/build.gradle @@ -51,8 +51,8 @@ pitest { spotbugs { toolVersion = '4.9.8' excludeFilter = file('spotbugs-exclude.xml') - effort = "max" - reportLevel = "low" + effort = com.github.spotbugs.snom.Effort.MAX + reportLevel = com.github.spotbugs.snom.Confidence.values()[0] reportsDir = layout.buildDirectory.dir('spotbugs') } diff --git a/checkstyle.xml b/checkstyle.xml index 3785d24..b8f2e90 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -58,7 +58,11 @@ - + + + + + diff --git a/src/main/java/dev/toonformat/jtoon/DecodeOptions.java b/src/main/java/dev/toonformat/jtoon/DecodeOptions.java index 6f085a0..452676e 100644 --- a/src/main/java/dev/toonformat/jtoon/DecodeOptions.java +++ b/src/main/java/dev/toonformat/jtoon/DecodeOptions.java @@ -35,7 +35,7 @@ public DecodeOptions() { * @param indent number of spaces per indentation level * @return a new DecodeOptions instance with the specified indent */ - public static DecodeOptions withIndent(int indent) { + public static DecodeOptions withIndent(final int indent) { return new DecodeOptions(indent, Delimiter.COMMA, true, PathExpansion.OFF); } @@ -45,7 +45,7 @@ public static DecodeOptions withIndent(int indent) { * @param delimiter the delimiter to use for tabular arrays and inline primitive arrays * @return a new DecodeOptions instance with the specified delimiter */ - public static DecodeOptions withDelimiter(Delimiter delimiter) { + public static DecodeOptions withDelimiter(final Delimiter delimiter) { return new DecodeOptions(2, delimiter, true, PathExpansion.OFF); } @@ -55,7 +55,7 @@ public static DecodeOptions withDelimiter(Delimiter delimiter) { * @param strict whether to enable strict validation mode * @return a new DecodeOptions instance with the specified strict mode */ - public static DecodeOptions withStrict(boolean strict) { + public static DecodeOptions withStrict(final boolean strict) { return new DecodeOptions(2, Delimiter.COMMA, strict, PathExpansion.OFF); } } diff --git a/src/main/java/dev/toonformat/jtoon/Delimiter.java b/src/main/java/dev/toonformat/jtoon/Delimiter.java index 072b759..6825120 100644 --- a/src/main/java/dev/toonformat/jtoon/Delimiter.java +++ b/src/main/java/dev/toonformat/jtoon/Delimiter.java @@ -21,7 +21,7 @@ public enum Delimiter { private final String value; - Delimiter(String delimiterValue) { + Delimiter(final String delimiterValue) { this.value = delimiterValue; } diff --git a/src/main/java/dev/toonformat/jtoon/EncodeOptions.java b/src/main/java/dev/toonformat/jtoon/EncodeOptions.java index 1e92a2d..c522826 100644 --- a/src/main/java/dev/toonformat/jtoon/EncodeOptions.java +++ b/src/main/java/dev/toonformat/jtoon/EncodeOptions.java @@ -40,7 +40,7 @@ public EncodeOptions() { * @param indent number of spaces per indentation level * @return a new EncodeOptions instance with the specified indent */ - public static EncodeOptions withIndent(int indent) { + public static EncodeOptions withIndent(final int indent) { return new EncodeOptions(indent, Delimiter.COMMA, false, KeyFolding.OFF, Integer.MAX_VALUE); } @@ -51,7 +51,7 @@ public static EncodeOptions withIndent(int indent) { * @param delimiter the delimiter to use for tabular arrays and inline primitive arrays * @return a new EncodeOptions instance with the specified delimiter */ - public static EncodeOptions withDelimiter(Delimiter delimiter) { + public static EncodeOptions withDelimiter(final Delimiter delimiter) { return new EncodeOptions(2, delimiter, false, KeyFolding.OFF, Integer.MAX_VALUE); } @@ -62,7 +62,7 @@ public static EncodeOptions withDelimiter(Delimiter delimiter) { * @param lengthMarker whether to include the # marker before array lengths * @return a new EncodeOptions instance with the specified length marker setting */ - public static EncodeOptions withLengthMarker(boolean lengthMarker) { + public static EncodeOptions withLengthMarker(final boolean lengthMarker) { return new EncodeOptions(2, Delimiter.COMMA, lengthMarker, KeyFolding.OFF, Integer.MAX_VALUE); } @@ -73,7 +73,7 @@ public static EncodeOptions withLengthMarker(boolean lengthMarker) { * @param flatten optional flag to flatten nested objects to a single level. * @return a new EncodeOptions instance with the flatten setting */ - public static EncodeOptions withFlatten(boolean flatten) { + public static EncodeOptions withFlatten(final boolean flatten) { return new EncodeOptions(2, Delimiter.COMMA, false, flatten ? KeyFolding.SAFE : KeyFolding.OFF, Integer.MAX_VALUE); } @@ -85,7 +85,7 @@ public static EncodeOptions withFlatten(boolean flatten) { * @param flattenDepth optional maximum depth to flatten nested objects. * @return a new EncodeOptions instance with the flatten setting and the depth of to flatten the nested objects. */ - public static EncodeOptions withFlattenDepth(int flattenDepth) { + public static EncodeOptions withFlattenDepth(final int flattenDepth) { return new EncodeOptions(2, Delimiter.COMMA, false, KeyFolding.SAFE, flattenDepth); } } diff --git a/src/main/java/dev/toonformat/jtoon/JToon.java b/src/main/java/dev/toonformat/jtoon/JToon.java index 09bc59b..250f730 100644 --- a/src/main/java/dev/toonformat/jtoon/JToon.java +++ b/src/main/java/dev/toonformat/jtoon/JToon.java @@ -26,7 +26,7 @@ private JToon() { * @param input The object to encode (can be null) * @return The JToon-formatted string */ - public static String encode(Object input) { + public static String encode(final Object input) { return encode(input, EncodeOptions.DEFAULT); } @@ -42,7 +42,7 @@ public static String encode(Object input) { * @param options Encoding options (indent, delimiter, length marker) * @return The JToon-formatted string */ - public static String encode(Object input, EncodeOptions options) { + public static String encode(final Object input, final EncodeOptions options) { final JsonNode normalizedValue = JsonNormalizer.normalize(input); return ValueEncoder.encodeValue(normalizedValue, options); } @@ -60,7 +60,7 @@ public static String encode(Object input, EncodeOptions options) { * @return The TOON-formatted string * @throws IllegalArgumentException if the input is not valid JSON */ - public static String encodeJson(String json) { + public static String encodeJson(final String json) { return encodeJson(json, EncodeOptions.DEFAULT); } @@ -78,7 +78,7 @@ public static String encodeJson(String json) { * @return The TOON-formatted string * @throws IllegalArgumentException if the input is not valid JSON */ - public static String encodeJson(String json, EncodeOptions options) { + public static String encodeJson(final String json, final EncodeOptions options) { final JsonNode parsed = JsonNormalizer.parse(json); return ValueEncoder.encodeValue(parsed, options); } @@ -96,7 +96,7 @@ public static String encodeJson(String json, EncodeOptions options) { * @throws IllegalArgumentException if strict mode is enabled and input is * invalid */ - public static Object decode(String toon) { + public static Object decode(final String toon) { return decode(toon, DecodeOptions.DEFAULT); } @@ -114,7 +114,7 @@ public static Object decode(String toon) { * @throws IllegalArgumentException if strict mode is enabled and input is * invalid */ - public static Object decode(String toon, DecodeOptions options) { + public static Object decode(final String toon, final DecodeOptions options) { return ValueDecoder.decode(toon, options); } @@ -132,7 +132,7 @@ public static Object decode(String toon, DecodeOptions options) { * @throws IllegalArgumentException if strict mode is enabled and input is * invalid */ - public static String decodeToJson(String toon) { + public static String decodeToJson(final String toon) { return decodeToJson(toon, DecodeOptions.DEFAULT); } @@ -151,7 +151,7 @@ public static String decodeToJson(String toon) { * @throws IllegalArgumentException if strict mode is enabled and input is * invalid */ - public static String decodeToJson(String toon, DecodeOptions options) { + public static String decodeToJson(final String toon, final DecodeOptions options) { return ValueDecoder.decodeToJson(toon, options); } } diff --git a/src/main/java/dev/toonformat/jtoon/decoder/ArrayDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/ArrayDecoder.java index 1b4193e..f49ad01 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/ArrayDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/ArrayDecoder.java @@ -32,7 +32,7 @@ private ArrayDecoder() { * @param context decode an object to deal with lines, delimiter and options * @return parsed array with delimiter */ - static List parseArray(String header, int depth, DecodeContext context) { + static List parseArray(final String header, final int depth, final DecodeContext context) { final Delimiter arrayDelimiter = extractDelimiterFromHeader(header, context); return parseArrayWithDelimiter(header, depth, arrayDelimiter, context); @@ -46,7 +46,7 @@ static List parseArray(String header, int depth, DecodeContext context) * @param context decode an object to deal with lines, delimiter and options * @return extracted delimiter from header */ - static Delimiter extractDelimiterFromHeader(String header, DecodeContext context) { + static Delimiter extractDelimiterFromHeader(final String header, final DecodeContext context) { final Matcher matcher = ARRAY_HEADER_PATTERN.matcher(header); if (matcher.find()) { final String delimiter = matcher.group(DELIMITER_GROUP_INDEX); @@ -74,8 +74,8 @@ static Delimiter extractDelimiterFromHeader(String header, DecodeContext context * @param context decode an object to deal with lines, delimiter and options * @return parsed array */ - static List parseArrayWithDelimiter(String header, int depth, Delimiter arrayDelimiter, - DecodeContext context) { + static List parseArrayWithDelimiter(final String header, final int depth, final Delimiter arrayDelimiter, + final DecodeContext context) { final Matcher tabularMatcher = TABULAR_HEADER_PATTERN.matcher(header); final Matcher arrayMatcher = ARRAY_HEADER_PATTERN.matcher(header); @@ -138,7 +138,7 @@ static List parseArrayWithDelimiter(String header, int depth, Delimiter * @param header header * @param actualLength actual length */ - static void validateArrayLength(String header, int actualLength) { + static void validateArrayLength(final String header, final int actualLength) { final Integer declaredLength = extractLengthFromHeader(header); if (declaredLength != null && declaredLength != actualLength) { throw new IllegalArgumentException( @@ -153,7 +153,7 @@ static void validateArrayLength(String header, int actualLength) { * @param header header string for length check * @return extracted length from header */ - private static Integer extractLengthFromHeader(String header) { + private static Integer extractLengthFromHeader(final String header) { final Matcher matcher = ARRAY_HEADER_PATTERN.matcher(header); if (matcher.find()) { return Integer.parseInt(matcher.group(2)); @@ -168,7 +168,7 @@ private static Integer extractLengthFromHeader(String header) { * @param arrayDelimiter array delimiter * @return parsed array values */ - static List parseArrayValues(String values, Delimiter arrayDelimiter) { + static List parseArrayValues(final String values, final Delimiter arrayDelimiter) { final List result = new ArrayList<>(); final List rawValues = parseDelimitedValues(values, arrayDelimiter); for (final String value : rawValues) { @@ -185,7 +185,7 @@ static List parseArrayValues(String values, Delimiter arrayDelimiter) { * @param arrayDelimiter array delimiter * @return parsed delimited values */ - static List parseDelimitedValues(String input, Delimiter arrayDelimiter) { + static List parseDelimitedValues(final String input, final Delimiter arrayDelimiter) { final List result = new ArrayList<>(); final StringBuilder stringBuilder = new StringBuilder(); boolean inQuotes = false; @@ -235,7 +235,7 @@ static List parseDelimitedValues(String input, Delimiter arrayDelimiter) * Parses list an array format where items are prefixed with "- ". * Example: items[2]:\n - item1\n - item2 */ - private static List parseListArray(int depth, String header, DecodeContext context) { + private static List parseListArray(final int depth, final String header, final DecodeContext context) { final List result = new ArrayList<>(); context.currentLine++; @@ -271,7 +271,7 @@ private static List parseListArray(int depth, String header, DecodeConte * @param context decode an object to deal with lines, delimiter and options * @return true if an array should terminate, false if a line should be skipped */ - private static boolean handleBlankLineInListArray(int depth, DecodeContext context) { + private static boolean handleBlankLineInListArray(final int depth, final DecodeContext context) { final int nextNonBlankLine = DecodeHelper.findNextNonBlankLine(context.currentLine + 1, context); if (nextNonBlankLine >= context.lines.length) { @@ -300,7 +300,7 @@ private static boolean handleBlankLineInListArray(int depth, DecodeContext conte * @param context decode an object to deal with lines, delimiter and options * @return true if an array should terminate, false otherwise. */ - private static boolean shouldTerminateListArray(int lineDepth, int depth, String line, DecodeContext context) { + private static boolean shouldTerminateListArray(final int lineDepth, final int depth, final String line, final DecodeContext context) { if (lineDepth < depth + 1) { return true; // Line depth is less than expected - terminate } diff --git a/src/main/java/dev/toonformat/jtoon/decoder/DecodeHelper.java b/src/main/java/dev/toonformat/jtoon/decoder/DecodeHelper.java index f15a59d..5903994 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/DecodeHelper.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/DecodeHelper.java @@ -26,7 +26,7 @@ private DecodeHelper() { * @param context decode an object to deal with lines, delimiter, and options * @return the depth of a line */ - public static int getDepth(String line, DecodeContext context) { + public static int getDepth(final String line, final DecodeContext context) { // Blank lines (including lines with only spaces) have depth 0 if (isBlankLine(line)) { return 0; @@ -42,7 +42,7 @@ public static int getDepth(String line, DecodeContext context) { * @param context decode object in order to deal with lines, delimiter and options * @return amount of leading spaces */ - private static int computeLeadingSpaces(String line, DecodeContext context) { + private static int computeLeadingSpaces(final String line, final DecodeContext context) { final int indentSize = context.options.indent(); int leadingSpaces = 0; @@ -81,7 +81,7 @@ private static int computeLeadingSpaces(String line, DecodeContext context) { * @param line the line string to parse * @return true or false depending on if the line is blank or not */ - static boolean isBlankLine(String line) { + static boolean isBlankLine(final String line) { return line.isBlank(); } @@ -92,7 +92,7 @@ static boolean isBlankLine(String line) { * @param content the content string to parse * @return the unquoted colon */ - static int findUnquotedColon(String content) { + static int findUnquotedColon(final String content) { boolean inQuotes = false; boolean escaped = false; @@ -120,7 +120,7 @@ static int findUnquotedColon(String content) { * @param context decode an object to deal with lines, delimiter, and options * @return index aiming for the next non-blank line */ - static int findNextNonBlankLine(int startIndex, DecodeContext context) { + static int findNextNonBlankLine(final int startIndex, final DecodeContext context) { int index = startIndex; while (index < context.lines.length && isBlankLine(context.lines[index])) { index++; @@ -137,7 +137,7 @@ static int findNextNonBlankLine(int startIndex, DecodeContext context) { * @param context decode an object to deal with lines, delimiter, and options * @throws IllegalArgumentException in case there's a expansion conflict */ - static void checkFinalValueConflict(String finalSegment, Object existing, Object value, DecodeContext context) { + static void checkFinalValueConflict(final String finalSegment, final Object existing, final Object value, final DecodeContext context) { if (existing != null && context.options.strict()) { // Check for conflicts in strict mode if (existing instanceof Map && !(value instanceof Map)) { @@ -162,7 +162,7 @@ static void checkFinalValueConflict(String finalSegment, Object existing, Object * @param value present value in a map * @param context decode an object to deal with lines, delimiter, and options */ - static void checkPathExpansionConflict(Map map, String key, Object value, DecodeContext context) { + static void checkPathExpansionConflict(final Map map, final String key, final Object value, final DecodeContext context) { if (!context.options.strict()) { return; } @@ -177,7 +177,7 @@ static void checkPathExpansionConflict(Map map, String key, Obje * @param context decode an object to deal with lines, delimiter, and options * @return the depth of the next non-blank line, or null if none exists */ - static Integer findNextNonBlankLineDepth(DecodeContext context) { + static Integer findNextNonBlankLineDepth(final DecodeContext context) { int nextLineIdx = context.currentLine; while (nextLineIdx < context.lines.length && isBlankLine(context.lines[nextLineIdx])) { nextLineIdx++; @@ -196,7 +196,7 @@ static Integer findNextNonBlankLineDepth(DecodeContext context) { * @param context decode an object to deal with lines, delimiter, and options * @throws IllegalArgumentException in case the next depth is equal to 0 */ - static void validateNoMultiplePrimitivesAtRoot(DecodeContext context) { + static void validateNoMultiplePrimitivesAtRoot(final DecodeContext context) { int lineIndex = context.currentLine; while (lineIndex < context.lines.length && isBlankLine(context.lines[lineIndex])) { lineIndex++; diff --git a/src/main/java/dev/toonformat/jtoon/decoder/KeyDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/KeyDecoder.java index 4dde16c..d4b4e08 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/KeyDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/KeyDecoder.java @@ -28,8 +28,8 @@ private KeyDecoder() { * @param parentDepth parent depth of keyed array line * @param context decode an object to deal with lines, delimiter and options */ - static void processKeyedArrayLine(Map result, String content, String originalKey, - int parentDepth, DecodeContext context) { + static void processKeyedArrayLine(final Map result, final String content, final String originalKey, + final int parentDepth, final DecodeContext context) { final String key = StringEscaper.unescape(originalKey); final String arrayHeader = content.substring(originalKey.length()); final List arrayValue = ArrayDecoder.parseArray(arrayHeader, parentDepth + 1, context); @@ -52,8 +52,8 @@ static void processKeyedArrayLine(Map result, String content, St * @param value value * @param context decode an object to deal with lines, delimiter and options */ - static void expandPathIntoMap(Map current, String dottedKey, Object value, - DecodeContext context) { + static void expandPathIntoMap(final Map current, final String dottedKey, final Object value, + final DecodeContext context) { final String[] segments = dottedKey.split("\\."); Map currentMap = current; @@ -107,7 +107,7 @@ static void expandPathIntoMap(Map current, String dottedKey, Obj * @param depth the depth of the value line * @param context decode an object to deal with lines, delimiter and options */ - static void processKeyValueLine(Map result, String content, int depth, DecodeContext context) { + static void processKeyValueLine(final Map result, final String content, final int depth, final DecodeContext context) { final int colonIdx = DecodeHelper.findUnquotedColon(content); if (colonIdx > 0) { @@ -133,8 +133,8 @@ static void processKeyValueLine(Map result, String content, int * @param depth the depth of the value pair * @param context decode an object to deal with lines, delimiter and options */ - static void parseKeyValuePairIntoMap(Map map, String key, String value, - int depth, DecodeContext context) { + static void parseKeyValuePairIntoMap(final Map map, final String key, final String value, + final int depth, final DecodeContext context) { final String unescapedKey = StringEscaper.unescape(key); final Object parsedValue = parseKeyValue(value, depth, context); @@ -150,7 +150,7 @@ static void parseKeyValuePairIntoMap(Map map, String key, String * @param context decode an object to deal with lines, delimiter and options * @return true if a key should be expanded or false if not */ - static boolean shouldExpandKey(String key, DecodeContext context) { + static boolean shouldExpandKey(final String key, final DecodeContext context) { if (context.options.expandPaths() != PathExpansion.SAFE) { return false; } @@ -182,7 +182,7 @@ static boolean shouldExpandKey(String key, DecodeContext context) { * @param depth the depth at which the key-value pair is located * @return the parsed value (Map, List, or primitive) */ - private static Object parseKeyValue(String value, int depth, DecodeContext context) { + private static Object parseKeyValue(final String value, final int depth, final DecodeContext context) { // Check if the next line is nested (deeper indentation) if (context.currentLine + 1 < context.lines.length) { final int nextDepth = DecodeHelper.getDepth(context.lines[context.currentLine + 1], context); @@ -223,8 +223,8 @@ private static Object parseKeyValue(String value, int depth, DecodeContext conte * @param unescapedKey the unescaped key * @param value the value to put */ - private static void putKeyValueIntoMap(Map map, String originalKey, String unescapedKey, - Object value, DecodeContext context) { + private static void putKeyValueIntoMap(final Map map, final String originalKey, final String unescapedKey, + final Object value, final DecodeContext context) { // Handle path expansion if (shouldExpandKey(originalKey, context)) { expandPathIntoMap(map, unescapedKey, value, context); @@ -244,8 +244,8 @@ private static void putKeyValueIntoMap(Map map, String originalK * @param context decode an object to deal with lines, delimiter, and options * @return parsed a key-value pair */ - static Object parseKeyValuePair(String key, String value, int depth, boolean parseRootFields, - DecodeContext context) { + static Object parseKeyValuePair(final String key, final String value, final int depth, final boolean parseRootFields, + final DecodeContext context) { final Map obj = new LinkedHashMap<>(); parseKeyValuePairIntoMap(obj, key, value, depth, context); @@ -264,7 +264,7 @@ static Object parseKeyValuePair(String key, String value, int depth, boolean par * @param context decode an object to deal with lines, delimiter, and options * @return parsed keyed array value */ - static Object parseKeyedArrayValue(Matcher keyedArray, String content, int depth, DecodeContext context) { + static Object parseKeyedArrayValue(final Matcher keyedArray, final String content, final int depth, final DecodeContext context) { final String originalKey = keyedArray.group(1).trim(); final String key = StringEscaper.unescape(originalKey); final String arrayHeader = content.substring(keyedArray.group(1).length()); @@ -298,8 +298,8 @@ static Object parseKeyedArrayValue(Matcher keyedArray, String content, int depth * @param context decode an object to deal with lines, delimiter and options * @return true if the field was processed as a keyed array, false otherwise */ - static boolean parseKeyedArrayField(String fieldContent, Map item, int depth, - DecodeContext context) { + static boolean parseKeyedArrayField(final String fieldContent, final Map item, final int depth, + final DecodeContext context) { final Matcher keyedArray = KEYED_ARRAY_PATTERN.matcher(fieldContent); if (!keyedArray.matches()) { return false; @@ -334,8 +334,8 @@ static boolean parseKeyedArrayField(String fieldContent, Map ite * @param context decode an object to deal with lines, delimiter and options * @return true if the field was processed as a key-value pair, false otherwise */ - static boolean parseKeyValueField(String fieldContent, Map item, int depth, - DecodeContext context) { + static boolean parseKeyValueField(final String fieldContent, final Map item, final int depth, + final DecodeContext context) { final int colonIdx = DecodeHelper.findUnquotedColon(fieldContent); if (colonIdx <= 0) { return false; diff --git a/src/main/java/dev/toonformat/jtoon/decoder/ListItemDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/ListItemDecoder.java index 58fadf7..3e3d2f4 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/ListItemDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/ListItemDecoder.java @@ -28,8 +28,8 @@ private ListItemDecoder() { * @param result the stored result of each list item parse * @param context decode an object to deal with lines, delimiter and options */ - public static void processListArrayItem(String line, int lineDepth, int depth, - List result, DecodeContext context) { + public static void processListArrayItem(final String line, final int lineDepth, final int depth, + final List result, final DecodeContext context) { if (lineDepth == depth + 1) { final String content = line.substring((depth + 1) * context.options.indent()); @@ -52,7 +52,7 @@ public static void processListArrayItem(String line, int lineDepth, int depth, * @param context decode an object to deal with lines, delimiter and options * @return parsed item (scalar value or object) */ - public static Object parseListItem(String content, int depth, DecodeContext context) { + public static Object parseListItem(final String content, final int depth, final DecodeContext context) { // Handle empty item: just "-" or "- " final String itemContent; if (content.length() > 2) { @@ -142,7 +142,7 @@ public static Object parseListItem(String content, int depth, DecodeContext cont * @param depth the depth of the item * @param context decode an object to deal with lines, delimiter and options * */ - private static void parseListItemFields(Map item, int depth, DecodeContext context) { + private static void parseListItemFields(final Map item, final int depth, final DecodeContext context) { while (context.currentLine < context.lines.length) { final String line = context.lines[context.currentLine]; final int lineDepth = DecodeHelper.getDepth(line, context); diff --git a/src/main/java/dev/toonformat/jtoon/decoder/ObjectDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/ObjectDecoder.java index e147d44..e9577ff 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/ObjectDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/ObjectDecoder.java @@ -23,7 +23,7 @@ private ObjectDecoder() { * @param context decode an object to deal with lines, delimiter and options * @return parsed nested object */ - static Map parseNestedObject(int parentDepth, DecodeContext context) { + static Map parseNestedObject(final int parentDepth, final DecodeContext context) { final Map result = new LinkedHashMap<>(); while (context.currentLine < context.lines.length) { @@ -56,8 +56,8 @@ static Map parseNestedObject(int parentDepth, DecodeContext cont * Returns true if the line was processed, false if it was a blank line that was * skipped. */ - private static void processDirectChildLine(Map result, String line, int parentDepth, int depth, - DecodeContext context) { + private static void processDirectChildLine(final Map result, final String line, final int parentDepth, final int depth, + final DecodeContext context) { final String content = line.substring((parentDepth + 1) * context.options.indent()); final Matcher keyedArray = KEYED_ARRAY_PATTERN.matcher(content); @@ -75,7 +75,7 @@ private static void processDirectChildLine(Map result, String li * @param depth the depth of the object field * @param context decode an object to deal with lines, delimiter and options */ - static void parseRootObjectFields(Map obj, int depth, DecodeContext context) { + static void parseRootObjectFields(final Map obj, final int depth, final DecodeContext context) { while (context.currentLine < context.lines.length) { final String line = context.lines[context.currentLine]; final int lineDepth = DecodeHelper.getDepth(line, context); @@ -118,8 +118,8 @@ static void parseRootObjectFields(Map obj, int depth, DecodeCont * @param depth the depth of the object field * @param context decode an object to deal with lines, delimiter and options */ - private static void processRootKeyedArrayLine(Map objectMap, String content, String originalKey, - int depth, DecodeContext context) { + private static void processRootKeyedArrayLine(final Map objectMap, final String content, final String originalKey, + final int depth, final DecodeContext context) { final String originalKeyTrimmed = originalKey.trim(); final String key = StringEscaper.unescape(originalKey); final String arrayHeader = content.substring(originalKey.length()); @@ -144,7 +144,7 @@ private static void processRootKeyedArrayLine(Map objectMap, Str * @param context decode an object to deal with lines, delimiter and options * @return the parsed scalar value */ - static Object parseBareScalarValue(String content, int depth, DecodeContext context) { + static Object parseBareScalarValue(final String content, final int depth, final DecodeContext context) { final Object result = PrimitiveDecoder.parse(content); context.currentLine++; @@ -164,7 +164,7 @@ static Object parseBareScalarValue(String content, int depth, DecodeContext cont * @param context decode an object to deal with lines, delimiter and options * @return the parsed value (Map, List, or primitive) */ - static Object parseFieldValue(String fieldValue, int fieldDepth, DecodeContext context) { + static Object parseFieldValue(final String fieldValue, final int fieldDepth, final DecodeContext context) { // Check if the next line is nested if (context.currentLine + 1 < context.lines.length) { final int nextDepth = DecodeHelper.getDepth(context.lines[context.currentLine + 1], context); @@ -204,7 +204,7 @@ static Object parseFieldValue(String fieldValue, int fieldDepth, DecodeContext c * @param context decode an object to deal with lines, delimiter and options * @return the parsed value (Map, List, or primitive) */ - static Object parseObjectItemValue(String value, int depth, DecodeContext context) { + static Object parseObjectItemValue(final String value, final int depth, final DecodeContext context) { final boolean isEmpty = value.isBlank(); // Find the next non-blank line and its depth diff --git a/src/main/java/dev/toonformat/jtoon/decoder/PrimitiveDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/PrimitiveDecoder.java index 5ffb466..3915c00 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/PrimitiveDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/PrimitiveDecoder.java @@ -45,7 +45,7 @@ private PrimitiveDecoder() { * @return The parsed value as {@code Boolean}, {@code Long}, {@code Double}, * {@code String}, or {@code null} */ - static Object parse(String value) { + static Object parse(final String value) { if (value == null || value.isEmpty()) { return ""; } diff --git a/src/main/java/dev/toonformat/jtoon/decoder/TabularArrayDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/TabularArrayDecoder.java index 87b1406..ed6e23e 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/TabularArrayDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/TabularArrayDecoder.java @@ -32,8 +32,8 @@ private TabularArrayDecoder() { * @param context decode an object to deal with lines, delimiter and options * @return tabular array converted to JSON format */ - public static List parseTabularArray(String header, int depth, Delimiter arrayDelimiter, - DecodeContext context) { + public static List parseTabularArray(final String header, final int depth, final Delimiter arrayDelimiter, + final DecodeContext context) { final Matcher matcher = TABULAR_HEADER_PATTERN.matcher(header); if (!matcher.find()) { return Collections.emptyList(); @@ -73,7 +73,7 @@ public static List parseTabularArray(String header, int depth, Delimiter * @param context decode an object to deal with lines, delimiter and options * @return list of keys */ - private static List parseTabularKeys(String keysStr, Delimiter arrayDelimiter, DecodeContext context) { + private static List parseTabularKeys(final String keysStr, final Delimiter arrayDelimiter, final DecodeContext context) { // Validate delimiter mismatch between bracket and brace fields if (context.options.strict()) { validateKeysDelimiter(keysStr, arrayDelimiter); @@ -93,7 +93,7 @@ private static List parseTabularKeys(String keysStr, Delimiter arrayDeli * @param keysStr the string representation of keys * @param expectedDelimiter the expected delimiter used in the array */ - private static void validateKeysDelimiter(String keysStr, Delimiter expectedDelimiter) { + private static void validateKeysDelimiter(final String keysStr, final Delimiter expectedDelimiter) { final char expectedChar = expectedDelimiter.toString().charAt(0); boolean inQuotes = false; boolean escaped = false; @@ -118,7 +118,7 @@ private static void validateKeysDelimiter(String keysStr, Delimiter expectedDeli * @param expectedChar the expected delimiter character * @param actualChar the actual delimiter character */ - private static void checkDelimiterMismatch(char expectedChar, char actualChar) { + private static void checkDelimiterMismatch(final char expectedChar, final char actualChar) { if (expectedChar == Delimiter.TAB.getValue() && actualChar == Delimiter.COMMA.getValue()) { throw new IllegalArgumentException( "Delimiter mismatch: bracket declares tab, brace fields use comma"); @@ -144,8 +144,8 @@ private static void checkDelimiterMismatch(char expectedChar, char actualChar) { * @param context decode an object to deal with lines, delimiter and options * @return true if parsing should continue, false if an array should terminate */ - private static boolean processTabularArrayLine(int expectedRowDepth, List keys, Delimiter arrayDelimiter, - List result, DecodeContext context) { + private static boolean processTabularArrayLine(final int expectedRowDepth, final List keys, final Delimiter arrayDelimiter, + final List result, final DecodeContext context) { final String line = context.lines[context.currentLine]; if (DecodeHelper.isBlankLine(line)) { @@ -170,7 +170,7 @@ private static boolean processTabularArrayLine(int expectedRowDepth, List keys, - Delimiter arrayDelimiter, List result, DecodeContext context) { + private static boolean processTabularRow(final String line, final int lineDepth, final int expectedRowDepth, final List keys, + final Delimiter arrayDelimiter, final List result, final DecodeContext context) { if (lineDepth == expectedRowDepth) { final String rowContent = line.substring(expectedRowDepth * context.options.indent()); final Map row = parseTabularRow(rowContent, keys, arrayDelimiter, context); @@ -264,8 +264,8 @@ private static boolean processTabularRow(String line, int lineDepth, int expecte * @param context decode an object to deal with lines, delimiter and options * @return a Map containing the parsed row values */ - private static Map parseTabularRow(String rowContent, List keys, - Delimiter arrayDelimiter, DecodeContext context) { + private static Map parseTabularRow(final String rowContent, final List keys, + final Delimiter arrayDelimiter, final DecodeContext context) { final Map row = new LinkedHashMap<>(); final List values = ArrayDecoder.parseArrayValues(rowContent, arrayDelimiter); diff --git a/src/main/java/dev/toonformat/jtoon/decoder/ValueDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/ValueDecoder.java index 2e30d60..b147060 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/ValueDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/ValueDecoder.java @@ -45,7 +45,7 @@ private ValueDecoder() { * @throws IllegalArgumentException if strict mode is enabled and input is * invalid */ - public static Object decode(String toon, DecodeOptions options) { + public static Object decode(final String toon, final DecodeOptions options) { if (toon == null || toon.isBlank()) { return new LinkedHashMap<>(); } @@ -116,7 +116,7 @@ public static Object decode(String toon, DecodeOptions options) { * @throws IllegalArgumentException if strict mode is enabled and input is * invalid */ - public static String decodeToJson(String toon, DecodeOptions options) { + public static String decodeToJson(final String toon, final DecodeOptions options) { try { final Object decoded = decode(toon, options); return MAPPER.writeValueAsString(decoded); diff --git a/src/main/java/dev/toonformat/jtoon/encoder/ArrayEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/ArrayEncoder.java index 0adf509..09ceda1 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/ArrayEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/ArrayEncoder.java @@ -30,7 +30,7 @@ private ArrayEncoder() { * @param depth Indentation depth * @param options Encoding options */ - public static void encodeArray(String key, ArrayNode value, LineWriter writer, int depth, EncodeOptions options) { + public static void encodeArray(final String key, final ArrayNode value, final LineWriter writer, final int depth, final EncodeOptions options) { if (value.isEmpty()) { final String header = PrimitiveEncoder.formatHeader(0, key, null, options.delimiter().toString(), options.lengthMarker()); @@ -77,7 +77,7 @@ public static void encodeArray(String key, ArrayNode value, LineWriter writer, i * @param array for testing that all items are primitives * @return true if all items in the array are primitive values, false otherwise */ - public static boolean isArrayOfPrimitives(JsonNode array) { + public static boolean isArrayOfPrimitives(final JsonNode array) { if (!array.isArray()) { return false; } @@ -95,7 +95,7 @@ public static boolean isArrayOfPrimitives(JsonNode array) { * @param array the array to check * @return true if all items in the array are arrays, false otherwise */ - public static boolean isArrayOfArrays(JsonNode array) { + public static boolean isArrayOfArrays(final JsonNode array) { if (!array.isArray()) { return false; } @@ -113,7 +113,7 @@ public static boolean isArrayOfArrays(JsonNode array) { * @param array the array to check * @return true if all items in the array are objects, false otherwise */ - public static boolean isArrayOfObjects(JsonNode array) { + public static boolean isArrayOfObjects(final JsonNode array) { if (!array.isArray()) { return false; } @@ -128,8 +128,8 @@ public static boolean isArrayOfObjects(JsonNode array) { /** * Encodes a primitive array inline: key[N]: v1,v2,v3. */ - private static void encodeInlinePrimitiveArray(String prefix, ArrayNode values, LineWriter writer, int depth, - EncodeOptions options) { + private static void encodeInlinePrimitiveArray(final String prefix, final ArrayNode values, final LineWriter writer, final int depth, + final EncodeOptions options) { final String formatted = formatInlineArray(values, options.delimiter().toString(), prefix, options.lengthMarker()); writer.push(depth, formatted); @@ -144,7 +144,7 @@ private static void encodeInlinePrimitiveArray(String prefix, ArrayNode values, * @param lengthMarker whether to include the # marker before the length * @return the formatted inline array string */ - public static String formatInlineArray(ArrayNode values, String delimiter, String prefix, boolean lengthMarker) { + public static String formatInlineArray(final ArrayNode values, final String delimiter, final String prefix, final boolean lengthMarker) { final List valueList = new ArrayList<>(); values.forEach(valueList::add); @@ -161,8 +161,8 @@ public static String formatInlineArray(ArrayNode values, String delimiter, Strin /** * Encodes an array of primitive arrays as list items. */ - private static void encodeArrayOfArraysAsListItems(String prefix, ArrayNode values, LineWriter writer, int depth, - EncodeOptions options) { + private static void encodeArrayOfArraysAsListItems(final String prefix, final ArrayNode values, final LineWriter writer, final int depth, + final EncodeOptions options) { final String header = PrimitiveEncoder.formatHeader(values.size(), prefix, null, options.delimiter().toString(), options.lengthMarker()); writer.push(depth, header); @@ -179,11 +179,11 @@ private static void encodeArrayOfArraysAsListItems(String prefix, ArrayNode valu /** * Encodes a mixed array (non-uniform) as list items. */ - private static void encodeMixedArrayAsListItems(String prefix, - ArrayNode items, - LineWriter writer, - int depth, - EncodeOptions options) { + private static void encodeMixedArrayAsListItems(final String prefix, + final ArrayNode items, + final LineWriter writer, + final int depth, + final EncodeOptions options) { final String header = PrimitiveEncoder.formatHeader(items.size(), prefix, null, options.delimiter().toString(), options.lengthMarker()); writer.push(depth, header); diff --git a/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java b/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java index 60d6e25..b9e97be 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java @@ -63,12 +63,12 @@ private record ChainResult(List segments, JsonNode tail, JsonNode leafVa * @param remainingDepth the remaining depth of the object * @return a {@link FoldResult}, or null if folding is not possible */ - public static FoldResult tryFoldKeyChain(String key, - JsonNode value, - Set siblings, - Set rootLiteralKeys, - String pathPrefix, - Integer remainingDepth) { + public static FoldResult tryFoldKeyChain(final String key, + final JsonNode value, + final Set siblings, + final Set rootLiteralKeys, + final String pathPrefix, + final Integer remainingDepth) { // Must be an object to begin folding if (!value.isObject() || remainingDepth <= 1) { return null; @@ -136,7 +136,7 @@ public static FoldResult tryFoldKeyChain(String key, * @param maxDepth maximum number of allowed segments * @return a {@link ChainResult} containing segments, tail, and leafValue */ - private static ChainResult collectSingleKeyChain(String startKey, JsonNode startValue, int maxDepth) { + private static ChainResult collectSingleKeyChain(final String startKey, final JsonNode startValue, final int maxDepth) { // normalize absolute key to its local segment final String localStartKey = startKey.contains(DOT) ? startKey.substring(startKey.lastIndexOf(DOT.charAt(0)) + 1) diff --git a/src/main/java/dev/toonformat/jtoon/encoder/HeaderFormatter.java b/src/main/java/dev/toonformat/jtoon/encoder/HeaderFormatter.java index dfb83b1..450c5f6 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/HeaderFormatter.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/HeaderFormatter.java @@ -41,7 +41,7 @@ public record HeaderConfig( * @param config Header configuration * @return Formatted header string */ - public static String format(HeaderConfig config) { + public static String format(final HeaderConfig config) { final StringBuilder header = new StringBuilder(); appendKeyIfPresent(header, config.key()); @@ -63,26 +63,26 @@ public static String format(HeaderConfig config) { * @return formatted header string */ public static String format( - int length, - String key, - List fields, - String delimiter, - boolean lengthMarker) { + final int length, + final String key, + final List fields, + final String delimiter, + final boolean lengthMarker) { final HeaderConfig config = new HeaderConfig(length, key, fields, delimiter, lengthMarker); return format(config); } - private static void appendKeyIfPresent(StringBuilder header, String key) { + private static void appendKeyIfPresent(final StringBuilder header, final String key) { if (key != null) { header.append(PrimitiveEncoder.encodeKey(key)); } } private static void appendArrayLength( - StringBuilder header, - int length, - String delimiter, - boolean lengthMarker) { + final StringBuilder header, + final int length, + final String delimiter, + final boolean lengthMarker) { header.append(OPEN_BRACKET); if (lengthMarker) { @@ -94,16 +94,16 @@ private static void appendArrayLength( header.append(CLOSE_BRACKET); } - private static void appendDelimiterIfNotDefault(StringBuilder header, String delimiter) { + private static void appendDelimiterIfNotDefault(final StringBuilder header, final String delimiter) { if (!COMMA.equals(delimiter)) { header.append(delimiter); } } private static void appendFieldsIfPresent( - StringBuilder header, - List fields, - String delimiter) { + final StringBuilder header, + final List fields, + final String delimiter) { if (fields == null || fields.isEmpty()) { return; } @@ -113,7 +113,7 @@ private static void appendFieldsIfPresent( header.append(CLOSE_BRACE); } - private static String formatFields(List fields, String delimiter) { + private static String formatFields(final List fields, final String delimiter) { return fields.stream() .map(PrimitiveEncoder::encodeKey) .reduce((a, b) -> a + delimiter + b) diff --git a/src/main/java/dev/toonformat/jtoon/encoder/LineWriter.java b/src/main/java/dev/toonformat/jtoon/encoder/LineWriter.java index 64dc296..8ab1631 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/LineWriter.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/LineWriter.java @@ -1,43 +1,69 @@ package dev.toonformat.jtoon.encoder; -import java.util.ArrayList; -import java.util.List; import static dev.toonformat.jtoon.util.Constants.SPACE; /** * Line writer that accumulates indented lines for building the final output. + * Uses StringBuilder for efficient string building. */ +@SuppressWarnings("PMD.AvoidStringBufferField") public final class LineWriter { - private final List lines = new ArrayList<>(); + private final StringBuilder stringBuilder; private final String indentationString; + private final String[] indentCache; + private boolean firstLine = true; + + private static final int MAX_INDENT_CACHE = 16; + private static final int INITIAL_BUFFER_SIZE = 1024; /** * Creates a LineWriter with the specified indentation size. - * + * * @param indentSize Number of spaces per indentation level */ - public LineWriter(int indentSize) { + public LineWriter(final int indentSize) { + this.stringBuilder = new StringBuilder(INITIAL_BUFFER_SIZE); this.indentationString = SPACE.repeat(indentSize); + this.indentCache = new String[MAX_INDENT_CACHE]; + + if (indentSize > 0) { + final StringBuilder indent = new StringBuilder(); + for (int i = 0; i < MAX_INDENT_CACHE; i++) { + indentCache[i] = indent.toString(); + indent.append(indentationString); + } + } } /** * Adds a line with the specified depth and content. - * + * * @param depth Indentation depth (0 = no indentation) * @param content Line content to add */ - public void push(int depth, String content) { - final String indent = indentationString.repeat(depth); - lines.add(indent + content); + public void push(final int depth, final String content) { + if (!firstLine) { + stringBuilder.append('\n'); + } + firstLine = false; + + if (depth > 0) { + if (depth < indentCache.length) { + stringBuilder.append(indentCache[depth]); + } else { + stringBuilder.append(String.valueOf(indentationString).repeat(depth)); + } + } + stringBuilder.append(content); } /** - * Joins all accumulated lines with newlines. - * + * Returns the complete output string. + * * @return The complete output string */ @Override public String toString() { - return String.join("\n", lines); + return stringBuilder.toString(); } } diff --git a/src/main/java/dev/toonformat/jtoon/encoder/ListItemEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/ListItemEncoder.java index 26e6fe5..bba13ec 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/ListItemEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/ListItemEncoder.java @@ -35,7 +35,7 @@ private ListItemEncoder() { * @param depth Indentation depth * @param options Encoding options */ - public static void encodeObjectAsListItem(ObjectNode obj, LineWriter writer, int depth, EncodeOptions options) { + public static void encodeObjectAsListItem(final ObjectNode obj, final LineWriter writer, final int depth, final EncodeOptions options) { final List keys = new ArrayList<>(obj.propertyNames()); if (keys.isEmpty()) { @@ -60,8 +60,8 @@ public static void encodeObjectAsListItem(ObjectNode obj, LineWriter writer, int * Encodes the first key-value pair of a list item. * Handles special formatting for arrays and objects. */ - private static void encodeFirstKeyValue(String key, JsonNode value, LineWriter writer, int depth, - EncodeOptions options) { + private static void encodeFirstKeyValue(final String key, final JsonNode value, final LineWriter writer, final int depth, + final EncodeOptions options) { final String encodedKey = PrimitiveEncoder.encodeKey(key); if (value.isValueNode()) { @@ -73,14 +73,14 @@ private static void encodeFirstKeyValue(String key, JsonNode value, LineWriter w } } - private static void encodeFirstValueAsPrimitive(String encodedKey, JsonNode value, LineWriter writer, int depth, - EncodeOptions options) { + private static void encodeFirstValueAsPrimitive(final String encodedKey, final JsonNode value, final LineWriter writer, final int depth, + final EncodeOptions options) { writer.push(depth, LIST_ITEM_PREFIX + encodedKey + COLON + SPACE + PrimitiveEncoder.encodePrimitive(value, options.delimiter().toString())); } - private static void encodeFirstValueAsArray(String key, String encodedKey, ArrayNode arrayValue, LineWriter writer, - int depth, EncodeOptions options) { + private static void encodeFirstValueAsArray(final String key, final String encodedKey, final ArrayNode arrayValue, final LineWriter writer, + final int depth, final EncodeOptions options) { if (ArrayEncoder.isArrayOfPrimitives(arrayValue)) { encodeFirstArrayAsPrimitives(key, arrayValue, writer, depth, options); } else if (ArrayEncoder.isArrayOfObjects(arrayValue)) { @@ -90,15 +90,15 @@ private static void encodeFirstValueAsArray(String key, String encodedKey, Array } } - private static void encodeFirstArrayAsPrimitives(String key, ArrayNode arrayValue, LineWriter writer, int depth, - EncodeOptions options) { + private static void encodeFirstArrayAsPrimitives(final String key, final ArrayNode arrayValue, final LineWriter writer, final int depth, + final EncodeOptions options) { final String formatted = ArrayEncoder.formatInlineArray(arrayValue, options.delimiter().toString(), key, options.lengthMarker()); writer.push(depth, LIST_ITEM_PREFIX + formatted); } - private static void encodeFirstArrayAsObjects(String key, String encodedKey, ArrayNode arrayValue, - LineWriter writer, int depth, EncodeOptions options) { + private static void encodeFirstArrayAsObjects(final String key, final String encodedKey, final ArrayNode arrayValue, + final LineWriter writer, final int depth, final EncodeOptions options) { final List header = TabularArrayEncoder.detectTabularHeader(arrayValue); if (!header.isEmpty()) { final String headerStr = PrimitiveEncoder.formatHeader(arrayValue.size(), key, header, @@ -118,8 +118,8 @@ private static void encodeFirstArrayAsObjects(String key, String encodedKey, Arr } } - private static void encodeFirstArrayAsComplex(String encodedKey, ArrayNode arrayValue, LineWriter writer, - int depth, EncodeOptions options) { + private static void encodeFirstArrayAsComplex(final String encodedKey, final ArrayNode arrayValue, final LineWriter writer, + final int depth, final EncodeOptions options) { writer.push(depth, LIST_ITEM_PREFIX + encodedKey + OPEN_BRACKET + arrayValue.size() + CLOSE_BRACKET + COLON); for (JsonNode item : arrayValue) { @@ -136,8 +136,8 @@ private static void encodeFirstArrayAsComplex(String encodedKey, ArrayNode array } } - private static void encodeFirstValueAsObject(String encodedKey, ObjectNode nestedObj, LineWriter writer, int depth, - EncodeOptions options) { + private static void encodeFirstValueAsObject(final String encodedKey, final ObjectNode nestedObj, final LineWriter writer, final int depth, + final EncodeOptions options) { writer.push(depth, LIST_ITEM_PREFIX + encodedKey + COLON); if (!nestedObj.isEmpty()) { ObjectEncoder.encodeObject(nestedObj, writer, depth + 2, options, Set.of(), null, null, new HashSet<>()); diff --git a/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java index 63df414..ce8af8f 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java @@ -36,9 +36,9 @@ private ObjectEncoder() { * @param remainingDepth optional override for the remaining depth * @param blockedKeys contains only keys that have undergone a successful flattening */ - public static void encodeObject(ObjectNode value, LineWriter writer, int depth, EncodeOptions options, - Set rootLiteralKeys, String pathPrefix, Integer remainingDepth, - Set blockedKeys) { + public static void encodeObject(final ObjectNode value, final LineWriter writer, final int depth, final EncodeOptions options, + final Set rootLiteralKeys, final String pathPrefix, final Integer remainingDepth, + final Set blockedKeys) { final List> fields = value.properties().stream().toList(); // At root level (depth 0), collect all literal dotted keys for collision checking @@ -76,16 +76,16 @@ public static void encodeObject(ObjectNode value, LineWriter writer, int depth, * @param flattenDepth optional override for depth limit * @param blockedKeys contains only keys that have undergone a successful flattening */ - public static void encodeKeyValuePair(String key, - JsonNode value, - LineWriter writer, - int depth, - EncodeOptions options, - Set siblings, - Set rootLiteralKeys, - String pathPrefix, - Integer flattenDepth, - Set blockedKeys + public static void encodeKeyValuePair(final String key, + final JsonNode value, + final LineWriter writer, + final int depth, + final EncodeOptions options, + final Set siblings, + final Set rootLiteralKeys, + final String pathPrefix, + final Integer flattenDepth, + final Set blockedKeys ) { if (key == null) { return; @@ -146,9 +146,9 @@ public static void encodeKeyValuePair(String key, * @param remainingDepth the depth that remind to the limit * @return EncodeOptions changes for Case 2 */ - private static EncodeOptions flatten(String key, Flatten.FoldResult foldResult, LineWriter writer, int depth, - EncodeOptions options, Set rootLiteralKeys, String pathPrefix, - Set blockedKeys, int remainingDepth) { + private static EncodeOptions flatten(final String key, final Flatten.FoldResult foldResult, final LineWriter writer, final int depth, + final EncodeOptions options, final Set rootLiteralKeys, final String pathPrefix, + final Set blockedKeys, final int remainingDepth) { final String foldedKey = foldResult.foldedKey(); EncodeOptions currentOptions = options; @@ -189,8 +189,8 @@ private static EncodeOptions flatten(String key, Flatten.FoldResult foldResult, return currentOptions; } - private static void handleFullyFoldedLeaf(Flatten.FoldResult foldResult, LineWriter writer, int depth, - EncodeOptions options, String encodedFoldedKey) { + private static void handleFullyFoldedLeaf(final Flatten.FoldResult foldResult, final LineWriter writer, final int depth, + final EncodeOptions options, final String encodedFoldedKey) { final JsonNode leaf = foldResult.leafValue(); // Primitive @@ -218,7 +218,7 @@ private static void handleFullyFoldedLeaf(Flatten.FoldResult foldResult, LineWri } } - private static String indentedLine(int depth, String content, int indentSize) { + private static String indentedLine(final int depth, final String content, final int indentSize) { return "%s%s".formatted(" ".repeat(indentSize * depth), content); } } diff --git a/src/main/java/dev/toonformat/jtoon/encoder/PrimitiveEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/PrimitiveEncoder.java index 78aab20..bf322d2 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/PrimitiveEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/PrimitiveEncoder.java @@ -5,7 +5,6 @@ import tools.jackson.databind.JsonNode; import java.math.BigDecimal; import java.util.List; -import java.util.Objects; import static dev.toonformat.jtoon.util.Constants.NULL_LITERAL; import static dev.toonformat.jtoon.util.Constants.DOUBLE_QUOTE; @@ -16,17 +15,20 @@ */ public final class PrimitiveEncoder { + private static final int INITIAL_BUFFER_SIZE = 128; + private PrimitiveEncoder() { throw new UnsupportedOperationException("Utility class cannot be instantiated"); } /** * Encodes a primitive JsonNode value. - * @param value the primitive value to encode + * + * @param value the primitive value to encode * @param delimiter the delimiter to use (for string validation) * @return the encoded string representation */ - public static String encodePrimitive(JsonNode value, String delimiter) { + public static String encodePrimitive(final JsonNode value, final String delimiter) { return switch (value.getNodeType()) { case BOOLEAN -> String.valueOf(value.asBoolean()); case NUMBER -> encodeNumber(value); @@ -40,7 +42,7 @@ public static String encodePrimitive(JsonNode value, String delimiter) { * Ensures LLM-safe output by converting all numbers to plain decimal * representation. */ - private static String encodeNumber(JsonNode value) { + private static String encodeNumber(final JsonNode value) { if (value.isIntegralNumber()) { return value.asString(); } @@ -57,28 +59,33 @@ private static String encodeNumber(JsonNode value) { * decimal point. * Examples: "1.500" -> "1.5", "1.0" -> "1", "0.000001" -> "0.000001" */ - private static String stripTrailingZeros(String value) { - if (!value.contains(".")) { + private static String stripTrailingZeros(final String value) { + final int dotIndex = value.indexOf('.'); + if (dotIndex < 0) { return value; } - String stripped = value.replaceAll("0+$", ""); + int lastNonZero = value.length() - 1; + while (lastNonZero > dotIndex && value.charAt(lastNonZero) == '0') { + lastNonZero--; + } - if (stripped.endsWith(".")) { - stripped = stripped.substring(0, stripped.length() - 1); + if (lastNonZero == dotIndex) { + return value.substring(0, dotIndex); } - return stripped; + return value.substring(0, lastNonZero + 1); } /** * Encodes a string literal, quoting if necessary. * Delegates validation to StringValidator and escaping to StringEscaper. - * @param value the string value to encode + * + * @param value the string value to encode * @param delimiter the delimiter to use (for validation) * @return the encoded string, quoted if necessary */ - public static String encodeStringLiteral(String value, String delimiter) { + public static String encodeStringLiteral(final String value, final String delimiter) { if (StringValidator.isSafeUnquoted(value, delimiter)) { return value; } @@ -89,10 +96,11 @@ public static String encodeStringLiteral(String value, String delimiter) { /** * Encodes an object key, quoting if necessary. * Delegates validation to StringValidator and escaping to StringEscaper. + * * @param key the key to encode * @return the encoded key, quoted if necessary */ - public static String encodeKey(String key) { + public static String encodeKey(final String key) { if (StringValidator.isValidUnquotedKey(key)) { return key; } @@ -102,22 +110,35 @@ public static String encodeKey(String key) { /** * Joins encoded primitive values with the specified delimiter. - * @param values the list of primitive values to join + * + * @param values the list of primitive values to join * @param delimiter the delimiter to use between values * @return the joined string of encoded values */ - public static String joinEncodedValues(List values, String delimiter) { - return values.stream() - .filter(Objects::nonNull) - .map(v -> encodePrimitive(v, delimiter)) - .reduce((a, b) -> a + delimiter + b) - .orElse(""); + public static String joinEncodedValues(final List values, final String delimiter) { + if (values == null || values.isEmpty()) { + return ""; + } + + final StringBuilder stringBuilder = new StringBuilder(INITIAL_BUFFER_SIZE); + boolean first = true; + for (final JsonNode node : values) { + if (node == null) { + continue; + } + if (!first) { + stringBuilder.append(delimiter); + } + first = false; + stringBuilder.append(encodePrimitive(node, delimiter)); + } + return stringBuilder.toString(); } /** * Formats a header for arrays and tables. * Delegates to HeaderFormatter for implementation. - * + * * @param length Array length * @param key Optional key prefix * @param fields Optional field names for tabular format @@ -126,11 +147,11 @@ public static String joinEncodedValues(List values, String delimiter) * @return Formatted header string */ public static String formatHeader( - int length, - String key, - List fields, - String delimiter, - boolean lengthMarker) { + final int length, + final String key, + final List fields, + final String delimiter, + final boolean lengthMarker) { return HeaderFormatter.format(length, key, fields, delimiter, lengthMarker); } } diff --git a/src/main/java/dev/toonformat/jtoon/encoder/TabularArrayEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/TabularArrayEncoder.java index 8cd24a8..ed07bfc 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/TabularArrayEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/TabularArrayEncoder.java @@ -25,7 +25,7 @@ private TabularArrayEncoder() { * @param rows The array to analyze * @return List of field names for tabular header, or empty list if not tabular */ - public static List detectTabularHeader(ArrayNode rows) { + public static List detectTabularHeader(final ArrayNode rows) { if (rows.isEmpty()) { return Collections.emptyList(); } @@ -52,7 +52,7 @@ public static List detectTabularHeader(ArrayNode rows) { /** * Checks if all rows in the array have the same keys with primitive values. */ - private static boolean isTabularArray(ArrayNode rows, List header) { + private static boolean isTabularArray(final ArrayNode rows, final List header) { for (JsonNode row : rows) { if (!row.isObject()) { return false; @@ -90,8 +90,8 @@ private static boolean isTabularArray(ArrayNode rows, List header) { * @param depth Indentation depth * @param options Encoding options */ - public static void encodeArrayOfObjectsAsTabular(String prefix, ArrayNode rows, List header, - LineWriter writer, int depth, EncodeOptions options) { + public static void encodeArrayOfObjectsAsTabular(final String prefix, final ArrayNode rows, final List header, + final LineWriter writer, final int depth, final EncodeOptions options) { final String headerStr = PrimitiveEncoder.formatHeader(rows.size(), prefix, header, options.delimiter().toString(), options.lengthMarker()); writer.push(depth, headerStr); @@ -109,8 +109,8 @@ public static void encodeArrayOfObjectsAsTabular(String prefix, ArrayNode rows, * @param depth Indentation depth * @param options Encoding options */ - public static void writeTabularRows(ArrayNode rows, List header, LineWriter writer, int depth, - EncodeOptions options) { + public static void writeTabularRows(final ArrayNode rows, final List header, final LineWriter writer, final int depth, + final EncodeOptions options) { for (JsonNode row : rows) { //skip non-object rows if (!row.isObject()) { diff --git a/src/main/java/dev/toonformat/jtoon/encoder/ValueEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/ValueEncoder.java index b3960a4..8c00180 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/ValueEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/ValueEncoder.java @@ -24,7 +24,7 @@ private ValueEncoder() { * @param options Encoding options (indent, delimiter, length marker) * @return The TOON-formatted string */ - public static String encodeValue(JsonNode value, EncodeOptions options) { + public static String encodeValue(final JsonNode value, final EncodeOptions options) { // Handle primitive values directly if (value.isValueNode()) { return PrimitiveEncoder.encodePrimitive(value, options.delimiter().toString()); diff --git a/src/main/java/dev/toonformat/jtoon/normalizer/JsonNormalizer.java b/src/main/java/dev/toonformat/jtoon/normalizer/JsonNormalizer.java index d10b443..ddc68e6 100644 --- a/src/main/java/dev/toonformat/jtoon/normalizer/JsonNormalizer.java +++ b/src/main/java/dev/toonformat/jtoon/normalizer/JsonNormalizer.java @@ -70,7 +70,7 @@ private JsonNormalizer() { * @return Parsed JsonNode * @throws IllegalArgumentException if the input is blank or not valid JSON */ - public static JsonNode parse(String json) { + public static JsonNode parse(final String json) { if (json == null || json.isBlank()) { throw new IllegalArgumentException("Invalid JSON"); } @@ -87,7 +87,7 @@ public static JsonNode parse(String json) { * @param value The value to normalize * @return The normalized JsonNode */ - public static JsonNode normalize(Object value) { + public static JsonNode normalize(final Object value) { if (value == null) { return NullNode.getInstance(); } else if (value instanceof JsonNode jsonNode) { @@ -106,7 +106,7 @@ public static JsonNode normalize(Object value) { /** * Attempts normalization using chain of responsibility pattern. */ - private static JsonNode normalizeWithStrategy(Object value) { + private static JsonNode normalizeWithStrategy(final Object value) { return NORMALIZERS.stream() .map(normalizer -> normalizer.apply(value)) .filter(Objects::nonNull) @@ -118,7 +118,7 @@ private static JsonNode normalizeWithStrategy(Object value) { * Attempts to normalize primitive types and their wrappers. * Returns null if the value is not a primitive type. */ - private static JsonNode tryNormalizePrimitive(Object value) { + private static JsonNode tryNormalizePrimitive(final Object value) { if (value instanceof String stringValue) { return StringNode.valueOf(stringValue); } else if (value instanceof Boolean boolValue) { @@ -143,7 +143,7 @@ private static JsonNode tryNormalizePrimitive(Object value) { /** * Normalizes Double values handling special cases. */ - private static JsonNode normalizeDouble(Double value) { + private static JsonNode normalizeDouble(final Double value) { if (!Double.isFinite(value)) { return NullNode.getInstance(); } @@ -157,7 +157,7 @@ private static JsonNode normalizeDouble(Double value) { /** * Normalizes Float values handling special cases. */ - private static JsonNode normalizeFloat(Float value) { + private static JsonNode normalizeFloat(final Float value) { return Float.isFinite(value) ? FloatNode.valueOf(value) : NullNode.getInstance(); @@ -166,7 +166,7 @@ private static JsonNode normalizeFloat(Float value) { /** * Attempts to convert a double to a long if it's a whole number. */ - private static Optional tryConvertToLong(Double value) { + private static Optional tryConvertToLong(final Double value) { if (value != Math.floor(value)) { return Optional.empty(); } @@ -181,7 +181,7 @@ private static Optional tryConvertToLong(Double value) { * Attempts to normalize BigInteger and BigDecimal. * Returns null if the value is not a big number type. */ - private static JsonNode tryNormalizeBigNumber(Object value) { + private static JsonNode tryNormalizeBigNumber(final Object value) { if (value instanceof BigInteger bigInteger) { return normalizeBigInteger(bigInteger); } else if (value instanceof BigDecimal bigDecimal) { @@ -194,7 +194,7 @@ private static JsonNode tryNormalizeBigNumber(Object value) { /** * Normalizes BigInteger, converting to long if within range. */ - private static JsonNode normalizeBigInteger(BigInteger value) { + private static JsonNode normalizeBigInteger(final BigInteger value) { final boolean fitsInLong = value.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0 && value.compareTo(BigInteger.valueOf(Long.MIN_VALUE)) >= 0; return fitsInLong @@ -206,7 +206,7 @@ private static JsonNode normalizeBigInteger(BigInteger value) { * Attempts to normalize temporal types (date/time) to ISO strings. * Returns null if the value is not a temporal type. */ - private static JsonNode tryNormalizeTemporal(Object value) { + private static JsonNode tryNormalizeTemporal(final Object value) { if (value instanceof LocalDateTime ldt) { return formatTemporal(ldt, DateTimeFormatter.ISO_LOCAL_DATE_TIME); } else if (value instanceof LocalDate ld) { @@ -237,7 +237,7 @@ private static JsonNode tryNormalizeTemporal(Object value) { /** * Helper method to format temporal values consistently. */ - private static JsonNode formatTemporal(T temporal, DateTimeFormatter formatter) { + private static JsonNode formatTemporal(final T temporal, final DateTimeFormatter formatter) { return StringNode.valueOf(formatter.format((java.time.temporal.TemporalAccessor) temporal)); } @@ -245,7 +245,7 @@ private static JsonNode formatTemporal(T temporal, DateTimeFormatter formatt * Attempts to normalize collections (Collection and Map). * Returns null if the value is not a collection type. */ - private static JsonNode tryNormalizeCollection(Object value) { + private static JsonNode tryNormalizeCollection(final Object value) { if (value instanceof Collection) { return normalizeCollection((Collection) value); } else if (value instanceof Map) { @@ -258,7 +258,7 @@ private static JsonNode tryNormalizeCollection(Object value) { /** * Normalizes a Collection to an ArrayNode. */ - private static ArrayNode normalizeCollection(Collection collection) { + private static ArrayNode normalizeCollection(final Collection collection) { final ArrayNode arrayNode = MAPPER.createArrayNode(); collection.forEach(item -> arrayNode.add(normalize(item))); return arrayNode; @@ -267,7 +267,7 @@ private static ArrayNode normalizeCollection(Collection collection) { /** * Normalizes a Map to an ObjectNode. */ - private static ObjectNode normalizeMap(Map map) { + private static ObjectNode normalizeMap(final Map map) { final ObjectNode objectNode = MAPPER.createObjectNode(); map.forEach((key, value) -> objectNode.set(String.valueOf(key), normalize(value))); return objectNode; @@ -277,7 +277,7 @@ private static ObjectNode normalizeMap(Map map) { * Attempts to normalize POJOs using Jackson's default conversion. * Returns null for non-serializable objects. */ - private static JsonNode tryNormalizePojo(Object value) { + private static JsonNode tryNormalizePojo(final Object value) { try { return MAPPER.valueToTree(value); } catch (Exception e) { @@ -288,7 +288,7 @@ private static JsonNode tryNormalizePojo(Object value) { /** * Normalizes arrays to ArrayNode. */ - private static JsonNode normalizeArray(Object array) { + private static JsonNode normalizeArray(final Object array) { if (array instanceof int[] intArr) { return buildArrayNode(intArr.length, i -> IntNode.valueOf(intArr[i])); } else if (array instanceof long[] longArr) { @@ -315,7 +315,7 @@ private static JsonNode normalizeArray(Object array) { /** * Builds an ArrayNode using a functional approach. */ - private static ArrayNode buildArrayNode(int length, IntFunction mapper) { + private static ArrayNode buildArrayNode(final int length, final IntFunction mapper) { final ArrayNode arrayNode = MAPPER.createArrayNode(); for (int i = 0; i < length; i++) { arrayNode.add(mapper.apply(i)); @@ -326,7 +326,7 @@ private static ArrayNode buildArrayNode(int length, IntFunction mapper /** * Normalizes a single double element from an array. */ - private static JsonNode normalizeDoubleElement(double value) { + private static JsonNode normalizeDoubleElement(final double value) { return Double.isFinite(value) ? DoubleNode.valueOf(value) : NullNode.getInstance(); @@ -335,7 +335,7 @@ private static JsonNode normalizeDoubleElement(double value) { /** * Normalizes a single float element from an array. */ - private static JsonNode normalizeFloatElement(float value) { + private static JsonNode normalizeFloatElement(final float value) { return Float.isFinite(value) ? FloatNode.valueOf(value) : NullNode.getInstance(); diff --git a/src/main/java/dev/toonformat/jtoon/util/StringEscaper.java b/src/main/java/dev/toonformat/jtoon/util/StringEscaper.java index 795bdc7..5f5fce2 100644 --- a/src/main/java/dev/toonformat/jtoon/util/StringEscaper.java +++ b/src/main/java/dev/toonformat/jtoon/util/StringEscaper.java @@ -17,7 +17,7 @@ private StringEscaper() { * @param value The string to escape * @return The escaped string */ - public static String escape(String value) { + public static String escape(final String value) { return value .replace("\\", "\\\\") .replace("\"", "\\\"") @@ -32,7 +32,7 @@ public static String escape(String value) { * @param value The string to validate * @throws IllegalArgumentException if the string has invalid escape sequences or is unterminated */ - public static void validateString(String value) { + public static void validateString(final String value) { if (value == null || value.isEmpty()) { return; } @@ -69,7 +69,7 @@ public static void validateString(String value) { /** * Checks if a character is a valid escape sequence. */ - private static boolean isValidEscapeChar(char c) { + private static boolean isValidEscapeChar(final char c) { return c == 'n' || c == 'r' || c == 't' || c == '"' || c == '\\'; } @@ -80,7 +80,7 @@ private static boolean isValidEscapeChar(char c) { * @param value The string to unescape (may be quoted) * @return The unescaped string with quotes removed */ - public static String unescape(String value) { + public static String unescape(final String value) { if (value == null || value.length() < 2) { return value; } @@ -113,7 +113,7 @@ public static String unescape(String value) { * @param c The character following a backslash * @return The unescaped character */ - private static char unescapeChar(char c) { + private static char unescapeChar(final char c) { return switch (c) { case 'n' -> '\n'; case 'r' -> '\r'; diff --git a/src/main/java/dev/toonformat/jtoon/util/StringValidator.java b/src/main/java/dev/toonformat/jtoon/util/StringValidator.java index d441665..42d0581 100644 --- a/src/main/java/dev/toonformat/jtoon/util/StringValidator.java +++ b/src/main/java/dev/toonformat/jtoon/util/StringValidator.java @@ -30,7 +30,7 @@ private StringValidator() { * @param delimiter the delimiter being used (for validation) * @return true if the string can be safely written without quotes, false otherwise */ - public static boolean isSafeUnquoted(String value, String delimiter) { + public static boolean isSafeUnquoted(final String value, final String delimiter) { if (isNullOrEmpty(value)) { return false; } @@ -72,52 +72,52 @@ public static boolean isSafeUnquoted(String value, String delimiter) { * @param key the key to validate * @return true if the key can be used without quotes, false otherwise */ - public static boolean isValidUnquotedKey(String key) { + public static boolean isValidUnquotedKey(final String key) { return UNQUOTED_KEY_PATTERN.matcher(key).matches(); } - private static boolean isNullOrEmpty(String value) { + private static boolean isNullOrEmpty(final String value) { return value == null || value.isEmpty(); } - private static boolean isPaddedWithWhitespace(String value) { + private static boolean isPaddedWithWhitespace(final String value) { return !value.equals(value.trim()); } - private static boolean looksLikeKeyword(String value) { + private static boolean looksLikeKeyword(final String value) { return TRUE_LITERAL.equals(value) || FALSE_LITERAL.equals(value) || NULL_LITERAL.equals(value); } - private static boolean looksLikeNumber(String value) { + private static boolean looksLikeNumber(final String value) { return OCTAL_PATTERN.matcher(value).matches() || LEADING_ZERO_PATTERN.matcher(value).matches() || NUMERIC_PATTERN.matcher(value).matches(); } - private static boolean containsColon(String value) { + private static boolean containsColon(final String value) { return value.contains(COLON); } - static boolean containsQuotesOrBackslash(String value) { + static boolean containsQuotesOrBackslash(final String value) { return value.indexOf(DOUBLE_QUOTE) >= 0 || value.indexOf(BACKSLASH) >= 0; } - private static boolean containsStructuralCharacters(String value) { + private static boolean containsStructuralCharacters(final String value) { return STRUCTURAL_CHARS.matcher(value).find(); } - private static boolean containsControlCharacters(String value) { + private static boolean containsControlCharacters(final String value) { return CONTROL_CHARS.matcher(value).find(); } - private static boolean containsDelimiter(String value, String delimiter) { + private static boolean containsDelimiter(final String value, final String delimiter) { return value.contains(delimiter); } - private static boolean startsWithListMarker(String value) { + private static boolean startsWithListMarker(final String value) { return value.startsWith(LIST_ITEM_MARKER); } } From c5e78cde39c08f8798bbccc2305a197c0b1b66fd Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Fri, 13 Feb 2026 19:52:10 +0100 Subject: [PATCH 19/22] cleanup run with spotbugs --- .gitignore | 1 + build.gradle | 4 +- spotbugs-exclude.xml | 72 +++++++++++++++++++ .../jtoon/decoder/ArrayDecoder.java | 6 +- .../jtoon/decoder/DecodeHelper.java | 14 ++-- .../toonformat/jtoon/decoder/KeyDecoder.java | 25 ++++--- .../jtoon/decoder/ListItemDecoder.java | 5 +- .../jtoon/decoder/ObjectDecoder.java | 6 +- .../jtoon/decoder/PrimitiveDecoder.java | 8 +-- .../jtoon/decoder/TabularArrayDecoder.java | 7 +- .../jtoon/encoder/ArrayEncoder.java | 15 ++-- .../dev/toonformat/jtoon/encoder/Flatten.java | 2 +- .../jtoon/encoder/HeaderFormatter.java | 7 +- .../jtoon/encoder/ObjectEncoder.java | 6 +- .../jtoon/encoder/PrimitiveEncoder.java | 5 +- .../jtoon/encoder/TabularArrayEncoder.java | 30 +++++--- .../jtoon/normalizer/JsonNormalizer.java | 4 +- .../jtoon/util/StringValidator.java | 8 ++- 18 files changed, 164 insertions(+), 61 deletions(-) diff --git a/.gitignore b/.gitignore index 93067e6..3512a58 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ bin/ ### Mac OS ### .DS_Store +/.idea/ diff --git a/build.gradle b/build.gradle index 65b2fe1..ed82cd5 100644 --- a/build.gradle +++ b/build.gradle @@ -51,8 +51,8 @@ pitest { spotbugs { toolVersion = '4.9.8' excludeFilter = file('spotbugs-exclude.xml') - effort = com.github.spotbugs.snom.Effort.MAX - reportLevel = com.github.spotbugs.snom.Confidence.values()[0] + effort = "max" + reportLevel = "low" reportsDir = layout.buildDirectory.dir('spotbugs') } diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index ba794cc..b9311e6 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -57,4 +57,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/dev/toonformat/jtoon/decoder/ArrayDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/ArrayDecoder.java index f49ad01..10b12a1 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/ArrayDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/ArrayDecoder.java @@ -151,12 +151,12 @@ static void validateArrayLength(final String header, final int actualLength) { * Returns the number specified in [n] or null if not found. * * @param header header string for length check - * @return extracted length from header + * @return extracted length from header, or null if not found */ private static Integer extractLengthFromHeader(final String header) { final Matcher matcher = ARRAY_HEADER_PATTERN.matcher(header); if (matcher.find()) { - return Integer.parseInt(matcher.group(2)); + return Integer.valueOf(matcher.group(2)); } return null; } @@ -169,8 +169,8 @@ private static Integer extractLengthFromHeader(final String header) { * @return parsed array values */ static List parseArrayValues(final String values, final Delimiter arrayDelimiter) { - final List result = new ArrayList<>(); final List rawValues = parseDelimitedValues(values, arrayDelimiter); + final List result = new ArrayList<>(rawValues.size()); for (final String value : rawValues) { result.add(PrimitiveDecoder.parse(value)); } diff --git a/src/main/java/dev/toonformat/jtoon/decoder/DecodeHelper.java b/src/main/java/dev/toonformat/jtoon/decoder/DecodeHelper.java index 5903994..cfc294a 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/DecodeHelper.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/DecodeHelper.java @@ -65,7 +65,7 @@ private static int computeLeadingSpaces(final String line, final DecodeContext c i++; } - if (context.options.strict() && leadingSpaces > 0 && indentSize > 0 && leadingSpaces % indentSize != 0) { + if (indentSize > 0 && leadingSpaces > 0 && leadingSpaces % indentSize != 0 && context.options.strict()) { throw new IllegalArgumentException( String.format("Non-multiple indentation: %d leadingSpaces with indent=%d at line %d", leadingSpaces, indentSize, context.currentLine + 1)); @@ -99,14 +99,14 @@ static int findUnquotedColon(final String content) { for (int i = 0; i < content.length(); i++) { final char c = content.charAt(i); - if (escaped) { + if (c == COLON.charAt(0) && !inQuotes) { + return i; + } else if (escaped) { escaped = false; } else if (c == BACKSLASH) { escaped = true; } else if (c == DOUBLE_QUOTE) { inQuotes = !inQuotes; - } else if (c == COLON.charAt(0) && !inQuotes) { - return i; } } @@ -137,7 +137,8 @@ static int findNextNonBlankLine(final int startIndex, final DecodeContext contex * @param context decode an object to deal with lines, delimiter, and options * @throws IllegalArgumentException in case there's a expansion conflict */ - static void checkFinalValueConflict(final String finalSegment, final Object existing, final Object value, final DecodeContext context) { + static void checkFinalValueConflict(final String finalSegment, final Object existing, + final Object value, final DecodeContext context) { if (existing != null && context.options.strict()) { // Check for conflicts in strict mode if (existing instanceof Map && !(value instanceof Map)) { @@ -162,7 +163,8 @@ static void checkFinalValueConflict(final String finalSegment, final Object exis * @param value present value in a map * @param context decode an object to deal with lines, delimiter, and options */ - static void checkPathExpansionConflict(final Map map, final String key, final Object value, final DecodeContext context) { + static void checkPathExpansionConflict(final Map map, final String key, + final Object value, final DecodeContext context) { if (!context.options.strict()) { return; } diff --git a/src/main/java/dev/toonformat/jtoon/decoder/KeyDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/KeyDecoder.java index d4b4e08..7dd4938 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/KeyDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/KeyDecoder.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Map; import java.util.regex.Matcher; +import java.util.regex.MatchResult; import static dev.toonformat.jtoon.util.Constants.DOT; import static dev.toonformat.jtoon.util.Headers.KEYED_ARRAY_PATTERN; @@ -107,7 +108,8 @@ static void expandPathIntoMap(final Map current, final String do * @param depth the depth of the value line * @param context decode an object to deal with lines, delimiter and options */ - static void processKeyValueLine(final Map result, final String content, final int depth, final DecodeContext context) { + static void processKeyValueLine(final Map result, final String content, + final int depth, final DecodeContext context) { final int colonIdx = DecodeHelper.findUnquotedColon(content); if (colonIdx > 0) { @@ -223,8 +225,8 @@ private static Object parseKeyValue(final String value, final int depth, final D * @param unescapedKey the unescaped key * @param value the value to put */ - private static void putKeyValueIntoMap(final Map map, final String originalKey, final String unescapedKey, - final Object value, final DecodeContext context) { + private static void putKeyValueIntoMap(final Map map, final String originalKey, + final String unescapedKey, final Object value, final DecodeContext context) { // Handle path expansion if (shouldExpandKey(originalKey, context)) { expandPathIntoMap(map, unescapedKey, value, context); @@ -244,8 +246,8 @@ private static void putKeyValueIntoMap(final Map map, final Stri * @param context decode an object to deal with lines, delimiter, and options * @return parsed a key-value pair */ - static Object parseKeyValuePair(final String key, final String value, final int depth, final boolean parseRootFields, - final DecodeContext context) { + static Object parseKeyValuePair(final String key, final String value, final int depth, + final boolean parseRootFields, final DecodeContext context) { final Map obj = new LinkedHashMap<>(); parseKeyValuePairIntoMap(obj, key, value, depth, context); @@ -264,10 +266,12 @@ static Object parseKeyValuePair(final String key, final String value, final int * @param context decode an object to deal with lines, delimiter, and options * @return parsed keyed array value */ - static Object parseKeyedArrayValue(final Matcher keyedArray, final String content, final int depth, final DecodeContext context) { - final String originalKey = keyedArray.group(1).trim(); + static Object parseKeyedArrayValue(final MatchResult keyedArray, final String content, + final int depth, final DecodeContext context) { + final String group1 = keyedArray.group(1); + final String originalKey = group1.trim(); final String key = StringEscaper.unescape(originalKey); - final String arrayHeader = content.substring(keyedArray.group(1).length()); + final String arrayHeader = content.substring(group1.length()); final List arrayValue = ArrayDecoder.parseArray(arrayHeader, depth, context); final Map obj = new LinkedHashMap<>(); @@ -305,9 +309,10 @@ static boolean parseKeyedArrayField(final String fieldContent, final Map result, final DecodeContext context) { + final Collection result, final DecodeContext context) { if (lineDepth == depth + 1) { final String content = line.substring((depth + 1) * context.options.indent()); @@ -52,7 +53,7 @@ public static void processListArrayItem(final String line, final int lineDepth, * @param context decode an object to deal with lines, delimiter and options * @return parsed item (scalar value or object) */ - public static Object parseListItem(final String content, final int depth, final DecodeContext context) { + static Object parseListItem(final String content, final int depth, final DecodeContext context) { // Handle empty item: just "-" or "- " final String itemContent; if (content.length() > 2) { diff --git a/src/main/java/dev/toonformat/jtoon/decoder/ObjectDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/ObjectDecoder.java index e9577ff..5190428 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/ObjectDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/ObjectDecoder.java @@ -56,8 +56,8 @@ static Map parseNestedObject(final int parentDepth, final Decode * Returns true if the line was processed, false if it was a blank line that was * skipped. */ - private static void processDirectChildLine(final Map result, final String line, final int parentDepth, final int depth, - final DecodeContext context) { + private static void processDirectChildLine(final Map result, final String line, + final int parentDepth, final int depth, final DecodeContext context) { final String content = line.substring((parentDepth + 1) * context.options.indent()); final Matcher keyedArray = KEYED_ARRAY_PATTERN.matcher(content); @@ -149,7 +149,7 @@ static Object parseBareScalarValue(final String content, final int depth, final context.currentLine++; // In strict mode, check if there are more primitives at the root level - if (context.options.strict() && depth == 0) { + if (depth == 0 && context.options.strict()) { DecodeHelper.validateNoMultiplePrimitivesAtRoot(context); } diff --git a/src/main/java/dev/toonformat/jtoon/decoder/PrimitiveDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/PrimitiveDecoder.java index 3915c00..d7a2c3a 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/PrimitiveDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/PrimitiveDecoder.java @@ -82,17 +82,17 @@ static Object parse(final String value) { // Try parsing as number try { // Check if it contains exponent notation or decimal point - if (value.contains(DOT) || value.contains("e") || value.contains("E")) { + if (value.contains("e") || value.contains("E") || value.contains(DOT)) { final double parsed = Double.parseDouble(value); // Handle negative zero - Java doesn't distinguish, but spec says it should be 0 if (parsed == 0.0) { return 0L; } // Check if the result is a whole number - if so, return as Long - if (parsed == Math.floor(parsed) - && !Double.isInfinite(parsed) + if (!Double.isInfinite(parsed) && parsed >= Long.MIN_VALUE - && parsed <= Long.MAX_VALUE) { + && parsed <= Long.MAX_VALUE + && parsed == Math.floor(parsed)) { return (long) parsed; } diff --git a/src/main/java/dev/toonformat/jtoon/decoder/TabularArrayDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/TabularArrayDecoder.java index ed6e23e..ee36873 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/TabularArrayDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/TabularArrayDecoder.java @@ -3,6 +3,7 @@ import dev.toonformat.jtoon.Delimiter; import dev.toonformat.jtoon.util.StringEscaper; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -79,8 +80,8 @@ private static List parseTabularKeys(final String keysStr, final Delimit validateKeysDelimiter(keysStr, arrayDelimiter); } - final List result = new ArrayList<>(); final List rawValues = ArrayDecoder.parseDelimitedValues(keysStr, arrayDelimiter); + final List result = new ArrayList<>(rawValues.size()); for (final String key : rawValues) { result.add(StringEscaper.unescape(key)); } @@ -121,11 +122,11 @@ private static void validateKeysDelimiter(final String keysStr, final Delimiter private static void checkDelimiterMismatch(final char expectedChar, final char actualChar) { if (expectedChar == Delimiter.TAB.getValue() && actualChar == Delimiter.COMMA.getValue()) { throw new IllegalArgumentException( - "Delimiter mismatch: bracket declares tab, brace fields use comma"); + "Delimiter mismatch: bracket declares tab (expected='" + expectedChar + "', actual='" + actualChar + "')"); } if (expectedChar == Delimiter.PIPE.getValue() && actualChar == Delimiter.COMMA.getValue()) { throw new IllegalArgumentException( - "Delimiter mismatch: bracket declares pipe, brace fields use comma"); + "Delimiter mismatch: bracket declares pipe (expected='" + expectedChar + "', actual='" + actualChar + "')"); } if (expectedChar == Delimiter.COMMA.getValue() && (actualChar == Delimiter.TAB.getValue() || actualChar == Delimiter.PIPE.getValue())) { diff --git a/src/main/java/dev/toonformat/jtoon/encoder/ArrayEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/ArrayEncoder.java index 09ceda1..fa1972c 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/ArrayEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/ArrayEncoder.java @@ -95,7 +95,7 @@ public static boolean isArrayOfPrimitives(final JsonNode array) { * @param array the array to check * @return true if all items in the array are arrays, false otherwise */ - public static boolean isArrayOfArrays(final JsonNode array) { + static boolean isArrayOfArrays(final JsonNode array) { if (!array.isArray()) { return false; } @@ -128,10 +128,10 @@ public static boolean isArrayOfObjects(final JsonNode array) { /** * Encodes a primitive array inline: key[N]: v1,v2,v3. */ - private static void encodeInlinePrimitiveArray(final String prefix, final ArrayNode values, final LineWriter writer, final int depth, - final EncodeOptions options) { + private static void encodeInlinePrimitiveArray(final String prefix, final ArrayNode values, + final LineWriter writer, final int depth, final EncodeOptions options) { final String formatted = formatInlineArray(values, options.delimiter().toString(), prefix, - options.lengthMarker()); + options.lengthMarker()); writer.push(depth, formatted); } @@ -144,7 +144,8 @@ private static void encodeInlinePrimitiveArray(final String prefix, final ArrayN * @param lengthMarker whether to include the # marker before the length * @return the formatted inline array string */ - public static String formatInlineArray(final ArrayNode values, final String delimiter, final String prefix, final boolean lengthMarker) { + public static String formatInlineArray(final ArrayNode values, final String delimiter, + final String prefix, final boolean lengthMarker) { final List valueList = new ArrayList<>(); values.forEach(valueList::add); @@ -161,8 +162,8 @@ public static String formatInlineArray(final ArrayNode values, final String deli /** * Encodes an array of primitive arrays as list items. */ - private static void encodeArrayOfArraysAsListItems(final String prefix, final ArrayNode values, final LineWriter writer, final int depth, - final EncodeOptions options) { + private static void encodeArrayOfArraysAsListItems(final String prefix, final ArrayNode values, + final LineWriter writer, final int depth, final EncodeOptions options) { final String header = PrimitiveEncoder.formatHeader(values.size(), prefix, null, options.delimiter().toString(), options.lengthMarker()); writer.push(depth, header); diff --git a/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java b/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java index b9e97be..faab4f0 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java @@ -149,7 +149,7 @@ private static ChainResult collectSingleKeyChain(final String startKey, final Js // track depth of folding int depthCounter = 1; - while (currentValue.isObject() && depthCounter < maxDepth) { + while (depthCounter < maxDepth && currentValue.isObject()) { final ObjectNode obj = (ObjectNode) currentValue; final Iterator> it = obj.properties().iterator(); diff --git a/src/main/java/dev/toonformat/jtoon/encoder/HeaderFormatter.java b/src/main/java/dev/toonformat/jtoon/encoder/HeaderFormatter.java index 450c5f6..2baec22 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/HeaderFormatter.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/HeaderFormatter.java @@ -1,5 +1,6 @@ package dev.toonformat.jtoon.encoder; +import java.util.Collection; import java.util.List; import static dev.toonformat.jtoon.util.Constants.COLON; import static dev.toonformat.jtoon.util.Constants.OPEN_BRACKET; @@ -41,7 +42,7 @@ public record HeaderConfig( * @param config Header configuration * @return Formatted header string */ - public static String format(final HeaderConfig config) { + static String format(final HeaderConfig config) { final StringBuilder header = new StringBuilder(); appendKeyIfPresent(header, config.key()); @@ -102,7 +103,7 @@ private static void appendDelimiterIfNotDefault(final StringBuilder header, fina private static void appendFieldsIfPresent( final StringBuilder header, - final List fields, + final Collection fields, final String delimiter) { if (fields == null || fields.isEmpty()) { return; @@ -113,7 +114,7 @@ private static void appendFieldsIfPresent( header.append(CLOSE_BRACE); } - private static String formatFields(final List fields, final String delimiter) { + private static String formatFields(final Collection fields, final String delimiter) { return fields.stream() .map(PrimitiveEncoder::encodeKey) .reduce((a, b) -> a + delimiter + b) diff --git a/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java index ce8af8f..b2a3417 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java @@ -99,11 +99,11 @@ public static void encodeKeyValuePair(final String key, EncodeOptions currentOptions = options; // Attempt key folding when enabled - if (KeyFolding.SAFE.equals(currentOptions.flatten()) + if (remainingDepth > 0 && !siblings.isEmpty() - && remainingDepth > 0 && blockedKeys != null - && !blockedKeys.contains(key)) { + && !blockedKeys.contains(key) + && KeyFolding.SAFE.equals(currentOptions.flatten())) { final Flatten.FoldResult foldResult = Flatten.tryFoldKeyChain(key, value, siblings, rootLiteralKeys, pathPrefix, remainingDepth); if (foldResult != null) { diff --git a/src/main/java/dev/toonformat/jtoon/encoder/PrimitiveEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/PrimitiveEncoder.java index bf322d2..e3f50cf 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/PrimitiveEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/PrimitiveEncoder.java @@ -4,6 +4,7 @@ import dev.toonformat.jtoon.util.StringValidator; import tools.jackson.databind.JsonNode; import java.math.BigDecimal; +import java.util.Collection; import java.util.List; import static dev.toonformat.jtoon.util.Constants.NULL_LITERAL; import static dev.toonformat.jtoon.util.Constants.DOUBLE_QUOTE; @@ -85,7 +86,7 @@ private static String stripTrailingZeros(final String value) { * @param delimiter the delimiter to use (for validation) * @return the encoded string, quoted if necessary */ - public static String encodeStringLiteral(final String value, final String delimiter) { + static String encodeStringLiteral(final String value, final String delimiter) { if (StringValidator.isSafeUnquoted(value, delimiter)) { return value; } @@ -115,7 +116,7 @@ public static String encodeKey(final String key) { * @param delimiter the delimiter to use between values * @return the joined string of encoded values */ - public static String joinEncodedValues(final List values, final String delimiter) { + public static String joinEncodedValues(final Collection values, final String delimiter) { if (values == null || values.isEmpty()) { return ""; } diff --git a/src/main/java/dev/toonformat/jtoon/encoder/TabularArrayEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/TabularArrayEncoder.java index ed07bfc..e4ce900 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/TabularArrayEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/TabularArrayEncoder.java @@ -52,7 +52,12 @@ public static List detectTabularHeader(final ArrayNode rows) { /** * Checks if all rows in the array have the same keys with primitive values. */ - private static boolean isTabularArray(final ArrayNode rows, final List header) { + private static boolean isTabularArray(final Iterable rows, final Iterable header) { + final List headerList = new ArrayList<>(); + for (String h : header) { + headerList.add(h); + } + for (JsonNode row : rows) { if (!row.isObject()) { return false; @@ -62,12 +67,12 @@ private static boolean isTabularArray(final ArrayNode rows, final List h final List keys = new ArrayList<>(obj.propertyNames()); // All objects must have the same keys (but order can differ) - if (keys.size() != header.size()) { + if (keys.size() != headerList.size()) { return false; } // Check that all header keys exist in the row and all values are primitives - for (String key : header) { + for (String key : headerList) { if (!obj.has(key)) { return false; } @@ -90,8 +95,9 @@ private static boolean isTabularArray(final ArrayNode rows, final List h * @param depth Indentation depth * @param options Encoding options */ - public static void encodeArrayOfObjectsAsTabular(final String prefix, final ArrayNode rows, final List header, - final LineWriter writer, final int depth, final EncodeOptions options) { + public static void encodeArrayOfObjectsAsTabular(final String prefix, final ArrayNode rows, + final List header, final LineWriter writer, final int depth, + final EncodeOptions options) { final String headerStr = PrimitiveEncoder.formatHeader(rows.size(), prefix, header, options.delimiter().toString(), options.lengthMarker()); writer.push(depth, headerStr); @@ -109,16 +115,22 @@ public static void encodeArrayOfObjectsAsTabular(final String prefix, final Arra * @param depth Indentation depth * @param options Encoding options */ - public static void writeTabularRows(final ArrayNode rows, final List header, final LineWriter writer, final int depth, - final EncodeOptions options) { + public static void writeTabularRows(final Iterable rows, final Iterable header, + final LineWriter writer, final int depth, final EncodeOptions options) { + final List headerList = new ArrayList<>(); + for (String h : header) { + headerList.add(h); + } + final int headerSize = headerList.size(); + for (JsonNode row : rows) { //skip non-object rows if (!row.isObject()) { continue; } final ObjectNode obj = (ObjectNode) row; - final List values = new ArrayList<>(); - for (String key : header) { + final List values = new ArrayList<>(headerSize); + for (String key : headerList) { values.add(obj.get(key)); } final String joinedValue = PrimitiveEncoder.joinEncodedValues(values, options.delimiter().toString()); diff --git a/src/main/java/dev/toonformat/jtoon/normalizer/JsonNormalizer.java b/src/main/java/dev/toonformat/jtoon/normalizer/JsonNormalizer.java index ddc68e6..3de03c6 100644 --- a/src/main/java/dev/toonformat/jtoon/normalizer/JsonNormalizer.java +++ b/src/main/java/dev/toonformat/jtoon/normalizer/JsonNormalizer.java @@ -111,7 +111,7 @@ private static JsonNode normalizeWithStrategy(final Object value) { .map(normalizer -> normalizer.apply(value)) .filter(Objects::nonNull) .findFirst() - .orElse(NullNode.getInstance()); + .orElseGet(NullNode::getInstance); } /** @@ -151,7 +151,7 @@ private static JsonNode normalizeDouble(final Double value) { return IntNode.valueOf(0); } return tryConvertToLong(value) - .orElse(DoubleNode.valueOf(value)); + .orElseGet(() -> DoubleNode.valueOf(value)); } /** diff --git a/src/main/java/dev/toonformat/jtoon/util/StringValidator.java b/src/main/java/dev/toonformat/jtoon/util/StringValidator.java index 42d0581..65d6b3b 100644 --- a/src/main/java/dev/toonformat/jtoon/util/StringValidator.java +++ b/src/main/java/dev/toonformat/jtoon/util/StringValidator.java @@ -1,7 +1,13 @@ package dev.toonformat.jtoon.util; import java.util.regex.Pattern; -import static dev.toonformat.jtoon.util.Constants.*; +import static dev.toonformat.jtoon.util.Constants.BACKSLASH; +import static dev.toonformat.jtoon.util.Constants.COLON; +import static dev.toonformat.jtoon.util.Constants.DOUBLE_QUOTE; +import static dev.toonformat.jtoon.util.Constants.FALSE_LITERAL; +import static dev.toonformat.jtoon.util.Constants.LIST_ITEM_MARKER; +import static dev.toonformat.jtoon.util.Constants.NULL_LITERAL; +import static dev.toonformat.jtoon.util.Constants.TRUE_LITERAL; /** * Validates strings for safe unquoted usage in TOON format. From 5ffca6fe46a6693e3922cd7fbfbf800e90161ad4 Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Fri, 13 Feb 2026 19:57:10 +0100 Subject: [PATCH 20/22] cleanup run with spotbugs part 2 --- .../jtoon/decoder/ArrayDecoder.java | 3 +- .../jtoon/decoder/ListItemDecoder.java | 5 ++-- .../jtoon/decoder/ObjectDecoder.java | 5 ++-- .../jtoon/decoder/TabularArrayDecoder.java | 30 ++++++++++--------- .../jtoon/encoder/ArrayEncoder.java | 3 +- 5 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/main/java/dev/toonformat/jtoon/decoder/ArrayDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/ArrayDecoder.java index 10b12a1..4c4accf 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/ArrayDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/ArrayDecoder.java @@ -300,7 +300,8 @@ private static boolean handleBlankLineInListArray(final int depth, final DecodeC * @param context decode an object to deal with lines, delimiter and options * @return true if an array should terminate, false otherwise. */ - private static boolean shouldTerminateListArray(final int lineDepth, final int depth, final String line, final DecodeContext context) { + private static boolean shouldTerminateListArray(final int lineDepth, final int depth, + final String line, final DecodeContext context) { if (lineDepth < depth + 1) { return true; // Line depth is less than expected - terminate } diff --git a/src/main/java/dev/toonformat/jtoon/decoder/ListItemDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/ListItemDecoder.java index 2405876..42b3806 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/ListItemDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/ListItemDecoder.java @@ -141,9 +141,10 @@ static Object parseListItem(final String content, final int depth, final DecodeC * * @param item the item to parse * @param depth the depth of the item - * @param context decode an object to deal with lines, delimiter and options * + * @param context decode an object to deal with lines, delimiter and options */ - private static void parseListItemFields(final Map item, final int depth, final DecodeContext context) { + private static void parseListItemFields(final Map item, + final int depth, final DecodeContext context) { while (context.currentLine < context.lines.length) { final String line = context.lines[context.currentLine]; final int lineDepth = DecodeHelper.getDepth(line, context); diff --git a/src/main/java/dev/toonformat/jtoon/decoder/ObjectDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/ObjectDecoder.java index 5190428..6ff16b2 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/ObjectDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/ObjectDecoder.java @@ -118,8 +118,9 @@ static void parseRootObjectFields(final Map obj, final int depth * @param depth the depth of the object field * @param context decode an object to deal with lines, delimiter and options */ - private static void processRootKeyedArrayLine(final Map objectMap, final String content, final String originalKey, - final int depth, final DecodeContext context) { + private static void processRootKeyedArrayLine(final Map objectMap, + final String content, final String originalKey, final int depth, + final DecodeContext context) { final String originalKeyTrimmed = originalKey.trim(); final String key = StringEscaper.unescape(originalKey); final String arrayHeader = content.substring(originalKey.length()); diff --git a/src/main/java/dev/toonformat/jtoon/decoder/TabularArrayDecoder.java b/src/main/java/dev/toonformat/jtoon/decoder/TabularArrayDecoder.java index ee36873..c75a321 100644 --- a/src/main/java/dev/toonformat/jtoon/decoder/TabularArrayDecoder.java +++ b/src/main/java/dev/toonformat/jtoon/decoder/TabularArrayDecoder.java @@ -3,7 +3,6 @@ import dev.toonformat.jtoon.Delimiter; import dev.toonformat.jtoon.util.StringEscaper; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -74,7 +73,8 @@ public static List parseTabularArray(final String header, final int dept * @param context decode an object to deal with lines, delimiter and options * @return list of keys */ - private static List parseTabularKeys(final String keysStr, final Delimiter arrayDelimiter, final DecodeContext context) { + private static List parseTabularKeys(final String keysStr, final Delimiter arrayDelimiter, + final DecodeContext context) { // Validate delimiter mismatch between bracket and brace fields if (context.options.strict()) { validateKeysDelimiter(keysStr, arrayDelimiter); @@ -121,15 +121,15 @@ private static void validateKeysDelimiter(final String keysStr, final Delimiter */ private static void checkDelimiterMismatch(final char expectedChar, final char actualChar) { if (expectedChar == Delimiter.TAB.getValue() && actualChar == Delimiter.COMMA.getValue()) { - throw new IllegalArgumentException( - "Delimiter mismatch: bracket declares tab (expected='" + expectedChar + "', actual='" + actualChar + "')"); + throw new IllegalArgumentException("Delimiter mismatch: bracket declares tab (expected='" + + expectedChar + "', actual='" + actualChar + "')"); } if (expectedChar == Delimiter.PIPE.getValue() && actualChar == Delimiter.COMMA.getValue()) { - throw new IllegalArgumentException( - "Delimiter mismatch: bracket declares pipe (expected='" + expectedChar + "', actual='" + actualChar + "')"); + throw new IllegalArgumentException("Delimiter mismatch: bracket declares pipe (expected='" + + expectedChar + "', actual='" + actualChar + "')"); } - if (expectedChar == Delimiter.COMMA.getValue() && - (actualChar == Delimiter.TAB.getValue() || actualChar == Delimiter.PIPE.getValue())) { + if (expectedChar == Delimiter.COMMA.getValue() + && (actualChar == Delimiter.TAB.getValue() || actualChar == Delimiter.PIPE.getValue())) { throw new IllegalArgumentException( "Delimiter mismatch: bracket declares comma, brace fields use different delimiter"); } @@ -145,8 +145,9 @@ private static void checkDelimiterMismatch(final char expectedChar, final char a * @param context decode an object to deal with lines, delimiter and options * @return true if parsing should continue, false if an array should terminate */ - private static boolean processTabularArrayLine(final int expectedRowDepth, final List keys, final Delimiter arrayDelimiter, - final List result, final DecodeContext context) { + private static boolean processTabularArrayLine(final int expectedRowDepth, final List keys, + final Delimiter arrayDelimiter, final List result, + final DecodeContext context) { final String line = context.lines[context.currentLine]; if (DecodeHelper.isBlankLine(line)) { @@ -202,8 +203,8 @@ private static boolean handleBlankLineInTabularArray(final int expectedRowDepth, * @param context decode an object to deal with lines, delimiter and options * @return true if an array should terminate, false otherwise. */ - private static boolean shouldTerminateTabularArray(final String line, final int lineDepth, final int expectedRowDepth, - final DecodeContext context) { + private static boolean shouldTerminateTabularArray(final String line, final int lineDepth, + final int expectedRowDepth, final DecodeContext context) { // Header depth is one level above the expected row depth final int headerDepth = expectedRowDepth - 1; @@ -240,8 +241,9 @@ private static boolean shouldTerminateTabularArray(final String line, final int * @param context decode an object to deal with lines, delimiter and options * @return true if a line was processed and the currentLine should be incremented, false otherwise. */ - private static boolean processTabularRow(final String line, final int lineDepth, final int expectedRowDepth, final List keys, - final Delimiter arrayDelimiter, final List result, final DecodeContext context) { + private static boolean processTabularRow(final String line, final int lineDepth, + final int expectedRowDepth, final List keys, final Delimiter arrayDelimiter, + final List result, final DecodeContext context) { if (lineDepth == expectedRowDepth) { final String rowContent = line.substring(expectedRowDepth * context.options.indent()); final Map row = parseTabularRow(rowContent, keys, arrayDelimiter, context); diff --git a/src/main/java/dev/toonformat/jtoon/encoder/ArrayEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/ArrayEncoder.java index fa1972c..d562d46 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/ArrayEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/ArrayEncoder.java @@ -30,7 +30,8 @@ private ArrayEncoder() { * @param depth Indentation depth * @param options Encoding options */ - public static void encodeArray(final String key, final ArrayNode value, final LineWriter writer, final int depth, final EncodeOptions options) { + public static void encodeArray(final String key, final ArrayNode value, + final LineWriter writer, final int depth, final EncodeOptions options) { if (value.isEmpty()) { final String header = PrimitiveEncoder.formatHeader(0, key, null, options.delimiter().toString(), options.lengthMarker()); From be6e71f893d481304484dfb6c4c3bc477b07becb Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Fri, 13 Feb 2026 20:02:09 +0100 Subject: [PATCH 21/22] adding exclude rule for pmd-test --- pmd-rules-test.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pmd-rules-test.xml b/pmd-rules-test.xml index 1f138b2..2811908 100644 --- a/pmd-rules-test.xml +++ b/pmd-rules-test.xml @@ -17,6 +17,7 @@ + From c6087bbb4ecd3397c614fdca387d013e3522c0f5 Mon Sep 17 00:00:00 2001 From: Jens Papenhagen Date: Fri, 13 Feb 2026 20:11:44 +0100 Subject: [PATCH 22/22] no warnings for checkstyle --- .../dev/toonformat/jtoon/encoder/Flatten.java | 4 +- .../jtoon/encoder/ListItemEncoder.java | 54 ++++++++++++++----- .../jtoon/encoder/ObjectEncoder.java | 30 ++++++++--- 3 files changed, 65 insertions(+), 23 deletions(-) diff --git a/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java b/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java index faab4f0..3126c0c 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/Flatten.java @@ -136,7 +136,9 @@ public static FoldResult tryFoldKeyChain(final String key, * @param maxDepth maximum number of allowed segments * @return a {@link ChainResult} containing segments, tail, and leafValue */ - private static ChainResult collectSingleKeyChain(final String startKey, final JsonNode startValue, final int maxDepth) { + private static ChainResult collectSingleKeyChain(final String startKey, + final JsonNode startValue, + final int maxDepth) { // normalize absolute key to its local segment final String localStartKey = startKey.contains(DOT) ? startKey.substring(startKey.lastIndexOf(DOT.charAt(0)) + 1) diff --git a/src/main/java/dev/toonformat/jtoon/encoder/ListItemEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/ListItemEncoder.java index bba13ec..9a1bf75 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/ListItemEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/ListItemEncoder.java @@ -35,7 +35,10 @@ private ListItemEncoder() { * @param depth Indentation depth * @param options Encoding options */ - public static void encodeObjectAsListItem(final ObjectNode obj, final LineWriter writer, final int depth, final EncodeOptions options) { + public static void encodeObjectAsListItem(final ObjectNode obj, + final LineWriter writer, + final int depth, + final EncodeOptions options) { final List keys = new ArrayList<>(obj.propertyNames()); if (keys.isEmpty()) { @@ -60,8 +63,11 @@ public static void encodeObjectAsListItem(final ObjectNode obj, final LineWriter * Encodes the first key-value pair of a list item. * Handles special formatting for arrays and objects. */ - private static void encodeFirstKeyValue(final String key, final JsonNode value, final LineWriter writer, final int depth, - final EncodeOptions options) { + private static void encodeFirstKeyValue(final String key, + final JsonNode value, + final LineWriter writer, + final int depth, + final EncodeOptions options) { final String encodedKey = PrimitiveEncoder.encodeKey(key); if (value.isValueNode()) { @@ -73,14 +79,21 @@ private static void encodeFirstKeyValue(final String key, final JsonNode value, } } - private static void encodeFirstValueAsPrimitive(final String encodedKey, final JsonNode value, final LineWriter writer, final int depth, - final EncodeOptions options) { + private static void encodeFirstValueAsPrimitive(final String encodedKey, + final JsonNode value, + final LineWriter writer, + final int depth, + final EncodeOptions options) { writer.push(depth, LIST_ITEM_PREFIX + encodedKey + COLON + SPACE + PrimitiveEncoder.encodePrimitive(value, options.delimiter().toString())); } - private static void encodeFirstValueAsArray(final String key, final String encodedKey, final ArrayNode arrayValue, final LineWriter writer, - final int depth, final EncodeOptions options) { + private static void encodeFirstValueAsArray(final String key, + final String encodedKey, + final ArrayNode arrayValue, + final LineWriter writer, + final int depth, + final EncodeOptions options) { if (ArrayEncoder.isArrayOfPrimitives(arrayValue)) { encodeFirstArrayAsPrimitives(key, arrayValue, writer, depth, options); } else if (ArrayEncoder.isArrayOfObjects(arrayValue)) { @@ -90,15 +103,22 @@ private static void encodeFirstValueAsArray(final String key, final String encod } } - private static void encodeFirstArrayAsPrimitives(final String key, final ArrayNode arrayValue, final LineWriter writer, final int depth, + private static void encodeFirstArrayAsPrimitives(final String key, + final ArrayNode arrayValue, + final LineWriter writer, + final int depth, final EncodeOptions options) { final String formatted = ArrayEncoder.formatInlineArray(arrayValue, options.delimiter().toString(), key, options.lengthMarker()); writer.push(depth, LIST_ITEM_PREFIX + formatted); } - private static void encodeFirstArrayAsObjects(final String key, final String encodedKey, final ArrayNode arrayValue, - final LineWriter writer, final int depth, final EncodeOptions options) { + private static void encodeFirstArrayAsObjects(final String key, + final String encodedKey, + final ArrayNode arrayValue, + final LineWriter writer, + final int depth, + final EncodeOptions options) { final List header = TabularArrayEncoder.detectTabularHeader(arrayValue); if (!header.isEmpty()) { final String headerStr = PrimitiveEncoder.formatHeader(arrayValue.size(), key, header, @@ -118,8 +138,11 @@ private static void encodeFirstArrayAsObjects(final String key, final String enc } } - private static void encodeFirstArrayAsComplex(final String encodedKey, final ArrayNode arrayValue, final LineWriter writer, - final int depth, final EncodeOptions options) { + private static void encodeFirstArrayAsComplex(final String encodedKey, + final ArrayNode arrayValue, + final LineWriter writer, + final int depth, + final EncodeOptions options) { writer.push(depth, LIST_ITEM_PREFIX + encodedKey + OPEN_BRACKET + arrayValue.size() + CLOSE_BRACKET + COLON); for (JsonNode item : arrayValue) { @@ -136,8 +159,11 @@ private static void encodeFirstArrayAsComplex(final String encodedKey, final Arr } } - private static void encodeFirstValueAsObject(final String encodedKey, final ObjectNode nestedObj, final LineWriter writer, final int depth, - final EncodeOptions options) { + private static void encodeFirstValueAsObject(final String encodedKey, + final ObjectNode nestedObj, + final LineWriter writer, + final int depth, + final EncodeOptions options) { writer.push(depth, LIST_ITEM_PREFIX + encodedKey + COLON); if (!nestedObj.isEmpty()) { ObjectEncoder.encodeObject(nestedObj, writer, depth + 2, options, Set.of(), null, null, new HashSet<>()); diff --git a/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java b/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java index b2a3417..8b9185a 100644 --- a/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java +++ b/src/main/java/dev/toonformat/jtoon/encoder/ObjectEncoder.java @@ -36,9 +36,14 @@ private ObjectEncoder() { * @param remainingDepth optional override for the remaining depth * @param blockedKeys contains only keys that have undergone a successful flattening */ - public static void encodeObject(final ObjectNode value, final LineWriter writer, final int depth, final EncodeOptions options, - final Set rootLiteralKeys, final String pathPrefix, final Integer remainingDepth, - final Set blockedKeys) { + public static void encodeObject(final ObjectNode value, + final LineWriter writer, + final int depth, + final EncodeOptions options, + final Set rootLiteralKeys, + final String pathPrefix, + final Integer remainingDepth, + final Set blockedKeys) { final List> fields = value.properties().stream().toList(); // At root level (depth 0), collect all literal dotted keys for collision checking @@ -146,9 +151,15 @@ public static void encodeKeyValuePair(final String key, * @param remainingDepth the depth that remind to the limit * @return EncodeOptions changes for Case 2 */ - private static EncodeOptions flatten(final String key, final Flatten.FoldResult foldResult, final LineWriter writer, final int depth, - final EncodeOptions options, final Set rootLiteralKeys, final String pathPrefix, - final Set blockedKeys, final int remainingDepth) { + private static EncodeOptions flatten(final String key, + final Flatten.FoldResult foldResult, + final LineWriter writer, + final int depth, + final EncodeOptions options, + final Set rootLiteralKeys, + final String pathPrefix, + final Set blockedKeys, + final int remainingDepth) { final String foldedKey = foldResult.foldedKey(); EncodeOptions currentOptions = options; @@ -189,8 +200,11 @@ private static EncodeOptions flatten(final String key, final Flatten.FoldResult return currentOptions; } - private static void handleFullyFoldedLeaf(final Flatten.FoldResult foldResult, final LineWriter writer, final int depth, - final EncodeOptions options, final String encodedFoldedKey) { + private static void handleFullyFoldedLeaf(final Flatten.FoldResult foldResult, + final LineWriter writer, + final int depth, + final EncodeOptions options, + final String encodedFoldedKey) { final JsonNode leaf = foldResult.leafValue(); // Primitive