org.apache.maven.cli.props.MavenProperties Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of maven-embedder Show documentation
Show all versions of maven-embedder Show documentation
Maven embeddable component, with CLI and logging support.
/*
* 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.maven.cli.props;
import java.io.FilterWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
/**
* Enhancement of the standard Properties
* managing the maintain of comments, etc.
*/
public class MavenProperties extends AbstractMap {
/** Constant for the supported comment characters.*/
private static final String COMMENT_CHARS = "#!";
/** The list of possible key/value separators */
private static final char[] SEPARATORS = new char[] {'=', ':'};
/** The white space characters used as key/value separators. */
private static final char[] WHITE_SPACE = new char[] {' ', '\t', '\f'};
/**
* Unless standard java props, use UTF-8
*/
static final String DEFAULT_ENCODING = StandardCharsets.UTF_8.name();
/** Constant for the platform specific line separator.*/
private static final String LINE_SEPARATOR = System.lineSeparator();
/** Constant for the radix of hex numbers.*/
private static final int HEX_RADIX = 16;
/** Constant for the length of a unicode literal.*/
private static final int UNICODE_LEN = 4;
private final Map storage = new LinkedHashMap<>();
private final Map layout = new LinkedHashMap<>();
private List header;
private List footer;
private Path location;
private Function callback;
boolean substitute = true;
boolean typed;
public MavenProperties() {}
public MavenProperties(Path location) throws IOException {
this(location, null);
}
public MavenProperties(Path location, Function callback) throws IOException {
this.location = location;
this.callback = callback;
if (Files.exists(location)) {
load(location);
}
}
public MavenProperties(boolean substitute) {
this.substitute = substitute;
}
public MavenProperties(Path location, boolean substitute) {
this.location = location;
this.substitute = substitute;
}
public void load(Path location) throws IOException {
try (InputStream is = Files.newInputStream(location)) {
load(is);
}
}
public void load(URL location) throws IOException {
try (InputStream is = location.openStream()) {
load(is);
}
}
public void load(InputStream is) throws IOException {
load(new InputStreamReader(is, DEFAULT_ENCODING));
}
public void load(Reader reader) throws IOException {
loadLayout(reader, false);
}
public void save() throws IOException {
save(this.location);
}
public void save(Path location) throws IOException {
try (OutputStream os = Files.newOutputStream(location)) {
save(os);
}
}
public void save(OutputStream os) throws IOException {
save(new OutputStreamWriter(os, DEFAULT_ENCODING));
}
public void save(Writer writer) throws IOException {
saveLayout(writer, typed);
}
/**
* Store a properties into a output stream, preserving comments, special character, etc.
* This method is mainly to be compatible with the java.util.Properties class.
*
* @param os an output stream.
* @param comment this parameter is ignored as this Properties
* @throws IOException If storing fails
*/
public void store(OutputStream os, String comment) throws IOException {
this.save(os);
}
/**
* Searches for the property with the specified key in this property list.
*
* @param key the property key.
* @return the value in this property list with the specified key value.
*/
public String getProperty(String key) {
return this.get(key);
}
/**
* Searches for the property with the specified key in this property list. If the key is not found in this property
* list, the default property list, and its defaults, recursively, are then checked. The method returns the default
* value argument if the property is not found.
*
* @param key the property key.
* @param defaultValue a default value.
* @return The property value of the default value
*/
public String getProperty(String key, String defaultValue) {
if (this.get(key) != null) {
return this.get(key);
}
return defaultValue;
}
@Override
public Set> entrySet() {
return new AbstractSet<>() {
@Override
public Iterator> iterator() {
return new Iterator<>() {
final Iterator> keyIterator =
storage.entrySet().iterator();
public boolean hasNext() {
return keyIterator.hasNext();
}
public Entry next() {
final Entry entry = keyIterator.next();
return new Entry() {
public String getKey() {
return entry.getKey();
}
public String getValue() {
return entry.getValue();
}
public String setValue(String value) {
String old = entry.setValue(value);
if (old == null || !old.equals(value)) {
Layout l = layout.get(entry.getKey());
if (l != null) {
l.clearValue();
}
}
return old;
}
};
}
public void remove() {
keyIterator.remove();
}
};
}
@Override
public int size() {
return storage.size();
}
};
}
/**
* Returns an enumeration of all the keys in this property list, including distinct keys in the default property
* list if a key of the same name has not already been found from the main properties list.
*
* @return an enumeration of all the keys in this property list, including the keys in the default property list.
*/
public Enumeration> propertyNames() {
return Collections.enumeration(storage.keySet());
}
/**
* Calls the map method put. Provided for parallelism with the getProperty method.
* Enforces use of strings for property keys and values. The value returned is the result of the map call to put.
*
* @param key the key to be placed into this property list.
* @param value the value corresponding to the key.
* @return the previous value of the specified key in this property list, or null if it did not have one.
*/
public Object setProperty(String key, String value) {
return this.put(key, value);
}
@Override
public String put(String key, String value) {
String old = storage.put(key, value);
if (old == null || !old.equals(value)) {
Layout l = layout.get(key);
if (l != null) {
l.clearValue();
}
}
return old;
}
void putAllSubstituted(Map extends String, ? extends String> m) {
storage.putAll(m);
}
public String put(String key, List commentLines, List valueLines) {
commentLines = new ArrayList<>(commentLines);
valueLines = new ArrayList<>(valueLines);
String escapedKey = escapeKey(key);
StringBuilder sb = new StringBuilder();
// int lastLine = valueLines.size() - 1;
if (valueLines.isEmpty()) {
valueLines.add(escapedKey + "=");
sb.append(escapedKey).append("=");
} else {
String val0 = valueLines.get(0);
String rv0 = typed ? val0 : escapeJava(val0);
if (!val0.trim().startsWith(escapedKey)) {
valueLines.set(0, escapedKey + " = " + rv0 /*+ (0 < lastLine? "\\": "")*/);
sb.append(escapedKey).append(" = ").append(rv0);
} else {
valueLines.set(0, rv0 /*+ (0 < lastLine? "\\": "")*/);
sb.append(rv0);
}
}
for (int i = 1; i < valueLines.size(); i++) {
String val = valueLines.get(i);
valueLines.set(i, typed ? val : escapeJava(val) /*+ (i < lastLine? "\\": "")*/);
while (!val.isEmpty() && Character.isWhitespace(val.charAt(0))) {
val = val.substring(1);
}
sb.append(val);
}
String[] property = PropertiesReader.parseProperty(sb.toString());
this.layout.put(key, new Layout(commentLines, valueLines));
return storage.put(key, property[1]);
}
public String put(String key, List commentLines, String value) {
commentLines = new ArrayList<>(commentLines);
this.layout.put(key, new Layout(commentLines, null));
return storage.put(key, value);
}
public String put(String key, String comment, String value) {
return put(key, Collections.singletonList(comment), value);
}
public boolean update(Map props) {
MavenProperties properties;
if (props instanceof MavenProperties) {
properties = (MavenProperties) props;
} else {
properties = new MavenProperties();
properties.putAll(props);
}
return update(properties);
}
public boolean update(MavenProperties properties) {
boolean modified = false;
// Remove "removed" properties from the cfg file
for (String key : new ArrayList(this.keySet())) {
if (!properties.containsKey(key)) {
this.remove(key);
modified = true;
}
}
// Update existing keys
for (String key : properties.keySet()) {
String v = this.get(key);
List comments = properties.getComments(key);
List value = properties.getRaw(key);
if (v == null) {
this.put(key, comments, value);
modified = true;
} else if (!v.equals(properties.get(key))) {
if (comments.isEmpty()) {
comments = this.getComments(key);
}
this.put(key, comments, value);
modified = true;
}
}
return modified;
}
public List getRaw(String key) {
if (layout.containsKey(key)) {
if (layout.get(key).getValueLines() != null) {
return new ArrayList(layout.get(key).getValueLines());
}
}
List result = new ArrayList();
if (storage.containsKey(key)) {
result.add(storage.get(key));
}
return result;
}
public List getComments(String key) {
if (layout.containsKey(key)) {
if (layout.get(key).getCommentLines() != null) {
return new ArrayList(layout.get(key).getCommentLines());
}
}
return new ArrayList();
}
@Override
public String remove(Object key) {
Layout l = layout.get(key);
if (l != null) {
l.clearValue();
}
return storage.remove(key);
}
@Override
public void clear() {
for (Layout l : layout.values()) {
l.clearValue();
}
storage.clear();
}
/**
* Return the comment header.
*
* @return the comment header
*/
public List getHeader() {
return header;
}
/**
* Set the comment header.
*
* @param header the header to use
*/
public void setHeader(List header) {
this.header = header;
}
/**
* Return the comment footer.
*
* @return the comment footer
*/
public List getFooter() {
return footer;
}
/**
* Set the comment footer.
*
* @param footer the footer to use
*/
public void setFooter(List footer) {
this.footer = footer;
}
/**
* Reads a properties file and stores its internal structure. The found
* properties will be added to the associated configuration object.
*
* @param in the reader to the properties file
* @throws IOException if an error occurs
*/
protected void loadLayout(Reader in, boolean maybeTyped) throws IOException {
PropertiesReader reader = new PropertiesReader(in, maybeTyped);
boolean hasProperty = false;
while (reader.nextProperty()) {
hasProperty = true;
storage.put(reader.getPropertyName(), reader.getPropertyValue());
int idx = checkHeaderComment(reader.getCommentLines());
layout.put(
reader.getPropertyName(),
new Layout(
idx < reader.getCommentLines().size()
? new ArrayList<>(reader.getCommentLines()
.subList(
idx,
reader.getCommentLines().size()))
: null,
new ArrayList<>(reader.getValueLines())));
}
typed = maybeTyped && reader.typed != null && reader.typed;
if (!typed) {
for (Entry e : storage.entrySet()) {
e.setValue(unescapeJava(e.getValue()));
}
}
if (hasProperty) {
footer = new ArrayList<>(reader.getCommentLines());
} else {
header = new ArrayList<>(reader.getCommentLines());
}
if (substitute) {
substitute();
}
}
public void substitute() {
substitute(callback);
}
public void substitute(Function callback) {
InterpolationHelper.performSubstitution(storage, callback);
}
/**
* Writes the properties file to the given writer, preserving as much of its
* structure as possible.
*
* @param out the writer
* @throws IOException if an error occurs
*/
protected void saveLayout(Writer out, boolean typed) throws IOException {
PropertiesWriter writer = new PropertiesWriter(out, typed);
if (header != null) {
for (String s : header) {
writer.writeln(s);
}
}
for (String key : storage.keySet()) {
Layout l = layout.get(key);
if (l != null && l.getCommentLines() != null) {
for (String s : l.getCommentLines()) {
writer.writeln(s);
}
}
if (l != null && l.getValueLines() != null) {
for (int i = 0; i < l.getValueLines().size(); i++) {
String s = l.getValueLines().get(i);
if (i < l.getValueLines().size() - 1) {
writer.writeln(s + "\\");
} else {
writer.writeln(s);
}
}
} else {
writer.writeProperty(key, storage.get(key));
}
}
if (footer != null) {
for (String s : footer) {
writer.writeln(s);
}
}
writer.flush();
}
/**
* Checks if parts of the passed in comment can be used as header comment.
* This method checks whether a header comment can be defined (i.e. whether
* this is the first comment in the loaded file). If this is the case, it is
* searched for the lates blank line. This line will mark the end of the
* header comment. The return value is the index of the first line in the
* passed in list, which does not belong to the header comment.
*
* @param commentLines the comment lines
* @return the index of the next line after the header comment
*/
private int checkHeaderComment(List commentLines) {
if (getHeader() == null && layout.isEmpty()) {
// This is the first comment. Search for blank lines.
int index = commentLines.size() - 1;
while (index >= 0 && !commentLines.get(index).isEmpty()) {
index--;
}
setHeader(new ArrayList(commentLines.subList(0, index + 1)));
return index + 1;
} else {
return 0;
}
}
/**
* Tests whether a line is a comment, i.e. whether it starts with a comment
* character.
*
* @param line the line
* @return a flag if this is a comment line
*/
static boolean isCommentLine(String line) {
String s = line.trim();
// blank lines are also treated as comment lines
return s.isEmpty() || COMMENT_CHARS.indexOf(s.charAt(0)) >= 0;
}
/**
* Unescapes any Java literals found in the String
to a
* Writer
.
This is a slightly modified version of the
* StringEscapeUtils.unescapeJava() function in commons-lang that doesn't
* drop escaped separators (i.e '\,').
*
* @param str the String
to unescape, may be null
* @return the processed string
* @throws IllegalArgumentException if the Writer is null
*/
protected static String unescapeJava(String str) {
if (str == null) {
return null;
}
int sz = str.length();
StringBuilder out = new StringBuilder(sz);
StringBuilder unicode = new StringBuilder(UNICODE_LEN);
boolean hadSlash = false;
boolean inUnicode = false;
for (int i = 0; i < sz; i++) {
char ch = str.charAt(i);
if (inUnicode) {
// if in unicode, then we're reading unicode
// values in somehow
unicode.append(ch);
if (unicode.length() == UNICODE_LEN) {
// unicode now contains the four hex digits
// which represents our unicode character
try {
int value = Integer.parseInt(unicode.toString(), HEX_RADIX);
out.append((char) value);
unicode.setLength(0);
inUnicode = false;
hadSlash = false;
} catch (NumberFormatException nfe) {
throw new IllegalArgumentException("Unable to parse unicode value: " + unicode, nfe);
}
}
continue;
}
if (hadSlash) {
// handle an escaped value
hadSlash = false;
switch (ch) {
case '\\':
out.append('\\');
break;
case '\'':
out.append('\'');
break;
case '\"':
out.append('"');
break;
case 'r':
out.append('\r');
break;
case 'f':
out.append('\f');
break;
case 't':
out.append('\t');
break;
case 'n':
out.append('\n');
break;
case 'b':
out.append('\b');
break;
case 'u':
// uh-oh, we're in unicode country....
inUnicode = true;
break;
default:
out.append(ch);
break;
}
continue;
} else if (ch == '\\') {
hadSlash = true;
continue;
}
out.append(ch);
}
if (hadSlash) {
// then we're in the weird case of a \ at the end of the
// string, let's output it anyway.
out.append('\\');
}
return out.toString();
}
/**
* Escapes the characters in a String
using Java String rules.
*
* Deals correctly with quotes and control-chars (tab, backslash, cr, ff, etc.)
*
* So a tab becomes the characters '\\'
and
* 't'
.
*
* The only difference between Java strings and JavaScript strings
* is that in JavaScript, a single quote must be escaped.
*
* Example:
*
* input string: He didn't say, "Stop!"
* output string: He didn't say, \"Stop!\"
*
*
*
* @param str String to escape values in, may be null
* @return String with escaped values, null
if null string input
*/
@SuppressWarnings("checkstyle:MagicNumber")
protected static String escapeJava(String str) {
if (str == null) {
return null;
}
int sz = str.length();
StringBuilder out = new StringBuilder(sz * 2);
for (int i = 0; i < sz; i++) {
char ch = str.charAt(i);
// handle unicode
if (ch > 0xfff) {
out.append("\\u").append(hex(ch));
} else if (ch > 0xff) {
out.append("\\u0").append(hex(ch));
} else if (ch > 0x7f) {
out.append("\\u00").append(hex(ch));
} else if (ch < 32) {
switch (ch) {
case '\b':
out.append('\\');
out.append('b');
break;
case '\n':
out.append('\\');
out.append('n');
break;
case '\t':
out.append('\\');
out.append('t');
break;
case '\f':
out.append('\\');
out.append('f');
break;
case '\r':
out.append('\\');
out.append('r');
break;
default:
if (ch > 0xf) {
out.append("\\u00").append(hex(ch));
} else {
out.append("\\u000").append(hex(ch));
}
break;
}
} else {
switch (ch) {
case '"':
out.append('\\');
out.append('"');
break;
case '\\':
out.append('\\');
out.append('\\');
break;
default:
out.append(ch);
break;
}
}
}
return out.toString();
}
/**
* Returns an upper case hexadecimal String
for the given
* character.
*
* @param ch The character to convert.
* @return An upper case hexadecimal String
*/
protected static String hex(char ch) {
return Integer.toHexString(ch).toUpperCase(Locale.ENGLISH);
}
/**
* Checks if the value is in the given array.
*
* The method returns false
if a null
array is passed in.
*
* @param array the array to search through
* @param valueToFind the value to find
* @return true
if the array contains the object
*/
public static boolean contains(char[] array, char valueToFind) {
if (array == null) {
return false;
}
for (char c : array) {
if (valueToFind == c) {
return true;
}
}
return false;
}
/**
* Escape the separators in the key.
*
* @param key the key
* @return the escaped key
*/
private static String escapeKey(String key) {
StringBuilder newkey = new StringBuilder();
for (int i = 0; i < key.length(); i++) {
char c = key.charAt(i);
if (contains(SEPARATORS, c) || contains(WHITE_SPACE, c)) {
// escape the separator
newkey.append('\\');
newkey.append(c);
} else {
newkey.append(c);
}
}
return newkey.toString();
}
/**
* This class is used to read properties lines. These lines do
* not terminate with new-line chars but rather when there is no
* backslash sign a the end of the line. This is used to
* concatenate multiple lines for readability.
*/
public static class PropertiesReader extends LineNumberReader {
/** Stores the comment lines for the currently processed property.*/
private final List commentLines;
/** Stores the value lines for the currently processed property.*/
private final List valueLines;
/** Stores the name of the last read property.*/
private String propertyName;
/** Stores the value of the last read property.*/
private String propertyValue;
private boolean maybeTyped;
/** Stores if the properties are typed or not */
Boolean typed;
/**
* Creates a new instance of PropertiesReader
and sets
* the underlaying reader and the list delimiter.
*
* @param reader the reader
*/
public PropertiesReader(Reader reader, boolean maybeTyped) {
super(reader);
commentLines = new ArrayList<>();
valueLines = new ArrayList<>();
this.maybeTyped = maybeTyped;
}
/**
* Reads a property line. Returns null if Stream is
* at EOF. Concatenates lines ending with "\".
* Skips lines beginning with "#" or "!" and empty lines.
* The return value is a property definition (<name>
* = <value>
)
*
* @return A string containing a property value or null
*
* @throws IOException in case of an I/O error
*/
public String readProperty() throws IOException {
commentLines.clear();
valueLines.clear();
StringBuilder buffer = new StringBuilder();
while (true) {
String line = readLine();
if (line == null) {
// EOF
return null;
}
if (isCommentLine(line)) {
commentLines.add(line);
continue;
}
boolean combine = checkCombineLines(line);
if (combine) {
line = line.substring(0, line.length() - 1);
}
valueLines.add(line);
while (line.length() > 0 && contains(WHITE_SPACE, line.charAt(0))) {
line = line.substring(1, line.length());
}
buffer.append(line);
if (!combine) {
break;
}
}
return buffer.toString();
}
/**
* Parses the next property from the input stream and stores the found
* name and value in internal fields. These fields can be obtained using
* the provided getter methods. The return value indicates whether EOF
* was reached (false) or whether further properties are
* available (true).
*
* @return a flag if further properties are available
* @throws IOException if an error occurs
*/
public boolean nextProperty() throws IOException {
String line = readProperty();
if (line == null) {
return false; // EOF
}
// parse the line
String[] property = parseProperty(line);
boolean typed = false;
if (maybeTyped && property[1].length() >= 2) {
typed = property[1].matches(
"\\s*[TILFDXSCBilfdxscb]?(\\[[\\S\\s]*\\]|\\([\\S\\s]*\\)|\\{[\\S\\s]*\\}|\"[\\S\\s]*\")\\s*");
}
if (this.typed == null) {
this.typed = typed;
} else {
this.typed = this.typed & typed;
}
propertyName = unescapeJava(property[0]);
propertyValue = property[1];
return true;
}
/**
* Returns the comment lines that have been read for the last property.
*
* @return the comment lines for the last property returned by
* readProperty()
*/
public List getCommentLines() {
return commentLines;
}
/**
* Returns the value lines that have been read for the last property.
*
* @return the raw value lines for the last property returned by
* readProperty()
*/
public List getValueLines() {
return valueLines;
}
/**
* Returns the name of the last read property. This method can be called
* after {@link #nextProperty()}
was invoked and its
* return value was true.
*
* @return the name of the last read property
*/
public String getPropertyName() {
return propertyName;
}
/**
* Returns the value of the last read property. This method can be
* called after {@link #nextProperty()}
was invoked and
* its return value was true.
*
* @return the value of the last read property
*/
public String getPropertyValue() {
return propertyValue;
}
/**
* Checks if the passed in line should be combined with the following.
* This is true, if the line ends with an odd number of backslashes.
*
* @param line the line
* @return a flag if the lines should be combined
*/
private static boolean checkCombineLines(String line) {
int bsCount = 0;
for (int idx = line.length() - 1; idx >= 0 && line.charAt(idx) == '\\'; idx--) {
bsCount++;
}
return bsCount % 2 != 0;
}
/**
* Parse a property line and return the key and the value in an array.
*
* @param line the line to parse
* @return an array with the property's key and value
*/
private static String[] parseProperty(String line) {
// sorry for this spaghetti code, please replace it as soon as
// possible with a regexp when the Java 1.3 requirement is dropped
String[] result = new String[2];
StringBuilder key = new StringBuilder();
StringBuilder value = new StringBuilder();
// state of the automaton:
// 0: key parsing
// 1: antislash found while parsing the key
// 2: separator crossing
// 3: white spaces
// 4: value parsing
int state = 0;
for (int pos = 0; pos < line.length(); pos++) {
char c = line.charAt(pos);
switch (state) {
case 0:
if (c == '\\') {
state = 1;
} else if (contains(WHITE_SPACE, c)) {
// switch to the separator crossing state
state = 2;
} else if (contains(SEPARATORS, c)) {
// switch to the value parsing state
state = 3;
} else {
key.append(c);
}
break;
case 1:
if (contains(SEPARATORS, c) || contains(WHITE_SPACE, c)) {
// this is an escaped separator or white space
key.append(c);
} else {
// another escaped character, the '\' is preserved
key.append('\\');
key.append(c);
}
// return to the key parsing state
state = 0;
break;
case 2:
if (contains(WHITE_SPACE, c)) {
// do nothing, eat all white spaces
state = 2;
} else if (contains(SEPARATORS, c)) {
// switch to the value parsing state
state = 3;
} else {
// any other character indicates we encoutered the beginning of the value
value.append(c);
// switch to the value parsing state
state = 4;
}
break;
case 3:
if (contains(WHITE_SPACE, c)) {
// do nothing, eat all white spaces
state = 3;
} else {
// any other character indicates we encoutered the beginning of the value
value.append(c);
// switch to the value parsing state
state = 4;
}
break;
case 4:
value.append(c);
break;
default:
throw new IllegalStateException();
}
}
result[0] = key.toString();
result[1] = value.toString();
return result;
}
} // class PropertiesReader
/**
* This class is used to write properties lines.
*/
public static class PropertiesWriter extends FilterWriter {
private boolean typed;
/**
* Constructor.
*
* @param writer a Writer object providing the underlying stream
*/
public PropertiesWriter(Writer writer, boolean typed) {
super(writer);
this.typed = typed;
}
/**
* Writes the given property and its value.
*
* @param key the property key
* @param value the property value
* @throws IOException if an error occurs
*/
public void writeProperty(String key, String value) throws IOException {
write(escapeKey(key));
write(" = ");
write(typed ? value : escapeJava(value));
writeln(null);
}
/**
* Helper method for writing a line with the platform specific line
* ending.
*
* @param s the content of the line (may be null)
* @throws IOException if an error occurs
*/
public void writeln(String s) throws IOException {
if (s != null) {
write(s);
}
write(LINE_SEPARATOR);
}
} // class PropertiesWriter
/**
* TODO
*/
protected static class Layout {
private List commentLines;
private List valueLines;
public Layout() {}
public Layout(List commentLines, List valueLines) {
this.commentLines = commentLines;
this.valueLines = valueLines;
}
public List getCommentLines() {
return commentLines;
}
public void setCommentLines(List commentLines) {
this.commentLines = commentLines;
}
public List getValueLines() {
return valueLines;
}
public void setValueLines(List valueLines) {
this.valueLines = valueLines;
}
public void clearValue() {
this.valueLines = null;
}
} // class Layout
}