Author: oheger Date: Mon Nov 20 11:05:19 2006 New Revision: 477290 URL: http://svn.apache.org/viewvc?view=rev&rev=477290 Log: Added new INIConfiguration class; thanks to Trevor Charles Miller for the patch
Added: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/INIConfiguration.java (with props) jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestINIConfiguration.java (with props) Modified: jakarta/commons/proper/configuration/trunk/xdocs/changes.xml Added: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/INIConfiguration.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/INIConfiguration.java?view=auto&rev=477290 ============================================================================== --- jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/INIConfiguration.java (added) +++ jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/INIConfiguration.java Mon Nov 20 11:05:19 2006 @@ -0,0 +1,364 @@ +/* + * 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.configuration; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.Writer; +import java.net.URL; +import java.util.Iterator; +import java.util.Set; +import java.util.TreeSet; + +/** + * <p> + * An initialization or ini file is a configuration file tpically 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 comosed 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 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 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 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. Thus, a section's + * parameters can easily be retrieved using the <code>subset</code> method + * using the section name as the prefix. + * </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 an empty key. This key + * is still added to section 2 and the value can be accessed using + * <code>getProperty("section2.")</code>, notice the period '.' 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> + * The set of sections in this configuration can be retrieved using the + * <code>getSections</code> method. + * </p> + * + * @author trevor.miller + * @version $Id$ + * @since 1.4 + */ +public class INIConfiguration extends AbstractFileConfiguration +{ + /** + * The characters that signal the start of a comment line. + */ + protected static final String COMMENT_CHARS = "#;"; + + /** + * The characters used to separate keys from values. + */ + protected static final String SEPARATOR_CHARS = "=:"; + + /** Constant for the used line separator.*/ + private static final String LINE_SEPARATOR = "\r\n"; + + /** + * Create a new empty INI Configuration. + */ + public INIConfiguration() + { + super(); + } + + /** + * Create and load the ini configuration from the given file. + * + * @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 + { + super(filename); + } + + /** + * Create and load the ini configuration from the given file. + * + * @param file The ini file to load. + * @throws ConfigurationException If an error occurs while loading the file + */ + public INIConfiguration(File file) throws ConfigurationException + { + super(file); + } + + /** + * Create and load the ini configuration from the given url. + * + * @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 + { + super(url); + } + + /** + * Save the configuration to the specified writer. + * + * @param writer - The writer to save the configuration to. + * @throws ConfigurationException If an error occurs while writing the + * configuration + */ + public void save(Writer writer) throws ConfigurationException + { + PrintWriter pw = new PrintWriter(writer); + Iterator iter = this.getSections().iterator(); + while (iter.hasNext()) + { + String section = (String) iter.next(); + pw.print("["); + pw.print(section); + pw.print("]"); + pw.print(LINE_SEPARATOR); + + Configuration values = this.subset(section); + Iterator iterator = values.getKeys(); + while (iterator.hasNext()) + { + String key = (String) iterator.next(); + String value = values.getString(key); + pw.print(key); + pw.print(" = "); + pw.print(value); + pw.print(LINE_SEPARATOR); + } + + pw.print(LINE_SEPARATOR); + } + } + + /** + * Load the configuration from the given reader. Note that the + * <code>clear</code> 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 + */ + public void load(Reader reader) throws ConfigurationException + { + try + { + BufferedReader bufferedReader = new BufferedReader(reader); + String line = bufferedReader.readLine(); + String section = ""; + while (line != null) + { + line = line.trim(); + if (!isCommentLine(line)) + { + if (isSectionLine(line)) + { + section = line.substring(1, line.length() - 1) + "."; + } + else + { + String key = ""; + String value = ""; + int index = line.indexOf("="); + if (index >= 0) + { + key = section + line.substring(0, index); + value = line.substring(index + 1); + } + else + { + index = line.indexOf(":"); + if (index >= 0) + { + key = section + line.substring(0, index); + value = line.substring(index + 1); + } + else + { + key = section + line; + } + } + this.addProperty(key.trim(), value.trim()); + } + } + line = bufferedReader.readLine(); + } + } + catch (IOException ioe) + { + throw new ConfigurationException(ioe.getMessage()); + } + } + + /** + * Determine if the given line is a comment line. + * + * @param s The line to check. + * @return true if the line is empty or starts with one of the comment + * characters + */ + protected boolean isCommentLine(String s) + { + if (s == null) + { + return false; + } + // blank lines are also treated as comment lines + return s.length() < 1 || COMMENT_CHARS.indexOf(s.charAt(0)) >= 0; + } + + /** + * Determine if the given line is a section. + * + * @param s The line to check. + * @return true if the line contains a secion + */ + protected boolean isSectionLine(String s) + { + if (s == null) + { + return false; + } + return ((s.startsWith("[")) && (s.endsWith("]"))); + } + + /** + * 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 getSections() + { + Set sections = new TreeSet(); + Iterator iter = this.getKeys(); + while (iter.hasNext()) + { + String key = (String) iter.next(); + int index = key.indexOf("."); + if (index >= 0) + sections.add(key.substring(0, index)); + } + return sections; + } +} Propchange: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/INIConfiguration.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/INIConfiguration.java ------------------------------------------------------------------------------ svn:keywords = HeadURL Id Added: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestINIConfiguration.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestINIConfiguration.java?view=auto&rev=477290 ============================================================================== --- jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestINIConfiguration.java (added) +++ jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestINIConfiguration.java Mon Nov 20 11:05:19 2006 @@ -0,0 +1,140 @@ +/* + * 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.configuration; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.util.HashSet; +import java.util.Set; + +import junit.framework.TestCase; + +/** + * Test class for INIConfiguration. + * + * @author trevor.miller + * @version $Id: TestHierarchicalConfiguration.java 439648 2006-09-02 20:42:10Z + * oheger $ + */ +public class TestINIConfiguration extends TestCase +{ + /** Constant for the content of an ini file. */ + private static final String INI_DATA = "[section1]\r\nvar1 = foo\r\n" + + "var2 = 451\r\n\r\n[section2]\r\nvar1 = 123.45\r\nvar2 = bar\r\n\r\n" + + "[section3]\r\nvar1 = true\r\n\r\n"; + + /** + * Test of save method, of class + * org.apache.commons.configuration.INIConfiguration. + */ + 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.save(writer); + assertEquals("Wrong content of ini file", INI_DATA, writer.toString()); + } + + /** + * Test of load method, of class + * org.apache.commons.configuration.INIConfiguration. + */ + 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('=', ':')); + } + + /** + * 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 + * org.apache.commons.configuration.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 + * org.apache.commons.configuration.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 + * org.apache.commons.configuration.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); + } +} Propchange: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestINIConfiguration.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestINIConfiguration.java ------------------------------------------------------------------------------ svn:keywords = HeadURL Id Modified: jakarta/commons/proper/configuration/trunk/xdocs/changes.xml URL: http://svn.apache.org/viewvc/jakarta/commons/proper/configuration/trunk/xdocs/changes.xml?view=diff&rev=477290&r1=477289&r2=477290 ============================================================================== --- jakarta/commons/proper/configuration/trunk/xdocs/changes.xml (original) +++ jakarta/commons/proper/configuration/trunk/xdocs/changes.xml Mon Nov 20 11:05:19 2006 @@ -23,6 +23,9 @@ <body> <release version="1.4-dev" date="in SVN"> + <action dev="oheger" type="add" issue="CONFIGURATION-197" due-to="Trevor Charles Miller"> + A new configuration class for windows ini files was added. + </action> <action dev="oheger" type="update" issue="CONFIGURATION-229"> For file-based configurations loaded by ConfigurationFactory the load() method was called before all of the properties specified by attributes --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]