@@ -79,31 +79,32 @@ import java.util.Properties;
*/
public class LayoutPreservingProperties extends Properties {
private static final String LS = System.getProperty("line.separator");
/**
* Logical lines have escaping and line continuation taken care of. Comments
* and blank lines are logical lines; they are not removed.
* Logical lines have escaping and line continuation taken care
* of. Comments and blank lines are logical lines; they are not
* removed.
*/
private ArrayList logicalLines = new ArrayList();
/**
* Position in the <code>logicalLines</code> list, keyed by property name.
*/
private HashMap keyedPairLines = new HashMap();
/**
* Flag to indicate that, when we remove a property from the file, we
* also want to remove the comments that precede it.
*/
private boolean removeComments;
/**
* Create a new, empty, Properties collection, with no defaults.
*/
public LayoutPreservingProperties() {
super();
}
/**
* Create a new, empty, Properties collection, with the specified defaults.
* @param defaults the default property values
@@ -113,58 +114,63 @@ public class LayoutPreservingProperties extends Properties {
}
/**
* Returns <code>true</code> if comments are removed along with properties, or
* <code>false</code> otherwise. If <code>true</code>, then when a property is
* removed, the comment preceding it in the original file is removed also.
* @return <code>true</code> if leading comments are removed when a property is
* removed; <code>false</code> otherwise
* Returns <code>true</code> if comments are removed along with
* properties, or <code>false</code> otherwise. If
* <code>true</code>, then when a property is removed, the comment
* preceding it in the original file is removed also.
* @return <code>true</code> if leading comments are removed when
* a property is removed; <code>false</code> otherwise
*/
public boolean isRemoveComments() {
return removeComments;
}
/**
* Sets the behaviour for comments accompanying properties that are being
* removed. If <code>true</code>, then when a property is removed, the comment
* preceding it in the original file is removed also.
* @param val <code>true</code> if leading comments are to be removed when a property is
* removed; <code>false</code> otherwise
* Sets the behaviour for comments accompanying properties that
* are being removed. If <code>true</code>, then when a property
* is removed, the comment preceding it in the original file is
* removed also.
* @param val <code>true</code> if leading comments are to be
* removed when a property is removed; <code>false</code>
* otherwise
*/
public void setRemoveComments(boolean val) {
removeComments = val;
}
public void load(InputStream inStream) throws IOException {
String s = readLines(inStream);
byte[] ba = s.getBytes("ISO-8859-1");
ByteArrayInputStream bais = new ByteArrayInputStream(ba);
super.load(bais);
}
public Object put(Object key, Object value) throws NullPointerException {
Object obj = super.put(key, value);
// the above call will have failed if key or value are null
innerSetProperty(key.toString(), value.toString());
return obj;
}
public Object setProperty(String key, String value) throws NullPointerException {
public Object setProperty(String key, String value)
throws NullPointerException {
Object obj = super.setProperty(key, value);
// the above call will have failed if key or value are null
innerSetProperty(key, value);
return obj;
}
/**
* Store a new key-value pair, or add a new one. The normal functionality is
* taken care of by the superclass in the call to {@link #setProperty}; this
* method takes care of this classes extensions.
* Store a new key-value pair, or add a new one. The normal
* functionality is taken care of by the superclass in the call to
* {@link #setProperty}; this method takes care of this classes
* extensions.
* @param key the key of the property to be stored
* @param value the value to be stored
*/
private void innerSetProperty(String key, String value) {
value = escapeValue(value);
if (keyedPairLines.containsKey(key)) {
Integer i = (Integer) keyedPairLines.get(key);
Pair p = (Pair) logicalLines.get(i.intValue());
@@ -177,13 +183,13 @@ public class LayoutPreservingProperties extends Properties {
logicalLines.add(p);
}
}
public void clear() {
super.clear();
keyedPairLines.clear();
logicalLines.clear();
}
public Object remove(Object key) {
Object obj = super.remove(key);
Integer i = (Integer) keyedPairLines.remove(key);
@@ -195,9 +201,10 @@ public class LayoutPreservingProperties extends Properties {
}
return obj;
}
public Object clone() {
LayoutPreservingProperties dolly = (LayoutPreservingProperties) super.clone();
LayoutPreservingProperties dolly =
(LayoutPreservingProperties) super.clone();
dolly.keyedPairLines = (HashMap) this.keyedPairLines.clone();
dolly.logicalLines = (ArrayList) this.logicalLines.clone();
for (int j = 0; j < dolly.logicalLines.size(); j++) {
@@ -210,7 +217,7 @@ public class LayoutPreservingProperties extends Properties {
}
return dolly;
}
/**
* Echo the lines of the properties (including blanks and comments) to the
* stream.
@@ -232,7 +239,7 @@ public class LayoutPreservingProperties extends Properties {
}
}
}
/**
* Save the properties to a file.
* @param dest the file to write to
@@ -242,15 +249,15 @@ public class LayoutPreservingProperties extends Properties {
store(fos, null);
fos.close();
}
public void store(OutputStream out, String header) throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(out, "ISO-8859-1");
if (header != null) {
osw.write("#" + header + LS);
}
osw.write("#" + (new Date()).toString() + LS);
boolean writtenSep = false;
for (Iterator i = logicalLines.iterator();i.hasNext();) {
LogicalLine line = (LogicalLine) i.next();
@@ -268,46 +275,48 @@ public class LayoutPreservingProperties extends Properties {
}
osw.close();
}
/**
* Reads a properties file into an internally maintained collection of logical
* lines (possibly spanning physcial lines), which make up the comments, blank
* lines and properties of the file.
* Reads a properties file into an internally maintained
* collection of logical lines (possibly spanning physcial lines),
* which make up the comments, blank lines and properties of the
* file.
* @param is the stream from which to read the data
*/
private String readLines(InputStream is) throws IOException {
InputStreamReader isr = new InputStreamReader(is, "ISO-8859-1");
BufferedReader br = new BufferedReader(isr);
if (logicalLines.size() > 0) {
// we add a blank line for spacing
logicalLines.add(new Blank());
}
String s = br.readLine();
boolean continuation = false;
boolean comment = false;
StringBuffer fileBuffer = new StringBuffer();
StringBuffer logicalLineBuffer = new StringBuffer();
while (s != null) {
fileBuffer.append(s).append(LS);
if (continuation) {
// put in the line feed that was removed
s = "\n" + s;
} else {
// could be a comment, if first non-whitespace is a # or !
// could be a comment, if first non-whitespace is a # or !
comment = s.matches("^( |\t|\f)*(#|!).*");
}
// continuation if not a comment and the line ends is an odd number of backslashes
// continuation if not a comment and the line ends is an
// odd number of backslashes
if (!comment) {
continuation = requiresContinuation(s);
}
logicalLineBuffer.append(s);
if (!continuation) {
LogicalLine line = null;
if (comment) {
@@ -331,12 +340,14 @@ public class LayoutPreservingProperties extends Properties {
}
return fileBuffer.toString();
}
/**
* Returns <code>true</code> if the line represented by <code>s</code> is to be continued
* on the next line of the file, or <code>false</code> otherwise.
* Returns <code>true</code> if the line represented by
* <code>s</code> is to be continued on the next line of the file,
* or <code>false</code> otherwise.
* @param s the contents of the line to examine
* @return <code>true</code> if the line is to be continued, <code>false</code> otherwise
* @return <code>true</code> if the line is to be continued,
* <code>false</code> otherwise
*/
private boolean requiresContinuation(String s) {
char[] ca = s.toCharArray();
@@ -348,10 +359,11 @@ public class LayoutPreservingProperties extends Properties {
int tb = ca.length - i - 1;
return tb % 2 == 1;
}
/**
* Unescape the string according to the rules for a Properites file, as laid out in
* the docs for <a href="http://java.sun.com/j2se/1.3/docs/api/java/util/Properties.html">java.util.Properties</a>.
* Unescape the string according to the rules for a Properites
* file, as laid out in the docs for <a
* href="http://java.sun.com/j2se/1.3/docs/api/java/util/Properties.html">java.util.Properties</a>.
* @param s the string to unescape (coming from the source file)
* @return the unescaped string
*/
@@ -367,7 +379,7 @@ public class LayoutPreservingProperties extends Properties {
* Any other slash is ignored, so
* \b becomes 'b'.
*/
char[] ch = new char[s.length() + 1];
s.getChars(0, s.length(), ch, 0);
ch[s.length()] = '\n';
@@ -404,10 +416,10 @@ public class LayoutPreservingProperties extends Properties {
}
return buffy.toString();
}
/**
* Retrieve the unicode character whose code is listed at position <code>i</code>
* in the character array <code>ch</code>.
* Retrieve the unicode character whose code is listed at position
* <code>i</code> in the character array <code>ch</code>.
* @param ch the character array containing the unicode character code
* @return the character extracted
*/
@@ -415,41 +427,46 @@ public class LayoutPreservingProperties extends Properties {
String s = new String(ch, i, 4);
return (char) Integer.parseInt(s, 16);
}
/**
* Escape the string <code>s</code> according to the rules in the docs for
* <a href="http://java.sun.com/j2se/1.3/docs/api/java/util/Properties.html">java.util.Properties</a>.
* Escape the string <code>s</code> according to the rules in the
* docs for <a
* href="http://java.sun.com/j2se/1.3/docs/api/java/util/Properties.html">java.util.Properties</a>.
* @param s the string to escape
* @return the escaped string
*/
private String escapeValue(String s) {
return escape(s, false);
}
/**
* Escape the string <code>s</code> according to the rules in the docs for
* <a href="http://java.sun.com/j2se/1.3/docs/api/java/util/Properties.html">java.util.Properties</a>.
* This method escapes all the whitespace, not just the stuff at the beginning.
* Escape the string <code>s</code> according to the rules in the
* docs for <a
* href="http://java.sun.com/j2se/1.3/docs/api/java/util/Properties.html">java.util.Properties</a>.
* This method escapes all the whitespace, not just the stuff at
* the beginning.
* @param s the string to escape
* @return the escaped string
*/
private String escapeName(String s) {
return escape(s, true);
}
/**
* Escape the string <code>s</code> according to the rules in the docs for
* <a href="http://java.sun.com/j2se/1.3/docs/api/java/util/Properties.html">java.util.Properties</a>.
* Escape the string <code>s</code> according to the rules in the
* docs for <a
* href="http://java.sun.com/j2se/1.3/docs/api/java/util/Properties.html">java.util.Properties</a>.
* @param s the string to escape
* @param escapeAllSpaces if <code>true</code> the method escapes all the spaces,
* if <code>false</code>, it escapes only the leading whitespace
* @param escapeAllSpaces if <code>true</code> the method escapes
* all the spaces, if <code>false</code>, it escapes only the
* leading whitespace
* @return the escaped string
*/
private String escape(String s, boolean escapeAllSpaces) {
if (s == null) {
return null;
}
char[] ch = new char[s.length()];
s.getChars(0, s.length(), ch, 0);
String forEscaping = "\t\f\r\n\\:=#!";
@@ -476,9 +493,10 @@ public class LayoutPreservingProperties extends Properties {
}
return buffy.toString();
}
/**
* Return the unicode escape sequence for a character, in the form \u005CuNNNN.
* Return the unicode escape sequence for a character, in the form
* \u005CuNNNN.
* @param ch the character to encode
* @return the unicode escape sequence
*/
@@ -489,66 +507,68 @@ public class LayoutPreservingProperties extends Properties {
buffy.append(hex);
return buffy.toString();
}
/**
* Remove the comments in the leading up the {@link logicalLines} list leading
* up to line <code>pos</code>.
* Remove the comments in the leading up the {@link logicalLines}
* list leading up to line <code>pos</code>.
* @param pos the line number to which the comments lead
*/
private void removeCommentsEndingAt(int pos) {
/* We want to remove comments preceding this position. Step back counting
* blank lines (call this range B1) until we hit something non-blank. If
* what we hit is not a comment, then exit. If what we hit is a comment,
* then step back counting comment lines (call this range C1). Nullify
* lines in C1 and B1.
/* We want to remove comments preceding this position. Step
* back counting blank lines (call this range B1) until we hit
* something non-blank. If what we hit is not a comment, then
* exit. If what we hit is a comment, then step back counting
* comment lines (call this range C1). Nullify lines in C1 and
* B1.
*/
int end = pos - 1;
// step pos back until it hits something non-blank
for (pos = end; pos > 0; pos--) {
if (!(logicalLines.get(pos) instanceof Blank)) {
break;
}
}
// if the thing it hits is not a comment, then we have nothing to remove
// if the thing it hits is not a comment, then we have nothing
// to remove
if (!(logicalLines.get(pos) instanceof Comment)) {
return;
}
// step back until we hit the start of the comment
for (; pos >= 0; pos--) {
if (!(logicalLines.get(pos) instanceof Comment)) {
break;
}
}
// now we want to delete from pos+1 to end
for (pos++ ;pos <= end; pos++) {
logicalLines.set(pos, null);
}
}
/**
* A logical line of the properties input stream.
*/
private static abstract class LogicalLine {
private String text;
public LogicalLine(String text) {
this.text = text;
}
public void setText(String text) {
this.text = text;
}
public String toString() {
return text;
}
}
/**
* A blank line of the input stream.
*/
@@ -557,7 +577,7 @@ public class LayoutPreservingProperties extends Properties {
super("");
}
}
/**
* A comment line of the input stream.
*/
@@ -566,48 +586,49 @@ public class LayoutPreservingProperties extends Properties {
super(text);
}
}
/**
* A key-value pair from the input stream. This may span more than one physical
* line, but it is constitutes as a single logical line.
* A key-value pair from the input stream. This may span more than
* one physical line, but it is constitutes as a single logical
* line.
*/
private static class Pair extends LogicalLine implements Cloneable {
private String name;
private String value;
private boolean added;
public Pair(String text) {
super(text);
parsePair(text);
}
public Pair(String name, String value) {
this(name + "=" + value);
}
public String getName() {
return name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
setText(name + "=" + value);
}
public boolean isNew() {
return added;
}
public void setNew(boolean val) {
added = val;
}
public Object clone() {
Object dolly = null;
Object dolly = null;
try {
dolly = super.clone();
}
@@ -617,7 +638,7 @@ public class LayoutPreservingProperties extends Properties {
}
return dolly;
}
private void parsePair(String text) {
// need to find first non-escaped '=', ':', '\t' or ' '.
int pos = findFirstSeparator(text);
@@ -633,12 +654,12 @@ public class LayoutPreservingProperties extends Properties {
// trim leading whitespace only
name = stripStart(name, " \t\f");
}
private String stripStart(String s, String chars) {
if (s == null) {
return null;
}
int i = 0;
for (;i < s.length(); i++) {
if (chars.indexOf(s.charAt(i)) == -1) {
@@ -650,29 +671,29 @@ public class LayoutPreservingProperties extends Properties {
}
return s.substring(i);
}
private int findFirstSeparator(String s) {
// Replace double backslashes with underscores so that they don't
// confuse us looking for '\t' or '\=', for example, but they also
// don't change the position of other characters
s = s.replaceAll("\\\\\\\\", "__");
// Replace single backslashes followed by separators, so we don't
// pick them up
s = s.replaceAll("\\\\=", "__");
s = s.replaceAll("\\\\:", "__");
s = s.replaceAll("\\\\ ", "__");
s = s.replaceAll("\\\\t", "__");
// Now only the unescaped separators are left
return indexOfAny(s, " :=\t");
}
private int indexOfAny(String s, String chars) {
if (s == null || chars == null) {
return -1;
}
int p = s.length() + 1;
for (int i = 0; i < chars.length(); i++) {
int x = s.indexOf(chars.charAt(i));