Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 24 additions & 23 deletions src/main/java/org/codejive/properties/Properties.java
Original file line number Diff line number Diff line change
Expand Up @@ -488,24 +488,17 @@ private Cursor addNewKeyValue(String rawKey, String key, String rawValue, String
// 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
// (*) = we'll always skip past the final comment's EOL
pos = afterHeader();
if (!pos.atStart()) {
// We have to make sure there are at least 2 EOLs after the last comment
if (pos.atEnd()) {
Cursor pp = last();
if (pp.isType(PropertiesParser.Type.COMMENT)) {
// If the last token is a comment, we're short two EOLs
pos.addEol(eolType);
pos.addEol(eolType);
} else if (pp.isEol()) {
// If the last element is an EOL we still need one (*)
pos.addEol(eolType);
}
} else if (pos.isEol()) {
// If the current element is an EOL we know there are at least 2 (*),
// so we can simply move past it
pos.next();
if (pos.atEnd()) {
// afterHeader() leaves at most one blank line after the last comment.
// Add only the missing line endings needed to keep the header free.
Cursor previous = pos.copy().prev();
if (previous.isType(PropertiesParser.Type.COMMENT)) {
pos.addEol(eolType);
pos.addEol(eolType);
} else if (previous.isEol()
&& previous.prev().isType(PropertiesParser.Type.COMMENT)) {
pos.addEol(eolType);
}
}
}
Expand Down Expand Up @@ -1123,8 +1116,8 @@ public Cursor last() {
/**
* Returns a Cursor pointing to the position right before the property with the given key. If no
* such property exists, {@code null} will be returned. If the property was found the position
* will take into account any leading whitespace and will be positioned at the start of the
* line.
* will take into account any attached comments and leading whitespace and will be positioned at
* the start of the first attached comment line, or the property line if there are no comments.
*
* @param key The name of property to look for
* @return a Cursor pointing to the right position or {@code null} if not found
Expand All @@ -1138,10 +1131,15 @@ public Cursor beforeProperty(String key) {
if (pos.atEnd()) {
return null;
} else {
return pos.home();
return beforePropertyComments(pos);
}
}

private Cursor beforePropertyComments(Cursor pos) {
List<Integer> comments = findPropertyCommentLines(pos);
return comments.isEmpty() ? pos.home() : index(comments.get(0)).home();
}

/**
* Returns a Cursor pointing to the position right after the property with the given key. If no
* such property exists, {@code null} will be returned. If the property was found, the position
Expand Down Expand Up @@ -1186,7 +1184,7 @@ public Cursor beforeFirstProperty() {
// Make sure we're either at the end or we've found a property
validate(pos.atEnd() || pos.isType(PropertiesParser.Type.KEY), pos);
if (!pos.atEnd()) {
pos.prev();
pos = beforePropertyComments(pos);
}
return pos;
}
Expand All @@ -1212,8 +1210,9 @@ public Cursor afterLastProperty() {
}

/**
* Returns a Cursor pointing to the position right after the last comment in the header. If no
* header comments are found, the cursor will point to the start of the file.
* Returns a Cursor pointing to the position after the last comment in the header, leaving a
* single empty separating line in between (if it exists). If no header comments are found, the
* cursor will be at the start of the list.
*
* @return a Cursor pointing to the right position
*/
Expand All @@ -1233,6 +1232,8 @@ public Cursor afterHeader() {
pos = first();
} else {
pos.home();
// Leave at most one empty line between the header and the cursor.
pos.nextIf(PropertiesParser.Token::isEol);
}
return pos;
}
Expand Down
67 changes: 65 additions & 2 deletions src/test/java/org/codejive/properties/TestProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -1195,11 +1195,11 @@ void testBeforeFirstProperty() throws IOException {
Properties.loadProperties(
new StringReader("# header\n\n # attached\n alpha=1\nbeta=2\n"));
Cursor beforeFirst = withHeader.beforeFirstProperty();
assertThat(beforeFirst.isWhitespace()).isTrue();
assertThat(beforeFirst.position()).isEqualTo(withHeader.beforeProperty("alpha").position());
assertThat(beforeFirst.raw()).isEqualTo(" ");

Properties withoutHeader = Properties.loadProperties(new StringReader("alpha=1\nbeta=2"));
assertThat(withoutHeader.beforeFirstProperty().atStart()).isTrue();
assertThat(withoutHeader.beforeFirstProperty().position()).isZero();

Properties commentsOnly =
Properties.loadProperties(new StringReader("# header\n\n# another\n"));
Expand Down Expand Up @@ -1312,6 +1312,69 @@ void testPutReplaceMissingSepOrValue() throws IOException, URISyntaxException {
assertThat(sw.toString()).isEqualTo(expected);
}

@Test
void testPutBeforeFirstProperty() throws IOException {
final String given = "# header comment\n" + "\n" + "key=val";
final String expected = "# header comment\n" + "\n" + "put=putVal\n" + "key=val";
Properties p = Properties.loadProperties(new StringReader(given));
p.put(p.beforeFirstProperty(), "put", "putVal");
expectStoreText(p, expected);
}

@Test
void testPutBeforeFirstPropertyWithComment() throws IOException {
final String given = "# header comment\n" + "\n" + "# key comment\n" + "key=val";
final String expected =
"# header comment\n" + "\n" + "put=putVal\n" + "# key comment\n" + "key=val";
Properties p = Properties.loadProperties(new StringReader(given));
p.put(p.beforeFirstProperty(), "put", "putVal");
expectStoreText(p, expected);
}

@Test
void testPutAfterHeader() throws IOException {
final String given = "# header comment\n" + "\n" + "key=val";
final String expected = "# header comment\n" + "\n" + "put=putVal\n" + "key=val";
Properties p = Properties.loadProperties(new StringReader(given));
p.put(p.afterHeader(), "put", "putVal");
expectStoreText(p, expected);
}

@Test
void testAfterHeaderLeavesAtMostOneBlankLine() throws IOException {
for (String eol : Arrays.asList("\n", "\r\n")) {
String input = "# header";
for (int lineEndings = 0; lineEndings <= 3; lineEndings++) {
Properties p = Properties.loadProperties(new StringReader(input));
assertThat(p.afterHeader().position()).isEqualTo(1 + Math.min(lineEndings, 2));
if (lineEndings == 3) {
assertThat(p.afterHeader().isEol()).isTrue();
} else {
assertThat(p.afterHeader().atEnd()).isTrue();
}
input += eol;
}
}
}

@Test
void testPutBeforeIndentedProperty() throws IOException {
final String given = "# header comment\n" + "\n" + " # key comment\n" + " key=val";
final String expected =
"# header comment\n" + "\n" + "put=putVal\n" + " # key comment\n" + " key=val";
Properties p = Properties.loadProperties(new StringReader(given));
p.put(p.beforeProperty("key"), "put", "putVal");
expectStoreText(p, expected);
assertThat(p.getComment("key")).containsExactly("# key comment");
assertThat(p.getComment("put")).isEmpty();
}

private void expectStoreText(Properties props, String expectedText) throws IOException {
StringWriter sw = new StringWriter();
props.store(sw);
assertThat(sw.toString()).isEqualTo(expectedText);
}

private Path getResource(String name) throws URISyntaxException {
URL resource = getClass().getResource(name);
if (resource == null)
Expand Down
Loading