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.


Reply via email to