001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.commons.configuration;
019:
020: import java.io.BufferedReader;
021: import java.io.File;
022: import java.io.IOException;
023: import java.io.PrintWriter;
024: import java.io.Reader;
025: import java.io.Writer;
026: import java.net.URL;
027: import java.util.Iterator;
028: import java.util.Set;
029: import java.util.TreeSet;
030:
031: /**
032: * <p>
033: * An initialization or ini file is a configuration file tpically found on
034: * Microsoft's Windows operating system and contains data for Windows based
035: * applications.
036: * </p>
037: *
038: * <p>
039: * Although popularized by Windows, ini files can be used on any system or
040: * platform due to the fact that they are merely text files that can easily be
041: * parsed and modified by both humans and computers.
042: * </p>
043: *
044: * <p>
045: * A typcial ini file could look something like:
046: * </p>
047: * <code>
048: * [section1]<br>
049: * ; this is a comment!<br>
050: * var1 = foo<br>
051: * var2 = bar<br>
052: *<br>
053: * [section2]<br>
054: * var1 = doo<br>
055: * </code>
056: *
057: * <p>
058: * The format of ini files is fairly straight forward and is comosed of three
059: * components:<br>
060: * <ul>
061: * <li><b>Sections:</b> Ini files are split into sections, each section
062: * starting with a section declaration. A section declaration starts with a '['
063: * and ends with a ']'. Sections occur on one line only.</li>
064: * <li><b>Parameters:</b> Items in a section are known as parameters.
065: * Parameters have a typical <code>key = value</code> format.</li>
066: * <li><b>Comments:</b> Lines starting with a ';' are assumed to be comments.
067: * </li>
068: * </ul>
069: * </p>
070: *
071: * <p>
072: * There are various implementations of the ini file format by various vendors
073: * which has caused a number of differences to appear. As far as possible this
074: * configuration tries to be lenient and support most of the differences.
075: * </p>
076: *
077: * <p>
078: * Some of the differences supported are as follows:
079: * <ul>
080: * <li><b>Comments:</b> The '#' character is also accepted as a comment
081: * signifier.</li>
082: * <li><b>Key value separtor:</b> The ':' character is also accepted in place
083: * of '=' to separate keys and values in parameters, for example
084: * <code>var1 : foo</code>.</li>
085: * <li><b>Duplicate sections:</b> Typically duplicate sections are not allowed ,
086: * this configuration does however support it. In the event of a duplicate
087: * section, the two section's values are merged.</li>
088: * <li><b>Duplicate parameters:</b> Typically duplicate parameters are only
089: * allowed if they are in two different sections, thus they are local to
090: * sections; this configuration simply merges duplicates; if a section has a
091: * duplicate parameter the values are then added to the key as a list. </li>
092: * </ul>
093: * </p>
094: * <p>
095: * Global parameters are also allowed; any parameters declared before a section
096: * is declared are added to a global section. It is important to note that this
097: * global section does not have a name.
098: * </p>
099: * <p>
100: * In all instances, a parameter's key is prepended with its section name and a
101: * '.' (period). Thus a parameter named "var1" in "section1" will have the key
102: * <code>section1.var1</code> in this configuration. Thus, a section's
103: * parameters can easily be retrieved using the <code>subset</code> method
104: * using the section name as the prefix.
105: * </p>
106: * <p>
107: * <h3>Implementation Details:</h3>
108: * Consider the following ini file:<br>
109: * <code>
110: * default = ok<br>
111: * <br>
112: * [section1]<br>
113: * var1 = foo<br>
114: * var2 = doodle<br>
115: * <br>
116: * [section2]<br>
117: * ; a comment<br>
118: * var1 = baz<br>
119: * var2 = shoodle<br>
120: * bad =<br>
121: * = worse<br>
122: * <br>
123: * [section3]<br>
124: * # another comment<br>
125: * var1 : foo<br>
126: * var2 : bar<br>
127: * var5 : test1<br>
128: * <br>
129: * [section3]<br>
130: * var3 = foo<br>
131: * var4 = bar<br>
132: * var5 = test2<br>
133: * </code>
134: * </p>
135: * <p>
136: * This ini file will be parsed without error. Note:
137: * <ul>
138: * <li>The parameter named "default" is added to the global section, it's value
139: * is accessed simply using <code>getProperty("default")</code>.</li>
140: * <li>Section 1's parameters can be accessed using
141: * <code>getProperty("section1.var1")</code>.</li>
142: * <li>The parameter named "bad" simply adds the parameter with an empty value.
143: * </li>
144: * <li>The empty key with value "= worse" is added using an empty key. This key
145: * is still added to section 2 and the value can be accessed using
146: * <code>getProperty("section2.")</code>, notice the period '.' following the
147: * section name.</li>
148: * <li>Section three uses both '=' and ':' to separate keys and values.</li>
149: * <li>Section 3 has a duplicate key named "var5". The value for this key is
150: * [test1, test2], and is represented as a List.</li>
151: * </ul>
152: * </p>
153: * <p>
154: * The set of sections in this configuration can be retrieved using the
155: * <code>getSections</code> method.
156: * </p>
157: *
158: * @author trevor.miller
159: * @version $Id: INIConfiguration.java 492216 2007-01-03 16:51:24Z oheger $
160: * @since 1.4
161: */
162: public class INIConfiguration extends AbstractFileConfiguration {
163: /**
164: * The characters that signal the start of a comment line.
165: */
166: protected static final String COMMENT_CHARS = "#;";
167:
168: /**
169: * The characters used to separate keys from values.
170: */
171: protected static final String SEPARATOR_CHARS = "=:";
172:
173: /** Constant for the used line separator.*/
174: private static final String LINE_SEPARATOR = "\r\n";
175:
176: /**
177: * Create a new empty INI Configuration.
178: */
179: public INIConfiguration() {
180: super ();
181: }
182:
183: /**
184: * Create and load the ini configuration from the given file.
185: *
186: * @param filename The name pr path of the ini file to load.
187: * @throws ConfigurationException If an error occurs while loading the file
188: */
189: public INIConfiguration(String filename)
190: throws ConfigurationException {
191: super (filename);
192: }
193:
194: /**
195: * Create and load the ini configuration from the given file.
196: *
197: * @param file The ini file to load.
198: * @throws ConfigurationException If an error occurs while loading the file
199: */
200: public INIConfiguration(File file) throws ConfigurationException {
201: super (file);
202: }
203:
204: /**
205: * Create and load the ini configuration from the given url.
206: *
207: * @param url The url of the ini file to load.
208: * @throws ConfigurationException If an error occurs while loading the file
209: */
210: public INIConfiguration(URL url) throws ConfigurationException {
211: super (url);
212: }
213:
214: /**
215: * Save the configuration to the specified writer.
216: *
217: * @param writer - The writer to save the configuration to.
218: * @throws ConfigurationException If an error occurs while writing the
219: * configuration
220: */
221: public void save(Writer writer) throws ConfigurationException {
222: PrintWriter pw = new PrintWriter(writer);
223: Iterator iter = this .getSections().iterator();
224: while (iter.hasNext()) {
225: String section = (String) iter.next();
226: pw.print("[");
227: pw.print(section);
228: pw.print("]");
229: pw.print(LINE_SEPARATOR);
230:
231: Configuration values = this .subset(section);
232: Iterator iterator = values.getKeys();
233: while (iterator.hasNext()) {
234: String key = (String) iterator.next();
235: String value = values.getString(key);
236: pw.print(key);
237: pw.print(" = ");
238: pw.print(value);
239: pw.print(LINE_SEPARATOR);
240: }
241:
242: pw.print(LINE_SEPARATOR);
243: }
244: }
245:
246: /**
247: * Load the configuration from the given reader. Note that the
248: * <code>clear</code> method is not called so the configuration read in
249: * will be merged with the current configuration.
250: *
251: * @param reader The reader to read the configuration from.
252: * @throws ConfigurationException If an error occurs while reading the
253: * configuration
254: */
255: public void load(Reader reader) throws ConfigurationException {
256: try {
257: BufferedReader bufferedReader = new BufferedReader(reader);
258: String line = bufferedReader.readLine();
259: String section = "";
260: while (line != null) {
261: line = line.trim();
262: if (!isCommentLine(line)) {
263: if (isSectionLine(line)) {
264: section = line.substring(1, line.length() - 1)
265: + ".";
266: } else {
267: String key = "";
268: String value = "";
269: int index = line.indexOf("=");
270: if (index >= 0) {
271: key = section + line.substring(0, index);
272: value = line.substring(index + 1);
273: } else {
274: index = line.indexOf(":");
275: if (index >= 0) {
276: key = section
277: + line.substring(0, index);
278: value = line.substring(index + 1);
279: } else {
280: key = section + line;
281: }
282: }
283: this .addProperty(key.trim(), value.trim());
284: }
285: }
286: line = bufferedReader.readLine();
287: }
288: } catch (IOException ioe) {
289: throw new ConfigurationException(ioe.getMessage());
290: }
291: }
292:
293: /**
294: * Determine if the given line is a comment line.
295: *
296: * @param s The line to check.
297: * @return true if the line is empty or starts with one of the comment
298: * characters
299: */
300: protected boolean isCommentLine(String s) {
301: if (s == null) {
302: return false;
303: }
304: // blank lines are also treated as comment lines
305: return s.length() < 1
306: || COMMENT_CHARS.indexOf(s.charAt(0)) >= 0;
307: }
308:
309: /**
310: * Determine if the given line is a section.
311: *
312: * @param s The line to check.
313: * @return true if the line contains a secion
314: */
315: protected boolean isSectionLine(String s) {
316: if (s == null) {
317: return false;
318: }
319: return s.startsWith("[") && s.endsWith("]");
320: }
321:
322: /**
323: * Return a set containing the sections in this ini configuration. Note that
324: * changes to this set do not affect the configuration.
325: *
326: * @return a set containing the sections.
327: */
328: public Set getSections() {
329: Set sections = new TreeSet();
330: Iterator iter = this .getKeys();
331: while (iter.hasNext()) {
332: String key = (String) iter.next();
333: int index = key.indexOf(".");
334: if (index >= 0) {
335: sections.add(key.substring(0, index));
336: }
337: }
338: return sections;
339: }
340: }
|