Return-Path: Delivered-To: apmail-commons-commits-archive@locus.apache.org Received: (qmail 45444 invoked from network); 16 Nov 2008 21:37:18 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.2) by minotaur.apache.org with SMTP; 16 Nov 2008 21:37:18 -0000 Received: (qmail 46479 invoked by uid 500); 16 Nov 2008 21:37:25 -0000 Delivered-To: apmail-commons-commits-archive@commons.apache.org Received: (qmail 46415 invoked by uid 500); 16 Nov 2008 21:37:25 -0000 Mailing-List: contact commits-help@commons.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@commons.apache.org Delivered-To: mailing list commits@commons.apache.org Received: (qmail 46404 invoked by uid 99); 16 Nov 2008 21:37:25 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 16 Nov 2008 13:37:25 -0800 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 16 Nov 2008 21:36:11 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 9A74A2388892; Sun, 16 Nov 2008 13:36:55 -0800 (PST) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r718120 - in /commons/proper/configuration/trunk: src/java/org/apache/commons/configuration/HierarchicalINIConfiguration.java src/test/org/apache/commons/configuration/TestHierarchicalINIConfiguration.java xdocs/changes.xml Date: Sun, 16 Nov 2008 21:36:55 -0000 To: commits@commons.apache.org From: oheger@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20081116213655.9A74A2388892@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: oheger Date: Sun Nov 16 13:36:54 2008 New Revision: 718120 URL: http://svn.apache.org/viewvc?rev=718120&view=rev Log: Initial version of a new hierarchical Configuration implementation for parsing Windows INI files. Added: commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalINIConfiguration.java - copied, changed from r701469, commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/INIConfiguration.java commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestHierarchicalINIConfiguration.java - copied, changed from r701469, commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestINIConfiguration.java Modified: commons/proper/configuration/trunk/xdocs/changes.xml Copied: commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalINIConfiguration.java (from r701469, commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/INIConfiguration.java) URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalINIConfiguration.java?p2=commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalINIConfiguration.java&p1=commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/INIConfiguration.java&r1=701469&r2=718120&rev=718120&view=diff ============================================================================== --- commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/INIConfiguration.java (original) +++ commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalINIConfiguration.java Sun Nov 16 13:36:54 2008 @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.commons.configuration; import java.io.BufferedReader; @@ -26,24 +25,31 @@ import java.net.URL; import java.util.Collection; import java.util.Iterator; +import java.util.List; import java.util.Set; -import java.util.TreeSet; +import org.apache.commons.collections.set.ListOrderedSet; +import org.apache.commons.configuration.tree.ConfigurationNode; +import org.apache.commons.configuration.tree.DefaultConfigurationNode; +import org.apache.commons.configuration.tree.ExpressionEngine; +import org.apache.commons.configuration.tree.ViewNode; import org.apache.commons.lang.StringUtils; /** *

+ * A specialized hierarchical configuration implementation for parsing ini + * files. + *

+ *

* An initialization or ini file is a configuration file typically found on * Microsoft's Windows operating system and contains data for Windows based * applications. *

- * *

* 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. *

- * *

* A typcial ini file could look something like: *

@@ -52,46 +58,42 @@ * ; this is a comment!
* var1 = foo
* var2 = bar
- *
+ *
* [section2]
* var1 = doo
* - * *

* The format of ini files is fairly straight forward and is composed of three * components:
*

    - *
  • Sections: 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.
  • - *
  • Parameters: Items in a section are known as parameters. - * Parameters have a typical key = value format.
  • - *
  • Comments: Lines starting with a ';' are assumed to be comments. - *
  • + *
  • Sections: 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.
  • + *
  • Parameters: Items in a section are known as parameters. Parameters + * have a typical key = value format.
  • + *
  • Comments: Lines starting with a ';' are assumed to be comments.
  • *
*

- * *

* 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 tries to be lenient and support most of the differences. *

- * *

