1 importjava.io.BufferedWriter;2 importjava.io.File;3 importjava.io.FileInputStream;4 importjava.io.IOException;5 importjava.io.InputStream;6 importjava.io.InputStreamReader;7 importjava.io.OutputStream;8 importjava.io.OutputStreamWriter;9 importjava.io.Reader;10 importjava.io.Writer;11 importjava.util.Iterator;12 importjava.util.LinkedHashMap;13 importjava.util.Map;14 importjava.util.Properties;15 importjava.util.Set;16
17 /**
18 * 扩展properties工具类19 *20 *@authortangming21 * @date 2017-11-1022 */
23 public classSafeProperties {24
25 /**
26 * 内部属性表27 */
28 private finalProperties props;29
30 /**
31 * 保存key与comment的映射, 同时利用这个映射来保证key的顺序。32 */
33 private final LinkedHashMap keyCommentMap = new LinkedHashMap();34
35 private static final String BLANK = "";36
37 publicSafeProperties() {38 super();39 props = newProperties();40 }41
42 publicSafeProperties(Properties defaults) {43 super();44 props = newProperties(defaults);45 }46
47 /**
48 * 设置一个属性,如果key已经存在,那么将其对应value值覆盖。49 *50 *@paramkey51 *@paramvalue52 *@return
53 */
54 publicString setProperty(String key, String value) {55 returnsetProperty(key, value, BLANK);56 }57
58 /**
59 * 设置一个属性,如果key已经存在,那么将其对应value值覆盖。60 *61 *@paramkey 键62 *@paramvalue 与键对应的值63 *@paramcomment 对键值对的说明64 *@return
65 */
66 public synchronizedString setProperty(String key, String value, String comment) {67 Object oldValue =props.setProperty(key, value);68 if(BLANK.equals(comment)) {69 if (!keyCommentMap.containsKey(key)) {70 keyCommentMap.put(key, comment);71 }72 } else{73 keyCommentMap.put(key, comment);74 }75 return(String) oldValue;76 }77
78 /**
79 * 根据key获取属性表中相应的value。80 *81 *@paramkey82 *@return
83 */
84 publicString getProperty(String key) {85 returnprops.getProperty(key);86 }87
88 /**
89 * 根据key获取属性表中相应的value。 如果没找到相应的value,返回defaultValue。90 *91 *@paramkey92 *@paramdefaultValue93 *@return
94 */
95 publicString getProperty(String key, String defaultValue) {96 returnprops.getProperty(key, defaultValue);97 }98
99 /**
100 * 从一个字符流中读取属性到属性表中101 *102 *@paramreader103 *@throwsIOException104 */
105 public synchronized void load(Reader reader) throwsIOException {106 load0(newLineReader(reader));107 }108
109 /**
110 * 从一个字节流中读取属性到属性表中111 *112 *@paraminStream113 *@throwsIOException114 */
115 public synchronized void load(InputStream inStream) throwsIOException {116 load0(newLineReader(inStream));117 }118
119 /**
120 * 从一个字节流中读取属性到属性表中121 *122 *@paraminStream123 *@paramcharset124 *@throwsIOException125 */
126 public synchronized void load(InputStream inStream, String charset) throwsIOException {127 InputStreamReader reader = newInputStreamReader(inStream, charset);128 load0(newLineReader(reader));129 }130
131 /**
132 * 从一个文件中读取属性到属性表中133 *134 *@paramfile 属性文件135 *@paramcharset 字符集136 *@throwsIOException137 */
138 public synchronized void load(File file, String charset) throwsIOException {139 FileInputStream inputStream = newFileInputStream(file);140 InputStreamReader reader = newInputStreamReader(inputStream, charset);141 load0(newLineReader(reader));142 }143
144 /**
145 * 从一个文件中读取属性到属性表中 默认字符集为utf-8146 *147 *@paramfile 属性文件148 *@throwsIOException149 */
150 public synchronized void load(File file) throwsIOException {151 FileInputStream inputStream = newFileInputStream(file);152 InputStreamReader reader = new InputStreamReader(inputStream, "utf-8");153 load0(newLineReader(reader));154 }155
156 /**
157 * 将属性表中的属性写到字符流里面。158 *159 *@paramwriter160 *@throwsIOException161 */
162 public void store(Writer writer) throwsIOException {163 store0((writer instanceof BufferedWriter) ? (BufferedWriter) writer : new BufferedWriter(writer), false);164 }165
166 /**
167 * 将属性表中的属性写到字节流里面。168 *169 *@paramout170 *@throwsIOException171 */
172 public void store(OutputStream out) throwsIOException {173 store0(new BufferedWriter(new OutputStreamWriter(out, "utf-8")), true);174 }175
176 /**
177 * 如果属性表中某个key对应的value值和参数value相同 那么返回true,否则返回false。178 *179 *@paramvalue180 *@return
181 */
182 public booleancontainsValue(String value) {183 returnprops.containsValue(value);184 }185
186 /**
187 * 如果属性表中存在参数key,返回true,否则返回false。188 *189 *@paramkey190 *@return
191 */
192 public booleancontainsKey(String key) {193 returnprops.containsKey(key);194 }195
196 /**
197 * 获取属性表中键值对数量198 *199 *@return
200 */
201 public intsize() {202 returnprops.size();203 }204
205 /**
206 * 检查属性表是否为空207 *208 *@return
209 */
210 public booleanisEmpty() {211 returnprops.isEmpty();212 }213
214 /**
215 * 清空属性表216 */
217 public synchronized voidclear() {218 props.clear();219 keyCommentMap.clear();220 }221
222 /**
223 * 获取属性表中所有key的集合。224 *225 *@return
226 */
227 public SetpropertyNames() {228 returnprops.stringPropertyNames();229 }230
231 public synchronizedString toString() {232 StringBuffer buffer = newStringBuffer();233 Iterator> kvIter =keyCommentMap.entrySet().iterator();234 buffer.append("[");235 while(kvIter.hasNext()) {236 buffer.append("{");237 Map.Entry entry =kvIter.next();238 String key =entry.getKey();239 String val =getProperty(key);240 String comment =entry.getValue();241 buffer.append("key=" + key + ",value=" + val + ",comment=" +comment);242 buffer.append("}");243 }244 buffer.append("]");245 returnbuffer.toString();246 }247
248 public booleanequals(Object o) {249 //不考虑注释说明是否相同
250 returnprops.equals(o);251 }252
253 public inthashCode() {254 returnprops.hashCode();255 }256
257 private void load0(LineReader lr) throwsIOException {258 char[] convtBuf = new char[1024];259 intlimit;260 intkeyLen;261 intvalueStart;262 charc;263 booleanhasSep;264 booleanprecedingBackslash;265 StringBuffer buffer = newStringBuffer();266
267 while ((limit = lr.readLine()) >= 0) {268 c = 0;269 keyLen = 0;270 valueStart =limit;271 hasSep = false;272 //获取注释
273 c =lr.lineBuf[keyLen];274 if (c == '#' || c == '!') {275 String comment = loadConvert(lr.lineBuf, 1, limit - 1, convtBuf);276 if (buffer.length() > 0) {277 buffer.append("\n");278 }279 buffer.append(comment);280 continue;281 }282 precedingBackslash = false;283 while (keyLen
286 if ((c == '=' || c == ':') && !precedingBackslash) {287 valueStart = keyLen + 1;288 hasSep = true;289 break;290 } else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) {291 valueStart = keyLen + 1;292 break;293 }294 if (c == '\\') {295 precedingBackslash = !precedingBackslash;296 } else{297 precedingBackslash = false;298 }299 keyLen++;300 }301 while (valueStart
316 buffer = newStringBuffer();317 }318 }319
320 /*
321 * 基于java.util.Properties.LineReader进行改造322 *323 * Read in a "logical line" from an InputStream/Reader, skip all comment and blank lines and filter out those leading whitespace characters ( , and ) from the beginning of a "natural line". Method324 * returns the char length of the "logical line" and stores the line in "lineBuf".325 */
326 classLineReader {327 publicLineReader(InputStream inStream) {328 this.inStream =inStream;329 inByteBuf = new byte[8192];330 }331
332 publicLineReader(Reader reader) {333 this.reader =reader;334 inCharBuf = new char[8192];335 }336
337 byte[] inByteBuf;338 char[] inCharBuf;339 char[] lineBuf = new char[1024];340 int inLimit = 0;341 int inOff = 0;342 InputStream inStream;343 Reader reader;344
345 int readLine() throwsIOException {346 int len = 0;347 char c = 0;348
349 boolean skipWhiteSpace = true;350 boolean isNewLine = true;351 boolean appendedLineBegin = false;352 boolean precedingBackslash = false;353 boolean skipLF = false;354
355 while (true) {356 if (inOff >=inLimit) {357 inLimit = (inStream == null) ?reader.read(inCharBuf) : inStream.read(inByteBuf);358 inOff = 0;359 if (inLimit <= 0) {360 if (len == 0) {361 return -1;362 }363 returnlen;364 }365 }366 if (inStream != null) {367 //The line below is equivalent to calling a368 //ISO8859-1 decoder.
369 c = (char) (0xff & inByteBuf[inOff++]);370 } else{371 c = inCharBuf[inOff++];372 }373 if(skipLF) {374 skipLF = false;375 if (c == '\n') {376 continue;377 }378 }379 if(skipWhiteSpace) {380 if (c == ' ' || c == '\t' || c == '\f') {381 continue;382 }383 if (!appendedLineBegin && (c == '\r' || c == '\n')) {384 continue;385 }386 skipWhiteSpace = false;387 appendedLineBegin = false;388 }389 if(isNewLine) {390 isNewLine = false;391 }392
393 if (c != '\n' && c != '\r') {394 lineBuf[len++] =c;395 if (len ==lineBuf.length) {396 int newLength = lineBuf.length * 2;397 if (newLength < 0) {398 newLength =Integer.MAX_VALUE;399 }400 char[] buf = new char[newLength];401 System.arraycopy(lineBuf, 0, buf, 0, lineBuf.length);402 lineBuf =buf;403 }404 //flip the preceding backslash flag
405 if (c == '\\') {406 precedingBackslash = !precedingBackslash;407 } else{408 precedingBackslash = false;409 }410 } else{411 //reached EOL
412 if (len == 0) {413 isNewLine = true;414 skipWhiteSpace = true;415 len = 0;416 continue;417 }418 if (inOff >=inLimit) {419 inLimit = (inStream == null) ?reader.read(inCharBuf) : inStream.read(inByteBuf);420 inOff = 0;421 if (inLimit <= 0) {422 returnlen;423 }424 }425 if(precedingBackslash) {426 len -= 1;427 //skip the leading whitespace characters in following line
428 skipWhiteSpace = true;429 appendedLineBegin = true;430 precedingBackslash = false;431 if (c == '\r') {432 skipLF = true;433 }434 } else{435 returnlen;436 }437 }438 }439 }440 }441
442 /*
443 * Converts encoded \uxxxx to unicode chars and changes special saved chars to their original forms444 */
445 private String loadConvert(char[] in, int off, int len, char[] convtBuf) {446 if (convtBuf.length
458 while (off
464 int value = 0;465 for (int i = 0; i < 4; i++) {466 aChar = in[off++];467 switch(aChar) {468 case '0':469 case '1':470 case '2':471 case '3':472 case '4':473 case '5':474 case '6':475 case '7':476 case '8':477 case '9':478 value = (value << 4) + aChar - '0';479 break;480 case 'a':481 case 'b':482 case 'c':483 case 'd':484 case 'e':485 case 'f':486 value = (value << 4) + 10 + aChar - 'a';487 break;488 case 'A':489 case 'B':490 case 'C':491 case 'D':492 case 'E':493 case 'F':494 value = (value << 4) + 10 + aChar - 'A';495 break;496 default:497 throw new IllegalArgumentException("Malformed \\uxxxx encoding.");498 }499 }500 out[outLen++] = (char) value;501 } else{502 if (aChar == 't')503 aChar = '\t';504 else if (aChar == 'r')505 aChar = '\r';506 else if (aChar == 'n')507 aChar = '\n';508 else if (aChar == 'f')509 aChar = '\f';510 out[outLen++] =aChar;511 }512 } else{513 out[outLen++] = (char) aChar;514 }515 }516 return new String(out, 0, outLen);517 }518
519 private void store0(BufferedWriter bw, boolean escUnicode) throwsIOException {520 synchronized (this) {521 Iterator> kvIter =keyCommentMap.entrySet().iterator();522 while(kvIter.hasNext()) {523 Map.Entry entry =kvIter.next();524 String key =entry.getKey();525 String val =getProperty(key);526 String comment =entry.getValue();527 key = saveConvert(key, true, escUnicode);528 /*
529 * No need to escape embedded and trailing spaces for value, hence pass false to flag.530 */
531 val = saveConvert(val, false, escUnicode);532 if (!comment.equals(BLANK))533 writeComments(bw, comment);534 bw.write(key + "=" +val);535 bw.newLine();536 }537 }538 bw.flush();539 }540
541 private static void writeComments(BufferedWriter bw, String comments) throwsIOException {542 bw.write("#");543 int len =comments.length();544 int current = 0;545 int last = 0;546 while (current '\u00ff' || c == '\n' || c == '\r') {549 if (last !=current)550 bw.write(comments.substring(last, current));551 if (c > '\u00ff') {552 bw.write(c);553 } else{554 bw.newLine();555 if (c == '\r' && current != len - 1 && comments.charAt(current + 1) == '\n') {556 current++;557 }558 if (current == len - 1
559 || (comments.charAt(current + 1) != '#' && comments.charAt(current + 1) != '!'))560 bw.write("#");561 }562 last = current + 1;563 }564 current++;565 }566 if (last !=current)567 bw.write(comments.substring(last, current));568 bw.newLine();569 }570
571 /*
572 * Converts unicodes to encoded \uxxxx and escapes special characters with a preceding slash573 */
574 private String saveConvert(String theString, boolean escapeSpace, booleanescapeUnicode) {575 int len =theString.length();576 int bufLen = len * 2;577 if (bufLen < 0) {578 bufLen =Integer.MAX_VALUE;579 }580 StringBuffer outBuffer = newStringBuffer(bufLen);581
582 for (int x = 0; x < len; x++) {583 char aChar =theString.charAt(x);584 //Handle common case first, selecting largest block that585 //avoids the specials below
586 if ((aChar > 61) && (aChar < 127)) {587 if (aChar == '\\') {588 outBuffer.append('\\');589 outBuffer.append('\\');590 continue;591 }592 outBuffer.append(aChar);593 continue;594 }595 switch(aChar) {596 case ' ':597 if (x == 0 ||escapeSpace)598 outBuffer.append('\\');599 outBuffer.append(' ');600 break;601 case '\t':602 outBuffer.append('\\');603 outBuffer.append('t');604 break;605 case '\n':606 outBuffer.append('\\');607 outBuffer.append('n');608 break;609 case '\r':610 outBuffer.append('\\');611 outBuffer.append('r');612 break;613 case '\f':614 outBuffer.append('\\');615 outBuffer.append('f');616 break;617 case '=': //Fall through
618 case ':': //Fall through
619 case '#': //Fall through
620 case '!':621 outBuffer.append('\\');622 outBuffer.append(aChar);623 break;624 default:625 if (((aChar < 0x0020) || (aChar > 0x007e)) &escapeUnicode) {626 outBuffer.append('\\');627 outBuffer.append('u');628 outBuffer.append(toHex((aChar >> 12) & 0xF));629 outBuffer.append(toHex((aChar >> 8) & 0xF));630 outBuffer.append(toHex((aChar >> 4) & 0xF));631 outBuffer.append(toHex(aChar & 0xF));632 } else{633 outBuffer.append(aChar);634 }635 }636 }637 returnoutBuffer.toString();638 }639
640 /**
641 * Convert a nibble to a hex character642 *643 *@paramnibble the nibble to convert.644 */
645 private static char toHex(intnibble) {646 return hexDigit[(nibble & 0xF)];647 }648
649 /**A table of hex digits*/
650 private static final char[] hexDigit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',651 'F'};652
653 }