diff --git a/src/main/java/org/codejive/properties/Properties.java b/src/main/java/org/codejive/properties/Properties.java index 6916b9b..3a48cee 100644 --- a/src/main/java/org/codejive/properties/Properties.java +++ b/src/main/java/org/codejive/properties/Properties.java @@ -483,11 +483,10 @@ private void replaceValue(String key, String rawValue, String value) { // Add a new property to the end of the list of tokens private Cursor addNewKeyValue(String rawKey, String key, String rawValue, String value) { - Cursor pos = afterLastProperty(); - if (pos.atStart()) { - // We're at the start, meaning there are no properties yet, - // but there might be comments, so we move forward again, - // skipping any header comments + Cursor pos; + if (values.isEmpty()) { + // There are no properties yet, but there might be comments, + // so insert after the header comments. pos = afterHeader(); if (pos.atEnd()) { // afterHeader() leaves at most one blank line after the last comment. @@ -501,6 +500,8 @@ private Cursor addNewKeyValue(String rawKey, String key, String rawValue, String pos.addEol(eolType); } } + } else { + pos = afterLastProperty(); } return addNewKeyValue(pos, rawKey, key, rawValue, value); } @@ -1211,10 +1212,10 @@ public Cursor beforeFirstProperty() { } /** - * Returns a Cursor pointing to the position past the last property, or to {@code -1} if the - * `Properties` object is empty. + * Returns a Cursor pointing to the position past the last property. If no properties exist, + * behaves like {@link #beforeFirstProperty()}. * - * @return a Cursor pointing to the right position or {@code -1} if not found + * @return a Cursor pointing past the last property, or before the first property if none exist */ public Cursor afterLastProperty() { // Track back from end until we encounter the last VALUE token @@ -1224,9 +1225,10 @@ public Cursor afterLastProperty() { } // Make sure we're either at the start or we've found a property validate(pos.atStart() || pos.isType(PropertiesParser.Type.VALUE), pos); - if (!pos.atStart()) { - pos.next().nextIf(Token::isEol); + if (pos.atStart()) { + return beforeFirstProperty(); } + pos.next().nextIf(Token::isEol); return pos; } diff --git a/src/test/java/org/codejive/properties/TestProperties.java b/src/test/java/org/codejive/properties/TestProperties.java index 653178f..eb8889e 100644 --- a/src/test/java/org/codejive/properties/TestProperties.java +++ b/src/test/java/org/codejive/properties/TestProperties.java @@ -1242,11 +1242,32 @@ void testAfterLastProperty() throws IOException { Properties keyOnly = Properties.loadProperties(new StringReader("alpha\n")); assertThat(keyOnly.afterLastProperty().atEnd()).isTrue(); - Properties commentsOnly = Properties.loadProperties(new StringReader("# header\n\n")); - assertThat(commentsOnly.afterLastProperty().atStart()).isTrue(); assertThat(new Properties().afterLastProperty().atStart()).isTrue(); } + @Test + void testAfterLastPropertyWithoutProperties() throws IOException { + for (String input : + Arrays.asList("", "\n\n", " ", "# header", "# header\n\n", " # header\r\n\r\n")) { + Properties p = Properties.loadProperties(new StringReader(input)); + assertThat(p.afterLastProperty().position()) + .as("Cursor position for %s", input) + .isEqualTo(p.beforeFirstProperty().position()); + assertThat(p.afterLastProperty().atEnd()).isTrue(); + } + } + + @Test + void testPutAfterLastPropertyWithoutProperties() throws IOException { + for (String eol : Arrays.asList("\n", "\r\n")) { + String given = "# header" + eol + eol; + Properties p = Properties.loadProperties(new StringReader(given)); + p.put(p.afterLastProperty(), "put", "val"); + expectStoreText(p, given + "put=val" + eol); + assertThat(p.getComment("put")).isEmpty(); + } + } + @Test void testCursor() throws IOException, URISyntaxException { Properties p = Properties.loadProperties(getResource("/test.properties"));