|
|
@@ -0,0 +1,689 @@ |
|
|
|
/*
|
|
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
|
|
* this work for additional information regarding copyright ownership.
|
|
|
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
|
|
* (the "License"); you may not use this file except in compliance with
|
|
|
|
* the License. You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
package org.apache.tools.ant.util;
|
|
|
|
|
|
|
|
import java.io.BufferedReader;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.FileOutputStream;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStream;
|
|
|
|
import java.io.InputStreamReader;
|
|
|
|
import java.io.OutputStream;
|
|
|
|
import java.io.OutputStreamWriter;
|
|
|
|
import java.io.PrintStream;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Date;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.Iterator;
|
|
|
|
import java.util.Properties;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* <p>A Properties collection which preserves comments and whitespace
|
|
|
|
* present in the input stream from which it was loaded.</p>
|
|
|
|
* <p>The class defers the usual work of the <a href="http://java.sun.com/j2se/1.3/docs/api/java/util/Properties.html">java.util.Properties</a>
|
|
|
|
* class to there, but it also keeps track of the contents of the
|
|
|
|
* input stream from which it was loaded (if applicable), so that in can
|
|
|
|
* write out the properties in as close a form as possible to the input.</p>
|
|
|
|
* If no changes occur to property values, the output should be the same
|
|
|
|
* as the input, except for the leading date stamp, as normal for a
|
|
|
|
* properties file. Properties added are appended to the file. Properties
|
|
|
|
* whose values are changed are changed in place. Properties that are
|
|
|
|
* removed are excised. If the <code>removeComments</code> flag is set,
|
|
|
|
* then the comments immediately preceding the property are also removed.</p>
|
|
|
|
* <p>If a second set of properties is loaded into an existing set, the
|
|
|
|
* lines of the second set are added to the end. Note however, that if a
|
|
|
|
* property already stored is present in a stream subsequently loaded, then
|
|
|
|
* that property is removed before the new value is set. For example,
|
|
|
|
* consider the file</p>
|
|
|
|
* <pre> # the first line
|
|
|
|
* alpha=one
|
|
|
|
*
|
|
|
|
* # the second line
|
|
|
|
* beta=two</pre>
|
|
|
|
* <p>This file is loaded, and then the following is also loaded into the
|
|
|
|
* same <code>LayoutPreservingProperties</code> object</p>
|
|
|
|
* <pre> # association
|
|
|
|
* beta=band
|
|
|
|
*
|
|
|
|
* # and finally
|
|
|
|
* gamma=rays</pre>
|
|
|
|
* </p>The resulting collection sequence of logical lines depends on whether
|
|
|
|
* or not <code>removeComments</code> was set at the time the second stream
|
|
|
|
* is loaded. If it is set, then the resulting list of lines is</p>
|
|
|
|
* <pre> # the first line
|
|
|
|
* alpha=one
|
|
|
|
*
|
|
|
|
* # association
|
|
|
|
* beta=band
|
|
|
|
*
|
|
|
|
* # and finally
|
|
|
|
* gamma=rays</pre>
|
|
|
|
* <p>If the flag is not set, then the comment "the second line" is retained,
|
|
|
|
* although the key-value pair <code>beta=two</code> is removed.</p>
|
|
|
|
*/
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
public LayoutPreservingProperties(Properties defaults) {
|
|
|
|
super(defaults);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
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 {
|
|
|
|
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.
|
|
|
|
* @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());
|
|
|
|
p.setValue(value);
|
|
|
|
} else {
|
|
|
|
key = escapeName(key);
|
|
|
|
Pair p = new Pair(key, value);
|
|
|
|
p.setNew(true);
|
|
|
|
keyedPairLines.put(key, new Integer(logicalLines.size()));
|
|
|
|
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);
|
|
|
|
if (null != i) {
|
|
|
|
if (removeComments) {
|
|
|
|
removeCommentsEndingAt(i.intValue());
|
|
|
|
}
|
|
|
|
logicalLines.set(i.intValue(), null);
|
|
|
|
}
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Object 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++) {
|
|
|
|
LogicalLine line = (LogicalLine) dolly.logicalLines.get(j);
|
|
|
|
if (line instanceof Pair) {
|
|
|
|
Pair p = (Pair) line;
|
|
|
|
dolly.logicalLines.set(j, p.clone());
|
|
|
|
}
|
|
|
|
// no reason to clone other lines are they are immutable
|
|
|
|
}
|
|
|
|
return dolly;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Echo the lines of the properties (including blanks and comments) to the
|
|
|
|
* stream.
|
|
|
|
* @param out the stream to write to
|
|
|
|
*/
|
|
|
|
public void listLines(PrintStream out) {
|
|
|
|
out.println("-- logical lines --");
|
|
|
|
Iterator i = logicalLines.iterator();
|
|
|
|
while (i.hasNext()) {
|
|
|
|
LogicalLine line = (LogicalLine) i.next();
|
|
|
|
if (line instanceof Blank) {
|
|
|
|
out.println("blank: \"" + line + "\"");
|
|
|
|
}
|
|
|
|
else if (line instanceof Comment) {
|
|
|
|
out.println("comment: \"" + line + "\"");
|
|
|
|
}
|
|
|
|
else if (line instanceof Pair) {
|
|
|
|
out.println("pair: \"" + line + "\"");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Save the properties to a file.
|
|
|
|
* @param dest the file to write to
|
|
|
|
*/
|
|
|
|
public void saveAs(File dest) throws IOException {
|
|
|
|
FileOutputStream fos = new FileOutputStream(dest);
|
|
|
|
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();
|
|
|
|
if (line instanceof Pair) {
|
|
|
|
if (((Pair)line).isNew()) {
|
|
|
|
if (!writtenSep) {
|
|
|
|
osw.write(LS);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
osw.write(line.toString() + LS);
|
|
|
|
}
|
|
|
|
else if (line != null) {
|
|
|
|
osw.write(line.toString() + LS);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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.
|
|
|
|
* @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 !
|
|
|
|
comment = s.matches("^( |\t|\f)*(#|!).*");
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
|
|
|
line = new Comment(logicalLineBuffer.toString());
|
|
|
|
} else if (logicalLineBuffer.toString().trim().length() == 0) {
|
|
|
|
line = new Blank();
|
|
|
|
} else {
|
|
|
|
line = new Pair(logicalLineBuffer.toString());
|
|
|
|
String key = unescape(((Pair)line).getName());
|
|
|
|
if (keyedPairLines.containsKey(key)) {
|
|
|
|
// this key is already present, so we remove it and add
|
|
|
|
// the new one
|
|
|
|
remove(key);
|
|
|
|
}
|
|
|
|
keyedPairLines.put(key, new Integer(logicalLines.size()));
|
|
|
|
}
|
|
|
|
logicalLines.add(line);
|
|
|
|
logicalLineBuffer.setLength(0);
|
|
|
|
}
|
|
|
|
s = br.readLine();
|
|
|
|
}
|
|
|
|
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.
|
|
|
|
* @param s the contents of the line to examine
|
|
|
|
* @return <code>true</code> if the line is to be continued, <code>false</code> otherwise
|
|
|
|
*/
|
|
|
|
private boolean requiresContinuation(String s) {
|
|
|
|
char[] ca = s.toCharArray();
|
|
|
|
int i = ca.length - 1;
|
|
|
|
while (i > 0 && ca[i] == '\\') {
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
// trailing backslashes
|
|
|
|
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>.
|
|
|
|
* @param s the string to unescape (coming from the source file)
|
|
|
|
* @return the unescaped string
|
|
|
|
*/
|
|
|
|
private String unescape(String s) {
|
|
|
|
/*
|
|
|
|
* The following combinations are converted:
|
|
|
|
* \n newline
|
|
|
|
* \r carraige return
|
|
|
|
* \f form feed
|
|
|
|
* \t tab
|
|
|
|
* \\ backslash
|
|
|
|
* \u0000 unicode character
|
|
|
|
* 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';
|
|
|
|
StringBuffer buffy = new StringBuffer(s.length());
|
|
|
|
for (int i = 0; i < ch.length; i++) {
|
|
|
|
char c = ch[i];
|
|
|
|
if (c == '\n') {
|
|
|
|
// we have hit out end-of-string marker
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (c == '\\') {
|
|
|
|
// possibly an escape sequence
|
|
|
|
c = ch[++i];
|
|
|
|
if (c == 'n')
|
|
|
|
buffy.append('\n');
|
|
|
|
else if (c == 'r')
|
|
|
|
buffy.append('\r');
|
|
|
|
else if (c == 'f')
|
|
|
|
buffy.append('\f');
|
|
|
|
else if (c == 't')
|
|
|
|
buffy.append('\t');
|
|
|
|
else if (c == 'u') {
|
|
|
|
// handle unicode escapes
|
|
|
|
c = unescapeUnicode(ch, i+1);
|
|
|
|
i += 4;
|
|
|
|
buffy.append(c);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
buffy.append(c);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
buffy.append(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return buffy.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
private char unescapeUnicode(char[] ch, int i) {
|
|
|
|
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>.
|
|
|
|
* @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.
|
|
|
|
* @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>.
|
|
|
|
* @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
|
|
|
|
* @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\\:=#!";
|
|
|
|
String escaped = "tfrn\\:=#!";
|
|
|
|
StringBuffer buffy = new StringBuffer(s.length());
|
|
|
|
boolean leadingSpace = true;
|
|
|
|
for (int i = 0; i < ch.length; i++) {
|
|
|
|
char c = ch[i];
|
|
|
|
if (c == ' ') {
|
|
|
|
if (escapeAllSpaces || leadingSpace) {
|
|
|
|
buffy.append("\\");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
leadingSpace = false;
|
|
|
|
}
|
|
|
|
int p = forEscaping.indexOf(c);
|
|
|
|
if (p != -1) {
|
|
|
|
buffy.append("\\").append(escaped.substring(p,p+1));
|
|
|
|
} else if (c < 0x0020 || c > 0x007e) {
|
|
|
|
buffy.append(escapeUnicode(c));
|
|
|
|
} else {
|
|
|
|
buffy.append(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return buffy.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the unicode escape sequence for a character, in the form \u005CuNNNN.
|
|
|
|
* @param ch the character to encode
|
|
|
|
* @return the unicode escape sequence
|
|
|
|
*/
|
|
|
|
private String escapeUnicode(char ch) {
|
|
|
|
StringBuffer buffy = new StringBuffer("\\u");
|
|
|
|
String hex = Integer.toHexString((int)ch);
|
|
|
|
buffy.append("0000".substring(4-hex.length()));
|
|
|
|
buffy.append(hex);
|
|
|
|
return buffy.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
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 (!(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.
|
|
|
|
*/
|
|
|
|
private static class Blank extends LogicalLine {
|
|
|
|
public Blank() {
|
|
|
|
super("");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A comment line of the input stream.
|
|
|
|
*/
|
|
|
|
private class Comment extends LogicalLine {
|
|
|
|
public Comment(String text) {
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
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;
|
|
|
|
try {
|
|
|
|
dolly = super.clone();
|
|
|
|
}
|
|
|
|
catch (CloneNotSupportedException e) {
|
|
|
|
// should be fine
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
return dolly;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void parsePair(String text) {
|
|
|
|
// need to find first non-escaped '=', ':', '\t' or ' '.
|
|
|
|
int pos = findFirstSeparator(text);
|
|
|
|
if (pos == -1) {
|
|
|
|
// trim leading whitespace only
|
|
|
|
name = text;
|
|
|
|
value = null;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
name = text.substring(0, pos);
|
|
|
|
value = text.substring(pos+1, text.length());
|
|
|
|
}
|
|
|
|
// 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) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (i == s.length()) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
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));
|
|
|
|
if (x != -1 && x < p) {
|
|
|
|
p = x;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (p == s.length() + 1) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} |