You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by oh...@apache.org on 2009/03/30 21:25:13 UTC

svn commit: r760116 - in /commons/proper/configuration/branches/configuration2_experimental: src/main/java/org/apache/commons/configuration2/ src/test/java/org/apache/commons/configuration2/ src/test/resources/ xdocs/ xdocs/userguide/

Author: oheger
Date: Mon Mar 30 19:25:12 2009
New Revision: 760116

URL: http://svn.apache.org/viewvc?rev=760116&view=rev
Log:
CONFIGURATION-371, CONFIGURATION-314: Ported changes to configuration2 branch.

Modified:
    commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/PropertiesConfiguration.java
    commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/PropertiesConfigurationLayout.java
    commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestPropertiesConfiguration.java
    commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestPropertiesConfigurationLayout.java
    commons/proper/configuration/branches/configuration2_experimental/src/test/resources/test.properties
    commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml
    commons/proper/configuration/branches/configuration2_experimental/xdocs/userguide/howto_properties.xml

Modified: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/PropertiesConfiguration.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/PropertiesConfiguration.java?rev=760116&r1=760115&r2=760116&view=diff
==============================================================================
--- commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/PropertiesConfiguration.java (original)
+++ commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/PropertiesConfiguration.java Mon Mar 30 19:25:12 2009
@@ -179,6 +179,9 @@
     /** Constant for the supported comment characters.*/
     static final String COMMENT_CHARS = "#!";
 
+    /** Constant for the default properties separator.*/
+    static final String DEFAULT_SEPARATOR = " = ";
+
     /**
      * Constant for the default <code>IOFactory</code>. This instance is used
      * when no specific factory was set.
@@ -600,7 +603,7 @@
     {
         /** The regular expression to parse the key and the value of a property. */
         private static final Pattern PROPERTY_PATTERN
-                = Pattern.compile("(([\\S&&[^\\\\" + new String(SEPARATORS) + "]]|\\\\.)*)\\s*(\\s+|[" + new String(SEPARATORS) +"])(.*)");
+                = Pattern.compile("(([\\S&&[^\\\\" + new String(SEPARATORS) + "]]|\\\\.)*)(\\s*(\\s+|[" + new String(SEPARATORS) +"])\\s*)(.*)");
 
         /** Stores the comment lines for the currently processed property.*/
         private List<String> commentLines;
@@ -611,6 +614,9 @@
         /** Stores the value of the last read property.*/
         private String propertyValue;
 
+        /** Stores the property separator of the last read property.*/
+        private String propertySeparator = DEFAULT_SEPARATOR;
+
         /** Stores the list delimiter character.*/
         private char delimiter;
 
