-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathMPropsParser.java
More file actions
131 lines (117 loc) · 4.18 KB
/
MPropsParser.java
File metadata and controls
131 lines (117 loc) · 4.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package com.github.mprops;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import org.jetbrains.annotations.NotNull;
/**
* Parser implementation for multiline properties (MProps) format.
*/
public class MPropsParser {
public static final String DEFAULT_KEY_PREFIX = "~";
@NotNull
public final String keyPrefix;
@NotNull
protected final String prefixToUnescape;
public MPropsParser() {
this(DEFAULT_KEY_PREFIX);
}
public MPropsParser(@NotNull String keyPrefix) {
this.keyPrefix = keyPrefix;
prefixToUnescape = " " + keyPrefix;
}
/**
* Parses multiline properties from the given text.
* Throws runtime exception if parsing error occurs.
*
* @param text text to parse.
* @return map of properties: [property name] : property value. Never returns null.
*/
@NotNull
public Map<String, String> parse(@NotNull String text) {
return parse(new StringReader(text));
}
/**
* Parses multiline properties from the given input.
* Throws runtime exception if parsing or IO error occurs.
* <p>
* Closes the reader.
*
* @param reader an input to read.
* @return map of properties: [property name] : property value. Never returns null.
*/
@NotNull
public Map<String, String> parse(@NotNull Reader reader) {
Map<String, String> result = new LinkedHashMap<>();
parse(reader, result::put);
return result;
}
/**
* Parses multiline properties from the given input.
* Throws runtime exception if parsing or IO error occurs.
* <p>
* Closes the reader.
*
* @param reader an input to read.
* @param propertiesConsumer consumer for key/values read.
*/
public void parse(@NotNull Reader reader, @NotNull BiConsumer<String, String> propertiesConsumer) {
try {
parseImpl(reader, propertiesConsumer);
} catch (IOException e) {
throw new IllegalArgumentException("Failed to read input", e);
}
}
private void parseImpl(@NotNull Reader reader, @NotNull BiConsumer<String, String> consumer) throws IOException {
String key = "";
StringBuilder value = new StringBuilder();
boolean readingHeader = true;
try (BufferedReader lineReader = new BufferedReader(reader)) {
int lineNumber = 0;
while (true) {
String line = lineReader.readLine();
if (line == null) { // EOF
break;
}
lineNumber++;
if (readingHeader && !line.startsWith(keyPrefix)) {
continue; // Skip header's comment
}
if (line.startsWith(keyPrefix)) {
if (!key.isEmpty()) { // add current property to the result, start a new one.
consumer.accept(key, value.toString());
value.setLength(0);
}
readingHeader = false;
key = parseKey(line, lineNumber);
} else {
if (value.length() > 0) {
value.append("\n");
}
value.append(unescape(line));
}
}
if (!key.isEmpty()) {
consumer.accept(key, value.toString());
}
}
}
@NotNull
String unescape(@NotNull String line) {
return line.startsWith(prefixToUnescape) ? line.substring(1) : line;
}
@NotNull
String parseKey(@NotNull String line, int lineNumber) {
if (!line.startsWith(keyPrefix)) {
throw new IllegalArgumentException("Expected key token at line: " + lineNumber + ", got: " + (line.length() < 50 ? line : line.substring(0, 50)));
}
String key = line.substring(keyPrefix.length()).trim();
if (key.isEmpty()) {
throw new IllegalArgumentException("Empty key value at line: " + lineNumber);
}
return key;
}
}