I am trying to modify a config file in Java using Properties.
I read, write and modify the lines successfully using Properties.store, load and setProperty, but I noticed that after doing such operation the file is overwritten and thus I loose al the lines in the config file that are not key-value pairs. Namely, I loose the comments.
Is there a way to keep such lines using java.util?
Placing a prefix in each line is not a problem. I know how to do it 'manually' reading line by line; I'am asking instead for an alternative
I don't think it is possible. Note that properties also don't promise that the ordering will be the same from load() to store(), or from one store() to another. If it is possible, the javadoc for Properties will tell you how.
import java.io.*;
import java.util.*;
/**
* The CommentedProperties class is an extension of java.util.Properties
* to allow retention of comment lines and blank (whitespace only) lines
* in the properties file.
*
* Written for Java version 1.4
*/
public class CommentedProperties extends java.util.Properties {
/**
* Use a Vector to keep a copy of lines that are a comment or 'blank'
*/
public Vector lineData = new Vector(0, 1);
/**
* Use a Vector to keep a copy of lines containing a key, i.e. they are a property.
*/
public Vector keyData = new Vector(0, 1);
/**
* Load properties from the specified InputStream.
* Overload the load method in Properties so we can keep comment and blank lines.
* @param inStream The InputStream to read.
*/
public void load(InputStream inStream) throws IOException
{
// The spec says that the file must be encoded using ISO-8859-1.
BufferedReader reader =
new BufferedReader(new InputStreamReader(inStream, "ISO-8859-1"));
String line;
while ((line = reader.readLine()) != null) {
char c = 0;
int pos = 0;
// Leading whitespaces must be deleted first.
while ( pos < line.length()
&& Character.isWhitespace(c = line.charAt(pos))) {
pos++;
}
// If empty line or begins with a comment character, save this line
// in lineData and save a "" in keyData.
if ( (line.length() - pos) == 0
|| line.charAt(pos) == '#' || line.charAt(pos) == '!') {
lineData.add(line);
keyData.add("");
continue;
}
// The characters up to the next Whitespace, ':', or '='
// describe the key. But look for escape sequences.
// Try to short-circuit when there is no escape char.
int start = pos;
boolean needsEscape = line.indexOf('\\', pos) != -1;
StringBuffer key = needsEscape ? new StringBuffer() : null;
while ( pos < line.length()
&& ! Character.isWhitespace(c = line.charAt(pos++))
&& c != '=' && c != ':') {
if (needsEscape && c == '\\') {
if (pos == line.length()) {
// The line continues on the next line. If there
// is no next line, just treat it as a key with an
// empty value.
line = reader.readLine();
if (line == null)
line = "";
pos = 0;
while ( pos < line.length()
&& Character.isWhitespace(c = line.charAt(pos)))
pos++;
} else {
c = line.charAt(pos++);
switch (c) {
case 'n':
key.append('\n');
break;
case 't':
key.append('\t');
break;
case 'r':
key.append('\r');
break;
case 'u':
if (pos + 4 <= line.length()) {
char uni = (char) Integer.parseInt
(line.substring(pos, pos + 4), 16);
key.append(uni);
pos += 4;
} // else throw exception?
break;
default:
key.append(c);
break;
}
}
} else if (needsEscape)
key.append(c);
}
boolean isDelim = (c == ':' || c == '=');
String keyString;
if (needsEscape)
keyString = key.toString();
else if (isDelim || Character.isWhitespace(c))
keyString = line.substring(start, pos - 1);
else
keyString = line.substring(start, pos);
while ( pos < line.length()
&& Character.isWhitespace(c = line.charAt(pos)))
pos++;
if (! isDelim && (c == ':' || c == '=')) {
pos++;
while ( pos < line.length()
&& Character.isWhitespace(c = line.charAt(pos)))
pos++;
}
// Short-circuit if no escape chars found.
if (!needsEscape) {
put(keyString, line.substring(pos));
// Save a "" in lineData and save this
// keyString in keyData.
lineData.add("");
keyData.add(keyString);
continue;
}
// Escape char found so iterate through the rest of the line.
StringBuffer element = new StringBuffer(line.length() - pos);
while (pos < line.length()) {
c = line.charAt(pos++);
if (c == '\\') {
if (pos == line.length()) {
// The line continues on the next line.
line = reader.readLine();
// We might have seen a backslash at the end of
// the file. The JDK ignores the backslash in
// this case, so we follow for compatibility.
if (line == null)
break;
pos = 0;
while ( pos < line.length()
&& Character.isWhitespace(c = line.charAt(pos)))
pos++;
element.ensureCapacity(line.length() - pos +
element.length());
} else {
c = line.charAt(pos++);
switch (c) {
case 'n':
element.append('\n');
break;
case 't':
element.append('\t');
break;
case 'r':
element.append('\r');
break;
case 'u':
if (pos + 4 <= line.length()) {
char uni = (char) Integer.parseInt
(line.substring(pos, pos + 4), 16);
element.append(uni);
pos += 4;
} // else throw exception?
break;
default:
element.append(c);
break;
}
}
} else
element.append(c);
}
put(keyString, element.toString());
// Save a "" in lineData and save this
// keyString in keyData.
lineData.add("");
keyData.add(keyString);
}
}
/**
* Write the properties to the specified OutputStream.
*
* Overloads the store method in Properties so we can put back comment
* and blank lines.
*
* @param out The OutputStream to write to.
* @param header Ignored, here for compatability w/ Properties.
*
* @exception IOException
*/
public void store(OutputStream out, String header) throws IOException
{
// The spec says that the file must be encoded using ISO-8859-1.
PrintWriter writer
= new PrintWriter(new OutputStreamWriter(out, "ISO-8859-1"));
// We ignore the header, because if we prepend a commented header
// then read it back in it is now a comment, which will be saved
// and then when we write again we would prepend Another header...
String line;
String key;
StringBuffer s = new StringBuffer ();
for (int i=0; i<lineData.size(); i++) {
line = (String) lineData.get(i);
key = (String) keyData.get(i);
if (key.length() > 0) { // This is a 'property' line, so rebuild it
formatForOutput (key, s, true);
s.append ('=');
formatForOutput ((String) get(key), s, false);
writer.println (s);
} else { // was a blank or comment line, so just restore it
writer.println (line);
}
}
writer.flush ();
}
/**
* Need this method from Properties because original code has StringBuilder,
* which is an element of Java 1.5, used StringBuffer instead (because
* this code was written for Java 1.4)
*
* @param str - the string to format
* @param buffer - buffer to hold the string
* @param key - true if str the key is formatted, false if the value is formatted
*/
private void formatForOutput(String str, StringBuffer buffer, boolean key)
{
if (key) {
buffer.setLength(0);
buffer.ensureCapacity(str.length());
} else
buffer.ensureCapacity(buffer.length() + str.length());
boolean head = true;
int size = str.length();
for (int i = 0; i < size; i++) {
char c = str.charAt(i);
switch (c) {
case '\n':
buffer.append("\\n");
break;
case '\r':
buffer.append("\\r");
break;
case '\t':
buffer.append("\\t");
break;
case ' ':
buffer.append(head ? "\\ " : " ");
break;
case '\\':
case '!':
case '#':
case '=':
case ':':
buffer.append('\\').append(c);
break;
default:
if (c < ' ' || c > '~') {
String hex = Integer.toHexString(c);
buffer.append("\\u0000".substring(0, 6 - hex.length()));
buffer.append(hex);
} else
buffer.append(c);
}
if (c != ' ')
head = key;
}
}
/**
* Add a Property to the end of the CommentedProperties.
*
* @param keyString The Property key.
* @param value The value of this Property.
*/
public void add(String keyString, String value)
{
put(keyString, value);
lineData.add("");
keyData.add(keyString);
}
/**
* Add a comment or blank line or comment to the end of the CommentedProperties.
*
* @param line The string to add to the end, make sure this is a comment
* or a 'whitespace' line.
*/
public void addLine(String line)
{
lineData.add(line);
keyData.add("");
}
}