* Some of the differences supported are as follows: *

    *
  • Comments: The '#' character is also accepted as a comment * signifier.
  • - *
  • Key value separtor: The ':' character is also accepted in place - * of '=' to separate keys and values in parameters, for example + *
  • Key value separtor: The ':' character is also accepted in place of + * '=' to separate keys and values in parameters, for example * var1 : foo.
  • - *
  • Duplicate sections: Typically duplicate sections are not allowed , + *
  • Duplicate sections: Typically duplicate sections are not allowed, * this configuration does however support it. In the event of a duplicate * section, the two section's values are merged.
  • *
  • Duplicate parameters: Typically duplicate parameters are only * allowed if they are in two different sections, thus they are local to * sections; this configuration simply merges duplicates; if a section has a - * duplicate parameter the values are then added to the key as a list.
  • + * duplicate parameter the values are then added to the key as a list. *
*

*

@@ -102,13 +104,12 @@ *

* 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 - * section1.var1 in this configuration. Thus, a section's - * parameters can easily be retrieved using the subset method - * using the section name as the prefix. + * section1.var1 in this configuration. (This is the default + * behavior. Because this is a hierarchical configuration you can change this by + * setting a different {@link ExpressionEngine}.) *

*

- *

Implementation Details:

- * Consider the following ini file:
+ *

Implementation Details:

