Browse Source

apply patch for #36901 LayoutPreservingProprties (includes tests)

git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@687402 13f79535-47bb-0310-9956-ffa450edef68
master
Kevin Jackson 17 years ago
parent
commit
75899750ab
6 changed files with 1053 additions and 13 deletions
  1. +33
    -11
      docs/manual/OptionalTasks/propertyfile.html
  2. +9
    -0
      src/etc/testcases/util/simple.properties
  3. +23
    -0
      src/etc/testcases/util/unusual.properties
  4. +15
    -2
      src/main/org/apache/tools/ant/taskdefs/optional/PropertyFile.java
  5. +689
    -0
      src/main/org/apache/tools/ant/util/LayoutPreservingProperties.java
  6. +284
    -0
      src/tests/junit/org/apache/tools/ant/util/LayoutPreservingPropertiesTest.java

+ 33
- 11
docs/manual/OptionalTasks/propertyfile.html View File

@@ -44,8 +44,8 @@
<p>Ant provides an optional task for editing property files. This is very useful
when wanting to make unattended modifications to configuration files for application
servers and applications. Currently, the task maintains a working property file with
the ability to add properties or make changes to existing ones. However, any comments
are lost.</p>
the ability to add properties or make changes to existing ones. Comments and layout
of the original properties file are preserved.</p>

<hr>
<h2><a name="proptask">PropertyFile Task</a></h2>
@@ -66,8 +66,15 @@ are lost.</p>
<td width="78%" valign="top">Header for the file itself</td>
<td width="10%" valign="top">no</td>
</tr>
<tr>
<td width="12%" valign="top">jdkproperties</td>
<td width="78%" valign="top">Use java.lang.Properties, which will loose comments and layout of file (default is 'false')</td>
<td width="10%" valign="top">no</td>
</tr>
</table>

<p>The boolean attribute 'jdkproperties' is provided to recover the previous behaviour of the task, in which the layout and any comments in the properties file were lost by the task.</p>

<h3>Parameters specified as nested elements</h3>
<h4><a name="entryElement">Entry</a></h4>
<p>Use nested <code>&lt;entry&gt;</code>
@@ -151,20 +158,35 @@ operation occurs <b>after</b> these rules are considered.</p>

<p>The following changes the my.properties file. Assume my.properties look like:</p>

<pre># A comment
akey=novalue</pre>
<pre># A string value
akey=original value

# The following is a counter, which will be incremented by 1 for
# each time the build is run.
anint=1</pre>

<p>After running, the file would now look like
</p>
<pre>#Thu Nov 02 23:41:47 EST 2000
<pre>#My properties
#Wed Aug 31 13:47:19 BST 2005
# A string value
akey=avalue
adate=2000/11/02 23\:41
anint=1

# The following is a counter, which will be incremented by 1 for
# each time the build is run.
anint=2

adate=2005/08/31 13\:47

formated.int=0014
formated.date=028 17\:34
</pre>

formated.date=243 13\:47</pre>
<p>
The slashes conform to the expectations of the Properties class. The file will be stored in a manner so that each character is examined and escaped if necessary.
</p>

<p>
The slashes conform to the expectations of the Properties class. The file will be stored in a manner so that each character is examined and escaped if necessary. Note that the original comment is now lost. Please keep this in mind when running this task against heavily commented properties files. It may be best to have a commented version in the source tree, copy it to a deployment area, and then run the modifications on the copy. Future versions of PropertyFile will hopefully eliminate this shortcoming.
The layout and comment of the original file is preserved. New properties are added at the end of the file. Existing properties are overwritten in place.
</p>

<blockquote><pre>&lt;propertyfile
@@ -172,7 +194,7 @@ The slashes conform to the expectations of the Properties class. The file will
comment=&quot;My properties&quot;&gt;
&lt;entry key=&quot;akey&quot; value=&quot;avalue&quot;/&gt;
&lt;entry key=&quot;adate&quot; type=&quot;date&quot; value=&quot;now&quot;/&gt;
&lt;entry key=&quot;anint&quot; type=&quot;int&quot; operation=&quot;+&quot;/&gt;
&lt;entry key=&quot;anint&quot; type=&quot;int&quot; default=&quot;0&quot; operation=&quot;+&quot;/&gt;
&lt;entry key=&quot;formated.int&quot; type=&quot;int&quot; default=&quot;0013&quot; operation=&quot;+&quot; pattern=&quot;0000&quot;/&gt;
&lt;entry key=&quot;formated.date&quot; type=&quot;date&quot; value=&quot;now&quot; pattern=&quot;DDD HH:mm&quot;/&gt;
&lt;/propertyfile&gt;


+ 9
- 0
src/etc/testcases/util/simple.properties View File

@@ -0,0 +1,9 @@
# a comment
prop.alpha=first property
! more comment
prop.beta=simple
# now a line wrapping one
prop.gamma=This is a long comment which \
contains a line wrap.

+ 23
- 0
src/etc/testcases/util/unusual.properties View File

@@ -0,0 +1,23 @@
\ prop\ one\ =\ \ leading and trailing spaces
prop\ttwo=contains\ttab
prop\nthree=contains\nnewline
prop\rfour=contains\rcarraige return
prop\ffive=contains\fform feed
prop\\six=contains\\backslash
prop\:seven=contains\:colon
prop\=eight=contains\=equals
prop\#nine=contains\#hash
prop\!ten=contains\!exclamation
alpha:set with a colon
beta set with a space

+ 15
- 2
src/main/org/apache/tools/ant/taskdefs/optional/PropertyFile.java View File

@@ -38,6 +38,7 @@ import java.util.Vector;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.util.LayoutPreservingProperties;
import org.apache.tools.ant.types.EnumeratedAttribute;

/**
@@ -118,6 +119,7 @@ public class PropertyFile extends Task {

private Properties properties;
private File propertyfile;
private boolean useJDKProperties;

private Vector entries = new Vector();

@@ -160,8 +162,12 @@ public class PropertyFile extends Task {
}

private void readFile() throws BuildException {
// Create the PropertyFile
properties = new Properties();
if (useJDKProperties) {
// user chose to use standard Java properties, which loose comments and layout
properties = new Properties();
} else {
properties = new LayoutPreservingProperties();
}
try {
if (propertyfile.exists()) {
log("Updating property file: "
@@ -216,6 +222,13 @@ public class PropertyFile extends Task {
comment = hdr;
}

/**
* optional flag to use original Java properties (as opposed to layout preserving properties)
*/
public void setJDKProperties(boolean val) {
useJDKProperties = val;
}
private void writeFile() throws BuildException {
BufferedOutputStream bos = null;
try {


+ 689
- 0
src/main/org/apache/tools/ant/util/LayoutPreservingProperties.java View File

@@ -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;
}
}
}

+ 284
- 0
src/tests/junit/org/apache/tools/ant/util/LayoutPreservingPropertiesTest.java View File

@@ -0,0 +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;
}
}

Loading…
Cancel
Save