Author: oheger Date: Sat Nov 22 08:57:36 2008 New Revision: 719869 URL: http://svn.apache.org/viewvc?rev=719869&view=rev Log: CONFIGURATION-324: Added support for line continuation to HierarchicalINIConfiguration.
Modified: commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalINIConfiguration.java commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestHierarchicalINIConfiguration.java commons/proper/configuration/trunk/xdocs/changes.xml Modified: commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalINIConfiguration.java URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalINIConfiguration.java?rev=719869&r1=719868&r2=719869&view=diff ============================================================================== --- commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalINIConfiguration.java (original) +++ commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalINIConfiguration.java Sat Nov 22 08:57:36 2008 @@ -204,6 +204,16 @@ protected static final String SEPARATOR_CHARS = "=:"; /** + * Constant for the line separator. + */ + private static final String LINE_SEPARATOR = System.getProperty("line.separator"); + + /** + * The line continuation character. + */ + private static final String LINE_CONT = "\\"; + + /** * Create a new empty INI Configuration. */ public HierarchicalINIConfiguration() @@ -337,7 +347,7 @@ if (index >= 0) { key = line.substring(0, index); - value = parseValue(line.substring(index + 1)); + value = parseValue(line.substring(index + 1), bufferedReader); } else { @@ -345,7 +355,7 @@ if (index >= 0) { key = line.substring(0, index); - value = parseValue(line.substring(index + 1)); + value = parseValue(line.substring(index + 1), bufferedReader); } else { @@ -385,72 +395,153 @@ * 'value' ; comment -> value * </pre> * - * @param value + * @param val the value to be parsed + * @param reader the reader (needed if multiple lines have to be read) + * @throws IOException if an IO error occurs */ - private String parseValue(String value) + private static String parseValue(String val, BufferedReader reader) throws IOException { - value = value.trim(); - - boolean quoted = value.startsWith("\"") || value.startsWith("'"); - boolean stop = false; - boolean escape = false; + StringBuffer propertyValue = new StringBuffer(); + boolean lineContinues; + String value = val.trim(); - char quote = quoted ? value.charAt(0) : 0; + do + { + boolean quoted = value.startsWith("\"") || value.startsWith("'"); + boolean stop = false; + boolean escape = false; - int i = quoted ? 1 : 0; + char quote = quoted ? value.charAt(0) : 0; - StringBuffer result = new StringBuffer(); - while (i < value.length() && !stop) - { - char c = value.charAt(i); + int i = quoted ? 1 : 0; - if (quoted) + StringBuffer result = new StringBuffer(); + while (i < value.length() && !stop) { - if ('\\' == c && !escape) - { - escape = true; - } - else if (!escape && quote == c) - { - stop = true; - } - else if (escape && quote == c) + char c = value.charAt(i); + + if (quoted) { - escape = false; - result.append(c); + if ('\\' == c && !escape) + { + escape = true; + } + else if (!escape && quote == c) + { + stop = true; + } + else if (escape && quote == c) + { + escape = false; + result.append(c); + } + else + { + if (escape) + { + escape = false; + result.append('\\'); + } + + result.append(c); + } } else { - if (escape) + if (!isCommentChar(c)) { - escape = false; - result.append('\\'); + result.append(c); + } + else + { + stop = true; } - - result.append(c); } + + i++; } - else + + String v = result.toString(); + if (!quoted) { - if (COMMENT_CHARS.indexOf(c) == -1) + v = v.trim(); + lineContinues = lineContinues(v); + if (lineContinues) { - result.append(c); - } - else - { - stop = true; + // remove trailing "\" + v = v.substring(0, v.length() - 1).trim(); } } + else + { + lineContinues = lineContinues(value, i); + } + propertyValue.append(v); - i++; - } + if (lineContinues) + { + propertyValue.append(LINE_SEPARATOR); + value = reader.readLine(); + } + } while (lineContinues && value != null); + + return propertyValue.toString(); + } + + /** + * Tests whether the specified string contains a line continuation marker. + * + * @param line the string to check + * @return a flag whether this line continues + */ + private static boolean lineContinues(String line) + { + String s = line.trim(); + return s.equals(LINE_CONT) + || (s.length() > 2 && s.endsWith(LINE_CONT) && Character + .isWhitespace(s.charAt(s.length() - 2))); + } - String v = result.toString(); - if (!quoted) + /** + * Tests whether the specified string contains a line continuation marker + * after the specified position. This method parses the string to remove a + * comment that might be present. Then it checks whether a line continuation + * marker can be found at the end. + * + * @param line the line to check + * @param pos the start position + * @return a flag whether this line continues + */ + private static boolean lineContinues(String line, int pos) + { + String s; + + if (pos >= line.length()) { - v = v.trim(); + s = line; } - return v; + else + { + int end = pos; + while (end < line.length() && !isCommentChar(line.charAt(end))) + { + end++; + } + s = line.substring(pos, end); + } + + return lineContinues(s); + } + + /** + * Tests whether the specified character is a comment character. + * + * @param c the character + * @return a flag whether this character starts a comment + */ + private static boolean isCommentChar(char c) + { + return COMMENT_CHARS.indexOf(c) >= 0; } /** Modified: commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestHierarchicalINIConfiguration.java URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestHierarchicalINIConfiguration.java?rev=719869&r1=719868&r2=719869&view=diff ============================================================================== --- commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestHierarchicalINIConfiguration.java (original) +++ commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestHierarchicalINIConfiguration.java Sat Nov 22 08:57:36 2008 @@ -57,7 +57,24 @@ + "var2 = \"quoted value\\nwith \\\"quotes\\\"\"" + LINE_SEPARATOR + "var3 = 123 ; comment" + LINE_SEPARATOR + "var4 = \"1;2;3\" ; comment" + LINE_SEPARATOR - + "var5 = '\\'quoted\\' \"value\"' ; comment"; + + "var5 = '\\'quoted\\' \"value\"' ; comment" + LINE_SEPARATOR + + "var6 = \"\"" + LINE_SEPARATOR; + + private static final String INI_DATA3 = "[section5]" + LINE_SEPARATOR + + "multiLine = one \\" + LINE_SEPARATOR + + " two \\" + LINE_SEPARATOR + + " three" + LINE_SEPARATOR + + "singleLine = C:\\Temp\\" + LINE_SEPARATOR + + "multiQuoted = one \\" + LINE_SEPARATOR + + "\" two \" \\" + LINE_SEPARATOR + + " three" + LINE_SEPARATOR + + "multiComment = one \\ ; a comment" + LINE_SEPARATOR + + "two" + LINE_SEPARATOR + + "multiQuotedComment = \" one \" \\ ; comment" + LINE_SEPARATOR + + "two" + LINE_SEPARATOR + + "noFirstLine = \\" + LINE_SEPARATOR + + " line 2" + LINE_SEPARATOR + + "continueNoLine = one \\" + LINE_SEPARATOR; /** An ini file with a global section. */ private static final String INI_DATA_GLOBAL = "globalVar = testGlobal" @@ -317,8 +334,7 @@ public void testQuotedValueWithWhitespace() throws Exception { final String content = "CmdPrompt = \" [EMAIL PROTECTED] ~]$ \""; - HierarchicalINIConfiguration config = new HierarchicalINIConfiguration(); - config.load(new StringReader(content)); + HierarchicalINIConfiguration config = setUpConfig(content); assertEquals("Wrong propert value", " [EMAIL PROTECTED] ~]$ ", config .getString("CmdPrompt")); } @@ -329,13 +345,22 @@ public void testQuotedValueWithWhitespaceAndComment() throws Exception { final String content = "CmdPrompt = \" [EMAIL PROTECTED] ~]$ \" ; a comment"; - HierarchicalINIConfiguration config = new HierarchicalINIConfiguration(); - config.load(new StringReader(content)); + HierarchicalINIConfiguration config = setUpConfig(content); assertEquals("Wrong propert value", " [EMAIL PROTECTED] ~]$ ", config .getString("CmdPrompt")); } /** + * Tests an empty quoted value. + */ + public void testQuotedValueEmpty() throws ConfigurationException + { + HierarchicalINIConfiguration config = setUpConfig(INI_DATA2); + assertEquals("Wrong value for empty property", "", config + .getString("section4.var6")); + } + + /** * Tests a property that has no value. */ public void testGetPropertyNoValue() throws ConfigurationException @@ -507,4 +532,80 @@ .getSection("Non existing section"); assertTrue("Sub config not empty", section.isEmpty()); } + + /** + * Tests a property whose value spans multiple lines. + */ + public void testLineContinuation() throws ConfigurationException + { + HierarchicalINIConfiguration config = setUpConfig(INI_DATA3); + assertEquals("Wrong value", "one" + LINE_SEPARATOR + "two" + + LINE_SEPARATOR + "three", config + .getString("section5.multiLine")); + } + + /** + * Tests a property value that ends on a backslash, which is no line + * continuation character. + */ + public void testLineContinuationNone() throws ConfigurationException + { + HierarchicalINIConfiguration config = setUpConfig(INI_DATA3); + assertEquals("Wrong value", "C:\\Temp\\", config + .getString("section5.singleLine")); + } + + /** + * Tests a property whose value spans multiple lines when quoting is + * involved. In this case whitespace must not be trimmed. + */ + public void testLineContinuationQuoted() throws ConfigurationException + { + HierarchicalINIConfiguration config = setUpConfig(INI_DATA3); + assertEquals("Wrong value", "one" + LINE_SEPARATOR + " two " + + LINE_SEPARATOR + "three", config + .getString("section5.multiQuoted")); + } + + /** + * Tests a property whose value spans multiple lines with a comment. + */ + public void testLineContinuationComment() throws ConfigurationException + { + HierarchicalINIConfiguration config = setUpConfig(INI_DATA3); + assertEquals("Wrong value", "one" + LINE_SEPARATOR + "two", config + .getString("section5.multiComment")); + } + + /** + * Tests a property with a quoted value spanning multiple lines and a + * comment. + */ + public void testLineContinuationQuotedComment() + throws ConfigurationException + { + HierarchicalINIConfiguration config = setUpConfig(INI_DATA3); + assertEquals("Wrong value", " one " + LINE_SEPARATOR + "two", config + .getString("section5.multiQuotedComment")); + } + + /** + * Tests a multi-line property value with an empty line. + */ + public void testLineContinuationEmptyLine() throws ConfigurationException + { + HierarchicalINIConfiguration config = setUpConfig(INI_DATA3); + assertEquals("Wrong value", LINE_SEPARATOR + "line 2", config + .getString("section5.noFirstLine")); + } + + /** + * Tests a line continuation at the end of the file. + */ + public void testLineContinuationAtEnd() throws ConfigurationException + { + HierarchicalINIConfiguration config = setUpConfig(INI_DATA3); + assertEquals("Wrong value", "one" + LINE_SEPARATOR, config + .getString("section5.continueNoLine")); + } } Modified: commons/proper/configuration/trunk/xdocs/changes.xml URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/xdocs/changes.xml?rev=719869&r1=719868&r2=719869&view=diff ============================================================================== --- commons/proper/configuration/trunk/xdocs/changes.xml (original) +++ commons/proper/configuration/trunk/xdocs/changes.xml Sat Nov 22 08:57:36 2008 @@ -125,6 +125,9 @@ INIConfiguration does not return the global section in its getSections() method. HierarchicalINIConfiguration fixes this problem. </action> + <action dev="oheger" type="add" issue="CONFIGURATION-324"> + HierarchicalINIConfiguration adds support for line continuation. + </action> <action dev="oheger" type="update"> INIConfiguration has been deprecated. Its functionality is now available through the new HierarchicalINIConfiguration class.