@@ -750,6 +756,19 @@
         }
 
         /**
+         * Returns the separator that was used for the last read property. The
+         * separator can be stored so that it can later be restored when saving
+         * the configuration.
+         *
+         * @return the separator for the last read property
+         * @since 1.7
+         */
+        public String getPropertySeparator()
+        {
+            return propertySeparator;
+        }
+
+        /**
          * Parses a line read from the properties file. This method is called
          * for each non-comment line read from the source file. Its task is to
          * split the passed in line into the property key and its value. The
@@ -764,6 +783,7 @@
             String[] property = doParseProperty(line);
             initPropertyName(property[0]);
             initPropertyValue(property[1]);
+            initPropertySeparator(property[2]);
         }
 
         /**
@@ -795,6 +815,20 @@
         }
 
         /**
+         * Sets the separator of the current property. This method can be called
+         * by <code>parseProperty()</code>. It allows the associated layout
+         * object to keep track of the property separators. When saving the
+         * configuration the separators can be restored.
+         *
+         * @param value the separator used for the current property
+         * @since 1.7
+         */
+        protected void initPropertySeparator(String value)
+        {
+            propertySeparator = value;
+        }
+
+        /**
          * 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.
          *
@@ -813,7 +847,7 @@
         }
 
         /**
-         * Parse a property line and return the key and the value in an array.
+         * Parse a property line and return the key, the value, and the separator in an array.
          *
          * @param line the line to parse
          * @return an array with the property's key and value
@@ -822,11 +856,12 @@
         {
             Matcher matcher = PROPERTY_PATTERN.matcher(line);
 
-            String[] result = {"", ""};
+            String[] result = {"", "", ""};
 
             if (matcher.matches()) {
                 result[0] = matcher.group(1).trim();
-                result[1] = matcher.group(4).trim();
+                result[1] = matcher.group(5).trim();
+                result[2] = matcher.group(3);
             }
 
             return result;
@@ -834,13 +869,24 @@
     } // class PropertiesReader
 
     /**
-     * This class is used to write properties lines.
+     * This class is used to write properties lines. The most important method
+     * is <code>writeProperty(String, Object, boolean)</code>, which is called
+     * during a save operation for each property found in the configuration.
      */
     public static class PropertiesWriter extends FilterWriter
     {
         /** The delimiter for multi-valued properties.*/
         private char delimiter;
 
+        /** The separator to be used for the current property. */
+        private String currentSeparator;
+
+        /** The global separator. If set, it overrides the current separator.*/
+        private String globalSeparator;
+
+        /** The line separator.*/
+        private String lineSeparator;
+
         /**
          * Constructor.
          *
@@ -854,6 +900,79 @@
         }
 
         /**
+         * Returns the current property separator.
+         *
+         * @return the current property separator
+         * @since 1.7
+         */
+        public String getCurrentSeparator()
+        {
+            return currentSeparator;
+        }
+
+        /**
+         * Sets the current property separator. This separator is used when
+         * writing the next property.
+         *
+         * @param currentSeparator the current property separator
+         * @since 1.7
+         */
+        public void setCurrentSeparator(String currentSeparator)
+        {
+            this.currentSeparator = currentSeparator;
+        }
+
+        /**
+         * Returns the global property separator.
+         *
+         * @return the global property separator
+         * @since 1.7
+         */
+        public String getGlobalSeparator()
+        {
+            return globalSeparator;
+        }
+
+        /**
+         * Sets the global property separator. This separator corresponds to the
+         * <code>globalSeparator</code> property of
+         * {@link PropertiesConfigurationLayout}. It defines the separator to be
+         * used for all properties. If it is undefined, the current separator is
+         * used.
+         *
+         * @param globalSeparator the global property separator
+         * @since 1.7
+         */
+        public void setGlobalSeparator(String globalSeparator)
+        {
+            this.globalSeparator = globalSeparator;
+        }
+
+        /**
+         * Returns the line separator.
+         *
+         * @return the line separator
+         * @since 1.7
+         */
+        public String getLineSeparator()
+        {
+            return (lineSeparator != null) ? lineSeparator : LINE_SEPARATOR;
+        }
+
+        /**
+         * Sets the line separator. Each line written by this writer is
+         * terminated with this separator. If not set, the platform-specific
+         * line separator is used.
+         *
+         * @param lineSeparator the line separator to be used
+         * @since 1.7
+         */
+        public void setLineSeparator(String lineSeparator)
+        {
+            this.lineSeparator = lineSeparator;
+        }
+
+        /**
          * Write a property.
          *
          * @param key the key of the property
@@ -918,7 +1037,7 @@
             }
 
             write(escapeKey(key));
-            write(" = ");
+            write(fetchSeparator(key, value));
             write(v);
 
             writeln(null);
@@ -1032,9 +1151,29 @@
             {
                 write(s);
             }
-            write(LINE_SEPARATOR);
+            write(getLineSeparator());
         }
 
+        /**
+         * Returns the separator to be used for the given property. This method
+         * is called by <code>writeProperty()</code>. The string returned here
+         * is used as separator between the property key and its value. Per
+         * default the method checks whether a global separator is set. If this
+         * is the case, it is returned. Otherwise the separator returned by
+         * <code>getCurrentSeparator()</code> is used, which was set by the
+         * associated layout object. Derived classes may implement a different
+         * strategy for defining the separator.
+         *
+         * @param key the property key
+         * @param value the value
+         * @return the separator to be used
+         * @since 1.7
+         */
+        protected String fetchSeparator(String key, Object value)
+        {
+            return (getGlobalSeparator() != null) ? getGlobalSeparator()
+                    : getCurrentSeparator();
+        }
     } // class PropertiesWriter
 
     /**

Modified: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/PropertiesConfigurationLayout.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/PropertiesConfigurationLayout.java?rev=760116&r1=760115&r2=760116&view=diff
==============================================================================
--- commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/PropertiesConfigurationLayout.java (original)
+++ commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/PropertiesConfigurationLayout.java Mon Mar 30 19:25:12 2009
@@ -102,7 +102,7 @@
  * which has multiple values defined in one line using the separator character.</li>
  * <li>The <code>AppVendor</code> property appears twice. The comment lines
  * are concatenated, so that <code>layout.getComment("AppVendor");</code> will
- * result in <code>Application vendor&lt;CR&gt;Another vendor</code>, whith
+ * result in <code>Application vendor&lt;CR&gt;Another vendor</code>, with
  * <code>&lt;CR&gt;</code> meaning the line separator. In addition the
  * &quot;single line&quot; flag is set to <b>false</b> for this property. When
  * the file is saved, two property definitions will be written (in series).</li>
@@ -118,7 +118,7 @@
 public class PropertiesConfigurationLayout implements ConfigurationListener
 {
     /** Constant for the line break character. */
