com.opensymphony.xwork2.util.PropertiesReader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xwork Show documentation
Show all versions of xwork Show documentation
XWork is an command-pattern framework that is used to power WebWork
as well as other applications. XWork provides an Inversion of Control
container, a powerful expression language, data type conversion,
validation, and pluggable configuration.
/*
* 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 com.opensymphony.xwork2.util;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
/**
* 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.
*
* This class was pulled out of Jakarta Commons Configuration and
* Jakarta Commons Lang trunk revision 476093
*/
public class PropertiesReader extends LineNumberReader
{
/** Stores the comment lines for the currently processed property.*/
private List commentLines;
/** Stores the name of the last read property.*/
private String propertyName;
/** Stores the value of the last read property.*/
private String propertyValue;
/** Stores the list delimiter character.*/
private char delimiter;
/** Constant for the supported comment characters.*/
static final String COMMENT_CHARS = "#!";
/** 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;
/** 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'};
/**
* Constructor.
*
* @param reader A Reader.
*/
public PropertiesReader(Reader reader)
{
this(reader, ',');
}
/**
* Creates a new instance of PropertiesReader
and sets
* the underlaying reader and the list delimiter.
*
* @param reader the reader
* @param listDelimiter the list delimiter character
* @since 1.3
*/
public PropertiesReader(Reader reader, char listDelimiter)
{
super(reader);
commentLines = new ArrayList();
delimiter = listDelimiter;
}
/**
* 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
* @since 1.3
*/
boolean isCommentLine(String line)
{
String s = line.trim();
// blanc lines are also treated as comment lines
return s.length() < 1 || COMMENT_CHARS.indexOf(s.charAt(0)) >= 0;
}
/**
* 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();
StringBuffer buffer = new StringBuffer();
while (true)
{
String line = readLine();
if (line == null)
{
// EOF
return null;
}
if (isCommentLine(line))
{
commentLines.add(line);
continue;
}
line = line.trim();
if (checkCombineLines(line))
{
line = line.substring(0, line.length() - 1);
buffer.append(line);
}
else
{
buffer.append(line);
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
* @since 1.3
*/
public boolean nextProperty() throws IOException
{
String line = readProperty();
if (line == null)
{
return false; // EOF
}
// parse the line
String[] property = parseProperty(line);
propertyName = unescapeJava(property[0]);
propertyValue = unescapeJava(property[1], delimiter);
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()
* @since 1.3
*/
public List getCommentLines()
{
return commentLines;
}
/**
* 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
* @since 1.3
*/
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
* @since 1.3
*/
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 boolean checkCombineLines(String line)
{
int bsCount = 0;
for (int idx = line.length() - 1; idx >= 0 && line.charAt(idx) == '\\'; idx--)
{
bsCount++;
}
return bsCount % 2 == 1;
}
/**
* 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
* @since 1.2
*/
private 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];
StringBuffer key = new StringBuffer();
StringBuffer value = new StringBuffer();
// state of the automaton:
// 0: key parsing
// 1: antislash found while parsing the key
// 2: separator crossing
// 3: 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 = 3;
}
break;
case 3:
value.append(c);
break;
}
}
result[0] = key.toString().trim();
result[1] = value.toString().trim();
return result;
}
/**
* 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
* @param delimiter the delimiter for multi-valued properties
* @return the processed string
* @throws IllegalArgumentException if the Writer is null
*/
protected static String unescapeJava(String str, char delimiter)
{
if (str == null)
{
return null;
}
int sz = str.length();
StringBuffer out = new StringBuffer(sz);
StringBuffer unicode = new StringBuffer(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 RuntimeException("Unable to parse unicode value: " + unicode, nfe);
}
}
continue;
}
if (hadSlash)
{
// handle an escaped value
hadSlash = false;
if (ch == '\\')
{
out.append('\\');
}
else if (ch == '\'')
{
out.append('\'');
}
else if (ch == '\"')
{
out.append('"');
}
else if (ch == 'r')
{
out.append('\r');
}
else if (ch == 'f')
{
out.append('\f');
}
else if (ch == 't')
{
out.append('\t');
}
else if (ch == 'n')
{
out.append('\n');
}
else if (ch == 'b')
{
out.append('\b');
}
else if (ch == delimiter)
{
out.append('\\');
out.append(delimiter);
}
else if (ch == 'u')
{
// uh-oh, we're in unicode country....
inUnicode = true;
}
else
{
out.append(ch);
}
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();
}
/**
* Checks if the object is in the given array.
*
* The method returns false
if a null
array is passed in.
*
* @param array the array to search through
* @param objectToFind the object to find
* @return true
if the array contains the object
*/
public boolean contains(char[] array, char objectToFind) {
if (array == null) {
return false;
}
for (int i = 0; i < array.length; i++) {
if (objectToFind == array[i]) {
return true;
}
}
return false;
}
/**
* Unescapes any Java literals found in the String
.
* For example, it will turn a sequence of '\'
and
* 'n'
into a newline character, unless the '\'
* is preceded by another '\'
.
*
* @param str the String
to unescape, may be null
* @return a new unescaped String
, null
if null string input
*/
public static String unescapeJava(String str) {
if (str == null) {
return null;
}
try {
StringWriter writer = new StringWriter(str.length());
unescapeJava(writer, str);
return writer.toString();
} catch (IOException ioe) {
// this should never ever happen while writing to a StringWriter
ioe.printStackTrace();
return null;
}
}
/**
* Unescapes any Java literals found in the String
to a
* Writer
.
*
* For example, it will turn a sequence of '\'
and
* 'n'
into a newline character, unless the '\'
* is preceded by another '\'
.
*
* A null
string input has no effect.
*
* @param out the Writer
used to output unescaped characters
* @param str the String
to unescape, may be null
* @throws IllegalArgumentException if the Writer is null
* @throws IOException if error occurs on underlying Writer
*/
public static void unescapeJava(Writer out, String str) throws IOException {
if (out == null) {
throw new IllegalArgumentException("The Writer must not be null");
}
if (str == null) {
return;
}
int sz = str.length();
StringBuffer unicode = new StringBuffer(4);
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() == 4) {
// unicode now contains the four hex digits
// which represents our unicode character
try {
int value = Integer.parseInt(unicode.toString(), 16);
out.write((char) value);
unicode.setLength(0);
inUnicode = false;
hadSlash = false;
} catch (NumberFormatException nfe) {
throw new RuntimeException("Unable to parse unicode value: " + unicode, nfe);
}
}
continue;
}
if (hadSlash) {
// handle an escaped value
hadSlash = false;
switch (ch) {
case '\\':
out.write('\\');
break;
case '\'':
out.write('\'');
break;
case '\"':
out.write('"');
break;
case 'r':
out.write('\r');
break;
case 'f':
out.write('\f');
break;
case 't':
out.write('\t');
break;
case 'n':
out.write('\n');
break;
case 'b':
out.write('\b');
break;
case 'u':
{
// uh-oh, we're in unicode country....
inUnicode = true;
break;
}
default :
out.write(ch);
break;
}
continue;
} else if (ch == '\\') {
hadSlash = true;
continue;
}
out.write(ch);
}
if (hadSlash) {
// then we're in the weird case of a \ at the end of the
// string, let's output it anyway.
out.write('\\');
}
}
}