Skip to content

Commit 6b574f4

Browse files
committed
fix: adjusted parsing to be the same as for JU Properties
We have to make sure 100% interop so we can read files written by JU Propeties and vice versa and that the result is the same in both cases. Also added a couple of interop methods to easily convert from OC Poperties to JU Properties.
1 parent 8d1b6cb commit 6b574f4

File tree

6 files changed

+283
-107
lines changed

6 files changed

+283
-107
lines changed

src/main/java/org/codejive/properties/Properties.java

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.codejive.properties;
22

3+
import static org.codejive.properties.PropertiesParser.unescape;
4+
35
import java.io.BufferedReader;
46
import java.io.IOException;
57
import java.io.Reader;
@@ -25,8 +27,6 @@
2527
import java.util.stream.Collectors;
2628
import java.util.stream.IntStream;
2729

28-
import static org.codejive.properties.PropertiesParser.unescape;
29-
3030
public class Properties extends AbstractMap<String, String> {
3131
private final LinkedHashMap<String, String> values = new LinkedHashMap<>();
3232
private final List<PropertiesParser.Token> tokens = new ArrayList<>();
@@ -85,7 +85,7 @@ public String get(Object key) {
8585

8686
public String getRaw(String rawKey) {
8787
int idx = indexOf(unescape(rawKey));
88-
if (idx >=0) {
88+
if (idx >= 0) {
8989
return tokens.get(idx + 2).getRaw();
9090
} else {
9191
return null;
@@ -106,8 +106,9 @@ public String put(String key, String value) {
106106
}
107107

108108
/**
109-
* Works like `put()` but uses raw values for keys and values.
110-
* This means these keys and values will not be escaped before being serialized.
109+
* Works like `put()` but uses raw values for keys and values. This means these keys and values
110+
* will not be escaped before being serialized.
111+
*
111112
* @param rawKey key with which the specified value is to be associated
112113
* @param rawValue value to be associated with the specified key
113114
* @return the previous value associated with key, or null if there was no mapping for key.
@@ -155,20 +156,23 @@ public String remove(Object key) {
155156
}
156157

157158
/**
158-
* Gather all the comments directly before the given key
159-
* and return them as a list. The list will only contain
160-
* those lines that immediately follow one another, once
161-
* a non-comment line is encountered gathering will stop.
159+
* Gather all the comments directly before the given key and return them as a list. The list
160+
* will only contain those lines that immediately follow one another, once a non-comment line is
161+
* encountered gathering will stop.
162+
*
162163
* @param key The key to look for
163-
* @return A list of comment strings or an empty list if
164-
* no comments lines were found or the key doesn't exist.
164+
* @return A list of comment strings or an empty list if no comments lines were found or the key
165+
* doesn't exist.
165166
*/
166167
public List<String> getComment(String key) {
167168
return getComment(findCommentLines(key));
168169
}
169170

170171
private List<String> getComment(List<Integer> indices) {
171-
return Collections.unmodifiableList(indices.stream().map(idx -> tokens.get(idx).getText()).collect(Collectors.toList()));
172+
return Collections.unmodifiableList(
173+
indices.stream()
174+
.map(idx -> tokens.get(idx).getText())
175+
.collect(Collectors.toList()));
172176
}
173177

174178
public List<String> setComment(String key, String... comments) {
@@ -205,19 +209,20 @@ public List<String> setComment(String key, List<String> comments) {
205209
// Add any additional lines (when there are more new lines than old ones)
206210
int ins = idx;
207211
for (int j = i; j < newcs.size(); j++) {
208-
tokens.add(ins++, new PropertiesParser.Token(PropertiesParser.Type.COMMENT, newcs.get(j)));
212+
tokens.add(
213+
ins++, new PropertiesParser.Token(PropertiesParser.Type.COMMENT, newcs.get(j)));
209214
tokens.add(ins++, new PropertiesParser.Token(PropertiesParser.Type.WHITESPACE, "\n"));
210215
}
211216

212217
return oldcs;
213218
}
214219

215220
/**
216-
* Takes a list of comments and makes sure each of them starts with
217-
* a valid comment character (either '#' or '!'). If only some lines
218-
* have missing comment prefixes it will use the ones that were used
219-
* on previous lines, if not the default will be the value passed as
221+
* Takes a list of comments and makes sure each of them starts with a valid comment character
222+
* (either '#' or '!'). If only some lines have missing comment prefixes it will use the ones
223+
* that were used on previous lines, if not the default will be the value passed as
220224
* `preferredPrefix`.
225+
*
221226
* @param comments list of comment lines
222227
* @param preferredPrefix the preferred prefix to use
223228
* @return list of comment lines
@@ -255,11 +260,9 @@ private List<Integer> findCommentLines(String key) {
255260
}
256261

257262
/**
258-
* Returns a list of token indices pointing to all the comment lines
259-
* in a comment block. A list of comments is considered a block when
260-
* they are consecutive lines, without any empty lines in between,
261-
* using the same comment symbol (so they are either all `!` comments
262-
* or all `#` ones).
263+
* Returns a list of token indices pointing to all the comment lines in a comment block. A list
264+
* of comments is considered a block when they are consecutive lines, without any empty lines in
265+
* between, using the same comment symbol (so they are either all `!` comments or all `#` ones).
263266
*/
264267
private List<Integer> findCommentLines(int idx) {
265268
List<Integer> result = new ArrayList<>();
@@ -280,7 +283,8 @@ private List<Integer> findCommentLines(int idx) {
280283
}
281284

282285
private int indexOf(String key) {
283-
return tokens.indexOf(new PropertiesParser.Token(PropertiesParser.Type.KEY, escape(key, true), key));
286+
return tokens.indexOf(
287+
new PropertiesParser.Token(PropertiesParser.Type.KEY, escape(key, true), key));
284288
}
285289

286290
private String escape(String raw, boolean forKey) {
@@ -290,12 +294,12 @@ private String escape(String raw, boolean forKey) {
290294
raw = raw.replace("\f", "\\f");
291295
if (forKey) {
292296
raw = raw.replace(" ", "\\ ");
293-
} else {
294-
if (raw.charAt(raw.length() - 1) == ' ') {
295-
raw = raw.substring(0, raw.length() - 1) + "\\ ";
296-
}
297297
}
298-
raw = replace(raw, "[^\\x{0000}-\\x{00FF}]", m -> "\\\\u" + Integer.toString(m.group(0).charAt(0), 16));
298+
raw =
299+
replace(
300+
raw,
301+
"[^\\x{0000}-\\x{00FF}]",
302+
m -> "\\\\u" + Integer.toString(m.group(0).charAt(0), 16));
299303
return raw;
300304
}
301305

@@ -314,6 +318,16 @@ private static String replace(String input, Pattern regex, Function<Matcher, Str
314318
return resultString.toString();
315319
}
316320

321+
public java.util.Properties asJUProperties() {
322+
return asJUProperties(null);
323+
}
324+
325+
public java.util.Properties asJUProperties(java.util.Properties defaults) {
326+
java.util.Properties p = new java.util.Properties(defaults);
327+
p.putAll(this);
328+
return p;
329+
}
330+
317331
public void load(Path file) throws IOException {
318332
try (Reader br = Files.newBufferedReader(file)) {
319333
load(br);
@@ -326,8 +340,7 @@ public void load(Reader reader) throws IOException {
326340
reader instanceof BufferedReader
327341
? (BufferedReader) reader
328342
: new BufferedReader(reader);
329-
List<PropertiesParser.Token> ts = PropertiesParser.tokens(br)
330-
.collect(Collectors.toList());
343+
List<PropertiesParser.Token> ts = PropertiesParser.tokens(br).collect(Collectors.toList());
331344
tokens.addAll(ts);
332345
String key = null;
333346
for (PropertiesParser.Token token : tokens) {

src/main/java/org/codejive/properties/PropertiesParser.java

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,11 @@ public Token nextToken() throws IOException {
123123
oldch = ch;
124124
ch = peekChar();
125125
} else {
126-
String text = (state == Type.VALUE || state == Type.COMMENT) ? trimmedString() : string();
127-
Token token = hasEscapes ? new Token(state, text, unescape(text)) : new Token(state, text);
126+
String text = string();
127+
Token token =
128+
hasEscapes
129+
? new Token(state, text, unescape(text))
130+
: new Token(state, text);
128131
hasEscapes = false;
129132
state = nextState;
130133
return token;
@@ -178,19 +181,6 @@ private String string() {
178181
return result;
179182
}
180183

181-
private String trimmedString() {
182-
String result = string();
183-
int last = result.length();
184-
while (last > 0 && isWhitespaceChar(result.charAt(last - 1))) {
185-
last--;
186-
}
187-
if (last < result.length()) {
188-
str.append(result.substring(last));
189-
result = result.substring(0, last);
190-
}
191-
return result;
192-
}
193-
194184
static String unescape(String escape) {
195185
StringBuilder txt = new StringBuilder();
196186
for (int i = 0; i < escape.length(); i++) {
@@ -215,6 +205,20 @@ static String unescape(String escape) {
215205
txt.append((char) Integer.parseInt(num, 16));
216206
i += 4;
217207
break;
208+
case '\n':
209+
// Skip the next character if it's a '\r'
210+
if (i < escape.length() && escape.charAt(i + 1) == '\r') {
211+
i++;
212+
}
213+
// fall-through!
214+
case '\r':
215+
// Skip any leading whitespace
216+
while (i < escape.length()
217+
&& isWhitespaceChar(ch = escape.charAt(i + 1))
218+
&& !isEol(ch)) {
219+
i++;
220+
}
221+
break;
218222
default:
219223
txt.append(ch);
220224
break;
@@ -252,22 +256,25 @@ private static boolean isEof(int ch) {
252256
}
253257

254258
public static Stream<Token> tokens(Reader rdr) throws IOException {
255-
return StreamSupport.stream(new Spliterators.AbstractSpliterator<Token>(0, 0) {
256-
final PropertiesParser p = new PropertiesParser(rdr);
257-
@Override
258-
public boolean tryAdvance(Consumer<? super Token> action) {
259-
try {
260-
Token token = p.nextToken();
261-
if (token != null) {
262-
action.accept(token);
263-
return true;
264-
} else {
265-
return false;
259+
return StreamSupport.stream(
260+
new Spliterators.AbstractSpliterator<Token>(0, 0) {
261+
final PropertiesParser p = new PropertiesParser(rdr);
262+
263+
@Override
264+
public boolean tryAdvance(Consumer<? super Token> action) {
265+
try {
266+
Token token = p.nextToken();
267+
if (token != null) {
268+
action.accept(token);
269+
return true;
270+
} else {
271+
return false;
272+
}
273+
} catch (IOException ex) {
274+
throw new RuntimeException(ex);
275+
}
266276
}
267-
} catch (IOException ex) {
268-
throw new RuntimeException(ex);
269-
}
270-
}
271-
}, false);
277+
},
278+
false);
272279
}
273280
}

0 commit comments

Comments
 (0)