View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.configuration;
19  
20  import java.io.BufferedReader;
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.PrintWriter;
24  import java.io.Reader;
25  import java.io.Writer;
26  import java.net.URL;
27  import java.util.Iterator;
28  import java.util.Set;
29  import java.util.TreeSet;
30  
31  /***
32   * <p>
33   * An initialization or ini file is a configuration file tpically found on
34   * Microsoft's Windows operating system and contains data for Windows based
35   * applications.
36   * </p>
37   *
38   * <p>
39   * Although popularized by Windows, ini files can be used on any system or
40   * platform due to the fact that they are merely text files that can easily be
41   * parsed and modified by both humans and computers.
42   * </p>
43   *
44   * <p>
45   * A typcial ini file could look something like:
46   * </p>
47   * <code>
48   * [section1]<br>
49   * ; this is a comment!<br>
50   * var1 = foo<br>
51   * var2 = bar<br>
52   *<br>
53   * [section2]<br>
54   * var1 = doo<br>
55   * </code>
56   *
57   * <p>
58   * The format of ini files is fairly straight forward and is comosed of three
59   * components:<br>
60   * <ul>
61   * <li><b>Sections:</b> Ini files are split into sections, each section
62   * starting with a section declaration. A section declaration starts with a '['
63   * and ends with a ']'. Sections occur on one line only.</li>
64   * <li><b>Parameters:</b> Items in a section are known as parameters.
65   * Parameters have a typical <code>key = value</code> format.</li>
66   * <li><b>Comments:</b> Lines starting with a ';' are assumed to be comments.
67   * </li>
68   * </ul>
69   * </p>
70   *
71   * <p>
72   * There are various implementations of the ini file format by various vendors
73   * which has caused a number of differences to appear. As far as possible this
74   * configuration tries to be lenient and support most of the differences.
75   * </p>
76   *
77   * <p>
78   * Some of the differences supported are as follows:
79   * <ul>
80   * <li><b>Comments:</b> The '#' character is also accepted as a comment
81   * signifier.</li>
82   * <li><b>Key value separtor:</b> The ':' character is also accepted in place
83   * of '=' to separate keys and values in parameters, for example
84   * <code>var1 : foo</code>.</li>
85   * <li><b>Duplicate sections:</b> Typically duplicate sections are not allowed ,
86   * this configuration does however support it. In the event of a duplicate
87   * section, the two section's values are merged.</li>
88   * <li><b>Duplicate parameters:</b> Typically duplicate parameters are only
89   * allowed if they are in two different sections, thus they are local to
90   * sections; this configuration simply merges duplicates; if a section has a
91   * duplicate parameter the values are then added to the key as a list. </li>
92   * </ul>
93   * </p>
94   * <p>
95   * Global parameters are also allowed; any parameters declared before a section
96   * is declared are added to a global section. It is important to note that this
97   * global section does not have a name.
98   * </p>
99   * <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     /***
165      * The characters that signal the start of a comment line.
166      */
167     protected static final String COMMENT_CHARS = "#;";
168 
169     /***
170      * The characters used to separate keys from values.
171      */
172     protected static final String SEPARATOR_CHARS = "=:";
173 
174     /*** Constant for the used line separator.*/
175     private static final String LINE_SEPARATOR = "\r\n";
176 
177     /***
178      * Create a new empty INI Configuration.
179      */
180     public INIConfiguration()
181     {
182         super();
183     }
184 
185     /***
186      * Create and load the ini configuration from the given file.
187      *
188      * @param filename The name pr path of the ini file to load.
189      * @throws ConfigurationException If an error occurs while loading the file
190      */
191     public INIConfiguration(String filename) throws ConfigurationException
192     {
193         super(filename);
194     }
195 
196     /***
197      * Create and load the ini configuration from the given file.
198      *
199      * @param file The ini file to load.
200      * @throws ConfigurationException If an error occurs while loading the file
201      */
202     public INIConfiguration(File file) throws ConfigurationException
203     {
204         super(file);
205     }
206 
207     /***
208      * Create and load the ini configuration from the given url.
209      *
210      * @param url The url of the ini file to load.
211      * @throws ConfigurationException If an error occurs while loading the file
212      */
213     public INIConfiguration(URL url) throws ConfigurationException
214     {
215         super(url);
216     }
217 
218     /***
219      * Save the configuration to the specified writer.
220      *
221      * @param writer - The writer to save the configuration to.
222      * @throws ConfigurationException If an error occurs while writing the
223      * configuration
224      */
225     public void save(Writer writer) throws ConfigurationException
226     {
227         PrintWriter pw = new PrintWriter(writer);
228         Iterator iter = this.getSections().iterator();
229         while (iter.hasNext())
230         {
231             String section = (String) iter.next();
232             pw.print("[");
233             pw.print(section);
234             pw.print("]");
235             pw.print(LINE_SEPARATOR);
236 
237             Configuration values = this.subset(section);
238             Iterator iterator = values.getKeys();
239             while (iterator.hasNext())
240             {
241                 String key = (String) iterator.next();
242                 String value = values.getString(key);
243                 pw.print(key);
244                 pw.print(" = ");
245                 pw.print(value);
246                 pw.print(LINE_SEPARATOR);
247             }
248 
249             pw.print(LINE_SEPARATOR);
250         }
251     }
252 
253     /***
254      * Load the configuration from the given reader. Note that the
255      * <code>clear</code> method is not called so the configuration read in
256      * will be merged with the current configuration.
257      *
258      * @param reader The reader to read the configuration from.
259      * @throws ConfigurationException If an error occurs while reading the
260      * configuration
261      */
262     public void load(Reader reader) throws ConfigurationException
263     {
264         try
265         {
266             BufferedReader bufferedReader = new BufferedReader(reader);
267             String line = bufferedReader.readLine();
268             String section = "";
269             while (line != null)
270             {
271                 line = line.trim();
272                 if (!isCommentLine(line))
273                 {
274                     if (isSectionLine(line))
275                     {
276                         section = line.substring(1, line.length() - 1) + ".";
277                     }
278                     else
279                     {
280                         String key = "";
281                         String value = "";
282                         int index = line.indexOf("=");
283                         if (index >= 0)
284                         {
285                             key = section + line.substring(0, index);
286                             value = line.substring(index + 1);
287                         }
288                         else
289                         {
290                             index = line.indexOf(":");
291                             if (index >= 0)
292                             {
293                                 key = section + line.substring(0, index);
294                                 value = line.substring(index + 1);
295                             }
296                             else
297                             {
298                                 key = section + line;
299                             }
300                         }
301                         this.addProperty(key.trim(), value.trim());
302                     }
303                 }
304                 line = bufferedReader.readLine();
305             }
306         }
307         catch (IOException ioe)
308         {
309             throw new ConfigurationException(ioe.getMessage());
310         }
311     }
312 
313     /***
314      * Determine if the given line is a comment line.
315      *
316      * @param s The line to check.
317      * @return true if the line is empty or starts with one of the comment
318      * characters
319      */
320     protected boolean isCommentLine(String s)
321     {
322         if (s == null)
323         {
324             return false;
325         }
326         // blank lines are also treated as comment lines
327         return s.length() < 1 || COMMENT_CHARS.indexOf(s.charAt(0)) >= 0;
328     }
329 
330     /***
331      * Determine if the given line is a section.
332      *
333      * @param s The line to check.
334      * @return true if the line contains a secion
335      */
336     protected boolean isSectionLine(String s)
337     {
338         if (s == null)
339         {
340             return false;
341         }
342         return s.startsWith("[") && s.endsWith("]");
343     }
344 
345     /***
346      * Return a set containing the sections in this ini configuration. Note that
347      * changes to this set do not affect the configuration.
348      *
349      * @return a set containing the sections.
350      */
351     public Set getSections()
352     {
353         Set sections = new TreeSet();
354         Iterator iter = this.getKeys();
355         while (iter.hasNext())
356         {
357             String key = (String) iter.next();
358             int index = key.indexOf(".");
359             if (index >= 0)
360             {
361                 sections.add(key.substring(0, index));
362             }
363         }
364         return sections;
365     }
366 }