-    private static final String CR = System.getProperty("line.separator");
+    private static final String CR = "\n";
 
     /** Constant for the default comment prefix. */
     private static final String COMMENT_PREFIX = "# ";
@@ -132,6 +132,12 @@
     /** Stores the header comment. */
     private String headerComment;
 
+    /** The global separator that will be used for all properties. */
+    private String globalSeparator;
+
+    /** The line separator.*/
+    private String lineSeparator;
+
     /** A counter for determining nested load calls. */
     private int loadCounter;
 
@@ -359,6 +365,93 @@
     }
 
     /**
+     * Returns the separator for the property with the given key.
+     *
+     * @param key the property key
+     * @return the property separator for this property
+     * @since 1.7
+     */
+    public String getSeparator(String key)
+    {
+        return fetchLayoutData(key).getSeparator();
+    }
+
+    /**
+     * Sets the separator to be used for the property with the given key. The
+     * separator is the string between the property key and its value. For new
+     * properties &quot; = &quot; is used. When a properties file is read, the
+     * layout tries to determine the separator for each property. With this
+     * method the separator can be changed. To be compatible with the properties
+     * format only the characters <code>=</code> and <code>:</code> (with or
+     * without whitespace) should be used, but this method does not enforce this
+     * - it accepts arbitrary strings. If the key refers to a property with
+     * multiple values that are written on multiple lines, this separator will
+     * be used on all lines.
+     *
+     * @param key the key for the property
+     * @param sep the separator to be used for this property
+     * @since 1.7
+     */
+    public void setSeparator(String key, String sep)
+    {
+        fetchLayoutData(key).setSeparator(sep);
+    }
+
+    /**
+     * Returns the global separator.
+     *
+     * @return the global properties separator
+     * @since 1.7
+     */
+    public String getGlobalSeparator()
+    {
+        return globalSeparator;
+    }
+
+    /**
+     * Sets the global separator for properties. With this method a separator
+     * can be set that will be used for all properties when writing the
+     * configuration. This is an easy way of determining the properties
+     * separator globally. To be compatible with the properties format only the
+     * characters <code>=</code> and <code>:</code> (with or without whitespace)
+     * should be used, but this method does not enforce this - it accepts
+     * arbitrary strings. If the global separator is set to <b>null</b>,
+     * property separators are not changed. This is the default behavior as it
+     * produces results that are closer to the original properties file.
+     *
+     * @param globalSeparator the separator to be used for all properties
+     * @since 1.7
+     */
+    public void setGlobalSeparator(String globalSeparator)
+    {
+        this.globalSeparator = globalSeparator;
+    }
+
+    /**
+     * Returns the line separator.
+     *
+     * @return the line separator
+     * @since 1.7
+     */
+    public String getLineSeparator()
+    {
+        return lineSeparator;
+    }
+
+    /**
+     * Sets the line separator. When writing the properties configuration, all
+     * lines are terminated with this separator. If no separator was set, the
+     * platform-specific default line separator is used.
+     *
+     * @param lineSeparator the line separator
+     * @since 1.7
+     */
+    public void setLineSeparator(String lineSeparator)
+    {
+        this.lineSeparator = lineSeparator;
+    }
+
+    /**
      * Returns a set with all property keys managed by this object.
      *
      * @return a set with all contained property keys
@@ -416,6 +509,7 @@
                     {
                         data.setComment(comment);
                         data.setBlancLines(blancLines);
+                        data.setSeparator(reader.getPropertySeparator());
                     }
                 }
             }
@@ -448,9 +542,15 @@
                     : getConfiguration().getListDelimiter();
             PropertiesConfiguration.PropertiesWriter writer = getConfiguration()
                     .getIOFactory().createPropertiesWriter(out, delimiter);
+            writer.setGlobalSeparator(getGlobalSeparator());
+            if (getLineSeparator() != null)
+            {
+                writer.setLineSeparator(getLineSeparator());
+            }
+
             if (headerComment != null)
             {
-                writer.writeln(getCanonicalHeaderComment(true));
+                writeComment(writer, getCanonicalHeaderComment(true));
                 writer.writeln(null);
             }
 
@@ -466,14 +566,12 @@
                     }
 
                     // Output the comment
-                    if (getComment(key) != null)
-                    {
-                        writer.writeln(getCanonicalComment(key, true));
-                    }
+                    writeComment(writer, getCanonicalComment(key, true));
 
                     // Output the property and its value
                     boolean singleLine = (isForceSingleLine() || isSingleLine(key))
                             && !getConfiguration().isDelimiterParsingDisabled();
+                    writer.setCurrentSeparator(getSeparator(key));
                     writer.writeProperty(key, getConfiguration().getProperty(
                             key), singleLine);
                 }
@@ -728,6 +826,25 @@
     }
 
     /**
+     * Helper method for writing a comment line. This method ensures that the
+     * correct line separator is used if the comment spans multiple lines.
+     *
+     * @param writer the writer
+     * @param comment the comment to write
+     * @throws IOException if an IO error occurs
+     */
+    private static void writeComment(
+            PropertiesConfiguration.PropertiesWriter writer, String comment)
+            throws IOException
+    {
+        if (comment != null)
+        {
+            writer.writeln(StringUtils.replace(comment, CR, writer
+                    .getLineSeparator()));
+        }
+    }
+
+    /**
      * A helper class for storing all layout related information for a
      * configuration property.
      */
