View Javadoc
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   * Created by melody on 2016/11/9.
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          //System.out.println("line=<" + new String(lineBuf, 0, limit) + ">");
50          precedingBackslash = false;
51          while (keyLen < limit) {
52            currentChar = lr.lineBuf[keyLen];
53            //need check if escaped.
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") // this code block is from JDK, we don't want to modify it.
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             // Read the xxxx
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           //The line below is equivalent to calling a
224           //ISO8859-1 decoder.
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           //flip the preceding backslash flag
265           if (currentChar == '\\') {
266             precedingBackslash = !precedingBackslash;
267           } else {
268             precedingBackslash = false;
269           }
270         } else {
271           // reached EOL
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             //skip the leading whitespace characters in following line
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 }