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
69 changes: 15 additions & 54 deletions src/main/java/org/codejive/properties/Properties.java
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ public Set<String> rawKeySet() {
public Collection<String> rawValues() {
if (tokens.isEmpty()) return Collections.emptyList();
List<String> result = new ArrayList<>();
walkProperties((key, value) -> result.add(value != null ? value.getRaw() : ""));
walkProperties((key, value) -> result.add(value.getRaw()));
return result;
}

Expand All @@ -330,11 +330,7 @@ public Collection<String> rawValues() {
public Set<Entry<String, String>> rawEntrySet() {
if (tokens.isEmpty()) return Collections.emptySet();
Set<Entry<String, String>> result = new LinkedHashSet<>();
walkProperties(
(key, value) ->
result.add(
new SimpleEntry<>(
key.getRaw(), value != null ? value.getRaw() : "")));
walkProperties((key, value) -> result.add(new SimpleEntry<>(key.getRaw(), value.getRaw())));
return result;
}

Expand All @@ -345,19 +341,9 @@ private void walkProperties(BiConsumer<Token, Token> func) {
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);
}
c.next(); // Skip the separator
func.accept(keyToken, c.token());
c.next();
} else {
c.next();
}
Expand Down Expand Up @@ -441,16 +427,14 @@ public String putRaw(String rawKey, String rawValue) {
private void replaceValue(String key, String rawValue, String value) {
Cursor pos = indexOf(key);
validate(pos.nextIf(PropertiesParser.Type.KEY), pos);
if (!pos.isType(PropertiesParser.Type.SEPARATOR)) {
pos.add(new PropertiesParser.Token(PropertiesParser.Type.SEPARATOR, "="));
} else {
pos.next();
}
if (pos.isType(PropertiesParser.Type.VALUE)) {
pos.replace(new PropertiesParser.Token(PropertiesParser.Type.VALUE, rawValue, value));
} else {
pos.add(new PropertiesParser.Token(PropertiesParser.Type.VALUE, rawValue, value));
validate(pos.isType(PropertiesParser.Type.SEPARATOR), pos);
// A key-only property has an empty separator token; adding a value needs a delimiter.
if (pos.raw().isEmpty()) {
pos.replace(new PropertiesParser.Token(PropertiesParser.Type.SEPARATOR, "="));
}
pos.next();
validate(pos.isType(PropertiesParser.Type.VALUE), pos);
pos.replace(new PropertiesParser.Token(PropertiesParser.Type.VALUE, rawValue, value));
}

// Add new tokens to the end of the list of tokens
Expand Down Expand Up @@ -904,22 +888,11 @@ private Properties load(List<PropertiesParser.Token> 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;
}

Expand Down Expand Up @@ -1126,13 +1099,7 @@ public Cursor beforeFirstProperty() {
pos.next();
}
// Make sure we're either at the end or we've found a property
validate(
pos.atEnd()
|| pos.isType(
PropertiesParser.Type.VALUE,
PropertiesParser.Type.SEPARATOR,
PropertiesParser.Type.KEY),
pos);
validate(pos.atEnd() || pos.isType(PropertiesParser.Type.KEY), pos);
if (!pos.atEnd()) {
pos.prev();
}
Expand All @@ -1146,19 +1113,13 @@ public Cursor beforeFirstProperty() {
* @return a Cursor pointing to the right position or {@code -1} if not found
*/
public Cursor afterLastProperty() {
// Track back from end until we encounter the last VALUE token (if any)
// Track back from end until we encounter the last VALUE token
Cursor pos = last();
while (pos.isType(PropertiesParser.Type.WHITESPACE, PropertiesParser.Type.COMMENT)) {
pos.prev();
}
// 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);
validate(pos.atStart() || pos.isType(PropertiesParser.Type.VALUE), pos);
if (!pos.atStart()) {
pos.next();
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/org/codejive/properties/PropertiesParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,8 @@ public boolean tryAdvance(Consumer<? super Token> action) {
*/
public Token nextToken() throws IOException {
int ch = peekChar();
if (isEof(ch)) {
// A property at EOF may still need its empty separator and/or value token.
if (isEof(ch) && state == null) {
return null;
}
Function<Integer, Boolean> isValid = (c) -> false;
Expand Down
29 changes: 29 additions & 0 deletions src/test/java/org/codejive/properties/TestPropertiesParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class TestPropertiesParser {
+ " two \\\r\n"
+ "\tthree\n"
+ "key.4 = \\u1234\r\n"
+ "line-with-missing-value = \n"
+ "line-with-missing-delim \n"
+ "multidelim===value\n"
+ " # final comment";
Expand Down Expand Up @@ -83,6 +84,10 @@ void testTokens() throws IOException {
new Token(Type.SEPARATOR, " = "),
new Token(Type.VALUE, "\\u1234", "\u1234"),
new Token(Type.WHITESPACE, "\r\n"),
new Token(Type.KEY, "line-with-missing-value"),
new Token(Type.SEPARATOR, " = "),
new Token(Type.VALUE, ""),
new Token(Type.WHITESPACE, "\n"),
new Token(Type.KEY, "line-with-missing-delim"),
new Token(Type.SEPARATOR, " "),
new Token(Type.VALUE, ""),
Expand All @@ -102,4 +107,28 @@ void testStringify() throws IOException {
String props2 = tokens.map(Token::getRaw).collect(Collectors.joining());
assertThat(props2).isEqualTo(props);
}

@Test
void testEmptySeparatorAndValueAtEof() throws IOException {
List<Token> tokens =
PropertiesParser.tokens(new StringReader("foo=")).collect(Collectors.toList());

assertThat(tokens)
.containsExactly(
new Token(Type.KEY, "foo"),
new Token(Type.SEPARATOR, "="),
new Token(Type.VALUE, ""));
}

@Test
void testMissingSeparatorAndValueAtEof() throws IOException {
List<Token> tokens =
PropertiesParser.tokens(new StringReader("foo")).collect(Collectors.toList());

assertThat(tokens)
.containsExactly(
new Token(Type.KEY, "foo"),
new Token(Type.SEPARATOR, ""),
new Token(Type.VALUE, ""));
}
}
Loading