From 53879762cc7f1ccee278c5346168f924122a39fd Mon Sep 17 00:00:00 2001 From: Tako Schotanus Date: Wed, 23 Sep 2026 16:27:55 +0200 Subject: [PATCH 1/3] test: added a bunch more corner cases --- .../codejive/properties/TestProperties.java | 369 +++++++++++++++++- src/test/resources/test-comment.properties | 3 + src/test/resources/test-escaped.properties | 3 + .../resources/test-getproperty.properties | 3 + .../resources/test-keyatend-putnew.properties | 3 + src/test/resources/test-keyatend.properties | 3 + src/test/resources/test-put.properties | 3 + src/test/resources/test-putnew.properties | 3 + src/test/resources/test-putraw.properties | 3 + .../resources/test-removecomment.properties | 3 + .../resources/test-removefirst.properties | 3 + src/test/resources/test-removelast.properties | 3 + .../resources/test-removemiddle.properties | 3 + .../test-separatoratend-putnew.properties | 3 + .../resources/test-separatoratend.properties | 3 + .../resources/test-setproperty.properties | 3 + .../resources/test-storeheader.properties | 3 + src/test/resources/test-unescaped.properties | 3 + src/test/resources/test.properties | 3 + src/test/resources/test2-putnew.properties | 3 + src/test/resources/test2.properties | 3 + src/test/resources/test3-putnew.properties | 3 + src/test/resources/test3.properties | 3 + .../resources/testcrlf-storeheader.properties | 3 + src/test/resources/testcrlf.properties | 3 + 25 files changed, 423 insertions(+), 18 deletions(-) diff --git a/src/test/java/org/codejive/properties/TestProperties.java b/src/test/java/org/codejive/properties/TestProperties.java index 10ad235..1d8ecc2 100644 --- a/src/test/java/org/codejive/properties/TestProperties.java +++ b/src/test/java/org/codejive/properties/TestProperties.java @@ -17,13 +17,31 @@ public class TestProperties { @Test void testLoad() throws IOException, URISyntaxException { Properties p = Properties.loadProperties(getResource("/test.properties")); - assertThat(p).size().isEqualTo(7); + assertThat(p).size().isEqualTo(10); assertThat(p.keySet()) .containsExactly( - "one", "two", "three", " with spaces", "altsep", "multiline", "key.4"); + "one", + "two", + "three", + " with spaces", + "altsep", + "multiline", + "novalue", + "keyonly", + "", + "key.4"); assertThat(p.rawKeySet()) .containsExactly( - "one", "two", "three", "\\ with\\ spaces", "altsep", "multiline", "key.4"); + "one", + "two", + "three", + "\\ with\\ spaces", + "altsep", + "multiline", + "novalue", + "keyonly", + "", + "key.4"); assertThat(p.values()) .containsExactly( "simple", @@ -32,6 +50,9 @@ void testLoad() throws IOException, URISyntaxException { "everywhere ", "value", "one two three", + "", + "", + "", "\u1234\u1234"); assertThat(p.rawValues()) .containsExactly( @@ -41,6 +62,9 @@ void testLoad() throws IOException, URISyntaxException { "everywhere ", "value", "one \\\n two \\\n\tthree", + "", + "", + "", "\\u1234\u1234"); assertThat(p.entrySet()) .containsExactly( @@ -50,6 +74,9 @@ void testLoad() throws IOException, URISyntaxException { new AbstractMap.SimpleEntry<>(" with spaces", "everywhere "), new AbstractMap.SimpleEntry<>("altsep", "value"), new AbstractMap.SimpleEntry<>("multiline", "one two three"), + new AbstractMap.SimpleEntry<>("novalue", ""), + new AbstractMap.SimpleEntry<>("keyonly", ""), + new AbstractMap.SimpleEntry<>("", ""), new AbstractMap.SimpleEntry<>("key.4", "\u1234\u1234")); assertThat(p.rawEntrySet()) .containsExactly( @@ -59,19 +86,40 @@ void testLoad() throws IOException, URISyntaxException { new AbstractMap.SimpleEntry<>("\\ with\\ spaces", "everywhere "), new AbstractMap.SimpleEntry<>("altsep", "value"), new AbstractMap.SimpleEntry<>("multiline", "one \\\n two \\\n\tthree"), + new AbstractMap.SimpleEntry<>("novalue", ""), + new AbstractMap.SimpleEntry<>("keyonly", ""), + new AbstractMap.SimpleEntry<>("", ""), new AbstractMap.SimpleEntry<>("key.4", "\\u1234\u1234")); } @Test void testLoadCrLf() throws IOException, URISyntaxException { Properties p = Properties.loadProperties(getResource("/testcrlf.properties")); - assertThat(p).size().isEqualTo(7); + assertThat(p).size().isEqualTo(10); assertThat(p.keySet()) .containsExactly( - "one", "two", "three", " with spaces", "altsep", "multiline", "key.4"); + "one", + "two", + "three", + " with spaces", + "altsep", + "multiline", + "novalue", + "keyonly", + "", + "key.4"); assertThat(p.rawKeySet()) .containsExactly( - "one", "two", "three", "\\ with\\ spaces", "altsep", "multiline", "key.4"); + "one", + "two", + "three", + "\\ with\\ spaces", + "altsep", + "multiline", + "novalue", + "keyonly", + "", + "key.4"); assertThat(p.values()) .containsExactly( "simple", @@ -80,6 +128,9 @@ void testLoadCrLf() throws IOException, URISyntaxException { "everywhere ", "value", "one two three", + "", + "", + "", "\u1234\u1234"); assertThat(p.rawValues()) .containsExactly( @@ -89,6 +140,9 @@ void testLoadCrLf() throws IOException, URISyntaxException { "everywhere ", "value", "one \\\r\n two \\\r\n\tthree", + "", + "", + "", "\\u1234\u1234"); assertThat(p.entrySet()) .containsExactly( @@ -98,6 +152,9 @@ void testLoadCrLf() throws IOException, URISyntaxException { new AbstractMap.SimpleEntry<>(" with spaces", "everywhere "), new AbstractMap.SimpleEntry<>("altsep", "value"), new AbstractMap.SimpleEntry<>("multiline", "one two three"), + new AbstractMap.SimpleEntry<>("novalue", ""), + new AbstractMap.SimpleEntry<>("keyonly", ""), + new AbstractMap.SimpleEntry<>("", ""), new AbstractMap.SimpleEntry<>("key.4", "\u1234\u1234")); assertThat(p.rawEntrySet()) .containsExactly( @@ -108,9 +165,180 @@ void testLoadCrLf() throws IOException, URISyntaxException { new AbstractMap.SimpleEntry<>("altsep", "value"), new AbstractMap.SimpleEntry<>( "multiline", "one \\\r\n two \\\r\n\tthree"), + new AbstractMap.SimpleEntry<>("novalue", ""), + new AbstractMap.SimpleEntry<>("keyonly", ""), + new AbstractMap.SimpleEntry<>("", ""), new AbstractMap.SimpleEntry<>("key.4", "\\u1234\u1234")); } + @Test + void testLoadKeyAtEnd() throws IOException, URISyntaxException { + Properties p = Properties.loadProperties(getResource("/test-keyatend.properties")); + assertThat(p).size().isEqualTo(11); + assertThat(p.keySet()) + .containsExactly( + "one", + "two", + "three", + " with spaces", + "altsep", + "multiline", + "novalue", + "keyonly", + "", + "key.4", + "key.at.end"); + assertThat(p.rawKeySet()) + .containsExactly( + "one", + "two", + "three", + "\\ with\\ spaces", + "altsep", + "multiline", + "novalue", + "keyonly", + "", + "key.4", + "key.at.end"); + assertThat(p.values()) + .containsExactly( + "simple", + "value containing spaces", + "and escapes\n\t\r\f", + "everywhere ", + "value", + "one two three", + "", + "", + "", + "\u1234\u1234", + ""); + assertThat(p.rawValues()) + .containsExactly( + "simple", + "value containing spaces", + "and escapes\\n\\t\\r\\f", + "everywhere ", + "value", + "one \\\n two \\\n\tthree", + "", + "", + "", + "\\u1234\u1234", + ""); + assertThat(p.entrySet()) + .containsExactly( + new AbstractMap.SimpleEntry<>("one", "simple"), + new AbstractMap.SimpleEntry<>("two", "value containing spaces"), + new AbstractMap.SimpleEntry<>("three", "and escapes\n\t\r\f"), + new AbstractMap.SimpleEntry<>(" with spaces", "everywhere "), + new AbstractMap.SimpleEntry<>("altsep", "value"), + new AbstractMap.SimpleEntry<>("multiline", "one two three"), + new AbstractMap.SimpleEntry<>("novalue", ""), + new AbstractMap.SimpleEntry<>("keyonly", ""), + new AbstractMap.SimpleEntry<>("", ""), + new AbstractMap.SimpleEntry<>("key.4", "\u1234\u1234"), + new AbstractMap.SimpleEntry<>("key.at.end", "")); + assertThat(p.rawEntrySet()) + .containsExactly( + new AbstractMap.SimpleEntry<>("one", "simple"), + new AbstractMap.SimpleEntry<>("two", "value containing spaces"), + new AbstractMap.SimpleEntry<>("three", "and escapes\\n\\t\\r\\f"), + new AbstractMap.SimpleEntry<>("\\ with\\ spaces", "everywhere "), + new AbstractMap.SimpleEntry<>("altsep", "value"), + new AbstractMap.SimpleEntry<>("multiline", "one \\\n two \\\n\tthree"), + new AbstractMap.SimpleEntry<>("novalue", ""), + new AbstractMap.SimpleEntry<>("keyonly", ""), + new AbstractMap.SimpleEntry<>("", ""), + new AbstractMap.SimpleEntry<>("key.4", "\\u1234\u1234"), + new AbstractMap.SimpleEntry<>("key.at.end", "")); + } + + @Test + void testLoadSeparatorAtEnd() throws IOException, URISyntaxException { + Properties p = Properties.loadProperties(getResource("/test-separatoratend.properties")); + assertThat(p).size().isEqualTo(11); + assertThat(p.keySet()) + .containsExactly( + "one", + "two", + "three", + " with spaces", + "altsep", + "multiline", + "novalue", + "keyonly", + "", + "key.4", + "separator.at.end"); + assertThat(p.rawKeySet()) + .containsExactly( + "one", + "two", + "three", + "\\ with\\ spaces", + "altsep", + "multiline", + "novalue", + "keyonly", + "", + "key.4", + "separator.at.end"); + assertThat(p.values()) + .containsExactly( + "simple", + "value containing spaces", + "and escapes\n\t\r\f", + "everywhere ", + "value", + "one two three", + "", + "", + "", + "\u1234\u1234", + ""); + assertThat(p.rawValues()) + .containsExactly( + "simple", + "value containing spaces", + "and escapes\\n\\t\\r\\f", + "everywhere ", + "value", + "one \\\n two \\\n\tthree", + "", + "", + "", + "\\u1234\u1234", + ""); + assertThat(p.entrySet()) + .containsExactly( + new AbstractMap.SimpleEntry<>("one", "simple"), + new AbstractMap.SimpleEntry<>("two", "value containing spaces"), + new AbstractMap.SimpleEntry<>("three", "and escapes\n\t\r\f"), + new AbstractMap.SimpleEntry<>(" with spaces", "everywhere "), + new AbstractMap.SimpleEntry<>("altsep", "value"), + new AbstractMap.SimpleEntry<>("multiline", "one two three"), + new AbstractMap.SimpleEntry<>("novalue", ""), + new AbstractMap.SimpleEntry<>("keyonly", ""), + new AbstractMap.SimpleEntry<>("", ""), + new AbstractMap.SimpleEntry<>("key.4", "\u1234\u1234"), + new AbstractMap.SimpleEntry<>("separator.at.end", "")); + assertThat(p.rawEntrySet()) + .containsExactly( + new AbstractMap.SimpleEntry<>("one", "simple"), + new AbstractMap.SimpleEntry<>("two", "value containing spaces"), + new AbstractMap.SimpleEntry<>("three", "and escapes\\n\\t\\r\\f"), + new AbstractMap.SimpleEntry<>("\\ with\\ spaces", "everywhere "), + new AbstractMap.SimpleEntry<>("altsep", "value"), + new AbstractMap.SimpleEntry<>("multiline", "one \\\n two \\\n\tthree"), + new AbstractMap.SimpleEntry<>("novalue", ""), + new AbstractMap.SimpleEntry<>("keyonly", ""), + new AbstractMap.SimpleEntry<>("", ""), + new AbstractMap.SimpleEntry<>("key.4", "\\u1234\u1234"), + new AbstractMap.SimpleEntry<>("separator.at.end", "")); + } + @Test void testStore() throws IOException, URISyntaxException { Path f = getResource("/test.properties"); @@ -201,7 +429,7 @@ void testGetProperty() throws IOException, URISyntaxException { p.setProperty("five", "5", "a new comment"); assertThat(p).size().isEqualTo(3); assertThat(p.keySet()).containsExactly("two", "altsep", "five"); - assertThat(p.stringPropertyNames()).size().isEqualTo(8); + assertThat(p.stringPropertyNames()).size().isEqualTo(11); assertThat(p.stringPropertyNames()) .containsExactly( "one", @@ -210,6 +438,9 @@ void testGetProperty() throws IOException, URISyntaxException { " with spaces", "altsep", "multiline", + "novalue", + "keyonly", + "", "key.4", "five"); assertThat(p.getProperty("one")).isEqualTo("simple"); @@ -222,6 +453,9 @@ void testGetProperty() throws IOException, URISyntaxException { assertThat(p.getProperty(" with spaces")).isEqualTo("everywhere "); assertThat(p.getProperty("altsep")).isEqualTo(""); assertThat(p.getProperty("multiline")).isEqualTo("one two three"); + assertThat(p.getProperty("novalue")).isEmpty(); + assertThat(p.getProperty("keyonly")).isEmpty(); + assertThat(p.getProperty("")).isEmpty(); assertThat(p.getProperty("key.4")).isEqualTo("\u1234\u1234"); assertThat(p.getProperty("five")).isEqualTo("5"); assertThat(p.getPropertyComment("five")).containsExactly("# a new comment"); @@ -318,14 +552,35 @@ void testPut() throws IOException, URISyntaxException { p.put(" with spaces", "everywhere "); p.put("altsep", "value"); p.put("multiline", "one two three"); + p.put("novalue", ""); + p.put("keyonly", ""); + p.put("", ""); p.put("key.4", "\u1234\u1234"); - assertThat(p).size().isEqualTo(7); + assertThat(p).size().isEqualTo(10); assertThat(p.keySet()) .containsExactly( - "one", "two", "three", " with spaces", "altsep", "multiline", "key.4"); + "one", + "two", + "three", + " with spaces", + "altsep", + "multiline", + "novalue", + "keyonly", + "", + "key.4"); assertThat(p.rawKeySet()) .containsExactly( - "one", "two", "three", "\\ with\\ spaces", "altsep", "multiline", "key.4"); + "one", + "two", + "three", + "\\ with\\ spaces", + "altsep", + "multiline", + "novalue", + "keyonly", + "", + "key.4"); assertThat(p.values()) .containsExactly( "simple", @@ -334,6 +589,9 @@ void testPut() throws IOException, URISyntaxException { "everywhere ", "value", "one two three", + "", + "", + "", "\u1234\u1234"); assertThat(p.rawValues()) .containsExactly( @@ -343,6 +601,9 @@ void testPut() throws IOException, URISyntaxException { "everywhere ", "value", "one two three", + "", + "", + "", "\u1234\u1234"); assertThat(p.entrySet()) .containsExactly( @@ -352,6 +613,9 @@ void testPut() throws IOException, URISyntaxException { new AbstractMap.SimpleEntry<>(" with spaces", "everywhere "), new AbstractMap.SimpleEntry<>("altsep", "value"), new AbstractMap.SimpleEntry<>("multiline", "one two three"), + new AbstractMap.SimpleEntry<>("novalue", ""), + new AbstractMap.SimpleEntry<>("keyonly", ""), + new AbstractMap.SimpleEntry<>("", ""), new AbstractMap.SimpleEntry<>("key.4", "\u1234\u1234")); assertThat(p.rawEntrySet()) .containsExactly( @@ -361,6 +625,9 @@ void testPut() throws IOException, URISyntaxException { new AbstractMap.SimpleEntry<>("\\ with\\ spaces", "everywhere "), new AbstractMap.SimpleEntry<>("altsep", "value"), new AbstractMap.SimpleEntry<>("multiline", "one two three"), + new AbstractMap.SimpleEntry<>("novalue", ""), + new AbstractMap.SimpleEntry<>("keyonly", ""), + new AbstractMap.SimpleEntry<>("", ""), new AbstractMap.SimpleEntry<>("key.4", "\u1234\u1234")); StringWriter sw = new StringWriter(); p.store(sw); @@ -377,6 +644,9 @@ void testSetProperty() throws IOException, URISyntaxException { p.setProperty(" with spaces", "everywhere "); p.setProperty("altsep", "value"); p.setProperty("multiline", "one two three"); + p.put("novalue", ""); + p.put("keyonly", ""); + p.put("", ""); p.setProperty("key.4", "\u1234\u1234"); StringWriter sw = new StringWriter(); p.store(sw); @@ -392,14 +662,35 @@ void testPutRaw() throws IOException, URISyntaxException { p.putRaw("\\ with\\ spaces", "everywhere "); p.putRaw("altsep", "value"); p.putRaw("multiline", "one \\\n two \\\n\tthree"); + p.putRaw("novalue", ""); + p.putRaw("keyonly", ""); + p.putRaw("", ""); p.putRaw("key.4", "\\u1234\u1234"); - assertThat(p).size().isEqualTo(7); + assertThat(p).size().isEqualTo(10); assertThat(p.keySet()) .containsExactly( - "one", "two", "three", " with spaces", "altsep", "multiline", "key.4"); + "one", + "two", + "three", + " with spaces", + "altsep", + "multiline", + "novalue", + "keyonly", + "", + "key.4"); assertThat(p.rawKeySet()) .containsExactly( - "one", "two", "three", "\\ with\\ spaces", "altsep", "multiline", "key.4"); + "one", + "two", + "three", + "\\ with\\ spaces", + "altsep", + "multiline", + "novalue", + "keyonly", + "", + "key.4"); assertThat(p.values()) .containsExactly( "simple", @@ -408,6 +699,9 @@ void testPutRaw() throws IOException, URISyntaxException { "everywhere ", "value", "one two three", + "", + "", + "", "\u1234\u1234"); assertThat(p.rawValues()) .containsExactly( @@ -417,6 +711,9 @@ void testPutRaw() throws IOException, URISyntaxException { "everywhere ", "value", "one \\\n two \\\n\tthree", + "", + "", + "", "\\u1234\u1234"); assertThat(p.entrySet()) .containsExactly( @@ -426,6 +723,9 @@ void testPutRaw() throws IOException, URISyntaxException { new AbstractMap.SimpleEntry<>(" with spaces", "everywhere "), new AbstractMap.SimpleEntry<>("altsep", "value"), new AbstractMap.SimpleEntry<>("multiline", "one two three"), + new AbstractMap.SimpleEntry<>("novalue", ""), + new AbstractMap.SimpleEntry<>("keyonly", ""), + new AbstractMap.SimpleEntry<>("", ""), new AbstractMap.SimpleEntry<>("key.4", "\u1234\u1234")); assertThat(p.rawEntrySet()) .containsExactly( @@ -435,6 +735,9 @@ void testPutRaw() throws IOException, URISyntaxException { new AbstractMap.SimpleEntry<>("\\ with\\ spaces", "everywhere "), new AbstractMap.SimpleEntry<>("altsep", "value"), new AbstractMap.SimpleEntry<>("multiline", "one \\\n two \\\n\tthree"), + new AbstractMap.SimpleEntry<>("novalue", ""), + new AbstractMap.SimpleEntry<>("keyonly", ""), + new AbstractMap.SimpleEntry<>("", ""), new AbstractMap.SimpleEntry<>("key.4", "\\u1234\u1234")); StringWriter sw = new StringWriter(); p.store(sw); @@ -615,6 +918,9 @@ void testRemoveAll() throws IOException, URISyntaxException { p.remove(" with spaces"); p.remove("altsep"); p.remove("multiline"); + p.remove("novalue"); + p.remove("keyonly"); + p.remove(""); p.remove("key.4"); StringWriter sw = new StringWriter(); p.store(sw); @@ -630,7 +936,7 @@ void testRemoveNonExistent() throws IOException, URISyntaxException { @Test void testRemoveMiddleIterator() throws IOException, URISyntaxException { Properties p = Properties.loadProperties(getResource("/test.properties")); - Iterator iter = p.keySet().iterator(); + Iterator iter = p.keySet().iterator(); while (iter.hasNext()) { if (iter.next().equals("three")) { iter.remove(); @@ -667,10 +973,19 @@ public void testInteropLoad() throws IOException, URISyntaxException { try (Reader br = Files.newBufferedReader(getResource("/test.properties"))) { p.load(br); } - assertThat(p).size().isEqualTo(7); + assertThat(p).size().isEqualTo(10); assertThat(p.keySet()) .containsExactlyInAnyOrder( - "one", "two", "three", " with spaces", "altsep", "multiline", "key.4"); + "one", + "two", + "three", + " with spaces", + "altsep", + "multiline", + "novalue", + "keyonly", + "", + "key.4"); assertThat(p.values()) .containsExactlyInAnyOrder( "simple", @@ -679,6 +994,9 @@ public void testInteropLoad() throws IOException, URISyntaxException { "everywhere ", "value", "one two three", + "", + "", + "", "\u1234\u1234"); } @@ -707,6 +1025,9 @@ void testInteropPutLoad() throws IOException, URISyntaxException { p.put(" with spaces", "everywhere "); p.put("altsep", "value"); p.put("multiline", "one two three"); + p.put("novalue", ""); + p.put("keyonly", ""); + p.put("", ""); p.put("key.4", "\u1234"); StringWriter sw = new StringWriter(); p.store(sw, null); @@ -721,10 +1042,19 @@ void testInteropPutLoad() throws IOException, URISyntaxException { assertThat(sw.toString()).contains("key.4=\u1234" + System.lineSeparator()); java.util.Properties p2 = new java.util.Properties(); p2.load(new StringReader(sw.toString())); - assertThat(p2).size().isEqualTo(7); + assertThat(p2).size().isEqualTo(10); assertThat(p2.keySet()) .containsExactlyInAnyOrder( - "one", "two", "three", " with spaces", "altsep", "multiline", "key.4"); + "one", + "two", + "three", + " with spaces", + "altsep", + "multiline", + "novalue", + "keyonly", + "", + "key.4"); assertThat(p2.values()) .containsExactlyInAnyOrder( "simple", @@ -733,6 +1063,9 @@ void testInteropPutLoad() throws IOException, URISyntaxException { "everywhere ", "value", "one two three", + "", + "", + "", "\u1234"); } diff --git a/src/test/resources/test-comment.properties b/src/test/resources/test-comment.properties index a181da2..cf24f6e 100644 --- a/src/test/resources/test-comment.properties +++ b/src/test/resources/test-comment.properties @@ -13,4 +13,7 @@ altsep:value multiline = one \ two \ three +novalue= +keyonly += key.4 = \u1234ሴ diff --git a/src/test/resources/test-escaped.properties b/src/test/resources/test-escaped.properties index dc8d068..843f915 100644 --- a/src/test/resources/test-escaped.properties +++ b/src/test/resources/test-escaped.properties @@ -13,4 +13,7 @@ altsep:value multiline = one \ two \ three +novalue= +keyonly += key.4 = \u1234\u1234 diff --git a/src/test/resources/test-getproperty.properties b/src/test/resources/test-getproperty.properties index 146224a..3a86258 100644 --- a/src/test/resources/test-getproperty.properties +++ b/src/test/resources/test-getproperty.properties @@ -4,5 +4,8 @@ three=and escapes\n\t\r\f \ with\ spaces=everywhere altsep= multiline=one two three +novalue= +keyonly= += key.4=ሴሴ five=5 \ No newline at end of file diff --git a/src/test/resources/test-keyatend-putnew.properties b/src/test/resources/test-keyatend-putnew.properties index 5ee5f32..d986472 100644 --- a/src/test/resources/test-keyatend-putnew.properties +++ b/src/test/resources/test-keyatend-putnew.properties @@ -13,6 +13,9 @@ altsep:value multiline = one \ two \ three +novalue= +keyonly += 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 index f67dc9a..1a2426b 100644 --- a/src/test/resources/test-keyatend.properties +++ b/src/test/resources/test-keyatend.properties @@ -13,5 +13,8 @@ altsep:value multiline = one \ two \ three +novalue= +keyonly += key.4 = \u1234ሴ key.at.end \ No newline at end of file diff --git a/src/test/resources/test-put.properties b/src/test/resources/test-put.properties index 17e673a..da7d93c 100644 --- a/src/test/resources/test-put.properties +++ b/src/test/resources/test-put.properties @@ -4,4 +4,7 @@ three=and escapes\n\t\r\f \ with\ spaces=everywhere altsep=value multiline=one two three +novalue= +keyonly= += key.4=ሴሴ \ No newline at end of file diff --git a/src/test/resources/test-putnew.properties b/src/test/resources/test-putnew.properties index 59700cb..2ccaa90 100644 --- a/src/test/resources/test-putnew.properties +++ b/src/test/resources/test-putnew.properties @@ -13,5 +13,8 @@ altsep:value multiline = one \ two \ three +novalue= +keyonly += key.4 = \u1234ሴ five=5 diff --git a/src/test/resources/test-putraw.properties b/src/test/resources/test-putraw.properties index 61b173f..f513065 100644 --- a/src/test/resources/test-putraw.properties +++ b/src/test/resources/test-putraw.properties @@ -6,4 +6,7 @@ altsep=value multiline=one \ two \ three +novalue= +keyonly= += key.4=\u1234ሴ \ No newline at end of file diff --git a/src/test/resources/test-removecomment.properties b/src/test/resources/test-removecomment.properties index 9e8acbb..7add4bb 100644 --- a/src/test/resources/test-removecomment.properties +++ b/src/test/resources/test-removecomment.properties @@ -12,4 +12,7 @@ altsep:value multiline = one \ two \ three +novalue= +keyonly += key.4 = \u1234ሴ diff --git a/src/test/resources/test-removefirst.properties b/src/test/resources/test-removefirst.properties index c81ca16..7754d0c 100644 --- a/src/test/resources/test-removefirst.properties +++ b/src/test/resources/test-removefirst.properties @@ -11,4 +11,7 @@ altsep:value multiline = one \ two \ three +novalue= +keyonly += key.4 = \u1234ሴ diff --git a/src/test/resources/test-removelast.properties b/src/test/resources/test-removelast.properties index 6411934..d60258f 100644 --- a/src/test/resources/test-removelast.properties +++ b/src/test/resources/test-removelast.properties @@ -13,3 +13,6 @@ altsep:value multiline = one \ two \ three +novalue= +keyonly += diff --git a/src/test/resources/test-removemiddle.properties b/src/test/resources/test-removemiddle.properties index d521515..4c97b20 100644 --- a/src/test/resources/test-removemiddle.properties +++ b/src/test/resources/test-removemiddle.properties @@ -9,4 +9,7 @@ altsep:value multiline = one \ two \ three +novalue= +keyonly += key.4 = \u1234ሴ diff --git a/src/test/resources/test-separatoratend-putnew.properties b/src/test/resources/test-separatoratend-putnew.properties index 18e90c3..dc4561f 100644 --- a/src/test/resources/test-separatoratend-putnew.properties +++ b/src/test/resources/test-separatoratend-putnew.properties @@ -13,6 +13,9 @@ altsep:value multiline = one \ two \ three +novalue= +keyonly += 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 index cc9bfcd..837d10b 100644 --- a/src/test/resources/test-separatoratend.properties +++ b/src/test/resources/test-separatoratend.properties @@ -13,5 +13,8 @@ altsep:value multiline = one \ two \ three +novalue= +keyonly += key.4 = \u1234ሴ separator.at.end= \ No newline at end of file diff --git a/src/test/resources/test-setproperty.properties b/src/test/resources/test-setproperty.properties index e4433fa..100be1c 100644 --- a/src/test/resources/test-setproperty.properties +++ b/src/test/resources/test-setproperty.properties @@ -8,4 +8,7 @@ three=and escapes\n\t\r\f \ with\ spaces=everywhere altsep=value multiline=one two three +novalue= +keyonly= += key.4=ሴሴ \ No newline at end of file diff --git a/src/test/resources/test-storeheader.properties b/src/test/resources/test-storeheader.properties index ceb3ad3..9c92b85 100644 --- a/src/test/resources/test-storeheader.properties +++ b/src/test/resources/test-storeheader.properties @@ -12,4 +12,7 @@ altsep:value multiline = one \ two \ three +novalue= +keyonly += key.4 = \u1234ሴ diff --git a/src/test/resources/test-unescaped.properties b/src/test/resources/test-unescaped.properties index 32374cd..24787fb 100644 --- a/src/test/resources/test-unescaped.properties +++ b/src/test/resources/test-unescaped.properties @@ -13,4 +13,7 @@ altsep:value multiline = one \ two \ three +novalue= +keyonly += key.4 = ሴሴ diff --git a/src/test/resources/test.properties b/src/test/resources/test.properties index 990d4a6..9346256 100644 --- a/src/test/resources/test.properties +++ b/src/test/resources/test.properties @@ -13,4 +13,7 @@ altsep:value multiline = one \ two \ three +novalue= +keyonly += key.4 = \u1234ሴ diff --git a/src/test/resources/test2-putnew.properties b/src/test/resources/test2-putnew.properties index 5e960de..195c451 100644 --- a/src/test/resources/test2-putnew.properties +++ b/src/test/resources/test2-putnew.properties @@ -13,6 +13,9 @@ altsep:value multiline = one \ two \ three +novalue= +keyonly += key.4 = \u1234ሴ five=5 # final comment diff --git a/src/test/resources/test2.properties b/src/test/resources/test2.properties index f2d73bd..d53b594 100644 --- a/src/test/resources/test2.properties +++ b/src/test/resources/test2.properties @@ -13,5 +13,8 @@ altsep:value multiline = one \ two \ three +novalue= +keyonly += key.4 = \u1234ሴ # final comment diff --git a/src/test/resources/test3-putnew.properties b/src/test/resources/test3-putnew.properties index ec400f0..43dde73 100644 --- a/src/test/resources/test3-putnew.properties +++ b/src/test/resources/test3-putnew.properties @@ -13,5 +13,8 @@ altsep:value multiline = one \ two \ three +novalue= +keyonly += 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 index 467c4d3..d409cbf 100644 --- a/src/test/resources/test3.properties +++ b/src/test/resources/test3.properties @@ -13,4 +13,7 @@ altsep:value multiline = one \ two \ three +novalue= +keyonly += 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 ceb3ad3..9c92b85 100644 --- a/src/test/resources/testcrlf-storeheader.properties +++ b/src/test/resources/testcrlf-storeheader.properties @@ -12,4 +12,7 @@ altsep:value multiline = one \ two \ three +novalue= +keyonly += key.4 = \u1234ሴ diff --git a/src/test/resources/testcrlf.properties b/src/test/resources/testcrlf.properties index 990d4a6..9346256 100644 --- a/src/test/resources/testcrlf.properties +++ b/src/test/resources/testcrlf.properties @@ -13,4 +13,7 @@ altsep:value multiline = one \ two \ three +novalue= +keyonly += key.4 = \u1234ሴ From b786aea83fdda166964d402d9dc9ea352cce6bdd Mon Sep 17 00:00:00 2001 From: Tako Schotanus Date: Wed, 23 Sep 2026 16:29:48 +0200 Subject: [PATCH 2/3] fix: fixed exception when dealing with empty values --- src/main/java/org/codejive/properties/PropertiesParser.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/codejive/properties/PropertiesParser.java b/src/main/java/org/codejive/properties/PropertiesParser.java index c43ec85..06174b2 100644 --- a/src/main/java/org/codejive/properties/PropertiesParser.java +++ b/src/main/java/org/codejive/properties/PropertiesParser.java @@ -103,7 +103,7 @@ public String getRaw() { * Returns the token's processed value. Meaning this value will not contain any escape * sequences but only actual characters. * - * @return + * @return a string containing the token's processed value */ public String getText() { return text != null ? text : raw; @@ -115,6 +115,7 @@ public String getText() { * @return true if whitespace ending in EOL, false otherwise */ public boolean isEol() { + if (raw.isEmpty()) return false; int ch = raw.charAt(raw.length() - 1); return type == Type.WHITESPACE && PropertiesParser.isEol(ch); } @@ -125,6 +126,7 @@ public boolean isEol() { * @return true if whitespace NOT ending in EOL, false otherwise */ public boolean isWs() { + if (raw.isEmpty()) return false; int ch = raw.charAt(raw.length() - 1); return type == Type.WHITESPACE && !PropertiesParser.isEol(ch); } From 5935ddfcf43fada1f4bb04c131b18b310d748b20 Mon Sep 17 00:00:00 2001 From: Tako Schotanus Date: Wed, 23 Sep 2026 16:31:19 +0200 Subject: [PATCH 3/3] fix: fixed not handling nameless and valueless properties correctly --- .../org/codejive/properties/Properties.java | 120 ++++++++++-------- 1 file changed, 68 insertions(+), 52 deletions(-) diff --git a/src/main/java/org/codejive/properties/Properties.java b/src/main/java/org/codejive/properties/Properties.java index 38d4fde..28aa0ff 100644 --- a/src/main/java/org/codejive/properties/Properties.java +++ b/src/main/java/org/codejive/properties/Properties.java @@ -8,12 +8,12 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.*; +import java.util.function.BiConsumer; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; +import org.codejive.properties.PropertiesParser.Token; /** * This class is a replacement for java.util.Properties, with the difference that it @@ -134,7 +134,7 @@ public Enumeration propertyNames() { * value are strings, including the keys in the default property list. */ public Set stringPropertyNames() { - return Collections.unmodifiableSet(flatten().keySet()); + return Collections.unmodifiableSet(flattened().keySet()); } /** @@ -144,7 +144,7 @@ public Set stringPropertyNames() { */ public void list(PrintStream out) { try { - flatten().store(out); + flattened().store(out); } catch (IOException e) { // Ignore any errors } @@ -157,7 +157,7 @@ public void list(PrintStream out) { */ public void list(PrintWriter out) { try { - flatten().store(out); + flattened().store(out); } catch (IOException e) { // Ignore any errors } @@ -230,6 +230,7 @@ private void flatten(Properties target) { @Override public Set> entrySet() { + if (tokens.isEmpty()) return Collections.emptySet(); return new AbstractSet>() { @Override public Iterator> iterator() { @@ -272,6 +273,7 @@ public int size() { * @return A set of raw key values */ public Set rawKeySet() { + if (tokens.isEmpty()) return Collections.emptySet(); return tokens.stream() .filter(t -> t.type == PropertiesParser.Type.KEY) .map(PropertiesParser.Token::getRaw) @@ -285,10 +287,10 @@ public Set rawKeySet() { * @return a collection of raw values. */ public Collection rawValues() { - return combined(tokens) - .filter(ts -> ts.get(0).type == PropertiesParser.Type.KEY) - .map(ts -> ts.get(2).getRaw()) - .collect(Collectors.toList()); + if (tokens.isEmpty()) return Collections.emptyList(); + List result = new ArrayList<>(); + walkProperties((key, value) -> result.add(value != null ? value.getRaw() : "")); + return result; } /** @@ -298,10 +300,40 @@ public Collection rawValues() { * @return A set of raw key-value entries */ public Set> rawEntrySet() { - return combined(tokens) - .filter(ts -> ts.get(0).type == PropertiesParser.Type.KEY) - .map(ts -> new SimpleEntry<>(ts.get(0).getRaw(), ts.get(2).getRaw())) - .collect(Collectors.toCollection(LinkedHashSet::new)); + if (tokens.isEmpty()) return Collections.emptySet(); + Set> result = new LinkedHashSet<>(); + walkProperties( + (key, value) -> + result.add( + new SimpleEntry<>( + key.getRaw(), value != null ? value.getRaw() : ""))); + return result; + } + + private void walkProperties(BiConsumer func) { + if (tokens.isEmpty()) return; + Cursor c = Cursor.first(tokens); + while (c.hasToken()) { + if (c.isType(PropertiesParser.Type.KEY)) { + PropertiesParser.Token keyToken = c.token(); + c.next(); + if (c.isType(PropertiesParser.Type.SEPARATOR)) { + c.next(); + if (c.isType(PropertiesParser.Type.VALUE)) { + func.accept(keyToken, c.token()); + c.next(); + } else { + // We're dealing with a value-less property + func.accept(keyToken, null); + } + } else { + // We're dealing with a key-only property + func.accept(keyToken, null); + } + } else { + c.next(); + } + } } @Override @@ -711,9 +743,7 @@ public Properties escaped() { } private static List escapeTokens(List tokens) { - return mapKeyValues( - tokens, - ts -> Arrays.asList(escapeToken(ts.get(0)), ts.get(1), escapeToken(ts.get(2)))); + return mapKeyValues(tokens, Properties::escapeToken, Properties::escapeToken); } private static PropertiesParser.Token escapeToken(PropertiesParser.Token token) { @@ -738,9 +768,7 @@ public Properties unescaped() { private static List unescapeTokens( List tokens) { - return mapKeyValues( - tokens, - ts -> Arrays.asList(unescapeToken(ts.get(0)), ts.get(1), unescapeToken(ts.get(2)))); + return mapKeyValues(tokens, Properties::unescapeToken, Properties::unescapeToken); } private static PropertiesParser.Token unescapeToken(PropertiesParser.Token token) { @@ -753,46 +781,22 @@ private static PropertiesParser.Token unescapeToken(PropertiesParser.Token token private static List mapKeyValues( List tokens, - Function, List> mapper) { - return combined(tokens) + Function keyMapper, + Function valueMapper) { + return tokens.stream() .map( - ts -> { - if (ts.get(0).type == PropertiesParser.Type.KEY) { - return mapper.apply(ts); + tk -> { + if (tk.type == PropertiesParser.Type.KEY) { + return keyMapper.apply(tk); + } else if (tk.type == PropertiesParser.Type.VALUE) { + return valueMapper.apply(tk); } else { - return ts; + return tk; } }) - .flatMap(Collection::stream) .collect(Collectors.toList()); } - private static Stream> combined( - List tokens) { - Iterator> iter = - new Iterator>() { - Iterator i = tokens.iterator(); - - @Override - public boolean hasNext() { - return i.hasNext(); - } - - @Override - public List next() { - PropertiesParser.Token t = i.next(); - if (t.type == PropertiesParser.Type.KEY) { - return Arrays.asList(t, i.next(), i.next()); - } else { - return Collections.singletonList(t); - } - } - }; - - return StreamSupport.stream( - Spliterators.spliterator(iter, tokens.size(), Spliterator.SORTED), false); - } - /** * Copies all entries from the java.util.Properties object to this object * @@ -865,10 +869,22 @@ private Properties load(List ts) { for (PropertiesParser.Token token : tokens) { if (token.type == PropertiesParser.Type.KEY) { key = token.getText(); + } + if (token.type == PropertiesParser.Type.SEPARATOR && key == null) { + // In case if a name-less property + key = ""; } else if (token.type == PropertiesParser.Type.VALUE) { values.put(key, token.getText()); + key = null; + } else if (token.isEol() && key != null) { + // In case of value-less properties + values.put(key, ""); } } + // In case of the last property being value-less + if (key != null) { + values.put(key, ""); + } return this; }