@@ -736,6 +853,9 @@
         /** Stores the comment for the property. */
         private StringBuilder comment;
 
+        /** The separator to be used for this property. */
+        private String separator;
+
         /** Stores the number of blanc lines before this property. */
         private int blancLines;
 
@@ -748,6 +868,7 @@
         public PropertyLayoutData()
         {
             singleLine = true;
+            separator = PropertiesConfiguration.DEFAULT_SEPARATOR;
         }
 
         /**
@@ -840,6 +961,26 @@
         }
 
         /**
+         * Returns the separator that was used for this property.
+         *
+         * @return the property separator
+         */
+        public String getSeparator()
+        {
+            return separator;
+        }
+
+        /**
+         * Sets the separator to be used for the represented property.
+         *
+         * @param separator the property separator
+         */
+        public void setSeparator(String separator)
+        {
+            this.separator = separator;
+        }
+
+        /**
          * Creates a copy of this object.
          *
          * @return the copy

Modified: commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestPropertiesConfiguration.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestPropertiesConfiguration.java?rev=760116&r1=760115&r2=760116&view=diff
==============================================================================
--- commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestPropertiesConfiguration.java (original)
+++ commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestPropertiesConfiguration.java Mon Mar 30 19:25:12 2009
@@ -17,8 +17,10 @@
 
 package org.apache.commons.configuration2;
 
+import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileOutputStream;
+import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
@@ -33,8 +35,10 @@
 import java.net.URLConnection;
 import java.net.URLStreamHandler;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 
 import junit.framework.TestCase;
 
@@ -913,8 +917,9 @@
     /**
      * Tests setting an IOFactory that uses a specialized writer.
      */
