##### Coverage Report - org.apache.commons.configuration.INIConfiguration

Classes in this File | Line Coverage | Branch Coverage | Complexity
INIConfiguration |
93% | 115/124
100% | 28/28
| 3.5454545454545454;3,545

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.Collection;
28 |  |
     import java.util.Iterator;
29 |  |
     import java.util.Set;
30 |  |
     import java.util.TreeSet;
31 |  |
32 |  |
     import org.apache.commons.lang.StringUtils;
33 |  |
34 |  |
     /**
35 |  |
      * <p>
36 |  |
      * An initialization or ini file is a configuration file tpically found on
37 |  |
      * Microsoft's Windows operating system and contains data for Windows based
38 |  |
      * applications.
39 |  |
      * </p>
40 |  |
      *
41 |  |
      * <p>
42 |  |
      * Although popularized by Windows, ini files can be used on any system or
43 |  |
      * platform due to the fact that they are merely text files that can easily be
44 |  |
      * parsed and modified by both humans and computers.
45 |  |
      * </p>
46 |  |
      *
47 |  |
      * <p>
48 |  |
      * A typcial ini file could look something like:
49 |  |
      * </p>
50 |  |
      * <code>
51 |  |
      * [section1]<br>
52 |  |
      * ; this is a comment!<br>
53 |  |
      * var1 = foo<br>
54 |  |
      * var2 = bar<br>
55 |  |
      *<br>
56 |  |
      * [section2]<br>
57 |  |
      * var1 = doo<br>
58 |  |
      * </code>
59 |  |
      *
60 |  |
      * <p>
61 |  |
      * The format of ini files is fairly straight forward and is composed of three
62 |  |
      * components:<br>
63 |  |
      * <ul>
64 |  |
      * <li><b>Sections:</b> Ini files are split into sections, each section
65 |  |
      * starting with a section declaration. A section declaration starts with a '['
66 |  |
      * and ends with a ']'. Sections occur on one line only.</li>
67 |  |
      * <li><b>Parameters:</b> Items in a section are known as parameters.
68 |  |
      * Parameters have a typical <code>key = value</code> format.</li>
69 |  |
      * <li><b>Comments:</b> Lines starting with a ';' are assumed to be comments.
70 |  |
      * </li>
71 |  |
      * </ul>
72 |  |
      * </p>
73 |  |
      *
74 |  |
      * <p>
75 |  |
      * There are various implementations of the ini file format by various vendors
76 |  |
      * which has caused a number of differences to appear. As far as possible this
77 |  |
      * configuration tries to be lenient and support most of the differences.
78 |  |
      * </p>
79 |  |
      *
80 |  |
      * <p>
81 |  |
      * Some of the differences supported are as follows:
82 |  |
      * <ul>
83 |  |
      * <li><b>Comments:</b> The '#' character is also accepted as a comment
84 |  |
      * signifier.</li>
85 |  |
      * <li><b>Key value separtor:</b> The ':' character is also accepted in place
86 |  |
      * of '=' to separate keys and values in parameters, for example
87 |  |
      * <code>var1 : foo</code>.</li>
88 |  |
      * <li><b>Duplicate sections:</b> Typically duplicate sections are not allowed ,
89 |  |
      * this configuration does however support it. In the event of a duplicate
90 |  |
      * section, the two section's values are merged.</li>
91 |  |
      * <li><b>Duplicate parameters:</b> Typically duplicate parameters are only
92 |  |
      * allowed if they are in two different sections, thus they are local to
93 |  |
      * sections; this configuration simply merges duplicates; if a section has a
94 |  |
      * duplicate parameter the values are then added to the key as a list. </li>
95 |  |
      * </ul>
96 |  |
      * </p>
97 |  |
      * <p>
98 |  |
      * Global parameters are also allowed; any parameters declared before a section
99 |  |
      * is declared are added to a global section. It is important to note that this
100 |  |
      * global section does not have a name.
101 |  |
      * </p>
102 |  |
      * <p>
103 |  |
      * In all instances, a parameter's key is prepended with its section name and a
104 |  |
      * '.' (period). Thus a parameter named "var1" in "section1" will have the key
105 |  |
      * <code>section1.var1</code> in this configuration. Thus, a section's
106 |  |
      * parameters can easily be retrieved using the <code>subset</code> method
107 |  |
      * using the section name as the prefix.
108 |  |
      * </p>
109 |  |
      * <p>
110 |  |
      * <h3>Implementation Details:</h3>
111 |  |
      * Consider the following ini file:<br>
112 |  |
      * <code>
113 |  |
      *  default = ok<br>
114 |  |
      *  <br>
115 |  |
      *  [section1]<br>
116 |  |
      *  var1 = foo<br>
117 |  |
      *  var2 = doodle<br>
118 |  |
      *   <br>
119 |  |
      *  [section2]<br>
120 |  |
      *  ; a comment<br>
121 |  |
      *  var1 = baz<br>
122 |  |
      *  var2 = shoodle<br>
123 |  |
      *  bad =<br>
124 |  |
      *  = worse<br>
125 |  |
      *  <br>
126 |  |
      *  [section3]<br>
127 |  |
      *  # another comment<br>
128 |  |
      *  var1 : foo<br>
129 |  |
      *  var2 : bar<br>
130 |  |
      *  var5 : test1<br>
131 |  |
      *  <br>
132 |  |
      *  [section3]<br>
133 |  |
      *  var3 = foo<br>
134 |  |
      *  var4 = bar<br>
135 |  |
      *  var5 = test2<br>
136 |  |
      *  </code>
137 |  |
      * </p>
138 |  |
      * <p>
139 |  |
      * This ini file will be parsed without error. Note:
140 |  |
      * <ul>
141 |  |
      * <li>The parameter named "default" is added to the global section, it's value
142 |  |
      * is accessed simply using <code>getProperty("default")</code>.</li>
143 |  |
      * <li>Section 1's parameters can be accessed using
144 |  |
      * <code>getProperty("section1.var1")</code>.</li>
145 |  |
      * <li>The parameter named "bad" simply adds the parameter with an empty value.
146 |  |
      * </li>
147 |  |
      * <li>The empty key with value "= worse" is added using an empty key. This key
148 |  |
      * is still added to section 2 and the value can be accessed using
149 |  |
      * <code>getProperty("section2.")</code>, notice the period '.' following the
150 |  |
      * section name.</li>
151 |  |
      * <li>Section three uses both '=' and ':' to separate keys and values.</li>
152 |  |
      * <li>Section 3 has a duplicate key named "var5". The value for this key is
153 |  |
      * [test1, test2], and is represented as a List.</li>
154 |  |
      * </ul>
155 |  |
      * </p>
156 |  |
      * <p>
157 |  |
      * The set of sections in this configuration can be retrieved using the
158 |  |
      * <code>getSections</code> method.
159 |  |
      * </p>
160 |  |
      * <p>
161 |  |
      * <em>Note:</em>Configuration objects of this type can be read concurrently
162 |  |
      * by multiple threads. However if one of these threads modifies the object,
163 |  |
      * synchronization has to be performed manually.
164 |  |
      * </p>
165 |  |
      *
166 |  |
      * @author Trevor Miller
167 |  |
      * @version $Id: INIConfiguration.java 589380 2007-10-28 16:37:35Z oheger $
168 |  |
      * @since 1.4
169 |  |
      */
170 |  |
     public class INIConfiguration extends AbstractFileConfiguration
171 |  |
     {
172 |  |
         /**
173 |  |
          * The characters that signal the start of a comment line.
174 |  |
          */
175 |  |
         protected static final String COMMENT_CHARS = "#;";
176 |  |
177 |  |
         /**
178 |  |
          * The characters used to separate keys from values.
179 |  |
          */
180 |  |
         protected static final String SEPARATOR_CHARS = "=:";
181 |  |
182 |  |
         /**
183 |  |
          * Create a new empty INI Configuration.
184 |  |
          */
185 |  |
         public INIConfiguration()
186 |  |
         {
187 | 13 |
             super();
188 | 13 |
         }
189 |  |
190 |  |
         /**
191 |  |
          * Create and load the ini configuration from the given file.
192 |  |
          *
193 |  |
          * @param filename The name pr path of the ini file to load.
194 |  |
          * @throws ConfigurationException If an error occurs while loading the file
195 |  |
          */
196 |  |
         public INIConfiguration(String filename) throws ConfigurationException
197 |  |
         {
198 | 0 |
             super(filename);
199 | 0 |
         }
200 |  |
201 |  |
         /**
202 |  |
          * Create and load the ini configuration from the given file.
203 |  |
          *
204 |  |
          * @param file The ini file to load.
205 |  |
          * @throws ConfigurationException If an error occurs while loading the file
206 |  |
          */
207 |  |
         public INIConfiguration(File file) throws ConfigurationException
208 |  |
         {
209 | 0 |
             super(file);
210 | 0 |
         }
211 |  |
212 |  |
         /**
213 |  |
          * Create and load the ini configuration from the given url.
214 |  |
          *
215 |  |
          * @param url The url of the ini file to load.
216 |  |
          * @throws ConfigurationException If an error occurs while loading the file
217 |  |
          */
218 |  |
         public INIConfiguration(URL url) throws ConfigurationException
219 |  |
         {
220 | 0 |
             super(url);
221 | 0 |
         }
222 |  |
223 |  |
         /**
224 |  |
          * Save the configuration to the specified writer.
225 |  |
          *
226 |  |
          * @param writer - The writer to save the configuration to.
227 |  |
          * @throws ConfigurationException If an error occurs while writing the
228 |  |
          * configuration
229 |  |
          */
230 |  |
         public void save(Writer writer) throws ConfigurationException
231 |  |
         {
232 | 2 |
             PrintWriter out = new PrintWriter(writer);
233 | 2 |
             Iterator it = getSections().iterator();
234 | 6 |
             while (it.hasNext())
235 |  |
             {
236 | 4 |
                 String section = (String) it.next();
237 | 4 |
                 out.print("[");
238 | 4 |
                 out.print(section);
239 | 4 |
                 out.print("]");
240 | 4 |
                 out.println();
241 |  |
242 | 4 |
                 Configuration subset = subset(section);
243 | 4 |
                 Iterator keys = subset.getKeys();
244 | 12 |
                 while (keys.hasNext())
245 |  |
                 {
246 | 8 |
                     String key = (String) keys.next();
247 | 8 |
                     Object value = subset.getProperty(key);
248 | 8 |
                     if (value instanceof Collection)
249 |  |
                     {
250 | 1 |
                         Iterator values = ((Collection) value).iterator();
251 | 3 |
                         while (values.hasNext())
252 |  |
                         {
253 | 2 |
                             value = (Object) values.next();
254 | 2 |
                             out.print(key);
255 | 2 |
                             out.print(" = ");
256 | 2 |
                             out.print(formatValue(value.toString()));
257 | 2 |
                             out.println();
258 | 2 |
                         }
259 | 1 |
                     }
260 |  |
                     else
261 |  |
                     {
262 | 7 |
                         out.print(key);
263 | 7 |
                         out.print(" = ");
264 | 7 |
                         out.print(formatValue(value.toString()));
265 | 7 |
                         out.println();
266 |  |
                     }
267 | 8 |
                 }
268 |  |
269 | 4 |
                 out.println();
270 | 4 |
             }
271 |  |
272 | 2 |
             out.flush();
273 | 2 |
         }
274 |  |
275 |  |
         /**
276 |  |
          * Load the configuration from the given reader. Note that the
277 |  |
          * <code>clear</code> method is not called so the configuration read in
278 |  |
          * will be merged with the current configuration.
279 |  |
          *
280 |  |
          * @param reader The reader to read the configuration from.
281 |  |
          * @throws ConfigurationException If an error occurs while reading the
282 |  |
          * configuration
283 |  |
          */
284 |  |
         public void load(Reader reader) throws ConfigurationException
285 |  |
         {
286 |  |
             try
287 |  |
             {
288 | 8 |
                 BufferedReader bufferedReader = new BufferedReader(reader);
289 | 8 |
                 String line = bufferedReader.readLine();
290 | 8 |
                 String section = "";
291 | 69 |
                 while (line != null)
292 |  |
                 {
293 | 61 |
                     line = line.trim();
294 | 61 |
                     if (!isCommentLine(line))
295 |  |
                     {
296 | 54 |
                         if (isSectionLine(line))
297 |  |
                         {
298 | 12 |
                             section = line.substring(1, line.length() - 1) + ".";
299 | 12 |
                         }
300 |  |
                         else
301 |  |
                         {
302 | 42 |
                             String key = "";
303 | 42 |
                             String value = "";
304 | 42 |
                             int index = line.indexOf("=");
305 | 42 |
                             if (index >= 0)
306 |  |
                             {
307 | 34 |
                                 key = section + line.substring(0, index);
308 | 34 |
                                 value = parseValue(line.substring(index + 1));
309 | 34 |
                             }
310 |  |
                             else
311 |  |
                             {
312 | 8 |
                                 index = line.indexOf(":");
313 | 8 |
                                 if (index >= 0)
314 |  |
                                 {
315 | 8 |
                                     key = section + line.substring(0, index);
316 | 8 |
                                     value = parseValue(line.substring(index + 1));
317 | 8 |
                                 }
318 |  |
                                 else
319 |  |
                                 {
320 | 0 |
                                     key = section + line;
321 |  |
                                 }
322 |  |
                             }
323 | 42 |
                             addProperty(key.trim(), value);
324 |  |
                         }
325 |  |
                     }
326 | 61 |
                     line = bufferedReader.readLine();
327 | 61 |
                 }
328 |  |
             }
329 | 0 |
             catch (IOException ioe)
330 |  |
             {
331 | 0 |
                 throw new ConfigurationException(ioe.getMessage());
332 | 8 |
             }
333 | 8 |
         }
334 |  |
335 |  |
         /**
336 |  |
          * Parse the value to remove the quotes and ignoring the comment.
337 |  |
          * Example:
338 |  |
          *
339 |  |
          * <pre>"value" ; comment -> value</pre>
340 |  |
          *
341 |  |
          * <pre>'value' ; comment -> value</pre>
342 |  |
          *
343 |  |
          * @param value
344 |  |
          */
345 |  |
         private String parseValue(String value)
346 |  |
         {
347 | 42 |
             value = value.trim();
348 |  |
349 | 42 |
             boolean quoted = value.startsWith("\"") || value.startsWith("'");
350 | 42 |
             boolean stop = false;
351 | 42 |
             boolean escape = false;
352 |  |
353 | 42 |
             char quote = quoted ? value.charAt(0) : 0;
354 |  |
355 | 42 |
             int i = quoted ? 1 : 0;
356 |  |
357 | 42 |
             StringBuffer result = new StringBuffer();
358 | 495 |
             while (i < value.length() && !stop)
359 |  |
             {
360 | 453 |
                 char c = value.charAt(i);
361 |  |
362 | 453 |
                 if (quoted)
363 |  |
                 {
364 | 346 |
                     if ('\\' == c && !escape)
365 |  |
                     {
366 | 25 |
                         escape = true;
367 | 25 |
                     }
368 | 321 |
                     else if (!escape && quote == c)
369 |  |
                     {
370 | 21 |
                         stop = true;
371 | 21 |
                     }
372 | 300 |
                     else if (escape && quote == c)
373 |  |
                     {
374 | 20 |
                         escape = false;
375 | 20 |
                         result.append(c);
376 | 20 |
                     }
377 |  |
                     else
378 |  |
                     {
379 | 280 |
                         if (escape)
380 |  |
                         {
381 | 5 |
                             escape = false;
382 | 5 |
                             result.append('\\');
383 |  |
                         }
384 |  |
385 | 280 |
                         result.append(c);
386 |  |
                     }
387 | 280 |
                 }
388 |  |
                 else
389 |  |
                 {
390 | 107 |
                     if (COMMENT_CHARS.indexOf(c) == -1)
391 |  |
                     {
392 | 102 |
                         result.append(c);
393 | 102 |
                     }
394 |  |
                     else
395 |  |
                     {
396 | 5 |
                         stop = true;
397 |  |
                     }
398 |  |
                 }
399 |  |
400 | 453 |
                 i++;
401 | 453 |
             }
402 |  |
403 | 42 |
             return result.toString().trim();
404 |  |
         }
405 |  |
406 |  |
         /**
407 |  |
          * Add quotes around the specified value if it contains a comment character.
408 |  |
          */
409 |  |
         private String formatValue(String value)
410 |  |
         {
411 | 9 |
             boolean quoted = false;
412 |  |
413 | 27 |
             for (int i = 0; i < COMMENT_CHARS.length() && !quoted; i++)
414 |  |
             {
415 | 18 |
                 char c = COMMENT_CHARS.charAt(i);
416 | 18 |
                 if (value.indexOf(c) != -1)
417 |  |
                 {
418 | 1 |
                     quoted = true;
419 |  |
                 }
420 |  |
             }
421 |  |
422 | 9 |
             if (quoted)
423 |  |
             {
424 | 1 |
                 return '"' + StringUtils.replace(value, "\"", "\\\"") + '"';
425 |  |
             }
426 |  |
             else
427 |  |
             {
428 | 8 |
                 return value;
429 |  |
             }
430 |  |
         }
431 |  |
432 |  |
         /**
433 |  |
          * Determine if the given line is a comment line.
434 |  |
          *
435 |  |
          * @param line The line to check.
436 |  |
          * @return true if the line is empty or starts with one of the comment
437 |  |
          * characters
438 |  |
          */
439 |  |
         protected boolean isCommentLine(String line)
440 |  |
         {
441 | 65 |
             if (line == null)
442 |  |
             {
443 | 1 |
                 return false;
444 |  |
             }
445 |  |
             // blank lines are also treated as comment lines
446 | 64 |
             return line.length() < 1 || COMMENT_CHARS.indexOf(line.charAt(0)) >= 0;
447 |  |
         }
448 |  |
449 |  |
         /**
450 |  |
          * Determine if the given line is a section.
451 |  |
          *
452 |  |
          * @param line The line to check.
453 |  |
          * @return true if the line contains a secion
454 |  |
          */
455 |  |
         protected boolean isSectionLine(String line)
456 |  |
         {
457 | 57 |
             if (line == null)
458 |  |
             {
459 | 1 |
                 return false;
460 |  |
             }
461 | 56 |
             return line.startsWith("[") && line.endsWith("]");
462 |  |
         }
463 |  |
464 |  |
         /**
465 |  |
          * Return a set containing the sections in this ini configuration. Note that
466 |  |
          * changes to this set do not affect the configuration.
467 |  |
          *
468 |  |
          * @return a set containing the sections.
469 |  |
          */
470 |  |
         public Set getSections()
471 |  |
         {
472 | 5 |
             Set sections = new TreeSet();
473 |  |
474 | 5 |
             Iterator keys = getKeys();
475 | 29 |
             while (keys.hasNext())
476 |  |
             {
477 | 24 |
                 String key = (String) keys.next();
478 | 24 |
                 int index = key.indexOf(".");
479 | 24 |
                 if (index >= 0)
480 |  |
                 {
481 | 24 |
                     sections.add(key.substring(0, index));
482 |  |
                 }
483 | 24 |
             }
484 |  |
485 | 5 |
             return sections;
486 |  |
         }
487 |  |
     }