packagecom.vrv.util;importjava.io.BufferedWriter;importjava.io.File;importjava.io.FileInputStream;importjava.io.IOException;importjava.io.InputStream;importjava.io.InputStreamReader;importjava.io.OutputStream;importjava.io.OutputStreamWriter;importjava.io.Reader;importjava.io.Writer;importjava.util.Iterator;importjava.util.LinkedHashMap;importjava.util.Map;importjava.util.Properties;importjava.util.Set;/*** CommentedProperties
* 针对Properties进行扩展的工具类
*
* 扩展的两个主要功能:
* 1.对Properties文件中注释的保存。
* CommentedProperties在读取和保存Properties文件时,会将其注释
* 一起读取保存。CommentedProperties中会提供方法来根据key获取
* 相应注释。在CommentedProperties中添加一个K-V对时,也会提供
* 添加相应注释的方法。
*
* 2.对Properties文件中Key值顺序的保证。
* CommentedProperties会保证Key的顺序。从一个Properties文件中
* 读取所有K-V对,保存到另一个Properties文件时,Key的顺序不会
* 改变。
*
*
*@authorBrokenDreams*/
public classCommentedProperties{/*** 内部属性表*/
private finalProperties props;/*** 保存key与comment的映射,
* 同时利用这个映射来保证key的顺序。*/
private final LinkedHashMap keyCommentMap = new LinkedHashMap();private static final String BLANK = "";publicCommentedProperties() {super();
props= newProperties();
}publicCommentedProperties(Properties defaults) {super();
props= newProperties(defaults);
}/*** 设置一个属性,如果key已经存在,那么将其对应value值覆盖。
*@paramkey
*@paramvalue
*@return
*/
publicString setProperty(String key, String value) {returnsetProperty(key, value, BLANK);
}/*** 设置一个属性,如果key已经存在,那么将其对应value值覆盖。
*
*@paramkey 键
*@paramvalue 与键对应的值
*@paramcomment 对键值对的说明
*@return
*/
public synchronizedString setProperty(String key, String value, String comment){
Object oldValue=props.setProperty(key, value);if(BLANK.equals(comment)){if(!keyCommentMap.containsKey(key)){
keyCommentMap.put(key, comment);
}
}else{
keyCommentMap.put(key, comment);
}return(String)oldValue;
}/*** 根据key获取属性表中相应的value。
*
*@paramkey
*@return
*/
publicString getProperty(String key) {returnprops.getProperty(key);
}/*** 根据key获取属性表中相应的value。
* 如果没找到相应的value,返回defaultValue。
*
*@paramkey
*@paramdefaultValue
*@return
*/
publicString getProperty(String key, String defaultValue) {returnprops.getProperty(key, defaultValue);
}/*** 从一个字符流中读取属性到属性表中
*
*@paramreader
*@throwsIOException*/
public synchronized void load(Reader reader) throwsIOException {
load0(newLineReader(reader));
}/*** 从一个字节流中读取属性到属性表中
*
*@paraminStream
*@throwsIOException*/
public synchronized void load(InputStream inStream) throwsIOException {
load0(newLineReader(inStream));
}/*** 从一个字节流中读取属性到属性表中
*
*@paraminStream
*@paramcharset
*@throwsIOException*/
public synchronized void load(InputStream inStream, String charset) throwsIOException {
InputStreamReader reader= newInputStreamReader(inStream, charset);
load0(newLineReader(reader));
}/*** 从一个文件中读取属性到属性表中
*
*@paramfile 属性文件
*@paramcharset 字符集
*@throwsIOException*/
public synchronized void load(File file, String charset) throwsIOException {
FileInputStream inputStream= newFileInputStream(file);
InputStreamReader reader= newInputStreamReader(inputStream, charset);
load0(newLineReader(reader));
}/*** 从一个文件中读取属性到属性表中
* 默认字符集为utf-8
*
*@paramfile 属性文件
*@throwsIOException*/
public synchronized void load(File file) throwsIOException {
FileInputStream inputStream= newFileInputStream(file);
InputStreamReader reader= new InputStreamReader(inputStream, "utf-8");
load0(newLineReader(reader));
}/*** 将属性表中的属性写到字符流里面。
*
*@paramwriter
*@throwsIOException*/
public void store(Writer writer) throwsIOException {
store0((writerinstanceof BufferedWriter)?(BufferedWriter)writer
:new BufferedWriter(writer),false);
}/*** 将属性表中的属性写到字节流里面。
*
*@paramout
*@throwsIOException*/
public void store(OutputStream out) throwsIOException {
store0(new BufferedWriter(new OutputStreamWriter(out, "utf-8")),true);
}/*** 如果属性表中某个key对应的value值和参数value相同
* 那么返回true,否则返回false。
*
*@paramvalue
*@return
*/
public booleancontainsValue(String value) {returnprops.containsValue(value);
}/*** 如果属性表中存在参数key,返回true,否则返回false。
*
*@paramkey
*@return
*/
public booleancontainsKey(String key) {returnprops.containsKey(key);
}/*** 获取属性表中键值对数量
*@return
*/
public intsize() {returnprops.size();
}/*** 检查属性表是否为空
*@return
*/
public booleanisEmpty() {returnprops.isEmpty();
}/*** 清空属性表*/
public synchronized voidclear() {
props.clear();
keyCommentMap.clear();
}/*** 获取属性表中所有key的集合。
*
*@return
*/
public SetpropertyNames() {returnprops.stringPropertyNames();
}public synchronizedString toString() {
StringBuffer buffer= newStringBuffer();
Iterator> kvIter =keyCommentMap.entrySet().iterator();
buffer.append("[");while(kvIter.hasNext()){
buffer.append("{");
Map.Entry entry =kvIter.next();
String key=entry.getKey();
String val=getProperty(key);
String comment=entry.getValue();
buffer.append("key="+key+",value="+val+",comment="+comment);
buffer.append("}");
}
buffer.append("]");returnbuffer.toString();
}public booleanequals(Object o) {//不考虑注释说明是否相同
returnprops.equals(o);
}public inthashCode() {returnprops.hashCode();
}private void load0(LineReader lr) throwsIOException {char[] convtBuf = new char[1024];intlimit;intkeyLen;intvalueStart;charc;booleanhasSep;booleanprecedingBackslash;
StringBuffer buffer= newStringBuffer();while ((limit = lr.readLine()) >= 0) {
c= 0;
keyLen= 0;
valueStart=limit;
hasSep= false;//获取注释
c =lr.lineBuf[keyLen];if(c == '#' || c == '!'){
String comment= loadConvert(lr.lineBuf, 1, limit - 1, convtBuf);if(buffer.length() > 0){
buffer.append("\n");
}
buffer.append(comment);continue;
}
precedingBackslash= false;while (keyLen
c=lr.lineBuf[keyLen];//need check if escaped.
if ((c == '=' || c == ':') && !precedingBackslash) {
valueStart= keyLen + 1;
hasSep= true;break;
}else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) {
valueStart= keyLen + 1;break;
}if (c == '\\') {
precedingBackslash= !precedingBackslash;
}else{
precedingBackslash= false;
}
keyLen++;
}while (valueStart
c=lr.lineBuf[valueStart];if (c != ' ' && c != '\t' && c != '\f') {if (!hasSep && (c == '=' || c == ':')) {
hasSep= true;
}else{break;
}
}
valueStart++;
}
String key= loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
String value= loadConvert(lr.lineBuf, valueStart, limit -valueStart, convtBuf);
setProperty(key, value, buffer.toString());//reset buffer
buffer = newStringBuffer();
}
}/** 基于java.util.Properties.LineReader进行改造
*
* Read in a "logical line" from an InputStream/Reader, skip all comment
* and blank lines and filter out those leading whitespace characters
* (\u0020, \u0009 and \u000c) from the beginning of a "natural line".
* Method returns the char length of the "logical line" and stores
* the line in "lineBuf".*/
classLineReader {publicLineReader(InputStream inStream) {this.inStream =inStream;
inByteBuf= new byte[8192];
}publicLineReader(Reader reader) {this.reader =reader;
inCharBuf= new char[8192];
}byte[] inByteBuf;char[] inCharBuf;char[] lineBuf = new char[1024];int inLimit = 0;int inOff = 0;
InputStream inStream;
Reader reader;int readLine() throwsIOException {int len = 0;char c = 0;boolean skipWhiteSpace = true;boolean isNewLine = true;boolean appendedLineBegin = false;boolean precedingBackslash = false;boolean skipLF = false;while (true) {if (inOff >=inLimit) {
inLimit= (inStream==null)?reader.read(inCharBuf)
:inStream.read(inByteBuf);
inOff= 0;if (inLimit <= 0) {if (len == 0) {return -1;
}returnlen;
}
}if (inStream != null) {//The line below is equivalent to calling a//ISO8859-1 decoder.
c = (char) (0xff & inByteBuf[inOff++]);
}else{
c= inCharBuf[inOff++];
}if(skipLF) {
skipLF= false;if (c == '\n') {continue;
}
}if(skipWhiteSpace) {if (c == ' ' || c == '\t' || c == '\f') {continue;
}if (!appendedLineBegin && (c == '\r' || c == '\n')) {continue;
}
skipWhiteSpace= false;
appendedLineBegin= false;
}if(isNewLine) {
isNewLine= false;
}if (c != '\n' && c != '\r') {
lineBuf[len++] =c;if (len ==lineBuf.length) {int newLength = lineBuf.length * 2;if (newLength < 0) {
newLength=Integer.MAX_VALUE;
}char[] buf = new char[newLength];
System.arraycopy(lineBuf,0, buf, 0, lineBuf.length);
lineBuf=buf;
}//flip the preceding backslash flag
if (c == '\\') {
precedingBackslash= !precedingBackslash;
}else{
precedingBackslash= false;
}
}else{//reached EOL
if (len == 0) {
isNewLine= true;
skipWhiteSpace= true;
len= 0;continue;
}if (inOff >=inLimit) {
inLimit= (inStream==null)?reader.read(inCharBuf)
:inStream.read(inByteBuf);
inOff= 0;if (inLimit <= 0) {returnlen;
}
}if(precedingBackslash) {
len-= 1;//skip the leading whitespace characters in following line
skipWhiteSpace = true;
appendedLineBegin= true;
precedingBackslash= false;if (c == '\r') {
skipLF= true;
}
}else{returnlen;
}
}
}
}
}/** Converts encoded \uxxxx to unicode chars
* and changes special saved chars to their original forms*/
private String loadConvert (char[] in, int off, int len, char[] convtBuf) {if (convtBuf.length
newLen=Integer.MAX_VALUE;
}
convtBuf= new char[newLen];
}charaChar;char[] out =convtBuf;int outLen = 0;int end = off +len;while (off
aChar= in[off++];if (aChar == '\\') {
aChar= in[off++];if(aChar == 'u') {//Read the xxxx
int value=0;for (int i=0; i<4; i++) {
aChar= in[off++];switch(aChar) {case '0': case '1': case '2': case '3': case '4':case '5': case '6': case '7': case '8': case '9':
value= (value << 4) + aChar - '0';break;case 'a': case 'b': case 'c':case 'd': case 'e': case 'f':
value= (value << 4) + 10 + aChar - 'a';break;case 'A': case 'B': case 'C':case 'D': case 'E': case 'F':
value= (value << 4) + 10 + aChar - 'A';break;default:throw newIllegalArgumentException("Malformed \\uxxxx encoding.");
}
}
out[outLen++] = (char)value;
}else{if (aChar == 't') aChar = '\t';else if (aChar == 'r') aChar = '\r';else if (aChar == 'n') aChar = '\n';else if (aChar == 'f') aChar = '\f';
out[outLen++] =aChar;
}
}else{
out[outLen++] = (char)aChar;
}
}return new String (out, 0, outLen);
}private void store0(BufferedWriter bw, booleanescUnicode)throwsIOException{synchronized (this) {
Iterator> kvIter =keyCommentMap.entrySet().iterator();while(kvIter.hasNext()){
Map.Entry entry =kvIter.next();
String key=entry.getKey();
String val=getProperty(key);
String comment=entry.getValue();
key= saveConvert(key, true, escUnicode);/*No need to escape embedded and trailing spaces for value, hence
* pass false to flag.*/val= saveConvert(val, false, escUnicode);if(!comment.equals(BLANK))
writeComments(bw, comment);
bw.write(key+ "=" +val);
bw.newLine();
}
}
bw.flush();
}private static voidwriteComments(BufferedWriter bw, String comments)throwsIOException {
bw.write("#");int len =comments.length();int current = 0;int last = 0;while (current '\u00ff' || c == '\n' || c == '\r') {if (last !=current)
bw.write(comments.substring(last, current));if (c > '\u00ff') {
bw.write(c);
}else{
bw.newLine();if (c == '\r' &¤t!= len - 1 &&comments.charAt(current+ 1) == '\n') {
current++;
}if (current == len - 1 ||(comments.charAt(current+ 1) != '#' &&comments.charAt(current+ 1) != '!'))
bw.write("#");
}
last= current + 1;
}
current++;
}if (last !=current)
bw.write(comments.substring(last, current));
bw.newLine();
}/** Converts unicodes to encoded \uxxxx and escapes
* special characters with a preceding slash*/
privateString saveConvert(String theString,booleanescapeSpace,booleanescapeUnicode) {int len =theString.length();int bufLen = len * 2;if (bufLen < 0) {
bufLen=Integer.MAX_VALUE;
}
StringBuffer outBuffer= newStringBuffer(bufLen);for(int x=0; x
if ((aChar > 61) && (aChar < 127)) {if (aChar == '\\') {
outBuffer.append('\\'); outBuffer.append('\\');continue;
}
outBuffer.append(aChar);continue;
}switch(aChar) {case ' ':if (x == 0 ||escapeSpace)
outBuffer.append('\\');
outBuffer.append(' ');break;case '\t':outBuffer.append('\\'); outBuffer.append('t');break;case '\n':outBuffer.append('\\'); outBuffer.append('n');break;case '\r':outBuffer.append('\\'); outBuffer.append('r');break;case '\f':outBuffer.append('\\'); outBuffer.append('f');break;case '=': //Fall through
case ':': //Fall through
case '#': //Fall through
case '!':
outBuffer.append('\\'); outBuffer.append(aChar);break;default:if (((aChar < 0x0020) || (aChar > 0x007e)) &escapeUnicode ) {
outBuffer.append('\\');
outBuffer.append('u');
outBuffer.append(toHex((aChar>> 12) & 0xF));
outBuffer.append(toHex((aChar>> 8) & 0xF));
outBuffer.append(toHex((aChar>> 4) & 0xF));
outBuffer.append(toHex( aChar& 0xF));
}else{
outBuffer.append(aChar);
}
}
}returnoutBuffer.toString();
}/*** Convert a nibble to a hex character
*@paramnibble the nibble to convert.*/
private static char toHex(intnibble) {return hexDigit[(nibble & 0xF)];
}/**A table of hex digits*/
private static final char[] hexDigit ={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
}