/*
* This software is distributed under the terms of the FSF
* Gnu Lesser General Public License (see lgpl.txt).
*
* This program is distributed WITHOUT ANY WARRANTY. See the
* GNU General Public License for more details.
*/
package com.scooterframework.common.util;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Vector;
/**
* The <tt>OrderedProperties</tt> class extends <tt>Properties</tt> class.
* The order of properties are preserved.
*
* @author (Fei) John Chen
*/
public class OrderedProperties extends Properties {
/**
* Generated serialVersionUID
*/
private static final long serialVersionUID = 5913286672979011301L;
private static final String commentChars = "#!";
private static final String separatorChars = "=:";
/**
* Holder of keys.
*/
protected List<Object> keys;
/**
* Creates an empty property list with no default values.
*/
public OrderedProperties() {
this(null);
}
public int hashCode() {
return super.hashCode();
}
public boolean equals(Object obj) {
return super.equals(obj);
}
/**
* Creates an empty property list with the specified defaults.
*
* @param defaults the defaults.
*/
public OrderedProperties(Properties defaults) {
super(defaults);
keys = new ArrayList<Object>();
if (defaults != null) {
Iterator<Object> it = defaults.keySet().iterator();
while(it.hasNext()) {
keys.add(it.next());
}
}
}
/**
* Returns an enumeration of keys.
*
* @return an enumeration of keys.
*/
public Enumeration<Object> keys() {
Vector<Object> v = new Vector<Object>(keys);
return v.elements();
}
/**
* Returns an iterator of keys.
*
* @return an iterator of keys.
*/
public Iterator<Object> keyIterator() {
return keys.iterator();
}
/**
* Returns a list of keys.
*
* @return a list of keys.
*/
public List<Object> keyList() {
return keys;
}
/**
* Reads a property list (key and element pairs) from an input stream
* with "utf-8" encoding.
*
* <p>
* The input stream remains open after this method returns.</p>
*
* @param inStream an input stream.
* @throws IOException if an error occurred when reading from
* the input stream.
*/
public void load(InputStream inStream) throws IOException {
load(inStream, "utf-8");
}
/**
* Reads a property list (key and element pairs) from an input stream.
*
* <p>
* The input stream remains open after this method returns.</p>
*
* @param is an input stream.
* @param encoding a character encoding scheme.
* @throws IOException if an error occurred when reading from
* the input stream.
*/
public void load(InputStream is, String encoding)
throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(is, encoding));
String lastLine = "";
boolean lastLineUncomplete = false;
while (true) {
String line = br.readLine();
if (line == null) break;
line = line.trim();
if (line.length() == 0) {
continue;
}
if (isCommentLine(line)) continue;
if (lastLineUncomplete) {
line = lastLine + line;
}
if (line.endsWith("\\")) {
lastLineUncomplete = true;
lastLine = line.substring(0, line.length() - 1);
}
else {
lastLineUncomplete = false;
lastLine = "";
}
if (!lastLineUncomplete) {
int separatorIndex = separatorIndex(line);
if (separatorIndex == -1) {
put(line, "");
}
else {
String key = line.substring(0, separatorIndex);
String value = line.substring(separatorIndex + 1);
put(key.trim(), value.trim());
}
}
}
}
private boolean isCommentLine(String line) {
char firstChar = line.charAt(0);
return (commentChars.indexOf(firstChar) != -1)?true:false;
}
private int separatorIndex(String line) {
int sep = -1;
int len = separatorChars.length();
for (int i = 0; i < len; i++) {
char c = separatorChars.charAt(i);
int findIndex = line.indexOf(c);
if (findIndex != -1) {
sep = findIndex;
break;
}
}
return sep;
}
/**
*
* Writes a property list (key and element pairs) to an output stream
* with "utf-8" encoding.
*
* <p>
* The output stream remains open after this method returns.</p>
*
* @param os an output stream.
* @param header a description of the property list.
* @throws IOException if an error occurred when writing to
* the output stream.
*/
public void store(OutputStream os, String header)
throws IOException {
store(os, header, "utf-8");
}
/**
*
* Writes a property list (key and element pairs) to an output stream.
*
* <p>
* The output stream remains open after this method returns.</p>
*
* @param os an output stream.
* @param header a description of the property list.
* @param encoding a character encoding scheme.
* @throws IOException if an error occurred when writing to
* the output stream.
*/
public void store(OutputStream os, String header, String encoding)
throws IOException {
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os, encoding));
if (header != null) {
bw.write("#" + header);
bw.newLine();
}
for (Enumeration<Object> e = keys(); e.hasMoreElements();) {
Object key = e.nextElement();
Object val = get(key);
bw.write(key + "=" + ((val != null)?storeConvert(val.toString()):null));
bw.newLine();
}
bw.flush();
}
/**
* Returns an enumeration of all the keys in this property list. The
* names are in the same order as they are added to the property. The
* value returned is the result of the <tt>OrderedProperties</tt> call
* to <tt>keys</tt>.
*
* @return an enumeration of all the keys in this property list.
*/
public Enumeration<Object> propertyNames() {
return keys();
}
/**
* Calls the <tt>OrderedProperties</tt> method <tt>put</tt>. Provided
* for parallelism with the <tt>getProperty</tt> method. Enforces use of
* strings for property keys and values. The value returned is the
* result of the <tt>OrderedProperties</tt> call to <tt>put</tt>.
*
* @param key the key to be placed into this property list.
* @param value the value corresponding to <tt>key</tt>.
* @return the previous value of the specified key in this property
* list, or <tt>null</tt> if it did not have one.
*/
public Object setProperty(String key, String value) {
return put(key, value);
}
/**
* <p>
* Maps the specified <tt>key</tt> to the specified
* <tt>value</tt> in this ordered properties. Neither the key nor
* the value can be <tt>null</tt>. </p>
*
* <p>
* The value can be retrieved by calling the <tt>get</tt> method
* with a key that is equal to the original key. </p>
*
* @param key a key.
* @param value a value.
* @return the previous value of the specified key in this properties,
* or <tt>null</tt> if it did not have one.
* @exception NullPointerException if the key or value is
* <tt>null</tt>.
* @see Object#equals(Object)
* @see #get(Object)
*/
public Object put(Object key, Object value) {
if (!keys.contains(key)) keys.add(key);
if (value != null) {
value = loadConvert(value.toString());
}
return super.put(key, value);
}
private String loadConvert(String s) {
if (s != null) {
s = StringUtil.replace(s, "\\\\", "\\");
}
return s;
}
private String storeConvert(String s) {
if (s != null) {
s = StringUtil.replace(s, "\\", "\\\\");
}
return s;
}
/**
* Clears this property so that it contains no keys.
*/
public void clear() {
keys.clear();
super.clear();
}
}