Consider the following ini file:
* * default = ok
*
@@ -142,34 +143,57 @@ * is accessed simply using getProperty("default"). *
  • Section 1's parameters can be accessed using * getProperty("section1.var1").
  • - *
  • The parameter named "bad" simply adds the parameter with an empty value. - *
  • - *
  • The empty key with value "= worse" is added using an empty key. This key - * is still added to section 2 and the value can be accessed using - * getProperty("section2."), notice the period '.' following the - * section name.
  • + *
  • The parameter named "bad" simply adds the parameter with an empty value.
  • + *
  • 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 getProperty("section2. "), notice the + * period '.' and the space following the section name.
  • *
  • Section three uses both '=' and ':' to separate keys and values.
  • *
  • Section 3 has a duplicate key named "var5". The value for this key is * [test1, test2], and is represented as a List.
  • * *

    *

    + * Internally, this configuration maps the content of the represented ini file + * to its node structure in the following way: + *

      + *
    • Sections are represented by direct child nodes of the root node.
    • + *
    • For the content of a section, corresponding nodes are created as children + * of the section node.
    • + *
    + * This explains how the keys for the properties can be constructed. You can + * also use other methods of {@link HierarchicalConfiguration} for querying or + * manipulating the hierarchy of configuration nodes, for instance the + * configurationAt() method for obtaining the data of a specific + * section. + *

    + *

    * The set of sections in this configuration can be retrieved using the - * getSections method. + * getSections() method. For obtaining a + * SubnodeConfiguration with the content of a specific section the + * getSection() method can be used. *

    *

    - * Note: Configuration objects of this type can be read concurrently - * by multiple threads. However if one of these threads modifies the object, + * Note: Configuration 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. *

    * - * @author Trevor Miller + * @author Commons + * Configuration team * @version $Id$ - * @since 1.4 + * @since 1.6 */ -public class INIConfiguration extends AbstractFileConfiguration +public class HierarchicalINIConfiguration extends + AbstractHierarchicalFileConfiguration { /** + * The serial version UID. + */ + private static final long serialVersionUID = 2548006161386850670L; + + /** * The characters that signal the start of a comment line. */ protected static final String COMMENT_CHARS = "#;"; @@ -182,7 +206,7 @@ /** * Create a new empty INI Configuration. */ - public INIConfiguration() + public HierarchicalINIConfiguration() { super(); } @@ -193,7 +217,8 @@ * @param filename The name pr path of the ini file to load. * @throws ConfigurationException If an error occurs while loading the file */ - public INIConfiguration(String filename) throws ConfigurationException + public HierarchicalINIConfiguration(String filename) + throws ConfigurationException { super(filename); } @@ -204,7 +229,8 @@ * @param file The ini file to load. * @throws ConfigurationException If an error occurs while loading the file */ - public INIConfiguration(File file) throws ConfigurationException + public HierarchicalINIConfiguration(File file) + throws ConfigurationException { super(file); } @@ -215,7 +241,7 @@ * @param url The url of the ini file to load. * @throws ConfigurationException If an error occurs while loading the file */ - public INIConfiguration(URL url) throws ConfigurationException + public HierarchicalINIConfiguration(URL url) throws ConfigurationException { super(url); } @@ -225,7 +251,7 @@ * * @param writer - The writer to save the configuration to. * @throws ConfigurationException If an error occurs while writing the - * configuration + * configuration */ public void save(Writer writer) throws ConfigurationException { @@ -234,12 +260,15 @@ while (it.hasNext()) { String section = (String) it.next(); - out.print("["); - out.print(section); - out.print("]"); - out.println(); + if (section != null) + { + out.print("["); + out.print(section); + out.print("]"); + out.println(); + } - Configuration subset = subset(section); + Configuration subset = getSection(section); Iterator keys = subset.getKeys(); while (keys.hasNext()) { @@ -274,20 +303,21 @@ /** * Load the configuration from the given reader. Note that the - * clear method is not called so the configuration read in - * will be merged with the current configuration. + * clear method is not called so the configuration read in will + * be merged with the current configuration. * * @param reader The reader to read the configuration from. * @throws ConfigurationException If an error occurs while reading the - * configuration + * configuration */ public void load(Reader reader) throws ConfigurationException { try { BufferedReader bufferedReader = new BufferedReader(reader); + ConfigurationNode sectionNode = getRootNode(); + String line = bufferedReader.readLine(); - String section = ""; while (line != null) { line = line.trim(); @@ -295,8 +325,10 @@ { if (isSectionLine(line)) { - section = line.substring(1, line.length() - 1) + "."; + String section = line.substring(1, line.length() - 1); + sectionNode = getSectionNode(section); } + else { String key = ""; @@ -304,7 +336,7 @@ int index = line.indexOf("="); if (index >= 0) { - key = section + line.substring(0, index); + key = line.substring(0, index); value = parseValue(line.substring(index + 1)); } else @@ -312,33 +344,46 @@ index = line.indexOf(":"); if (index >= 0) { - key = section + line.substring(0, index); + key = line.substring(0, index); value = parseValue(line.substring(index + 1)); } else { - key = section + line; + key = line; } } - addProperty(key.trim(), value); + key = key.trim(); + if (key.length() < 1) + { + // use space for properties with no key + key = " "; + } + ConfigurationNode node = createNode(key); + node.setValue(value); + sectionNode.addChild(node); } } + line = bufferedReader.readLine(); } } catch (IOException e) { - throw new ConfigurationException("Unable to load the configuration", e); + throw new ConfigurationException( + "Unable to load the configuration", e); } } /** - * Parse the value to remove the quotes and ignoring the comment. - * Example: + * Parse the value to remove the quotes and ignoring the comment. Example: * - *
    "value" ; comment -> value
    + *
    +     * "value" ; comment -> value
    +     * 
    * - *
    'value' ; comment -> value
    + *
    +     * 'value' ; comment -> value
    +     * 
    * * @param value */ @@ -401,7 +446,7 @@ } String v = result.toString(); - if(!quoted) + if (!quoted) { v = v.trim(); } @@ -439,7 +484,7 @@ * * @param line The line to check. * @return true if the line is empty or starts with one of the comment - * characters + * characters */ protected boolean isCommentLine(String line) { @@ -474,19 +519,138 @@ */ public Set getSections() { - Set sections = new TreeSet(); + Set sections = new ListOrderedSet(); + boolean globalSection = false; - Iterator keys = getKeys(); - while (keys.hasNext()) + for (Iterator it = getRootNode().getChildren().iterator(); it.hasNext();) { - String key = (String) keys.next(); - int index = key.indexOf("."); - if (index >= 0) + ConfigurationNode node = (ConfigurationNode) it.next(); + if (isSectionNode(node)) + { + if (globalSection) + { + sections.add(null); + globalSection = false; + } + sections.add(node.getName()); + } + else { - sections.add(key.substring(0, index)); + globalSection = true; } } return sections; } + + /** + * Returns a configuration with the content of the specified section. This + * provides an easy way of working with a single section only. The way this + * configuration is structured internally, this method is very similar to + * calling + * {@link HierarchicalConfiguration#configurationAt(String)} + * with the name of the section in question. There are the following + * differences however: + *
      + *
    • This method never throws an exception. If the section does not exist, + * an empty configuration is returned.
    • + *
    • There is special support for the global section: Passing in + * null as section name returns a configuration with the content of + * the global section (which may also be empty).
    • + *
    + * + * @param name the name of the section in question; null represents + * the global section + * @return a configuration containing only the properties of the specified + * section + */ + public SubnodeConfiguration getSection(String name) + { + if (name == null) + { + return getGlobalSection(); + } + + else + { + try + { + return configurationAt(name); + } + catch (IllegalArgumentException iex) + { + // the passed in key does not map to exactly one node + // return an empty configuration + return new SubnodeConfiguration(this, + new DefaultConfigurationNode()); + } + } + } + + /** + * 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 nodes = getRootNode().getChildren(sectionName); + if (!nodes.isEmpty()) + { + return (ConfigurationNode) nodes.get(0); + } + + ConfigurationNode node = createNode(sectionName); + markSectionNode(node); + getRootNode().addChild(node); + return node; + } + + /** + * Creates a sub configuration for the global section of the represented INI + * configuration. + * + * @return the sub configuration for the global section + */ + private SubnodeConfiguration getGlobalSection() + { + ViewNode parent = new ViewNode(); + + for (Iterator it = getRootNode().getChildren().iterator(); it.hasNext();) + { + ConfigurationNode node = (ConfigurationNode) it.next(); + if (!isSectionNode(node)) + { + parent.addChild(node); + } + } + + return createSubnodeConfiguration(parent); + } + + /** + * 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; + } } Copied: commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestHierarchicalINIConfiguration.java (from r701469, commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestINIConfiguration.java) URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestHierarchicalINIConfiguration.java?p2=commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestHierarchicalINIConfiguration.java&p1=commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestINIConfiguration.java&r1=701469&r2=718120&rev=718120&view=diff ============================================================================== --- commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestINIConfiguration.java (original) +++ commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestHierarchicalINIConfiguration.java Sun Nov 16 13:36:54 2008 @@ -17,195 +17,295 @@ package org.apache.commons.configuration; +import java.io.File; +import java.io.FileWriter; import java.io.IOException; -import java.io.Reader; +import java.io.PrintWriter; import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; import java.util.HashSet; +import java.util.Iterator; import java.util.Set; import junit.framework.TestCase; /** - * Test class for INIConfiguration. + * Test class for HierarchicalINIConfiguration. * - * @author Trevor Miller + * @author Commons + * Configuration team * @version $Id$ */ -public class TestINIConfiguration extends TestCase +public class TestHierarchicalINIConfiguration extends TestCase { 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 + 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 + + "multi = foo" + LINE_SEPARATOR + "multi = bar" + LINE_SEPARATOR + LINE_SEPARATOR; - private static final String INI_DATA2 = - "[section4]" + 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"; + /** An ini file with a global section. */ + private static final String INI_DATA_GLOBAL = "globalVar = testGlobal" + + LINE_SEPARATOR + LINE_SEPARATOR + INI_DATA; + + /** A test ini file. */ + private static final File TEST_FILE = new File("target/test.ini"); + + protected void tearDown() throws Exception + { + if (TEST_FILE.exists()) + { + assertTrue("Cannot remove test file: " + TEST_FILE, TEST_FILE + .delete()); + } + + super.tearDown(); + } + /** - * Test of save method, of class {@link INIConfiguration}. + * Creates a HierarchicalINIConfiguration object that is initialized from + * the given data. + * + * @param data the data of the configuration (an ini file as string) + * @return the initialized configuration + * @throws ConfigurationException if an error occurs */ - public void testSave() throws Exception - { - Writer writer = new StringWriter(); - INIConfiguration instance = new INIConfiguration(); - instance.addProperty("section1.var1", "foo"); - instance.addProperty("section1.var2", "451"); - instance.addProperty("section2.var1", "123.45"); - instance.addProperty("section2.var2", "bar"); - instance.addProperty("section3.var1", "true"); - instance.addProperty("section3.interpolated", "${section3.var1}"); - instance.addProperty("section3.multi", "foo"); - instance.addProperty("section3.multi", "bar"); - instance.save(writer); + private static HierarchicalINIConfiguration setUpConfig(String data) + throws ConfigurationException + { + StringReader reader = new StringReader(data); + HierarchicalINIConfiguration instance = new HierarchicalINIConfiguration(); + instance.load(reader); + reader.close(); + return instance; + } + + /** + * 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(TEST_FILE)); + try + { + out.println(content); + } + finally + { + out.close(); + } + } + + /** + * Test of save method, of class {@link HierarchicalINIConfiguration}. + */ + public void testSave() throws Exception + { + Writer writer = new StringWriter(); + HierarchicalINIConfiguration instance = new HierarchicalINIConfiguration(); + instance.addProperty("section1.var1", "foo"); + instance.addProperty("section1.var2", "451"); + instance.addProperty("section2.var1", "123.45"); + instance.addProperty("section2.var2", "bar"); + instance.addProperty("section3.var1", "true"); + instance.addProperty("section3.interpolated", "${section3.var1}"); + instance.addProperty("section3.multi", "foo"); + instance.addProperty("section3.multi", "bar"); + instance.save(writer); assertEquals("Wrong content of ini file", INI_DATA, writer.toString()); - } + } - /** - * Test of load method, of class {@link INIConfiguration}. + /** + * Tests saving a configuration that contains a global section. */ - public void testLoad() throws Exception - { - checkLoad(INI_DATA); - } + public void testSaveWithGlobalSection() throws ConfigurationException + { + HierarchicalINIConfiguration config = setUpConfig(INI_DATA_GLOBAL); + StringWriter writer = new StringWriter(); + config.save(writer); + assertEquals("Wrong content of ini file", INI_DATA_GLOBAL, writer + .toString()); + } - /** + /** + * Test of load method, of class {@link HierarchicalINIConfiguration}. + */ + public void testLoad() throws Exception + { + checkLoad(INI_DATA); + } + + /** * Tests the load() method when the alternative value separator is used (a * ':' for '='). */ - public void testLoadAlternativeSeparator() throws Exception - { - checkLoad(INI_DATA.replace('=', ':')); - } + public void testLoadAlternativeSeparator() throws Exception + { + checkLoad(INI_DATA.replace('=', ':')); + } + + /** + * Tests loading a configuration from a File. + */ + public void testLoadFile() throws ConfigurationException, IOException + { + writeTestFile(INI_DATA); + HierarchicalINIConfiguration config = new HierarchicalINIConfiguration( + TEST_FILE); + checkContent(config); + } - /** + /** + * Tests loading a configuration from a file name. + */ + public void testLoadFileName() throws ConfigurationException, IOException + { + writeTestFile(INI_DATA); + HierarchicalINIConfiguration config = new HierarchicalINIConfiguration( + TEST_FILE.getAbsolutePath()); + checkContent(config); + } + + /** + * Tests loading a configuration from a URL. + */ + public void testLoadURL() throws ConfigurationException, IOException + { + writeTestFile(INI_DATA); + HierarchicalINIConfiguration config = new HierarchicalINIConfiguration( + TEST_FILE.toURL()); + checkContent(config); + } + + /** + * Tests the values of some properties to ensure that the configuration was + * correctly loaded. + * + * @param instance the configuration to check + */ + private void checkContent(HierarchicalINIConfiguration instance) + { + assertTrue(instance.getString("section1.var1").equals("foo")); + assertTrue(instance.getInt("section1.var2") == 451); + assertTrue(instance.getDouble("section2.var1") == 123.45); + assertTrue(instance.getString("section2.var2").equals("bar")); + assertTrue(instance.getBoolean("section3.var1")); + assertTrue(instance.getSections().size() == 3); + } + + /** * 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, IOException - { - Reader reader = new StringReader(data); - INIConfiguration instance = new INIConfiguration(); - instance.load(reader); - reader.close(); - assertTrue(instance.getString("section1.var1").equals("foo")); - assertTrue(instance.getInt("section1.var2") == 451); - assertTrue(instance.getDouble("section2.var1") == 123.45); - assertTrue(instance.getString("section2.var2").equals("bar")); - assertTrue(instance.getBoolean("section3.var1")); - assertTrue(instance.getSections().size() == 3); - } - - /** - * Test of isCommentLine method, of class {@link INIConfiguration}. - */ - public void testIsCommentLine() - { - INIConfiguration instance = new INIConfiguration(); - assertTrue(instance.isCommentLine("#comment1")); - assertTrue(instance.isCommentLine(";comment1")); - assertFalse(instance.isCommentLine("nocomment=true")); - assertFalse(instance.isCommentLine(null)); - } - - /** - * Test of isSectionLine method, of class {@link INIConfiguration}. - */ - public void testIsSectionLine() - { - INIConfiguration instance = new INIConfiguration(); - assertTrue(instance.isSectionLine("[section]")); - assertFalse(instance.isSectionLine("nosection=true")); - assertFalse(instance.isSectionLine(null)); - } - - /** - * Test of getSections method, of class {@link INIConfiguration}. - */ - public void testGetSections() - { - INIConfiguration instance = new INIConfiguration(); - instance.addProperty("test1.foo", "bar"); - instance.addProperty("test2.foo", "abc"); - Set expResult = new HashSet(); - expResult.add("test1"); - expResult.add("test2"); - Set result = instance.getSections(); - assertEquals(expResult, result); - } + private void checkLoad(String data) throws ConfigurationException + { + HierarchicalINIConfiguration instance = setUpConfig(data); + checkContent(instance); + } - public void testQuotedValue() throws Exception + /** + * Test of isCommentLine method, of class + * {@link HierarchicalINIConfiguration}. + */ + public void testIsCommentLine() + { + HierarchicalINIConfiguration instance = new HierarchicalINIConfiguration(); + assertTrue(instance.isCommentLine("#comment1")); + assertTrue(instance.isCommentLine(";comment1")); + assertFalse(instance.isCommentLine("nocomment=true")); + assertFalse(instance.isCommentLine(null)); + } + + /** + * Test of isSectionLine method, of class + * {@link HierarchicalINIConfiguration}. + */ + public void testIsSectionLine() + { + HierarchicalINIConfiguration instance = new HierarchicalINIConfiguration(); + assertTrue(instance.isSectionLine("[section]")); + assertFalse(instance.isSectionLine("nosection=true")); + assertFalse(instance.isSectionLine(null)); + } + + /** + * Test of getSections method, of class {@link HierarchicalINIConfiguration} + * . + */ + public void testGetSections() { - INIConfiguration config = new INIConfiguration(); - config.load(new StringReader(INI_DATA2)); + HierarchicalINIConfiguration instance = new HierarchicalINIConfiguration(); + instance.addProperty("test1.foo", "bar"); + instance.addProperty("test2.foo", "abc"); + Set expResult = new HashSet(); + expResult.add("test1"); + expResult.add("test2"); + Set result = instance.getSections(); + assertEquals(expResult, result); + } + public void testQuotedValue() throws Exception + { + HierarchicalINIConfiguration config = setUpConfig(INI_DATA2); assertEquals("value", "quoted value", config.getString("section4.var1")); } public void testQuotedValueWithQuotes() throws Exception { - INIConfiguration config = new INIConfiguration(); - config.load(new StringReader(INI_DATA2)); - - assertEquals("value", "quoted value\\nwith \"quotes\"", config.getString("section4.var2")); + HierarchicalINIConfiguration config = setUpConfig(INI_DATA2); + assertEquals("value", "quoted value\\nwith \"quotes\"", config + .getString("section4.var2")); } public void testValueWithComment() throws Exception { - INIConfiguration config = new INIConfiguration(); - config.load(new StringReader(INI_DATA2)); - + HierarchicalINIConfiguration config = setUpConfig(INI_DATA2); assertEquals("value", "123", config.getString("section4.var3")); } public void testQuotedValueWithComment() throws Exception { - INIConfiguration config = new INIConfiguration(); - config.load(new StringReader(INI_DATA2)); - + HierarchicalINIConfiguration config = setUpConfig(INI_DATA2); assertEquals("value", "1;2;3", config.getString("section4.var4")); } public void testQuotedValueWithSingleQuotes() throws Exception { - INIConfiguration config = new INIConfiguration(); - config.load(new StringReader(INI_DATA2)); - - assertEquals("value", "'quoted' \"value\"", config.getString("section4.var5")); + HierarchicalINIConfiguration config = setUpConfig(INI_DATA2); + assertEquals("value", "'quoted' \"value\"", config + .getString("section4.var5")); } public void testWriteValueWithCommentChar() throws Exception { - INIConfiguration config = new INIConfiguration(); + HierarchicalINIConfiguration config = new HierarchicalINIConfiguration(); config.setProperty("section.key1", "1;2;3"); StringWriter writer = new StringWriter(); config.save(writer); - INIConfiguration config2 = new INIConfiguration(); + HierarchicalINIConfiguration config2 = new HierarchicalINIConfiguration(); config2.load(new StringReader(writer.toString())); assertEquals("value", "1;2;3", config2.getString("section.key1")); @@ -217,7 +317,7 @@ public void testQuotedValueWithWhitespace() throws Exception { final String content = "CmdPrompt = \" [test@cmd ~]$ \""; - INIConfiguration config = new INIConfiguration(); + HierarchicalINIConfiguration config = new HierarchicalINIConfiguration(); config.load(new StringReader(content)); assertEquals("Wrong propert value", " [test@cmd ~]$ ", config .getString("CmdPrompt")); @@ -229,9 +329,182 @@ public void testQuotedValueWithWhitespaceAndComment() throws Exception { final String content = "CmdPrompt = \" [test@cmd ~]$ \" ; a comment"; - INIConfiguration config = new INIConfiguration(); + HierarchicalINIConfiguration config = new HierarchicalINIConfiguration(); config.load(new StringReader(content)); assertEquals("Wrong propert value", " [test@cmd ~]$ ", config .getString("CmdPrompt")); } + + /** + * Tests a property that has no value. + */ + public void testGetPropertyNoValue() throws ConfigurationException + { + final String data = INI_DATA2 + LINE_SEPARATOR + "noValue =" + + LINE_SEPARATOR; + HierarchicalINIConfiguration config = setUpConfig(data); + assertEquals("Wrong value of key", "", config + .getString("section4.noValue")); + } + + /** + * Tests a property that has no key. + */ + public void testGetPropertyNoKey() throws ConfigurationException + { + final String data = INI_DATA2 + LINE_SEPARATOR + "= noKey" + + LINE_SEPARATOR; + HierarchicalINIConfiguration config = setUpConfig(data); + assertEquals("Cannot find property with no key", "noKey", config + .getString("section4. ")); + } + + /** + * Tests reading a property from the global section. + */ + public void testGlobalProperty() throws ConfigurationException + { + HierarchicalINIConfiguration config = setUpConfig(INI_DATA_GLOBAL); + assertEquals("Wrong value of global property", "testGlobal", config + .getString("globalVar")); + } + + /** + * Tests whether the specified configuration contains exactly the expected + * sections. + * + * @param config the configuration to check + * @param expected an array with the expected sections + */ + private void checkSectionNames(HierarchicalINIConfiguration config, + String[] expected) + { + Set sectionNames = config.getSections(); + Iterator 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. + * + * @param data the data of the ini configuration + * @param expected the expected section names + * @return the configuration instance + */ + private HierarchicalINIConfiguration checkSectionNames(String data, + String[] expected) throws ConfigurationException + { + HierarchicalINIConfiguration config = setUpConfig(data); + checkSectionNames(config, expected); + return config; + } + + /** + * Tests querying the sections if a global section if available. + */ + 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. + */ + 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. + */ + public void testGetSectionsDottedVar() throws ConfigurationException + { + final String data = "dotted.var = 1" + LINE_SEPARATOR + INI_DATA_GLOBAL; + HierarchicalINIConfiguration 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(). + */ + public void testGetSectionsAdded() throws ConfigurationException + { + HierarchicalINIConfiguration config = setUpConfig(INI_DATA2); + config.addProperty("section5.test", Boolean.TRUE); + checkSectionNames(config, new String[] { + "section4", "section5" + }); + } + + /** + * Tests querying the properties of an existing section. + */ + public void testGetSectionExisting() throws ConfigurationException + { + HierarchicalINIConfiguration config = setUpConfig(INI_DATA); + SubnodeConfiguration section = config.getSection("section1"); + assertEquals("Wrong value of var1", "foo", section.getString("var1")); + assertEquals("Wrong value of var2", "451", section.getString("var2")); + } + + /** + * Tests querying the properties of a section that was merged from two + * sections with the same name. + */ + public void testGetSectionMerged() throws ConfigurationException + { + final String data = INI_DATA + "[section1]" + LINE_SEPARATOR + + "var3 = merged" + LINE_SEPARATOR; + HierarchicalINIConfiguration config = setUpConfig(data); + SubnodeConfiguration section = config.getSection("section1"); + assertEquals("Wrong value of var1", "foo", section.getString("var1")); + assertEquals("Wrong value of var2", "451", section.getString("var2")); + assertEquals("Wrong value of var3", "merged", section.getString("var3")); + } + + /** + * Tests querying the content of the global section. + */ + public void testGetSectionGlobal() throws ConfigurationException + { + HierarchicalINIConfiguration config = setUpConfig(INI_DATA_GLOBAL); + SubnodeConfiguration section = config.getSection(null); + assertEquals("Wrong value of global variable", "testGlobal", section + .getString("globalVar")); + } + + /** + * Tests querying the content of the global section if there is none. + */ + public void testGetSectionGlobalNonExisting() throws ConfigurationException + { + HierarchicalINIConfiguration config = setUpConfig(INI_DATA); + SubnodeConfiguration section = config.getSection(null); + assertTrue("Sub config not empty", section.isEmpty()); + } + + /** + * Tests querying a non existing section. + */ + public void testGetSectionNonExisting() throws ConfigurationException + { + HierarchicalINIConfiguration config = setUpConfig(INI_DATA); + SubnodeConfiguration section = config + .getSection("Non existing section"); + assertTrue("Sub config not empty", section.isEmpty()); + } } Modified: commons/proper/configuration/trunk/xdocs/changes.xml URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/xdocs/changes.xml?rev=718120&r1=718119&r2=718120&view=diff ============================================================================== --- commons/proper/configuration/trunk/xdocs/changes.xml (original) +++ commons/proper/configuration/trunk/xdocs/changes.xml Sun Nov 16 13:36:54 2008 @@ -98,6 +98,13 @@ method can be overridden by derived classes for injecting custom BeanDeclaration implementations. + + With HierarchicalINIConfiguration a complete new Configuration + implementation for parsing Windows INI files is available. This new + class is a full replacement of INIConfiguration and addresses some of its + shortcomings. Being derived from HierarchicalConfiguration it offers + the enhanced functionality of hierarchical configurations. + XMLConfiguration now supports the xml:space attribute. This attribute can be used to preserve whitespace in the content of XML elements.