1 package cn.home1.oss.environment.configlint;
2
3 import org.slf4j.Logger;
4 import org.slf4j.LoggerFactory;
5
6 import java.io.BufferedReader;
7 import java.io.FileReader;
8 import java.io.IOException;
9 import java.io.InputStream;
10 import java.io.Reader;
11 import java.util.HashSet;
12 import java.util.Set;
13
14
15
16
17 public class PropertyValidator implements FileValidator {
18
19 private static final Logger log = LoggerFactory.getLogger(PropertyValidator.class);
20
21 public void validate(final String path) {
22 try (final FileReader fileReader = new FileReader(path)) {
23 final StrictPropertyLoader spLoader = new StrictPropertyLoader();
24 spLoader.load(new BufferedReader(fileReader));
25 } catch (final IOException ex) {
26 log.warn("error loading property file {}", path, ex);
27 }
28 }
29
30 private static final class StrictPropertyLoader {
31 private StringBuilder sb = new StringBuilder();
32 private Set<String> uniqSet = new HashSet<>();
33
34 private void load(final Reader reader) throws IOException {
35 final char[] convtBuf = new char[1024];
36 int limit;
37 int keyLen;
38 int valueStart;
39 char currentChar;
40 boolean hasSep;
41 boolean precedingBackslash;
42
43 final LineReader lr = new LineReader(reader);
44 while ((limit = lr.readLine()) >= 0) {
45 keyLen = 0;
46 valueStart = limit;
47 hasSep = false;
48
49
50 precedingBackslash = false;
51 while (keyLen < limit) {
52 currentChar = lr.lineBuf[keyLen];
53
54 if ((currentChar == '=' || currentChar == ':') && !precedingBackslash) {
55 valueStart = keyLen + 1;
56 hasSep = true;
57 break;
58 } else if ((currentChar == ' ' || currentChar == '\t' || currentChar == '\f') && !precedingBackslash) {
59 valueStart = keyLen + 1;
60 break;
61 }
62 if (currentChar == '\\') {
63 precedingBackslash = !precedingBackslash;
64 } else {
65 precedingBackslash = false;
66 }
67 keyLen++;
68 }
69 while (valueStart < limit) {
70 currentChar = lr.lineBuf[valueStart];
71 if (currentChar != ' ' && currentChar != '\t' && currentChar != '\f') {
72 if (!hasSep && (currentChar == '=' || currentChar == ':')) {
73 hasSep = true;
74 } else {
75 break;
76 }
77 }
78 valueStart++;
79 }
80 final String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
81 final String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf);
82 if (log.isTraceEnabled()) {
83 log.trace("found key: {}, value: {}", key, value);
84 }
85
86 if (this.uniqSet.contains(key)) {
87 this.sb.append("duplicated key: ").append(key).append("\n");
88 } else {
89 this.uniqSet.add(key);
90 }
91 }
92
93 if (this.sb.length() != 0) {
94 throw new IllegalArgumentException(this.sb.toString());
95 }
96 }
97
98 @SuppressWarnings("squid:S1226")
99 private String loadConvert(final char[] in, int off, final int len, char[] convtBuf) {
100 if (convtBuf.length < len) {
101 int newLen = len * 2;
102 if (newLen < 0) {
103 newLen = Integer.MAX_VALUE;
104 }
105 convtBuf = new char[newLen];
106 }
107 char oneChar;
108 char[] out = convtBuf;
109 int outLen = 0;
110 int end = off + len;
111
112 while (off < end) {
113 oneChar = in[off++];
114 if (oneChar == '\\') {
115 oneChar = in[off++];
116 if (oneChar == 'u') {
117
118 int value = 0;
119 for (int i = 0; i < 4; i++) {
120 oneChar = in[off++];
121 switch (oneChar) {
122 case '0':
123 case '1':
124 case '2':
125 case '3':
126 case '4':
127 case '5':
128 case '6':
129 case '7':
130 case '8':
131 case '9':
132 value = (value << 4) + oneChar - '0';
133 break;
134 case 'a':
135 case 'b':
136 case 'c':
137 case 'd':
138 case 'e':
139 case 'f':
140 value = (value << 4) + 10 + oneChar - 'a';
141 break;
142 case 'A':
143 case 'B':
144 case 'C':
145 case 'D':
146 case 'E':
147 case 'F':
148 value = (value << 4) + 10 + oneChar - 'A';
149 break;
150 default:
151 throw new IllegalArgumentException(
152 "Malformed \\uxxxx encoding.");
153 }
154 }
155 out[outLen++] = (char) value;
156 } else {
157 if (oneChar == 't') {
158 oneChar = '\t';
159 } else if (oneChar == 'r') {
160 oneChar = '\r';
161 } else if (oneChar == 'n') {
162 oneChar = '\n';
163 } else if (oneChar == 'f') {
164 oneChar = '\f';
165 }
166 out[outLen++] = oneChar;
167 }
168 } else {
169 out[outLen++] = oneChar;
170 }
171 }
172 return new String(out, 0, outLen);
173 }
174 }
175
176 static final class LineReader {
177
178 public LineReader(final InputStream inStream) {
179 this.inStream = inStream;
180 this.inByteBuf = new byte[8192];
181 }
182
183 public LineReader(final Reader reader) {
184 this.reader = reader;
185 this.inCharBuf = new char[8192];
186 }
187
188 byte[] inByteBuf;
189 char[] inCharBuf;
190 char[] lineBuf = new char[1024];
191 int inLimit = 0;
192 int inOff = 0;
193 InputStream inStream;
194 Reader reader;
195
196 int readLine() throws IOException {
197 int len = 0;
198 char currentChar;
199
200 boolean skipWhiteSpace = true;
201 boolean isCommentLine = false;
202 boolean isNewLine = true;
203 boolean appendedLineBegin = false;
204 boolean precedingBackslash = false;
205 boolean skipLf = false;
206
207 while (true) {
208 if (inOff >= inLimit) {
209 inLimit = (inStream == null) ? reader.read(inCharBuf)
210 : inStream.read(inByteBuf);
211 inOff = 0;
212 if (inLimit <= 0) {
213 if (len == 0 || isCommentLine) {
214 return -1;
215 }
216 if (precedingBackslash) {
217 len--;
218 }
219 return len;
220 }
221 }
222 if (inStream != null) {
223
224
225 currentChar = (char) (0xff & inByteBuf[inOff++]);
226 } else {
227 currentChar = inCharBuf[inOff++];
228 }
229 if (skipLf) {
230 skipLf = false;
231 if (currentChar == '\n') {
232 continue;
233 }
234 }
235 if (skipWhiteSpace) {
236 if (currentChar == ' ' || currentChar == '\t' || currentChar == '\f') {
237 continue;
238 }
239 if (!appendedLineBegin && (currentChar == '\r' || currentChar == '\n')) {
240 continue;
241 }
242 skipWhiteSpace = false;
243 appendedLineBegin = false;
244 }
245 if (isNewLine) {
246 isNewLine = false;
247 if (currentChar == '#' || currentChar == '!') {
248 isCommentLine = true;
249 continue;
250 }
251 }
252
253 if (currentChar != '\n' && currentChar != '\r') {
254 lineBuf[len++] = currentChar;
255 if (len == lineBuf.length) {
256 int newLength = lineBuf.length * 2;
257 if (newLength < 0) {
258 newLength = Integer.MAX_VALUE;
259 }
260 char[] buf = new char[newLength];
261 System.arraycopy(lineBuf, 0, buf, 0, lineBuf.length);
262 lineBuf = buf;
263 }
264
265 if (currentChar == '\\') {
266 precedingBackslash = !precedingBackslash;
267 } else {
268 precedingBackslash = false;
269 }
270 } else {
271
272 if (isCommentLine || len == 0) {
273 isCommentLine = false;
274 isNewLine = true;
275 skipWhiteSpace = true;
276 len = 0;
277 continue;
278 }
279 if (inOff >= inLimit) {
280 inLimit = (inStream == null)
281 ? reader.read(inCharBuf)
282 : inStream.read(inByteBuf);
283 inOff = 0;
284 if (inLimit <= 0) {
285 if (precedingBackslash) {
286 len--;
287 }
288 return len;
289 }
290 }
291 if (precedingBackslash) {
292 len -= 1;
293
294 skipWhiteSpace = true;
295 appendedLineBegin = true;
296 precedingBackslash = false;
297 if (currentChar == '\r') {
298 skipLf = true;
299 }
300 } else {
301 return len;
302 }
303 }
304 }
305 }
306 }
307 }