From 2590d23de456857ec763fa444f48457b430c9657 Mon Sep 17 00:00:00 2001 From: Tako Schotanus Date: Wed, 23 Sep 2026 12:26:05 +0200 Subject: [PATCH 1/2] fix: adding new properties to the end now works Adding new properties at the end of a properties list caused elements to be added in the wrong order resulting in incorrect placement of the EOL token. --- .../java/org/codejive/properties/Cursor.java | 9 ++++-- .../codejive/properties/TestProperties.java | 29 +++++++++++++++++++ src/test/resources/test-comment.properties | 1 - src/test/resources/test-escaped.properties | 1 - src/test/resources/test-putnew.properties | 1 - src/test/resources/test-removeall.properties | 1 - .../resources/test-removecomment.properties | 1 - .../resources/test-removefirst.properties | 1 - src/test/resources/test-removelast.properties | 1 - .../resources/test-removemiddle.properties | 1 - .../resources/test-storeheader.properties | 1 - src/test/resources/test-unescaped.properties | 1 - src/test/resources/test.properties | 1 - src/test/resources/test2-putnew.properties | 18 ++++++++++++ src/test/resources/test2.properties | 17 +++++++++++ src/test/resources/test3-putnew.properties | 17 +++++++++++ src/test/resources/test3.properties | 16 ++++++++++ .../resources/testcrlf-storeheader.properties | 1 - src/test/resources/testcrlf.properties | 1 - 19 files changed, 104 insertions(+), 15 deletions(-) create mode 100644 src/test/resources/test2-putnew.properties create mode 100644 src/test/resources/test2.properties create mode 100644 src/test/resources/test3-putnew.properties create mode 100644 src/test/resources/test3.properties diff --git a/src/main/java/org/codejive/properties/Cursor.java b/src/main/java/org/codejive/properties/Cursor.java index 0da8375..bf355cd 100644 --- a/src/main/java/org/codejive/properties/Cursor.java +++ b/src/main/java/org/codejive/properties/Cursor.java @@ -33,7 +33,11 @@ public int position() { } public boolean hasToken() { - return index >= 0 && index < tokens.size(); + return hasToken(index); + } + + private boolean hasToken(int idx) { + return idx >= 0 && idx < tokens.size(); } public PropertiesParser.Token token() { @@ -142,6 +146,7 @@ public int prevCount(Predicate accept) { } public Cursor add(PropertiesParser.Token token) { + index = Math.max(index, 0); addToken(index++, token); return this; } @@ -151,7 +156,7 @@ public Cursor addEol() { } private void addToken(int index, PropertiesParser.Token token) { - if (hasToken()) { + if (hasToken(index)) { tokens.add(index, token); } else { tokens.add(token); diff --git a/src/test/java/org/codejive/properties/TestProperties.java b/src/test/java/org/codejive/properties/TestProperties.java index 20c1da1..bff99b6 100644 --- a/src/test/java/org/codejive/properties/TestProperties.java +++ b/src/test/java/org/codejive/properties/TestProperties.java @@ -157,6 +157,15 @@ void testStoreHeaderCrLf() throws IOException, URISyntaxException { .isEqualTo(readAll(getResource("/testcrlf-storeheader.properties"))); } + @Test + void testStoreTest2() throws IOException, URISyntaxException { + Path f = getResource("/test2.properties"); + Properties p = Properties.loadProperties(f); + StringWriter sw = new StringWriter(); + p.store(sw); + assertThat(sw.toString()).isEqualTo(readAll(f)); + } + @Test void testLf() throws IOException, URISyntaxException { Path f = getResource("/test.properties"); @@ -481,6 +490,26 @@ void testPutNew() throws IOException, URISyntaxException { assertThat(sw.toString()).isEqualTo(readAll(getResource("/test-putnew.properties"))); } + @Test + void testPutNewTest2() throws IOException, URISyntaxException { + Path f = getResource("/test2.properties"); + Properties p = Properties.loadProperties(f); + p.put("five", "5"); + StringWriter sw = new StringWriter(); + p.store(sw); + assertThat(sw.toString()).isEqualTo(readAll(getResource("/test2-putnew.properties"))); + } + + @Test + void testPutNewTest3() throws IOException, URISyntaxException { + Path f = getResource("/test3.properties"); + Properties p = Properties.loadProperties(f); + p.put("five", "5"); + StringWriter sw = new StringWriter(); + p.store(sw); + assertThat(sw.toString()).isEqualTo(readAll(getResource("/test3-putnew.properties"))); + } + @Test void testPutFirstWithHeader() throws IOException, URISyntaxException { try (StringReader sr = new StringReader("# A header comment")) { diff --git a/src/test/resources/test-comment.properties b/src/test/resources/test-comment.properties index 0e0d3d1..a181da2 100644 --- a/src/test/resources/test-comment.properties +++ b/src/test/resources/test-comment.properties @@ -14,4 +14,3 @@ multiline = one \ two \ three key.4 = \u1234ሴ -# final comment diff --git a/src/test/resources/test-escaped.properties b/src/test/resources/test-escaped.properties index 7c6e49c..dc8d068 100644 --- a/src/test/resources/test-escaped.properties +++ b/src/test/resources/test-escaped.properties @@ -14,4 +14,3 @@ multiline = one \ two \ three key.4 = \u1234\u1234 -# final comment diff --git a/src/test/resources/test-putnew.properties b/src/test/resources/test-putnew.properties index 5e960de..59700cb 100644 --- a/src/test/resources/test-putnew.properties +++ b/src/test/resources/test-putnew.properties @@ -15,4 +15,3 @@ multiline = one \ three key.4 = \u1234ሴ five=5 -# final comment diff --git a/src/test/resources/test-removeall.properties b/src/test/resources/test-removeall.properties index 613f145..7802946 100644 --- a/src/test/resources/test-removeall.properties +++ b/src/test/resources/test-removeall.properties @@ -1,4 +1,3 @@ #comment1 # comment2 -# final comment diff --git a/src/test/resources/test-removecomment.properties b/src/test/resources/test-removecomment.properties index f3fb147..9e8acbb 100644 --- a/src/test/resources/test-removecomment.properties +++ b/src/test/resources/test-removecomment.properties @@ -13,4 +13,3 @@ multiline = one \ two \ three key.4 = \u1234ሴ -# final comment diff --git a/src/test/resources/test-removefirst.properties b/src/test/resources/test-removefirst.properties index 369c459..c81ca16 100644 --- a/src/test/resources/test-removefirst.properties +++ b/src/test/resources/test-removefirst.properties @@ -12,4 +12,3 @@ multiline = one \ two \ three key.4 = \u1234ሴ -# final comment diff --git a/src/test/resources/test-removelast.properties b/src/test/resources/test-removelast.properties index 0f4cd11..6411934 100644 --- a/src/test/resources/test-removelast.properties +++ b/src/test/resources/test-removelast.properties @@ -13,4 +13,3 @@ altsep:value multiline = one \ two \ three -# final comment diff --git a/src/test/resources/test-removemiddle.properties b/src/test/resources/test-removemiddle.properties index 6d9e10b..d521515 100644 --- a/src/test/resources/test-removemiddle.properties +++ b/src/test/resources/test-removemiddle.properties @@ -10,4 +10,3 @@ multiline = one \ two \ three key.4 = \u1234ሴ -# final comment diff --git a/src/test/resources/test-storeheader.properties b/src/test/resources/test-storeheader.properties index 3ab1ef7..ceb3ad3 100644 --- a/src/test/resources/test-storeheader.properties +++ b/src/test/resources/test-storeheader.properties @@ -13,4 +13,3 @@ multiline = one \ two \ three key.4 = \u1234ሴ -# final comment diff --git a/src/test/resources/test-unescaped.properties b/src/test/resources/test-unescaped.properties index bac7a7f..32374cd 100644 --- a/src/test/resources/test-unescaped.properties +++ b/src/test/resources/test-unescaped.properties @@ -14,4 +14,3 @@ multiline = one \ two \ three key.4 = ሴሴ -# final comment diff --git a/src/test/resources/test.properties b/src/test/resources/test.properties index f2d73bd..990d4a6 100644 --- a/src/test/resources/test.properties +++ b/src/test/resources/test.properties @@ -14,4 +14,3 @@ multiline = one \ two \ three key.4 = \u1234ሴ -# final comment diff --git a/src/test/resources/test2-putnew.properties b/src/test/resources/test2-putnew.properties new file mode 100644 index 0000000..5e960de --- /dev/null +++ b/src/test/resources/test2-putnew.properties @@ -0,0 +1,18 @@ +#comment1 +# comment2 + +! comment3 +one=simple +two=value containing spaces +# another comment +! and a comment +! block +three=and escapes\n\t\r\f +\ with\ spaces = everywhere +altsep:value +multiline = one \ + two \ + three +key.4 = \u1234ሴ +five=5 +# final comment diff --git a/src/test/resources/test2.properties b/src/test/resources/test2.properties new file mode 100644 index 0000000..f2d73bd --- /dev/null +++ b/src/test/resources/test2.properties @@ -0,0 +1,17 @@ +#comment1 +# comment2 + +! comment3 +one=simple +two=value containing spaces +# another comment +! and a comment +! block +three=and escapes\n\t\r\f +\ with\ spaces = everywhere +altsep:value +multiline = one \ + two \ + three +key.4 = \u1234ሴ +# final comment diff --git a/src/test/resources/test3-putnew.properties b/src/test/resources/test3-putnew.properties new file mode 100644 index 0000000..ec400f0 --- /dev/null +++ b/src/test/resources/test3-putnew.properties @@ -0,0 +1,17 @@ +#comment1 +# comment2 + +! comment3 +one=simple +two=value containing spaces +# another comment +! and a comment +! block +three=and escapes\n\t\r\f +\ with\ spaces = everywhere +altsep:value +multiline = one \ + two \ + three +key.4 = \u1234ሴ +five=5 \ No newline at end of file diff --git a/src/test/resources/test3.properties b/src/test/resources/test3.properties new file mode 100644 index 0000000..467c4d3 --- /dev/null +++ b/src/test/resources/test3.properties @@ -0,0 +1,16 @@ +#comment1 +# comment2 + +! comment3 +one=simple +two=value containing spaces +# another comment +! and a comment +! block +three=and escapes\n\t\r\f +\ with\ spaces = everywhere +altsep:value +multiline = one \ + two \ + three +key.4 = \u1234ሴ \ No newline at end of file diff --git a/src/test/resources/testcrlf-storeheader.properties b/src/test/resources/testcrlf-storeheader.properties index 3ab1ef7..ceb3ad3 100644 --- a/src/test/resources/testcrlf-storeheader.properties +++ b/src/test/resources/testcrlf-storeheader.properties @@ -13,4 +13,3 @@ multiline = one \ two \ three key.4 = \u1234ሴ -# final comment diff --git a/src/test/resources/testcrlf.properties b/src/test/resources/testcrlf.properties index f2d73bd..990d4a6 100644 --- a/src/test/resources/testcrlf.properties +++ b/src/test/resources/testcrlf.properties @@ -14,4 +14,3 @@ multiline = one \ two \ three key.4 = \u1234ሴ -# final comment From 8973ef5339662e05c6cea31acbd6700efa40da42 Mon Sep 17 00:00:00 2001 From: Tako Schotanus Date: Wed, 23 Sep 2026 13:40:06 +0200 Subject: [PATCH 2/2] fix: adding properties after partial properties now works This was thirowing an illegal state exception because the code assumed that any previous property had to end in a VALUE. But this didn't take into account partial properties that either don't have a value or don't even have a separator (=). --- .../org/codejive/properties/Properties.java | 10 +++++++-- .../codejive/properties/TestProperties.java | 22 +++++++++++++++++++ .../resources/test-keyatend-putnew.properties | 18 +++++++++++++++ src/test/resources/test-keyatend.properties | 17 ++++++++++++++ .../test-separatoratend-putnew.properties | 18 +++++++++++++++ .../resources/test-separatoratend.properties | 17 ++++++++++++++ 6 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/test-keyatend-putnew.properties create mode 100644 src/test/resources/test-keyatend.properties create mode 100644 src/test/resources/test-separatoratend-putnew.properties create mode 100644 src/test/resources/test-separatoratend.properties diff --git a/src/main/java/org/codejive/properties/Properties.java b/src/main/java/org/codejive/properties/Properties.java index 384a6e9..38d4fde 100644 --- a/src/main/java/org/codejive/properties/Properties.java +++ b/src/main/java/org/codejive/properties/Properties.java @@ -393,8 +393,14 @@ private Cursor addNewKeyValue(String rawKey, String key, String rawValue, String while (pos.isType(PropertiesParser.Type.WHITESPACE, PropertiesParser.Type.COMMENT)) { pos.prev(); } - // Make sure we're either at the start or we've found a VALUE - validate(pos.atStart() || pos.isType(PropertiesParser.Type.VALUE), pos); + // Make sure we're either at the start or we've found a property + validate( + pos.atStart() + || pos.isType( + PropertiesParser.Type.VALUE, + PropertiesParser.Type.SEPARATOR, + PropertiesParser.Type.KEY), + pos); // Add a newline whitespace token if necessary if (pos.hasToken()) { pos.next(); diff --git a/src/test/java/org/codejive/properties/TestProperties.java b/src/test/java/org/codejive/properties/TestProperties.java index bff99b6..10ad235 100644 --- a/src/test/java/org/codejive/properties/TestProperties.java +++ b/src/test/java/org/codejive/properties/TestProperties.java @@ -510,6 +510,28 @@ void testPutNewTest3() throws IOException, URISyntaxException { assertThat(sw.toString()).isEqualTo(readAll(getResource("/test3-putnew.properties"))); } + @Test + void testPutNewAfterKey() throws IOException, URISyntaxException { + Path f = getResource("/test-keyatend.properties"); + Properties p = Properties.loadProperties(f); + p.put("five", "5"); + StringWriter sw = new StringWriter(); + p.store(sw); + assertThat(sw.toString()) + .isEqualTo(readAll(getResource("/test-keyatend-putnew.properties"))); + } + + @Test + void testPutNewAfterSeparator() throws IOException, URISyntaxException { + Path f = getResource("/test-separatoratend.properties"); + Properties p = Properties.loadProperties(f); + p.put("five", "5"); + StringWriter sw = new StringWriter(); + p.store(sw); + assertThat(sw.toString()) + .isEqualTo(readAll(getResource("/test-separatoratend-putnew.properties"))); + } + @Test void testPutFirstWithHeader() throws IOException, URISyntaxException { try (StringReader sr = new StringReader("# A header comment")) { diff --git a/src/test/resources/test-keyatend-putnew.properties b/src/test/resources/test-keyatend-putnew.properties new file mode 100644 index 0000000..5ee5f32 --- /dev/null +++ b/src/test/resources/test-keyatend-putnew.properties @@ -0,0 +1,18 @@ +#comment1 +# comment2 + +! comment3 +one=simple +two=value containing spaces +# another comment +! and a comment +! block +three=and escapes\n\t\r\f +\ with\ spaces = everywhere +altsep:value +multiline = one \ + two \ + three +key.4 = \u1234ሴ +key.at.end +five=5 \ No newline at end of file diff --git a/src/test/resources/test-keyatend.properties b/src/test/resources/test-keyatend.properties new file mode 100644 index 0000000..f67dc9a --- /dev/null +++ b/src/test/resources/test-keyatend.properties @@ -0,0 +1,17 @@ +#comment1 +# comment2 + +! comment3 +one=simple +two=value containing spaces +# another comment +! and a comment +! block +three=and escapes\n\t\r\f +\ with\ spaces = everywhere +altsep:value +multiline = one \ + two \ + three +key.4 = \u1234ሴ +key.at.end \ No newline at end of file diff --git a/src/test/resources/test-separatoratend-putnew.properties b/src/test/resources/test-separatoratend-putnew.properties new file mode 100644 index 0000000..18e90c3 --- /dev/null +++ b/src/test/resources/test-separatoratend-putnew.properties @@ -0,0 +1,18 @@ +#comment1 +# comment2 + +! comment3 +one=simple +two=value containing spaces +# another comment +! and a comment +! block +three=and escapes\n\t\r\f +\ with\ spaces = everywhere +altsep:value +multiline = one \ + two \ + three +key.4 = \u1234ሴ +separator.at.end= +five=5 \ No newline at end of file diff --git a/src/test/resources/test-separatoratend.properties b/src/test/resources/test-separatoratend.properties new file mode 100644 index 0000000..cc9bfcd --- /dev/null +++ b/src/test/resources/test-separatoratend.properties @@ -0,0 +1,17 @@ +#comment1 +# comment2 + +! comment3 +one=simple +two=value containing spaces +# another comment +! and a comment +! block +three=and escapes\n\t\r\f +\ with\ spaces = everywhere +altsep:value +multiline = one \ + two \ + three +key.4 = \u1234ሴ +separator.at.end= \ No newline at end of file