From e03bfc445eec32f9d566e42c48eb8e1198a94034 Mon Sep 17 00:00:00 2001
From: Stefan Bodewig A Properties collection which preserves comments and whitespace
- * present in the input stream from which it was loaded. The class defers the usual work of the java.util.Properties
- * 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.removeComments
flag is set,
- * then the comments immediately preceding the property are also removed.
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
- *# the first line - * alpha=one - * - * # the second line - * beta=two- *
This file is loaded, and then the following is also loaded into the
- * same LayoutPreservingProperties
object
# association - * beta=band - * - * # and finally - * gamma=rays- * The resulting collection sequence of logical lines depends on whether - * or not
removeComments
was set at the time the second stream
- * is loaded. If it is set, then the resulting list of lines is
- * # the first line - * alpha=one - * - * # association - * beta=band - * - * # and finally - * gamma=rays- *
If the flag is not set, then the comment "the second line" is retained,
- * although the key-value pair beta=two
is removed.
logicalLines
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 true
if comments are removed along with properties, or
- * false
otherwise. If true
, then when a property is
- * removed, the comment preceding it in the original file is removed also.
- * @return true
if leading comments are removed when a property is
- * removed; false
otherwise
- */
- public boolean isRemoveComments() {
- return removeComments;
- }
-
- /**
- * Sets the behaviour for comments accompanying properties that are being
- * removed. If true
, then when a property is removed, the comment
- * preceding it in the original file is removed also.
- * @param val true
if leading comments are to be removed when a property is
- * removed; false
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 true
if the line represented by s
is to be continued
- * on the next line of the file, or false
otherwise.
- * @param s the contents of the line to examine
- * @return true
if the line is to be continued, false
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 java.util.Properties.
- * @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 i
- * in the character array ch
.
- * @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 s
according to the rules in the docs for
- * java.util.Properties.
- * @param s the string to escape
- * @return the escaped string
- */
- private String escapeValue(String s) {
- return escape(s, false);
- }
-
- /**
- * Escape the string s
according to the rules in the docs for
- * java.util.Properties.
- * 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 s
according to the rules in the docs for
- * java.util.Properties.
- * @param s the string to escape
- * @param escapeAllSpaces if true
the method escapes all the spaces,
- * if false
, 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 pos
.
- * @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;
- }
- }
-}
\ No newline at end of file
+/*
+ * 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;
+
+/**
+ * A Properties collection which preserves comments and whitespace + * present in the input stream from which it was loaded.
+ *The class defers the usual work of the java.util.Properties + * 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.
+ * 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 theremoveComments
flag is set,
+ * then the comments immediately preceding the property are also removed.
+ * 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
+ *# the first line + * alpha=one + * + * # the second line + * beta=two+ *
This file is loaded, and then the following is also loaded into the
+ * same LayoutPreservingProperties
object
# association + * beta=band + * + * # and finally + * gamma=rays+ * The resulting collection sequence of logical lines depends on whether + * or not
removeComments
was set at the time the second stream
+ * is loaded. If it is set, then the resulting list of lines is
+ * # the first line + * alpha=one + * + * # association + * beta=band + * + * # and finally + * gamma=rays+ *
If the flag is not set, then the comment "the second line" is retained,
+ * although the key-value pair beta=two
is removed.
logicalLines
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 true
if comments are removed along with properties, or
+ * false
otherwise. If true
, then when a property is
+ * removed, the comment preceding it in the original file is removed also.
+ * @return true
if leading comments are removed when a property is
+ * removed; false
otherwise
+ */
+ public boolean isRemoveComments() {
+ return removeComments;
+ }
+
+ /**
+ * Sets the behaviour for comments accompanying properties that are being
+ * removed. If true
, then when a property is removed, the comment
+ * preceding it in the original file is removed also.
+ * @param val true
if leading comments are to be removed when a property is
+ * removed; false
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 true
if the line represented by s
is to be continued
+ * on the next line of the file, or false
otherwise.
+ * @param s the contents of the line to examine
+ * @return true
if the line is to be continued, false
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 java.util.Properties.
+ * @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 i
+ * in the character array ch
.
+ * @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 s
according to the rules in the docs for
+ * java.util.Properties.
+ * @param s the string to escape
+ * @return the escaped string
+ */
+ private String escapeValue(String s) {
+ return escape(s, false);
+ }
+
+ /**
+ * Escape the string s
according to the rules in the docs for
+ * java.util.Properties.
+ * 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 s
according to the rules in the docs for
+ * java.util.Properties.
+ * @param s the string to escape
+ * @param escapeAllSpaces if true
the method escapes all the spaces,
+ * if false
, 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 pos
.
+ * @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;
+ }
+ }
+}
diff --git a/src/tests/junit/org/apache/tools/ant/util/LayoutPreservingPropertiesTest.java b/src/tests/junit/org/apache/tools/ant/util/LayoutPreservingPropertiesTest.java
index 0115f74cf..0cfdd8f44 100644
--- a/src/tests/junit/org/apache/tools/ant/util/LayoutPreservingPropertiesTest.java
+++ b/src/tests/junit/org/apache/tools/ant/util/LayoutPreservingPropertiesTest.java
@@ -1,284 +1,284 @@
-/*
- * 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.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStreamReader;
-import java.io.IOException;
-import java.util.Properties;
-
-import junit.framework.TestCase;
-
-public class LayoutPreservingPropertiesTest extends TestCase {
- public LayoutPreservingPropertiesTest(String s) {
- super(s);
- }
-
- /**
- * Tests that a properties file read by the LayoutPreservingPropertiesFile
- * and then saves the properties in it.
- */
- public void testPreserve() throws Exception {
- File simple = new File(System.getProperty("root"), "src/etc/testcases/util/simple.properties");
- FileInputStream fis = new FileInputStream(simple);
- LayoutPreservingProperties lpf = new LayoutPreservingProperties();
- lpf.load(fis);
-
- File tmp = File.createTempFile("tmp", "props");
- tmp.deleteOnExit();
- lpf.saveAs(tmp);
-
- // now compare original and tmp for property equivalence
- Properties originalProps = new Properties();
- originalProps.load(new FileInputStream(simple));
-
- Properties tmpProps = new Properties();
- tmpProps.load(new FileInputStream(tmp));
-
- assertEquals("properties corrupted", originalProps, tmpProps);
-
- // and now make sure that the comments made it into the new file
- String s = readFile(tmp);
- assertTrue("missing comment", s.indexOf("# a comment") > -1);
- assertTrue("missing comment", s.indexOf("! more comment") > -1);
- }
-
- /**
- * Tests that names and value are properly escaped when being
- * written out.
- */
- public void testEscaping() throws Exception {
- LayoutPreservingProperties lpf = new LayoutPreservingProperties();
-
- lpf.setProperty(" prop one ", " leading and trailing spaces ");
- lpf.setProperty("prop\ttwo", "contains\ttab");
- lpf.setProperty("prop\nthree", "contains\nnewline");
- lpf.setProperty("prop\rfour", "contains\rcarraige return");
- lpf.setProperty("prop\ffive", "contains\fform feed");
- lpf.setProperty("prop\\six", "contains\\backslash");
- lpf.setProperty("prop:seven", "contains:colon");
- lpf.setProperty("prop=eight", "contains=equals");
- lpf.setProperty("prop#nine", "contains#hash");
- lpf.setProperty("prop!ten", "contains!exclamation");
-
- File tmp = File.createTempFile("tmp", "props");
- tmp.deleteOnExit();
- lpf.saveAs(tmp);
-
- // and check that the resulting file looks okay
- String s = readFile(tmp);
-
- assertTrue(s.indexOf("\\ prop\\ one\\ =\\ \\ leading and trailing spaces ") > -1);
- assertTrue(s.indexOf("prop\\ttwo=contains\\ttab") > -1);
- assertTrue(s.indexOf("prop\\nthree=contains\\nnewline") > -1);
- assertTrue(s.indexOf("prop\\rfour=contains\\rcarraige return") > -1);
- assertTrue(s.indexOf("prop\\\\six=contains\\\\backslash") > -1);
- assertTrue(s.indexOf("prop\\:seven=contains\\:colon") > -1);
- assertTrue(s.indexOf("prop\\=eight=contains\\=equals") > -1);
- assertTrue(s.indexOf("prop\\#nine=contains\\#hash") > -1);
- assertTrue(s.indexOf("prop\\!ten=contains\\!exclamation") > -1);
- }
-
- /**
- * Tests that properties are correctly indexed, so that when we set
- * an existing property, it updates the logical line, and it doesn't
- * append a new one.
- */
- public void testOverwrite() throws Exception {
- File unusual = new File(System.getProperty("root"), "src/etc/testcases/util/unusual.properties");
- FileInputStream fis = new FileInputStream(unusual);
- LayoutPreservingProperties lpf = new LayoutPreservingProperties();
- lpf.load(fis);
-
- lpf.setProperty(" prop one ", "new one");
- lpf.setProperty("prop\ttwo", "new two");
- lpf.setProperty("prop\nthree", "new three");
-
- File tmp = File.createTempFile("tmp", "props");
- tmp.deleteOnExit();
- lpf.saveAs(tmp);
-
- // and check that the resulting file looks okay
- String s = readFile(tmp);
-
- assertTrue(s.indexOf("\\ prop\\ one\\ =\\ \\ leading and trailing spaces ") == -1);
- assertTrue(s.indexOf("\\ prop\\ one\\ =new one") > -1);
- assertTrue(s.indexOf("prop\\ttwo=contains\\ttab") == -1);
- assertTrue(s.indexOf("prop\\ttwo=new two") > -1);
- assertTrue(s.indexOf("prop\\nthree=contains\\nnewline") == -1);
- assertTrue(s.indexOf("prop\\nthree=new three") > -1);
- }
-
- public void testStoreWithHeader() throws Exception {
- File simple = new File(System.getProperty("root"), "src/etc/testcases/util/simple.properties");
- FileInputStream fis = new FileInputStream(simple);
- LayoutPreservingProperties lpf = new LayoutPreservingProperties();
- lpf.load(fis);
-
- File tmp = File.createTempFile("tmp", "props");
- tmp.deleteOnExit();
- FileOutputStream fos = new FileOutputStream(tmp);
- lpf.store(fos, "file-header");
- fos.close();
-
- // and check that the resulting file looks okay
- String s = readFile(tmp);
-
- assertTrue("should have had header ", s.startsWith("#file-header"));
- }
-
- public void testClear() throws Exception {
- File simple = new File(System.getProperty("root"), "src/etc/testcases/util/simple.properties");
- FileInputStream fis = new FileInputStream(simple);
- LayoutPreservingProperties lpf = new LayoutPreservingProperties();
- lpf.load(fis);
-
- lpf.clear();
-
- File tmp = File.createTempFile("tmp", "props");
- tmp.deleteOnExit();
- lpf.saveAs(tmp);
-
- // and check that the resulting file looks okay
- String s = readFile(tmp);
-
- assertTrue("should have had no properties ", s.indexOf("prop.alpha") == -1);
- assertTrue("should have had no properties ", s.indexOf("prop.beta") == -1);
- assertTrue("should have had no properties ", s.indexOf("prop.gamma") == -1);
-
- assertTrue("should have had no comments", s.indexOf("# a comment") == -1);
- assertTrue("should have had no comments", s.indexOf("! more comment") == -1);
- assertTrue("should have had no comments", s.indexOf("# now a line wrapping one") == -1);
- }
-
- public void testRemove() throws Exception {
- File simple = new File(System.getProperty("root"), "src/etc/testcases/util/simple.properties");
- FileInputStream fis = new FileInputStream(simple);
- LayoutPreservingProperties lpf = new LayoutPreservingProperties();
- lpf.load(fis);
-
- lpf.remove("prop.beta");
-
- File tmp = File.createTempFile("tmp", "props");
- tmp.deleteOnExit();
- lpf.saveAs(tmp);
-
- // and check that the resulting file looks okay
- String s = readFile(tmp);
-
- assertTrue("should not have had prop.beta", s.indexOf("prop.beta") == -1);
- assertTrue("should have had prop.beta's comment", s.indexOf("! more comment") > -1);
- }
-
- public void testRemoveWithComment() throws Exception {
- File simple = new File(System.getProperty("root"), "src/etc/testcases/util/simple.properties");
- FileInputStream fis = new FileInputStream(simple);
- LayoutPreservingProperties lpf = new LayoutPreservingProperties();
- lpf.load(fis);
-
- lpf.setRemoveComments(true);
-
- lpf.remove("prop.beta");
-
- File tmp = File.createTempFile("tmp", "props");
- tmp.deleteOnExit();
- lpf.saveAs(tmp);
-
- // and check that the resulting file looks okay
- String s = readFile(tmp);
-
- assertTrue("should not have had prop.beta", s.indexOf("prop.beta") == -1);
- assertTrue("should not have had prop.beta's comment", s.indexOf("! more comment") == -1);
- }
-
- public void testClone() throws Exception {
- File simple = new File(System.getProperty("root"), "src/etc/testcases/util/simple.properties");
- FileInputStream fis = new FileInputStream(simple);
- LayoutPreservingProperties lpf1 = new LayoutPreservingProperties();
- lpf1.load(fis);
-
- LayoutPreservingProperties lpf2 = (LayoutPreservingProperties) lpf1.clone();
-
- lpf2.setProperty("prop.new", "a new property");
- lpf2.setProperty("prop.beta", "a new value for beta");
-
- assertEquals("size of original is wrong", 3, lpf1.size());
- assertEquals("size of clone is wrong", 4, lpf2.size());
-
- File tmp1 = File.createTempFile("tmp", "props");
- tmp1.deleteOnExit();
- lpf1.saveAs(tmp1);
- String s1 = readFile(tmp1);
-
- File tmp2 = File.createTempFile("tmp", "props");
- tmp2.deleteOnExit();
- lpf2.saveAs(tmp2);
- String s2 = readFile(tmp2);
-
- // check original is untouched
- assertTrue("should have had 'simple'", s1.indexOf("simple") > -1);
- assertTrue("should not have had prop.new", s1.indexOf("prop.new") == -1);
-
- // check clone has the changes
- assertTrue("should have had 'a new value for beta'", s2.indexOf("a new value for beta") > -1);
- assertTrue("should have had prop.new", s2.indexOf("prop.new") > -1);
- }
-
- public void testPreserveEsacpeName() throws Exception {
- LayoutPreservingProperties lpf = new LayoutPreservingProperties();
- File unusual = new File(System.getProperty("root"), "src/etc/testcases/util/unusual.properties");
- FileInputStream fis = new FileInputStream(unusual);
- lpf.load(fis);
-
- lpf.setProperty("prop:seven", "new value for seven");
- lpf.setProperty("prop=eight", "new value for eight");
- lpf.setProperty("prop eleven", "new value for eleven");
-
- lpf.setProperty("alpha", "new value for alpha");
- lpf.setProperty("beta", "new value for beta");
-
- File tmp = File.createTempFile("tmp", "props");
- tmp.deleteOnExit();
- lpf.saveAs(tmp);
-
- // and check that the resulting file looks okay
- String s = readFile(tmp);
-
- assertTrue(s.indexOf("prop\\:seven=new value for seven") > -1);
- assertTrue(s.indexOf("prop\\=eight=new value for eight") > -1);
- assertTrue(s.indexOf("prop\\ eleven=new value for eleven") > -1);
- assertTrue(s.indexOf("alpha=new value for alpha") > -1);
- assertTrue(s.indexOf("beta=new value for beta") > -1);
-
- assertTrue(s.indexOf("prop\\:seven=contains\\:colon") == -1);
- assertTrue(s.indexOf("prop\\=eight=contains\\=equals") == -1);
- assertTrue(s.indexOf("alpha:set with a colon") == -1);
- assertTrue(s.indexOf("beta set with a space") == -1);
- }
-
- private String readFile(File f) throws IOException {
- FileInputStream fis = new FileInputStream(f);
- InputStreamReader isr = new InputStreamReader(fis);
- String s = FileUtils.readFully(isr);
- isr.close();
- fis.close();
- return s;
- }
-}
+/*
+ * 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.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.util.Properties;
+
+import junit.framework.TestCase;
+
+public class LayoutPreservingPropertiesTest extends TestCase {
+ public LayoutPreservingPropertiesTest(String s) {
+ super(s);
+ }
+
+ /**
+ * Tests that a properties file read by the LayoutPreservingPropertiesFile
+ * and then saves the properties in it.
+ */
+ public void testPreserve() throws Exception {
+ File simple = new File(System.getProperty("root"), "src/etc/testcases/util/simple.properties");
+ FileInputStream fis = new FileInputStream(simple);
+ LayoutPreservingProperties lpf = new LayoutPreservingProperties();
+ lpf.load(fis);
+
+ File tmp = File.createTempFile("tmp", "props");
+ tmp.deleteOnExit();
+ lpf.saveAs(tmp);
+
+ // now compare original and tmp for property equivalence
+ Properties originalProps = new Properties();
+ originalProps.load(new FileInputStream(simple));
+
+ Properties tmpProps = new Properties();
+ tmpProps.load(new FileInputStream(tmp));
+
+ assertEquals("properties corrupted", originalProps, tmpProps);
+
+ // and now make sure that the comments made it into the new file
+ String s = readFile(tmp);
+ assertTrue("missing comment", s.indexOf("# a comment") > -1);
+ assertTrue("missing comment", s.indexOf("! more comment") > -1);
+ }
+
+ /**
+ * Tests that names and value are properly escaped when being
+ * written out.
+ */
+ public void testEscaping() throws Exception {
+ LayoutPreservingProperties lpf = new LayoutPreservingProperties();
+
+ lpf.setProperty(" prop one ", " leading and trailing spaces ");
+ lpf.setProperty("prop\ttwo", "contains\ttab");
+ lpf.setProperty("prop\nthree", "contains\nnewline");
+ lpf.setProperty("prop\rfour", "contains\rcarraige return");
+ lpf.setProperty("prop\ffive", "contains\fform feed");
+ lpf.setProperty("prop\\six", "contains\\backslash");
+ lpf.setProperty("prop:seven", "contains:colon");
+ lpf.setProperty("prop=eight", "contains=equals");
+ lpf.setProperty("prop#nine", "contains#hash");
+ lpf.setProperty("prop!ten", "contains!exclamation");
+
+ File tmp = File.createTempFile("tmp", "props");
+ tmp.deleteOnExit();
+ lpf.saveAs(tmp);
+
+ // and check that the resulting file looks okay
+ String s = readFile(tmp);
+
+ assertTrue(s.indexOf("\\ prop\\ one\\ =\\ \\ leading and trailing spaces ") > -1);
+ assertTrue(s.indexOf("prop\\ttwo=contains\\ttab") > -1);
+ assertTrue(s.indexOf("prop\\nthree=contains\\nnewline") > -1);
+ assertTrue(s.indexOf("prop\\rfour=contains\\rcarraige return") > -1);
+ assertTrue(s.indexOf("prop\\\\six=contains\\\\backslash") > -1);
+ assertTrue(s.indexOf("prop\\:seven=contains\\:colon") > -1);
+ assertTrue(s.indexOf("prop\\=eight=contains\\=equals") > -1);
+ assertTrue(s.indexOf("prop\\#nine=contains\\#hash") > -1);
+ assertTrue(s.indexOf("prop\\!ten=contains\\!exclamation") > -1);
+ }
+
+ /**
+ * Tests that properties are correctly indexed, so that when we set
+ * an existing property, it updates the logical line, and it doesn't
+ * append a new one.
+ */
+ public void testOverwrite() throws Exception {
+ File unusual = new File(System.getProperty("root"), "src/etc/testcases/util/unusual.properties");
+ FileInputStream fis = new FileInputStream(unusual);
+ LayoutPreservingProperties lpf = new LayoutPreservingProperties();
+ lpf.load(fis);
+
+ lpf.setProperty(" prop one ", "new one");
+ lpf.setProperty("prop\ttwo", "new two");
+ lpf.setProperty("prop\nthree", "new three");
+
+ File tmp = File.createTempFile("tmp", "props");
+ tmp.deleteOnExit();
+ lpf.saveAs(tmp);
+
+ // and check that the resulting file looks okay
+ String s = readFile(tmp);
+
+ assertTrue(s.indexOf("\\ prop\\ one\\ =\\ \\ leading and trailing spaces ") == -1);
+ assertTrue(s.indexOf("\\ prop\\ one\\ =new one") > -1);
+ assertTrue(s.indexOf("prop\\ttwo=contains\\ttab") == -1);
+ assertTrue(s.indexOf("prop\\ttwo=new two") > -1);
+ assertTrue(s.indexOf("prop\\nthree=contains\\nnewline") == -1);
+ assertTrue(s.indexOf("prop\\nthree=new three") > -1);
+ }
+
+ public void testStoreWithHeader() throws Exception {
+ File simple = new File(System.getProperty("root"), "src/etc/testcases/util/simple.properties");
+ FileInputStream fis = new FileInputStream(simple);
+ LayoutPreservingProperties lpf = new LayoutPreservingProperties();
+ lpf.load(fis);
+
+ File tmp = File.createTempFile("tmp", "props");
+ tmp.deleteOnExit();
+ FileOutputStream fos = new FileOutputStream(tmp);
+ lpf.store(fos, "file-header");
+ fos.close();
+
+ // and check that the resulting file looks okay
+ String s = readFile(tmp);
+
+ assertTrue("should have had header ", s.startsWith("#file-header"));
+ }
+
+ public void testClear() throws Exception {
+ File simple = new File(System.getProperty("root"), "src/etc/testcases/util/simple.properties");
+ FileInputStream fis = new FileInputStream(simple);
+ LayoutPreservingProperties lpf = new LayoutPreservingProperties();
+ lpf.load(fis);
+
+ lpf.clear();
+
+ File tmp = File.createTempFile("tmp", "props");
+ tmp.deleteOnExit();
+ lpf.saveAs(tmp);
+
+ // and check that the resulting file looks okay
+ String s = readFile(tmp);
+
+ assertTrue("should have had no properties ", s.indexOf("prop.alpha") == -1);
+ assertTrue("should have had no properties ", s.indexOf("prop.beta") == -1);
+ assertTrue("should have had no properties ", s.indexOf("prop.gamma") == -1);
+
+ assertTrue("should have had no comments", s.indexOf("# a comment") == -1);
+ assertTrue("should have had no comments", s.indexOf("! more comment") == -1);
+ assertTrue("should have had no comments", s.indexOf("# now a line wrapping one") == -1);
+ }
+
+ public void testRemove() throws Exception {
+ File simple = new File(System.getProperty("root"), "src/etc/testcases/util/simple.properties");
+ FileInputStream fis = new FileInputStream(simple);
+ LayoutPreservingProperties lpf = new LayoutPreservingProperties();
+ lpf.load(fis);
+
+ lpf.remove("prop.beta");
+
+ File tmp = File.createTempFile("tmp", "props");
+ tmp.deleteOnExit();
+ lpf.saveAs(tmp);
+
+ // and check that the resulting file looks okay
+ String s = readFile(tmp);
+
+ assertTrue("should not have had prop.beta", s.indexOf("prop.beta") == -1);
+ assertTrue("should have had prop.beta's comment", s.indexOf("! more comment") > -1);
+ }
+
+ public void testRemoveWithComment() throws Exception {
+ File simple = new File(System.getProperty("root"), "src/etc/testcases/util/simple.properties");
+ FileInputStream fis = new FileInputStream(simple);
+ LayoutPreservingProperties lpf = new LayoutPreservingProperties();
+ lpf.load(fis);
+
+ lpf.setRemoveComments(true);
+
+ lpf.remove("prop.beta");
+
+ File tmp = File.createTempFile("tmp", "props");
+ tmp.deleteOnExit();
+ lpf.saveAs(tmp);
+
+ // and check that the resulting file looks okay
+ String s = readFile(tmp);
+
+ assertTrue("should not have had prop.beta", s.indexOf("prop.beta") == -1);
+ assertTrue("should not have had prop.beta's comment", s.indexOf("! more comment") == -1);
+ }
+
+ public void testClone() throws Exception {
+ File simple = new File(System.getProperty("root"), "src/etc/testcases/util/simple.properties");
+ FileInputStream fis = new FileInputStream(simple);
+ LayoutPreservingProperties lpf1 = new LayoutPreservingProperties();
+ lpf1.load(fis);
+
+ LayoutPreservingProperties lpf2 = (LayoutPreservingProperties) lpf1.clone();
+
+ lpf2.setProperty("prop.new", "a new property");
+ lpf2.setProperty("prop.beta", "a new value for beta");
+
+ assertEquals("size of original is wrong", 3, lpf1.size());
+ assertEquals("size of clone is wrong", 4, lpf2.size());
+
+ File tmp1 = File.createTempFile("tmp", "props");
+ tmp1.deleteOnExit();
+ lpf1.saveAs(tmp1);
+ String s1 = readFile(tmp1);
+
+ File tmp2 = File.createTempFile("tmp", "props");
+ tmp2.deleteOnExit();
+ lpf2.saveAs(tmp2);
+ String s2 = readFile(tmp2);
+
+ // check original is untouched
+ assertTrue("should have had 'simple'", s1.indexOf("simple") > -1);
+ assertTrue("should not have had prop.new", s1.indexOf("prop.new") == -1);
+
+ // check clone has the changes
+ assertTrue("should have had 'a new value for beta'", s2.indexOf("a new value for beta") > -1);
+ assertTrue("should have had prop.new", s2.indexOf("prop.new") > -1);
+ }
+
+ public void testPreserveEsacpeName() throws Exception {
+ LayoutPreservingProperties lpf = new LayoutPreservingProperties();
+ File unusual = new File(System.getProperty("root"), "src/etc/testcases/util/unusual.properties");
+ FileInputStream fis = new FileInputStream(unusual);
+ lpf.load(fis);
+
+ lpf.setProperty("prop:seven", "new value for seven");
+ lpf.setProperty("prop=eight", "new value for eight");
+ lpf.setProperty("prop eleven", "new value for eleven");
+
+ lpf.setProperty("alpha", "new value for alpha");
+ lpf.setProperty("beta", "new value for beta");
+
+ File tmp = File.createTempFile("tmp", "props");
+ tmp.deleteOnExit();
+ lpf.saveAs(tmp);
+
+ // and check that the resulting file looks okay
+ String s = readFile(tmp);
+
+ assertTrue(s.indexOf("prop\\:seven=new value for seven") > -1);
+ assertTrue(s.indexOf("prop\\=eight=new value for eight") > -1);
+ assertTrue(s.indexOf("prop\\ eleven=new value for eleven") > -1);
+ assertTrue(s.indexOf("alpha=new value for alpha") > -1);
+ assertTrue(s.indexOf("beta=new value for beta") > -1);
+
+ assertTrue(s.indexOf("prop\\:seven=contains\\:colon") == -1);
+ assertTrue(s.indexOf("prop\\=eight=contains\\=equals") == -1);
+ assertTrue(s.indexOf("alpha:set with a colon") == -1);
+ assertTrue(s.indexOf("beta set with a space") == -1);
+ }
+
+ private String readFile(File f) throws IOException {
+ FileInputStream fis = new FileInputStream(f);
+ InputStreamReader isr = new InputStreamReader(fis);
+ String s = FileUtils.readFully(isr);
+ isr.close();
+ fis.close();
+ return s;
+ }
+}