-    public void testSetIOFactoryWriter() throws ConfigurationException
+    public void testSetIOFactoryWriter() throws ConfigurationException, IOException
     {
+        final PropertiesWriterTestImpl testWriter = new PropertiesWriterTestImpl(',');
         conf.setIOFactory(new PropertiesConfiguration.IOFactory()
         {
             public PropertiesConfiguration.PropertiesReader createPropertiesReader(
@@ -926,22 +931,52 @@
             public PropertiesConfiguration.PropertiesWriter createPropertiesWriter(
                     Writer out, char delimiter)
             {
-                try
-                {
-                    return new PropertiesWriterTestImpl(out, delimiter);
-                }
-                catch (IOException ioex)
-                {
-                    fail("Unexpected exception: " + ioex);
-                    return null;
-                }
+                return testWriter;
             }
         });
         conf.save(new StringWriter());
+        testWriter.close();
         checkSavedConfig();
     }
 
     /**
+     * Tests that the property separators are retained when saving the
+     * configuration.
+     */
+    public void testKeepSeparators() throws ConfigurationException, IOException
+    {
+        conf.save(testSavePropertiesFile);
+        final String[] separatorTests = {
+                "test.separator.equal = foo", "test.separator.colon : foo",
+                "test.separator.tab\tfoo", "test.separator.whitespace foo",
+                "test.separator.no.space=foo"
+        };
+        Set<String> foundLines = new HashSet<String>();
+        BufferedReader in = new BufferedReader(new FileReader(
+                testSavePropertiesFile));
+        try
+        {
+            String s;
+            while ((s = in.readLine()) != null)
+            {
+                for (int i = 0; i < separatorTests.length; i++)
+                {
+                    if (separatorTests[i].equals(s))
+                    {
+                        foundLines.add(s);
+                    }
+                }
+            }
+        }
+        finally
+        {
+            in.close();
+        }
+        assertEquals("No all separators were found: " + foundLines,
+                separatorTests.length, foundLines.size());
+    }
+
+    /**
      * Creates a configuration that can be used for testing copy operations.
      *
      * @return the configuration to be copied
@@ -1130,8 +1165,7 @@
     private static class PropertiesWriterTestImpl extends
             PropertiesConfiguration.PropertiesWriter
     {
-        public PropertiesWriterTestImpl(Writer writer, char delimiter)
-                throws IOException
+        public PropertiesWriterTestImpl(char delimiter) throws IOException
         {
             super(new FileWriter(testSavePropertiesFile), delimiter);
         }

Modified: commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestPropertiesConfigurationLayout.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestPropertiesConfigurationLayout.java?rev=760116&r1=760115&r2=760116&view=diff
==============================================================================
--- commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestPropertiesConfigurationLayout.java (original)
+++ commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestPropertiesConfigurationLayout.java Mon Mar 30 19:25:12 2009
@@ -36,7 +36,10 @@
 public class TestPropertiesConfigurationLayout extends TestCase
 {
     /** Constant for the line break character. */
-    static final String CR = System.getProperty("line.separator");
+    private static final String CR = System.getProperty("line.separator");
+
+    /** Constant for the normalized line break character. */
+    private static final String CRNORM = "\n";
 
     /** Constant for a test property key. */
     static final String TEST_KEY = "myProperty";
@@ -138,7 +141,7 @@
         layout.load(builder.getReader());
         assertEquals("Wrong number of blanc lines", 2, layout
                 .getBlancLinesBefore(TEST_KEY));
-        assertEquals("Wrong comment", TEST_COMMENT + CR, layout
+        assertEquals("Wrong comment", TEST_COMMENT + CRNORM, layout
                 .getCanonicalComment(TEST_KEY, false));
         assertEquals("Wrong property value", TEST_VALUE, config
                 .getString(TEST_KEY));
@@ -182,7 +185,7 @@
         builder.addProperty(TEST_KEY, TEST_VALUE + "2");
         layout.load(builder.getReader());
         assertEquals("Wrong combined comment",
-                TEST_COMMENT + CR + TEST_COMMENT, layout.getCanonicalComment(
+                TEST_COMMENT + CRNORM + TEST_COMMENT, layout.getCanonicalComment(
                         TEST_KEY, false));
         assertEquals("Wrong combined blanc numbers", 0, layout
                 .getBlancLinesBefore(TEST_KEY));
@@ -214,7 +217,7 @@
         builder.addComment(null);
         builder.addProperty(TEST_KEY, TEST_VALUE);
         layout.load(builder.getReader());
-        assertEquals("Wrong header comment", TEST_COMMENT + CR + CR
+        assertEquals("Wrong header comment", TEST_COMMENT + CRNORM + CRNORM
                 + TEST_COMMENT, layout.getCanonicalHeaderComment(false));
         assertNull("Wrong comment for property", layout.getComment(TEST_KEY));
     }
@@ -233,7 +236,7 @@
         builder.addComment(TEST_COMMENT);
         builder.addProperty(TEST_KEY, TEST_VALUE);
         layout.load(builder.getReader());
-        assertEquals("Wrong header comment", TEST_COMMENT + CR + CR
+        assertEquals("Wrong header comment", TEST_COMMENT + CRNORM + CRNORM
                 + TEST_COMMENT, layout.getCanonicalHeaderComment(false));
         assertEquals("Wrong comment for property", TEST_COMMENT, layout
                 .getCanonicalComment(TEST_KEY, false));
@@ -422,9 +425,9 @@
                 .getCanonicalHeaderComment(false));
         assertFalse("Include property was stored", layout.getKeys().contains(
                 PropertiesConfiguration.getInclude()));
-        assertEquals("Wrong comment for property", TEST_COMMENT + CR
-                + "A nested header comment." + CR + "With multiple lines" + CR
-                + CR + "Second comment", layout.getCanonicalComment(TEST_KEY,
+        assertEquals("Wrong comment for property", TEST_COMMENT + CRNORM
+                + "A nested header comment." + CRNORM + "With multiple lines" + CRNORM
+                + CRNORM + "Second comment", layout.getCanonicalComment(TEST_KEY,
                 false));
     }
 
@@ -460,7 +463,7 @@
         layout.setComment("AnotherProperty", "AnotherComment");
         layout.setBlancLinesBefore("AnotherProperty", 2);
         layout.setSingleLine("AnotherProperty", true);
-        layout.setHeaderComment("A header comment" + CR + "for my properties");
+        layout.setHeaderComment("A header comment" + CRNORM + "for my properties");
         checkLayoutString("# A header comment" + CR + "# for my properties"
                 + CR + CR + "# " + TEST_COMMENT + CR + TEST_KEY + " = "
                 + TEST_VALUE + CR + TEST_KEY + " = " + TEST_VALUE + "2" + CR
@@ -616,6 +619,62 @@
     }
 
     /**
+     * Tests changing the separator for a property.
+     */
+    public void testSetSeparator() throws ConfigurationException
+    {
+        config.addProperty(TEST_KEY, TEST_VALUE);
+        layout.setSeparator(TEST_KEY, ":");
+        checkLayoutString(TEST_KEY + ":" + TEST_VALUE + CR);
+    }
+
+    /**
+     * Tests setting the global separator. This separator should override the
+     * separators for all properties.
+     */
+    public void testSetGlobalSeparator() throws ConfigurationException
+    {
+        final String sep = "=";
+        config.addProperty(TEST_KEY, TEST_VALUE);
+        config.addProperty("key2", "value2");
+        layout.setSeparator(TEST_KEY, " : ");
+        layout.setGlobalSeparator(sep);
+        checkLayoutString(TEST_KEY + sep + TEST_VALUE + CR + "key2" + sep
+                + "value2" + CR);
+    }
+
+    /**
+     * Tests setting the line separator.
+     */
+    public void testSetLineSeparator() throws ConfigurationException
+    {
+        final String lf = CR + CR;
+        config.addProperty(TEST_KEY, TEST_VALUE);
+        layout.setBlancLinesBefore(TEST_KEY, 2);
+        layout.setComment(TEST_KEY, TEST_COMMENT);
+        layout.setHeaderComment("Header comment");
+        layout.setLineSeparator(lf);
+        checkLayoutString("# Header comment" + lf + lf + lf + lf + "# "
+                + TEST_COMMENT + lf + TEST_KEY + " = " + TEST_VALUE + lf);
+    }
+
+    /**
+     * Tests whether the line separator is also taken into account within
+     * comments.
+     */
+    public void testSetLineSeparatorInComments() throws ConfigurationException
+    {
+        final String lf = "<-\n";
+        config.addProperty(TEST_KEY, TEST_VALUE);
+        layout.setComment(TEST_KEY, TEST_COMMENT + "\nMore comment");
+        layout.setHeaderComment("Header\ncomment");
+        layout.setLineSeparator(lf);
+        checkLayoutString("# Header" + lf + "# comment" + lf + lf + "# "
+                + TEST_COMMENT + lf + "# More comment" + lf + TEST_KEY + " = "
+                + TEST_VALUE + lf);
+    }
+
+    /**
      * Helper method for filling the layout object with some properties.
      */
     private void fillLayout()
@@ -722,6 +781,7 @@
          *
          * @return the buffer as string
          */
+        @Override
         public String toString()
         {
             return buf.toString();
@@ -741,6 +801,7 @@
          * Simulates the propertyLoaded() callback. If a builder was set, a
          * load() call on the layout is invoked.
          */
+        @Override
         boolean propertyLoaded(String key, String value)
                 throws ConfigurationException
         {

Modified: commons/proper/configuration/branches/configuration2_experimental/src/test/resources/test.properties
URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/resources/test.properties?rev=760116&r1=760115&r2=760116&view=diff
==============================================================================
--- commons/proper/configuration/branches/configuration2_experimental/src/test/resources/test.properties (original)
+++ commons/proper/configuration/branches/configuration2_experimental/src/test/resources/test.properties Mon Mar 30 19:25:12 2009
@@ -100,3 +100,5 @@
 test.separator.tab	foo
 test.separator.formfeedfoo
 test.separator.whitespace foo
+test.separator.no.space=foo
+

Modified: commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml
URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml?rev=760116&r1=760115&r2=760116&view=diff
==============================================================================
--- commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml (original)
+++ commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml Mon Mar 30 19:25:12 2009
@@ -89,7 +89,17 @@
         File system access has been abstracted to a FileSystem interface. Two implementations
         are provided, DefaultFileSystem that behaves in a backward compatible manner and
         VFSFileSystem which uses Commons VFS to retreive and store files.
-      </action>      
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-314">
+        PropertiesConfigurationLayout now allows setting the line separator to
+        be used when writing the properties file.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-371">
+        PropertiesConfigurationLayout now also stores the property separators used for
+        the single properties. It is also possible to change them for specific
+        properties or set a global properties separator. In earlier versions
+        the separator was hard-coded to &quot; = &quot;.
+      </action>
       <action dev="oheger" type="add" issue="CONFIGURATION-370">
         PropertiesConfiguration now defines a nested interface IOFactory. Using
         this interface it is possible to inject custom PropertiesReader and

Modified: commons/proper/configuration/branches/configuration2_experimental/xdocs/userguide/howto_properties.xml
URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/xdocs/userguide/howto_properties.xml?rev=760116&r1=760115&r2=760116&view=diff
==============================================================================
--- commons/proper/configuration/branches/configuration2_experimental/xdocs/userguide/howto_properties.xml (original)
+++ commons/proper/configuration/branches/configuration2_experimental/xdocs/userguide/howto_properties.xml Mon Mar 30 19:25:12 2009
@@ -197,14 +197,58 @@
       </p>
       <p>
         Normally a developer does not have to deal with these layout objects.
-        However there are some methods that might be of interest in certain use
-        cases. For instance <code>PropertiesConfigurationLayout</code> defines
-        methods for obtaining and setting the comment for a property key. A
-        header comment for the whole properties file is also supported. If the
-        values of multi-valued properties should always be written on a
-        single line rather than adding a new property definition for each value
-        (which would be incompatible with <code>java.util.Properties</code>)
-        the <code>setForceSingleLine()</code> method can be used.
+        However, there are some methods that might be of interest if enhanced
+        control over the output of properties files is needed. The following
+        list describes these methods (note that corresponding get methods are
+        of course also provided):
+        <ul>
+        <li><code>setComment()</code><br/>
+        With this method a comment can be set for a specified property. When
+        storing the configuration the comment is output before the property,
+        followed by a line break. The comment can span multiple lines; in this
+        case the newline character &quot;\n&quot; must be used as line
+        separator.</li>
+        <li><code>setHeaderComment()</code><br/>
+        With <code>setHeaderComment()</code> a global comment can be set for the
+        properties file. This comment is written at the very start of the file,
+        followed by an empty line.</li>
+        <li><code>setBlancLinesBefore()</code><br/>
+        This methods allows defining the number of empty lines to be written
+        before the specified property. It can be used, for instance, for
+        deviding the properties file into multiple logic sections.</li>
+        <li><code>setSingleLine()</code><br/>
+        If a property has multiple values, with <code>setSingleLine()</code> it
+        can be specified that all these values should be written into a single
+        line separated by the default list separator. It is also possible to
+        write multiple definitions for this property (i.e. multiple lines of the
+        form <code>property = value1</code>, <code>property = value2</code> etc.).
+        This is supported by <code>PropertiesConfiguration</code>, but will
+        probably not work when processing the properties file with other tools.
+        </li>
+        <li><code>setForceSingleLine()</code><br/>
+        This is similar to <code>setSingleLine()</code>, but sets a global
+        single line flag. If set to <b>true</b>, all properties with multiple
+        values are always written on a single line.</li>
+        <li><code>setGlobalSeparator()</code><br/>
+        Sometimes it may be necessary to define the properties separator, i.e.
+        the string that separates the property key from the value. This can be
+        done using <code>setGlobalSeparator()</code>. Here an arbitrary string
+        can be specified that will be used as separator. (Note: In order to
+        produce valid properties files only the characters <code>=</code> and
+        <code>:</code> should be used as separators (with or without leading or
+        trailing whitespace), but the method does not enforce this.</li>
+        <li><code>setSeparator()</code><br/>
+        This method is similar to <code>setGlobalSeparator()</code>, but
+        allows setting the property separator for a specific property.</li>
+        <li><code>setLineSeparator()</code><br/>
+        Using this method the line separator can be specified. Per default the
+        platform-specific line separator is used (e.g. <code>\n</code> on unix).
+        </li>
+        </ul>
+        The default settings of <code>PropertiesConfigurationLayout</code> are
+        chosen in a way that most of the original layout of a properties file
+        is retained. With the methods listed above specific layout restrictions
+        can be enforced.
       </p>
       </subsection>