All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.opensymphony.xwork2.util.PropertiesReader Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 2.1.3
Show newest version
/*
 * 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('\\'); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy