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/10/31 17:11:38 UTC
svn commit: r831560 - in
/commons/proper/configuration/branches/configuration2_experimental/src:
main/java/org/apache/commons/configuration2/base/impl/
test/java/org/apache/commons/configuration2/base/impl/
Author: oheger
Date: Sat Oct 31 16:11:38 2009
New Revision: 831560
URL: http://svn.apache.org/viewvc?rev=831560&view=rev
Log:
Added INIConfigurationSource as a proof of concept for a file-based configuration source.
Added:
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/impl/
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/impl/INIConfigurationSource.java (with props)
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/impl/
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/impl/TestINIConfigurationSource.java (with props)
Added: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/impl/INIConfigurationSource.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/impl/INIConfigurationSource.java?rev=831560&view=auto
==============================================================================
--- commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/impl/INIConfigurationSource.java (added)
+++ commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/impl/INIConfigurationSource.java Sat Oct 31 16:11:38 2009
@@ -0,0 +1,643 @@
+/*
+ * 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 org.apache.commons.configuration2.base.impl;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.configuration2.ConfigurationException;
+import org.apache.commons.configuration2.base.Capability;
+import org.apache.commons.configuration2.base.DefaultLocatorSupport;
+import org.apache.commons.configuration2.base.InMemoryConfigurationSource;
+import org.apache.commons.configuration2.base.LocatorSupport;
+import org.apache.commons.configuration2.base.StreamBasedSource;
+import org.apache.commons.configuration2.expr.ExpressionEngine;
+import org.apache.commons.configuration2.tree.ConfigurationNode;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * <p>
+ * A specialized hierarchical configuration source implementation for parsing
+ * ini files.
+ * </p>
+ * <p>
+ * An initialization or ini file is a configuration file typically found on
+ * Microsoft's Windows operating system and contains data for Windows based
+ * applications.
+ * </p>
+ * <p>
+ * Although popularized by Windows, ini files can be used on any system or
+ * platform due to the fact that they are merely text files that can easily be
+ * parsed and modified by both humans and computers.
+ * </p>
+ * <p>
+ * A typcial ini file could look something like:
+ * </p>
+ * <code>
+ * [section1]<br>
+ * ; this is a comment!<br>
+ * var1 = foo<br>
+ * var2 = bar<br>
+ * <br>
+ * [section2]<br>
+ * var1 = doo<br>
+ * </code>
+ * <p>
+ * The format of ini files is fairly straight forward and is composed of three
+ * components:<br>
+ * <ul>
+ * <li><b>Sections:</b> Ini files are split into sections, each section starting
+ * with a section declaration. A section declaration starts with a '[' and ends
+ * with a ']'. Sections occur on one line only.</li>
+ * <li><b>Parameters:</b> Items in a section are known as parameters. Parameters
+ * have a typical <code>key = value</code> format.</li>
+ * <li><b>Comments:</b> Lines starting with a ';' are assumed to be comments.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * There are various implementations of the ini file format by various vendors
+ * which has caused a number of differences to appear. As far as possible this
+ * configuration source tries to be lenient and support most of the differences.
+ * </p>
+ * <p>
+ * Some of the differences supported are as follows:
+ * <ul>
+ * <li><b>Comments:</b> The '#' character is also accepted as a comment
+ * signifier.</li>
+ * <li><b>Key value separtor:</b> The ':' character is also accepted in place of
+ * '=' to separate keys and values in parameters, for example
+ * <code>var1 : foo</code>.</li>
+ * <li><b>Duplicate sections:</b> Typically duplicate sections are not allowed,
+ * this configuration source does however support it. In the event of a
+ * duplicate section, the two section's values are merged.</li>
+ * <li><b>Duplicate parameters:</b> Typically duplicate parameters are only
+ * allowed if they are in two different sections, thus they are local to
+ * sections; this configuration source simply merges duplicates; if a section
+ * has a duplicate parameter the values are then added to the key as a list.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * Global parameters are also allowed; any parameters declared before a section
+ * is declared are added to a global section. It is important to note that this
+ * global section does not have a name.
+ * </p>
+ * <p>
+ * In all instances, a parameter's key is prepended with its section name and a
+ * '.' (period). Thus a parameter named "var1" in "section1" will have the key
+ * <code>section1.var1</code> in this configuration source. (This is the default
+ * behavior. Because this is a hierarchical configuration source you can change
+ * this by setting a different {@link ExpressionEngine}.)
+ * </p>
+ * <p>
+ * <h3>Implementation Details:</h3> Consider the following ini file:<br>
+ * <code>
+ * default = ok<br>
+ * <br>
+ * [section1]<br>
+ * var1 = foo<br>
+ * var2 = doodle<br>
+ * <br>
+ * [section2]<br>
+ * ; a comment<br>
+ * var1 = baz<br>
+ * var2 = shoodle<br>
+ * bad =<br>
+ * = worse<br>
+ * <br>
+ * [section3]<br>
+ * # another comment<br>
+ * var1 : foo<br>
+ * var2 : bar<br>
+ * var5 : test1<br>
+ * <br>
+ * [section3]<br>
+ * var3 = foo<br>
+ * var4 = bar<br>
+ * var5 = test2<br>
+ * </code>
+ * </p>
+ * <p>
+ * This ini file will be parsed without error. Note:
+ * <ul>
+ * <li>The parameter named "default" is added to the global section, it's value
+ * is accessed simply using <code>getProperty("default")</code>.</li>
+ * <li>Section 1's parameters can be accessed using
+ * <code>getProperty("section1.var1")</code>.</li>
+ * <li>The parameter named "bad" simply adds the parameter with an empty value.</li>
+ * <li>The empty key with value "= worse" is added using a key consisting of a
+ * single space character. This key is still added to section 2 and the value
+ * can be accessed using <code>getProperty("section2. ")</code>, notice the
+ * period '.' and the space following the section name.</li>
+ * <li>Section three uses both '=' and ':' to separate keys and values.</li>
+ * <li>Section 3 has a duplicate key named "var5". The value for this key is
+ * [test1, test2], and is represented as a List.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * Internally, this configuration source maps the content of the represented ini
+ * file to its node structure in the following way:
+ * <ul>
+ * <li>Sections are represented by direct child nodes of the root node.</li>
+ * <li>For the content of a section, corresponding nodes are created as children
+ * of the section node.</li>
+ * </ul>
+ * This explains how the keys for the properties can be constructed. You can
+ * also use other methods of {@link InMemoryConfigurationSource} for querying or
+ * manipulating the hierarchy of configuration nodes or use the extended {@code
+ * Configuration} interface. For instance, the {@code configurationAt()} method
+ * is useful for obtaining the data of a specific section.
+ * </p>
+ * <p>
+ * The set of sections in this configuration source can be retrieved using the
+ * {@code getSections()} method. For obtaining a {@link SubConfiguration} with
+ * the content of a specific section the {@code getSection()} method can be
+ * used.
+ * </p>
+ * <p>
+ * <em>Note:</em> Configuration source objects of this type can be read
+ * concurrently by multiple threads. However if one of these threads modifies
+ * the object, synchronization has to be performed manually.
+ * </p>
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id$
+ */
+public class INIConfigurationSource extends InMemoryConfigurationSource
+ implements StreamBasedSource
+{
+ /**
+ * The characters that signal the start of a comment line.
+ */
+ private static final String COMMENT_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 = "\\";
+
+ /**
+ * Return a set containing the sections in this ini configuration. Note that
+ * changes to this set do not affect the configuration.
+ *
+ * @return a set containing the sections.
+ */
+ public Set<String> getSections()
+ {
+ Set<String> sections = new LinkedHashSet<String>();
+ boolean globalSection = false;
+
+ for (ConfigurationNode node : getRootNode().getChildren())
+ {
+ if (isSectionNode(node))
+ {
+ if (globalSection)
+ {
+ sections.add(null);
+ globalSection = false;
+ }
+ sections.add(node.getName());
+ }
+ else
+ {
+ globalSection = true;
+ }
+ }
+
+ return sections;
+ }
+
+ /**
+ * Loads the data of this {@code ConfigurationSource} from the specified
+ * reader.
+ *
+ * @param reader the reader
+ * @throws IOException if an I/O error occurs
+ * @throws ConfigurationException if the data cannot be parsed
+ */
+ public void load(Reader reader) throws IOException, ConfigurationException
+ {
+ BufferedReader bufferedReader = new BufferedReader(reader);
+ ConfigurationNode sectionNode = getRootNode();
+
+ String line = bufferedReader.readLine();
+ while (line != null)
+ {
+ line = line.trim();
+ if (!isCommentLine(line))
+ {
+ if (isSectionLine(line))
+ {
+ String section = line.substring(1, line.length() - 1);
+ sectionNode = getSectionNode(section);
+ }
+
+ else
+ {
+ String key = "";
+ String value = "";
+ int index = line.indexOf("=");
+ if (index >= 0)
+ {
+ key = line.substring(0, index);
+ value = parseValue(line.substring(index + 1),
+ bufferedReader);
+ }
+ else
+ {
+ index = line.indexOf(":");
+ if (index >= 0)
+ {
+ key = line.substring(0, index);
+ value = parseValue(line.substring(index + 1),
+ bufferedReader);
+ }
+ else
+ {
+ key = line;
+ }
+ }
+ key = key.trim();
+ if (key.length() < 1)
+ {
+ // use space for properties with no key
+ key = " ";
+ }
+ getNodeHandler().addChild(sectionNode, key, value);
+ }
+ }
+
+ line = bufferedReader.readLine();
+ }
+ }
+
+ /**
+ * Writes the data of this {@code ConfigurationSource} into the specified
+ * writer. This creates a valid ini file.
+ *
+ * @param writer the writer
+ * @throws IOException if an I/O error occurs
+ * @throws ConfigurationException for all other errors
+ */
+ public void save(Writer writer) throws IOException, ConfigurationException
+ {
+ PrintWriter out = new PrintWriter(writer);
+
+ // First print the global section
+ boolean hasGlobal = false;
+ for (ConfigurationNode node : getRootNode().getChildren())
+ {
+ if (!isSectionNode(node))
+ {
+ printNode(out, node);
+ hasGlobal = true;
+ }
+ }
+ if (hasGlobal)
+ {
+ out.println(); // add an empty line
+ }
+
+ // Now process the remaining sections
+ for (String section : getSections())
+ {
+ if (section != null)
+ {
+ out.print("[");
+ out.print(section);
+ out.print("]");
+ out.println();
+
+ ConfigurationNode sectionNode = getSectionNode(section);
+ for (ConfigurationNode node : sectionNode.getChildren())
+ {
+ printNode(out, node);
+ }
+
+ out.println();
+ }
+ }
+
+ out.flush();
+ }
+
+ /**
+ * Prints the specified node into the given writer. This method is called by
+ * {@code save()} for each processed node.
+ *
+ * @param out the writer
+ * @param node the node to be printed
+ */
+ protected void printNode(PrintWriter out, ConfigurationNode node)
+ {
+ out.print(node.getName());
+ out.print(" = ");
+ out.print(formatValue(String.valueOf(node.getValue())));
+ out.println();
+ }
+
+ /**
+ * Determine if the given line is a comment line.
+ *
+ * @param line The line to check.
+ * @return true if the line is empty or starts with one of the comment
+ * characters
+ */
+ protected boolean isCommentLine(String line)
+ {
+ if (line == null)
+ {
+ return false;
+ }
+ // blank lines are also treated as comment lines
+ return line.length() < 1 || COMMENT_CHARS.indexOf(line.charAt(0)) >= 0;
+ }
+
+ /**
+ * Determine if the given line is a section.
+ *
+ * @param line The line to check.
+ * @return true if the line contains a section
+ */
+ protected boolean isSectionLine(String line)
+ {
+ if (line == null)
+ {
+ return false;
+ }
+ return line.startsWith("[") && line.endsWith("]");
+ }
+
+ /**
+ * Adds custom capabilities to the specified capabilities collection. This
+ * implementation adds a {@link LocatorSupport} capability.
+ *
+ * @param caps the capabilities collection
+ */
+ @Override
+ protected void appendCapabilities(Collection<Capability> caps)
+ {
+ caps.add(new Capability(LocatorSupport.class,
+ new DefaultLocatorSupport(this)));
+ }
+
+ /**
+ * Obtains the node representing the specified section. This method is
+ * called while the configuration is loaded. If a node for this section
+ * already exists, it is returned. Otherwise a new node is created.
+ *
+ * @param sectionName the name of the section
+ * @return the node for this section
+ */
+ private ConfigurationNode getSectionNode(String sectionName)
+ {
+ List<ConfigurationNode> nodes = getRootNode().getChildren(sectionName);
+ if (!nodes.isEmpty())
+ {
+ return nodes.get(0);
+ }
+
+ ConfigurationNode node = getNodeHandler().addChild(getRootNode(),
+ sectionName);
+ markSectionNode(node);
+ return node;
+ }
+
+ /**
+ * Parse the value to remove the quotes and ignoring the comment. Example:
+ *
+ * <pre>
+ * "value" ; comment -> value
+ * </pre>
+ *
+ * <pre>
+ * 'value' ; comment -> value
+ * </pre>
+ *
+ * @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 static String parseValue(String val, BufferedReader reader)
+ throws IOException
+ {
+ StringBuilder propertyValue = new StringBuilder();
+ boolean lineContinues;
+ String value = val.trim();
+
+ do
+ {
+ boolean quoted = value.startsWith("\"") || value.startsWith("'");
+ boolean stop = false;
+ boolean escape = false;
+
+ char quote = quoted ? value.charAt(0) : 0;
+
+ int i = quoted ? 1 : 0;
+
+ StringBuilder result = new StringBuilder();
+ while (i < value.length() && !stop)
+ {
+ char c = value.charAt(i);
+
+ if (quoted)
+ {
+ 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 (!isCommentChar(c))
+ {
+ result.append(c);
+ }
+ else
+ {
+ stop = true;
+ }
+ }
+
+ i++;
+ }
+
+ String v = result.toString();
+ if (!quoted)
+ {
+ v = v.trim();
+ lineContinues = lineContinues(v);
+ if (lineContinues)
+ {
+ // remove trailing "\"
+ v = v.substring(0, v.length() - 1).trim();
+ }
+ }
+ else
+ {
+ lineContinues = lineContinues(value, i);
+ }
+ propertyValue.append(v);
+
+ 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)));
+ }
+
+ /**
+ * 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())
+ {
+ s = line;
+ }
+ else
+ {
+ int end = pos;
+ while (end < line.length() && !isCommentChar(line.charAt(end)))
+ {
+ end++;
+ }
+ s = line.substring(pos, end);
+ }
+
+ return lineContinues(s);
+ }
+
+ /**
+ * Add quotes around the specified value if it contains a comment character.
+ */
+ private String formatValue(String value)
+ {
+ boolean quoted = false;
+
+ for (int i = 0; i < COMMENT_CHARS.length() && !quoted; i++)
+ {
+ char c = COMMENT_CHARS.charAt(i);
+ if (value.indexOf(c) != -1)
+ {
+ quoted = true;
+ }
+ }
+
+ if (quoted)
+ {
+ return '"' + StringUtils.replace(value, "\"", "\\\"") + '"';
+ }
+ else
+ {
+ return value;
+ }
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Marks a configuration node as a section node. This means that this node
+ * represents a section header. This implementation uses the node's
+ * reference property to store a flag.
+ *
+ * @param node the node to be marked
+ */
+ private static void markSectionNode(ConfigurationNode node)
+ {
+ node.setReference(Boolean.TRUE);
+ }
+
+ /**
+ * Checks whether the specified configuration node represents a section.
+ *
+ * @param node the node in question
+ * @return a flag whether this node represents a section
+ */
+ private static boolean isSectionNode(ConfigurationNode node)
+ {
+ return node.getReference() != null || node.getChildrenCount() > 0;
+ }
+}
Propchange: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/impl/INIConfigurationSource.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/impl/INIConfigurationSource.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL
Propchange: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/impl/INIConfigurationSource.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/impl/TestINIConfigurationSource.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/impl/TestINIConfigurationSource.java?rev=831560&view=auto
==============================================================================
--- commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/impl/TestINIConfigurationSource.java (added)
+++ commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/impl/TestINIConfigurationSource.java Sat Oct 31 16:11:38 2009
@@ -0,0 +1,659 @@
+/*
+ * 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 org.apache.commons.configuration2.base.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.net.URL;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.apache.commons.configuration2.ConfigurationAssert;
+import org.apache.commons.configuration2.ConfigurationException;
+import org.apache.commons.configuration2.base.Configuration;
+import org.apache.commons.configuration2.base.ConfigurationImpl;
+import org.apache.commons.configuration2.base.LocatorSupport;
+import org.apache.commons.configuration2.fs.Locator;
+import org.apache.commons.configuration2.fs.URLLocator;
+import org.apache.commons.configuration2.tree.ConfigurationNode;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Test class for {@code INIConfigurationSource}
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id$
+ */
+public class TestINIConfigurationSource
+{
+ private static String LINE_SEPARATOR = System.getProperty("line.separator");
+
+ /** Constant for the content of an ini file. */
+ private static final String INI_DATA = "[section1]" + LINE_SEPARATOR
+ + "var1 = foo" + LINE_SEPARATOR + "var2 = 451" + LINE_SEPARATOR
+ + LINE_SEPARATOR + "[section2]" + LINE_SEPARATOR + "var1 = 123.45"
+ + LINE_SEPARATOR + "var2 = bar" + LINE_SEPARATOR + LINE_SEPARATOR
+ + "[section3]" + LINE_SEPARATOR + "var1 = true" + LINE_SEPARATOR
+ + "interpolated = ${section3.var1}" + LINE_SEPARATOR
+ + "multi = foo" + LINE_SEPARATOR + "multi = bar" + LINE_SEPARATOR
+ + LINE_SEPARATOR;
+
+ private static final String INI_DATA2 = "[section4]" + LINE_SEPARATOR
+ + "var1 = \"quoted value\"" + LINE_SEPARATOR
+ + "var2 = \"quoted value\\nwith \\\"quotes\\\"\"" + LINE_SEPARATOR
+ + "var3 = 123 ; comment" + LINE_SEPARATOR
+ + "var4 = \"1;2;3\" ; comment" + LINE_SEPARATOR
+ + "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"
+ + LINE_SEPARATOR + LINE_SEPARATOR + INI_DATA;
+
+ /** Constant for the name of the test output file. */
+ private static final String TEST_OUT_FILE = "test.ini";
+
+ /** A test ini file. */
+ private static File testFile;
+
+ /** The URL to the test file. */
+ private static URL testFileURL;
+
+ /** The source to be tested. */
+ private INIConfigurationSource source;
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception
+ {
+ testFile = ConfigurationAssert.getOutFile(TEST_OUT_FILE);
+ testFileURL = ConfigurationAssert.getOutURL(TEST_OUT_FILE);
+ }
+
+ @Before
+ public void setUp() throws Exception
+ {
+ source = new INIConfigurationSource();
+ }
+
+ @After
+ public void tearDown() throws Exception
+ {
+ if (testFile.exists())
+ {
+ assertTrue("Cannot remove test file: " + testFile, testFile
+ .delete());
+ }
+ }
+
+ /**
+ * Populates the test INIConfigurationSource object with the given data.
+ *
+ * @param data the data of the configuration source (an ini file as string)
+ * @throws ConfigurationException if an error occurs
+ */
+ private void load(String data) throws ConfigurationException
+ {
+ StringReader reader = new StringReader(data);
+ try
+ {
+ source.load(reader);
+ }
+ catch (IOException ioex)
+ {
+ throw new ConfigurationException(ioex);
+ }
+ reader.close();
+ }
+
+ /**
+ * Creates a configuration with the test configuration source and
+ * initializes the source with the given data.
+ *
+ * @param data the data (an ini file as string)
+ * @return the configuration
+ * @throws ConfigurationException if an error occurs
+ */
+ private Configuration<ConfigurationNode> setUpConfig(String data)
+ throws ConfigurationException
+ {
+ load(data);
+ return new ConfigurationImpl<ConfigurationNode>(source);
+ }
+
+ /**
+ * Tests whether a source can be correctly saved.
+ */
+ @Test
+ public void testSave() throws Exception
+ {
+ Writer writer = new StringWriter();
+ Configuration<ConfigurationNode> config = new ConfigurationImpl<ConfigurationNode>(
+ source);
+ config.addProperty("section1.var1", "foo");
+ config.addProperty("section1.var2", "451");
+ config.addProperty("section2.var1", "123.45");
+ config.addProperty("section2.var2", "bar");
+ config.addProperty("section3.var1", "true");
+ config.addProperty("section3.interpolated", "${section3.var1}");
+ config.addProperty("section3.multi", "foo");
+ config.addProperty("section3.multi", "bar");
+ source.save(writer);
+
+ assertEquals("Wrong content of ini file", INI_DATA, writer.toString());
+ }
+
+ /**
+ * Tests whether a configuration source with a global section can be saved.
+ */
+ @Test
+ public void testSaveWithGlobalSection() throws ConfigurationException,
+ IOException
+ {
+ load(INI_DATA_GLOBAL);
+ StringWriter writer = new StringWriter();
+ source.save(writer);
+ assertEquals("Wrong content of ini file", INI_DATA_GLOBAL, writer
+ .toString());
+ }
+
+ /**
+ * Tests whether the configuration contains the expected data.
+ *
+ * @param config the configuration to check
+ */
+ private void checkContent(Configuration<ConfigurationNode> config)
+ {
+ assertEquals("Wrong string 1", "foo", config.getString("section1.var1"));
+ assertEquals("Wrong int", 451, config.getInt("section1.var2"));
+ assertEquals("Wrong double", 123.45, config.getDouble("section2.var1"),
+ .001);
+ assertEquals("Wrong string 2", "bar", config.getString("section2.var2"));
+ assertTrue("Wrong boolean", config.getBoolean("section3.var1"));
+ assertEquals("Wrong number of sections", 3, source.getSections().size());
+ }
+
+ /**
+ * Helper method for testing the load operation. Loads the specified content
+ * into a configuration and then checks some properties.
+ *
+ * @param data the data to load
+ */
+ private void checkLoad(String data) throws ConfigurationException
+ {
+ Configuration<ConfigurationNode> config = setUpConfig(data);
+ checkContent(config);
+ }
+
+ /**
+ * Tests whether an ini file can be parsed.
+ */
+ @Test
+ public void testLoad() throws ConfigurationException
+ {
+ checkLoad(INI_DATA);
+ }
+
+ /**
+ * Tests the load() method if the alternative value separator is used (a ':'
+ * for '=').
+ */
+ @Test
+ public void testLoadAlternativeSeparator() throws ConfigurationException
+ {
+ checkLoad(INI_DATA.replace('=', ':'));
+ }
+
+ /**
+ * Test whether comment lines are correctly detected.
+ */
+ @Test
+ public void testIsCommentLine()
+ {
+ assertTrue("# not detected", source.isCommentLine("#comment1"));
+ assertTrue("; not detected", source.isCommentLine(";comment1"));
+ assertFalse("Wrong comment", source.isCommentLine("nocomment=true"));
+ assertFalse("Null detected as comment", source.isCommentLine(null));
+ }
+
+ /**
+ * Test whether section declarations are correctly detected.
+ */
+ @Test
+ public void testIsSectionLine()
+ {
+ assertTrue("Not a section", source.isSectionLine("[section]"));
+ assertFalse("A section", source.isSectionLine("nosection=true"));
+ assertFalse("Null a section", source.isSectionLine(null));
+ }
+
+ /**
+ * Tests whether sections are correctly extracted.
+ */
+ @Test
+ public void testGetSections()
+ {
+ Configuration<ConfigurationNode> config = new ConfigurationImpl<ConfigurationNode>(
+ source);
+ config.addProperty("test1.foo", "bar");
+ config.addProperty("test2.foo", "abc");
+ Set<String> expResult = new HashSet<String>();
+ expResult.add("test1");
+ expResult.add("test2");
+ Set<String> result = source.getSections();
+ assertEquals("Wrong set with sections", expResult, result);
+ }
+
+ @Test
+ public void testQuotedValue() throws Exception
+ {
+ Configuration<ConfigurationNode> config = setUpConfig(INI_DATA2);
+ assertEquals("value", "quoted value", config.getString("section4.var1"));
+ }
+
+ @Test
+ public void testQuotedValueWithQuotes() throws Exception
+ {
+ Configuration<ConfigurationNode> config = setUpConfig(INI_DATA2);
+ assertEquals("value", "quoted value\\nwith \"quotes\"", config
+ .getString("section4.var2"));
+ }
+
+ @Test
+ public void testValueWithComment() throws Exception
+ {
+ Configuration<ConfigurationNode> config = setUpConfig(INI_DATA2);
+ assertEquals("value", "123", config.getString("section4.var3"));
+ }
+
+ @Test
+ public void testQuotedValueWithComment() throws Exception
+ {
+ Configuration<ConfigurationNode> config = setUpConfig(INI_DATA2);
+ assertEquals("value", "1;2;3", config.getString("section4.var4"));
+ }
+
+ @Test
+ public void testQuotedValueWithSingleQuotes() throws Exception
+ {
+ Configuration<ConfigurationNode> config = setUpConfig(INI_DATA2);
+ assertEquals("value", "'quoted' \"value\"", config
+ .getString("section4.var5"));
+ }
+
+ @Test
+ public void testWriteValueWithCommentChar() throws Exception
+ {
+ Configuration<ConfigurationNode> config = new ConfigurationImpl<ConfigurationNode>(
+ source);
+ config.setProperty("section.key1", "1;2;3");
+
+ StringWriter writer = new StringWriter();
+ source.save(writer);
+ source = new INIConfigurationSource();
+
+ Configuration<ConfigurationNode> config2 = setUpConfig(writer
+ .toString());
+ assertEquals("value", "1;2;3", config2.getString("section.key1"));
+ }
+
+ /**
+ * Tests whether whitespace is left unchanged for quoted values.
+ */
+ @Test
+ public void testQuotedValueWithWhitespace() throws Exception
+ {
+ final String content = "CmdPrompt = \" [test@cmd ~]$ \"";
+ Configuration<ConfigurationNode> config = setUpConfig(content);
+ assertEquals("Wrong propert value", " [test@cmd ~]$ ", config
+ .getString("CmdPrompt"));
+ }
+
+ /**
+ * Tests a quoted value with space and a comment.
+ */
+ @Test
+ public void testQuotedValueWithWhitespaceAndComment() throws Exception
+ {
+ final String content = "CmdPrompt = \" [test@cmd ~]$ \" ; a comment";
+ Configuration<ConfigurationNode> config = setUpConfig(content);
+ assertEquals("Wrong propert value", " [test@cmd ~]$ ", config
+ .getString("CmdPrompt"));
+ }
+
+ /**
+ * Tests an empty quoted value.
+ */
+ @Test
+ public void testQuotedValueEmpty() throws ConfigurationException
+ {
+ Configuration<ConfigurationNode> config = setUpConfig(INI_DATA2);
+ assertEquals("Wrong value for empty property", "", config
+ .getString("section4.var6"));
+ }
+
+ /**
+ * Tests a property that has no value.
+ */
+ @Test
+ public void testGetPropertyNoValue() throws ConfigurationException
+ {
+ final String data = INI_DATA2 + LINE_SEPARATOR + "noValue ="
+ + LINE_SEPARATOR;
+ Configuration<ConfigurationNode> config = setUpConfig(data);
+ assertEquals("Wrong value of key", "", config
+ .getString("section4.noValue"));
+ }
+
+ /**
+ * Tests a property that has no key.
+ */
+ @Test
+ public void testGetPropertyNoKey() throws ConfigurationException
+ {
+ final String data = INI_DATA2 + LINE_SEPARATOR + "= noKey"
+ + LINE_SEPARATOR;
+ Configuration<ConfigurationNode> config = setUpConfig(data);
+ assertEquals("Cannot find property with no key", "noKey", config
+ .getString("section4. "));
+ }
+
+ /**
+ * Tests reading a property from the global section.
+ */
+ @Test
+ public void testGlobalProperty() throws ConfigurationException
+ {
+ Configuration<ConfigurationNode> config = setUpConfig(INI_DATA_GLOBAL);
+ assertEquals("Wrong value of global property", "testGlobal", config
+ .getString("globalVar"));
+ }
+
+ /**
+ * Tests whether the test configuration source contains exactly the expected
+ * sections.
+ *
+ * @param expected an array with the expected sections
+ */
+ private void checkSectionNames(String[] expected)
+ {
+ Set<String> sectionNames = source.getSections();
+ Iterator<String> it = sectionNames.iterator();
+ for (int idx = 0; idx < expected.length; idx++)
+ {
+ assertEquals("Wrong section at " + idx, expected[idx], it.next());
+ }
+ assertFalse("Too many sections", it.hasNext());
+ }
+
+ /**
+ * Tests the names of the sections returned by the configuration source.
+ *
+ * @param data the data of the ini configuration source
+ * @param expected the expected section names
+ * @return a configuration wrapping the test source
+ */
+ private Configuration<ConfigurationNode> checkSectionNames(String data,
+ String[] expected) throws ConfigurationException
+ {
+ Configuration<ConfigurationNode> config = setUpConfig(data);
+ checkSectionNames(expected);
+ return config;
+ }
+
+ /**
+ * Tests querying the sections if a global section if available.
+ */
+ @Test
+ public void testGetSectionsWithGlobal() throws ConfigurationException
+ {
+ checkSectionNames(INI_DATA_GLOBAL, new String[] {
+ null, "section1", "section2", "section3"
+ });
+ }
+
+ /**
+ * Tests querying the sections if there is no global section.
+ */
+ @Test
+ public void testGetSectionsNoGlobal() throws ConfigurationException
+ {
+ checkSectionNames(INI_DATA, new String[] {
+ "section1", "section2", "section3"
+ });
+ }
+
+ /**
+ * Tests whether variables containing a dot are not misinterpreted as
+ * sections. This test is related to CONFIGURATION-327.
+ */
+ @Test
+ public void testGetSectionsDottedVar() throws ConfigurationException
+ {
+ final String data = "dotted.var = 1" + LINE_SEPARATOR + INI_DATA_GLOBAL;
+ Configuration<ConfigurationNode> config = checkSectionNames(data,
+ new String[] {
+ null, "section1", "section2", "section3"
+ });
+ assertEquals("Wrong value of dotted variable", 1, config
+ .getInt("dotted..var"));
+ }
+
+ /**
+ * Tests whether a section added later is also found by getSections().
+ */
+ @Test
+ public void testGetSectionsAdded() throws ConfigurationException
+ {
+ Configuration<ConfigurationNode> config = setUpConfig(INI_DATA2);
+ config.addProperty("section5.test", Boolean.TRUE);
+ checkSectionNames(new String[] {
+ "section4", "section5"
+ });
+ }
+
+ /**
+ * Tests whether a sub configuration with the content of a section can be
+ * queried.
+ */
+ @Test
+ public void testGetSubConfigurationForSection()
+ throws ConfigurationException
+ {
+ Configuration<ConfigurationNode> config = setUpConfig(INI_DATA);
+ Configuration<ConfigurationNode> section = config
+ .configurationAt("section1");
+ assertEquals("Wrong value of var1", "foo", section.getString("var1"));
+ assertEquals("Wrong value of var2", "451", section.getString("var2"));
+ }
+
+ /**
+ * Tests a property whose value spans multiple lines.
+ */
+ @Test
+ public void testLineContinuation() throws ConfigurationException
+ {
+ Configuration<ConfigurationNode> 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.
+ */
+ @Test
+ public void testLineContinuationNone() throws ConfigurationException
+ {
+ Configuration<ConfigurationNode> 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.
+ */
+ @Test
+ public void testLineContinuationQuoted() throws ConfigurationException
+ {
+ Configuration<ConfigurationNode> 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.
+ */
+ @Test
+ public void testLineContinuationComment() throws ConfigurationException
+ {
+ Configuration<ConfigurationNode> 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.
+ */
+ @Test
+ public void testLineContinuationQuotedComment()
+ throws ConfigurationException
+ {
+ Configuration<ConfigurationNode> 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.
+ */
+ @Test
+ public void testLineContinuationEmptyLine() throws ConfigurationException
+ {
+ Configuration<ConfigurationNode> 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.
+ */
+ @Test
+ public void testLineContinuationAtEnd() throws ConfigurationException
+ {
+ Configuration<ConfigurationNode> config = setUpConfig(INI_DATA3);
+ assertEquals("Wrong value", "one" + LINE_SEPARATOR, config
+ .getString("section5.continueNoLine"));
+ }
+
+ /**
+ * Writes a test ini file.
+ *
+ * @param content the content of the file
+ * @throws IOException if an error occurs
+ */
+ private static void writeTestFile(String content) throws IOException
+ {
+ PrintWriter out = new PrintWriter(new FileWriter(testFile));
+ try
+ {
+ out.println(content);
+ }
+ finally
+ {
+ out.close();
+ }
+ }
+
+ /**
+ * Tests whether the LocatorSupport capability is provided.
+ */
+ @Test
+ public void testLocatorSupport()
+ {
+ LocatorSupport locSupport = source.getCapability(LocatorSupport.class);
+ assertNotNull("No locator support", locSupport);
+ assertNull("Got already a locator", locSupport.getLocator());
+ }
+
+ /**
+ * Tests whether the configuration source can be loaded from a locator.
+ */
+ @Test
+ public void testLoadFromLocator() throws IOException,
+ ConfigurationException
+ {
+ writeTestFile(INI_DATA);
+ LocatorSupport locSupport = source.getCapability(LocatorSupport.class);
+ locSupport.setLocator(new URLLocator(testFileURL));
+ locSupport.load();
+ Configuration<ConfigurationNode> config = new ConfigurationImpl<ConfigurationNode>(
+ source);
+ checkContent(config);
+ }
+
+ /**
+ * Tests whether the source can save its data to a locator.
+ */
+ @Test
+ public void testSaveToLocator() throws IOException, ConfigurationException
+ {
+ Locator loc = new URLLocator(testFileURL);
+ load(INI_DATA);
+ LocatorSupport locSupport = source.getCapability(LocatorSupport.class);
+ locSupport.save(loc);
+ INIConfigurationSource source2 = new INIConfigurationSource();
+ LocatorSupport locSupport2 = source2
+ .getCapability(LocatorSupport.class);
+ locSupport2.load(loc);
+ Configuration<ConfigurationNode> config = new ConfigurationImpl<ConfigurationNode>(
+ source2);
+ checkContent(config);
+ }
+}
Propchange: commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/impl/TestINIConfigurationSource.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/impl/TestINIConfigurationSource.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL
Propchange: commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/impl/TestINIConfigurationSource.java
------------------------------------------------------------------------------
svn:mime-type = text/plain