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

Classes in this File | Line Coverage | Branch Coverage | Complexity
INIConfiguration |
92% | 103/112
100% | 29/29
| 3.6363636363636362;3,636

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 typically 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 720600 2008-11-25 21:20:01Z oheger $
168 |  |
      * @since 1.4
169 |  |
      * @deprecated This class has been replaced by HierarchicalINIConfiguration,
170 |  |
      * which provides a superset of the functionality offered by this class.
171 |  |
      */
172 |  |
     public class INIConfiguration extends AbstractFileConfiguration
173 |  |
     {
174 |  |
         /**
175 |  |
          * The characters that signal the start of a comment line.
176 |  |
          */
177 |  |
         protected static final String COMMENT_CHARS = "#;";
178 |  |
179 |  |
         /**
180 |  |
          * The characters used to separate keys from values.
181 |  |
          */
182 |  |
         protected static final String SEPARATOR_CHARS = "=:";
183 |  |
184 |  |
         /**
185 |  |
          * Create a new empty INI Configuration.
186 |  |
          */
187 |  |
         public INIConfiguration()
188 |  |
         {
189 | 15 |
             super();
190 | 15 |
         }
191 |  |
192 |  |
         /**
193 |  |
          * Create and load the ini configuration from the given file.
194 |  |
          *
195 |  |
          * @param filename The name pr path of the ini file to load.
196 |  |
          * @throws ConfigurationException If an error occurs while loading the file
197 |  |
          */
198 |  |
         public INIConfiguration(String filename) throws ConfigurationException
199 |  |
         {
200 | 0 |
             super(filename);
201 | 0 |
         }
202 |  |
203 |  |
         /**
204 |  |
          * Create and load the ini configuration from the given file.
205 |  |
          *
206 |  |
          * @param file The ini file to load.
207 |  |
          * @throws ConfigurationException If an error occurs while loading the file
208 |  |
          */
209 |  |
         public INIConfiguration(File file) throws ConfigurationException
210 |  |
         {
211 | 0 |
             super(file);
212 | 0 |
         }
213 |  |
214 |  |
         /**
215 |  |
          * Create and load the ini configuration from the given url.
216 |  |
          *
217 |  |
          * @param url The url of the ini file to load.
218 |  |
          * @throws ConfigurationException If an error occurs while loading the file
219 |  |
          */
220 |  |
         public INIConfiguration(URL url) throws ConfigurationException
221 |  |
         {
222 | 0 |
             super(url);
223 | 0 |
         }
224 |  |
225 |  |
         /**
226 |  |
          * Save the configuration to the specified writer.
227 |  |
          *
228 |  |
          * @param writer - The writer to save the configuration to.
229 |  |
          * @throws ConfigurationException If an error occurs while writing the
230 |  |
          * configuration
231 |  |
          */
232 |  |
         public void save(Writer writer) throws ConfigurationException
233 |  |
         {
234 | 2 |
             PrintWriter out = new PrintWriter(writer);
235 | 2 |
             Iterator it = getSections().iterator();
236 | 6 |
             while (it.hasNext())
237 |  |
             {
238 | 4 |
                 String section = (String) it.next();
239 | 4 |
                 out.print("[");
240 | 4 |
                 out.print(section);
241 | 4 |
                 out.print("]");
242 | 4 |
                 out.println();
243 |  |
244 | 4 |
                 Configuration subset = subset(section);
245 | 4 |
                 Iterator keys = subset.getKeys();
246 | 12 |
                 while (keys.hasNext())
247 |  |
                 {
248 | 8 |
                     String key = (String) keys.next();
249 | 8 |
                     Object value = subset.getProperty(key);
250 | 8 |
                     if (value instanceof Collection)
251 |  |
                     {
252 | 1 |
                         Iterator values = ((Collection) value).iterator();
253 | 3 |
                         while (values.hasNext())
254 |  |
                         {
255 | 2 |
                             value = (Object) values.next();
256 | 2 |
                             out.print(key);
257 | 2 |
                             out.print(" = ");
258 | 2 |
                             out.print(formatValue(value.toString()));
259 | 2 |
                             out.println();
260 |  |
                         }
261 |  |
                     }
262 |  |
                     else
263 |  |
                     {
264 | 7 |
                         out.print(key);
265 | 7 |
                         out.print(" = ");
266 | 7 |
                         out.print(formatValue(value.toString()));
267 | 7 |
                         out.println();
268 |  |
                     }
269 |  |
                 }
270 |  |
271 | 4 |
                 out.println();
272 |  |
             }
273 |  |
274 | 2 |
             out.flush();
275 | 2 |
         }
276 |  |
277 |  |
         /**
278 |  |
          * Load the configuration from the given reader. Note that the
279 |  |
          * <code>clear</code> method is not called so the configuration read in
280 |  |
          * will be merged with the current configuration.
281 |  |
          *
282 |  |
          * @param reader The reader to read the configuration from.
283 |  |
          * @throws ConfigurationException If an error occurs while reading the
284 |  |
          * configuration
285 |  |
          */
286 |  |
         public void load(Reader reader) throws ConfigurationException
287 |  |
         {
288 |  |
             try
289 |  |
             {
290 | 10 |
                 BufferedReader bufferedReader = new BufferedReader(reader);
291 | 10 |
                 String line = bufferedReader.readLine();
292 | 10 |
                 String section = "";
293 | 73 |
                 while (line != null)
294 |  |
                 {
295 | 63 |
                     line = line.trim();
296 | 63 |
                     if (!isCommentLine(line))
297 |  |
                     {
298 | 56 |
                         if (isSectionLine(line))
299 |  |
                         {
300 | 12 |
                             section = line.substring(1, line.length() - 1) + ".";
301 |  |
                         }
302 |  |
                         else
303 |  |
                         {
304 | 44 |
                             String key = "";
305 | 44 |
                             String value = "";
306 | 44 |
                             int index = line.indexOf("=");
307 | 44 |
                             if (index >= 0)
308 |  |
                             {
309 | 36 |
                                 key = section + line.substring(0, index);
310 | 36 |
                                 value = parseValue(line.substring(index + 1));
311 |  |
                             }
312 |  |
                             else
313 |  |
                             {
314 | 8 |
                                 index = line.indexOf(":");
315 | 8 |
                                 if (index >= 0)
316 |  |
                                 {
317 | 8 |
                                     key = section + line.substring(0, index);
318 | 8 |
                                     value = parseValue(line.substring(index + 1));
319 |  |
                                 }
320 |  |
                                 else
321 |  |
                                 {
322 | 0 |
                                     key = section + line;
323 |  |
                                 }
324 |  |
                             }
325 | 44 |
                             addProperty(key.trim(), value);
326 |  |
                         }
327 |  |
                     }
328 | 63 |
                     line = bufferedReader.readLine();
329 |  |
                 }
330 |  |
             }
331 | 0 |
             catch (IOException e)
332 |  |
             {
333 | 0 |
                 throw new ConfigurationException("Unable to load the configuration", e);
334 | 10 |
             }
335 | 10 |
         }
336 |  |
337 |  |
         /**
338 |  |
          * Parse the value to remove the quotes and ignoring the comment.
339 |  |
          * Example:
340 |  |
          *
341 |  |
          * <pre>"value" ; comment -> value</pre>
342 |  |
          *
343 |  |
          * <pre>'value' ; comment -> value</pre>
344 |  |
          *
345 |  |
          * @param value
346 |  |
          */
347 |  |
         private String parseValue(String value)
348 |  |
         {
349 | 44 |
             value = value.trim();
350 |  |
351 | 44 |
             boolean quoted = value.startsWith("\"") || value.startsWith("'");
352 | 44 |
             boolean stop = false;
353 | 44 |
             boolean escape = false;
354 |  |
355 | 44 |
             char quote = quoted ? value.charAt(0) : 0;
356 |  |
357 | 44 |
             int i = quoted ? 1 : 0;
358 |  |
359 | 44 |
             StringBuffer result = new StringBuffer();
360 | 529 |
             while (i < value.length() && !stop)
361 |  |
             {
362 | 485 |
                 char c = value.charAt(i);
363 |  |
364 | 485 |
                 if (quoted)
365 |  |
                 {
366 | 378 |
                     if ('\\' == c && !escape)
367 |  |
                     {
368 | 25 |
                         escape = true;
369 |  |
                     }
370 | 353 |
                     else if (!escape && quote == c)
371 |  |
                     {
372 | 23 |
                         stop = true;
373 |  |
                     }
374 | 330 |
                     else if (escape && quote == c)
375 |  |
                     {
376 | 20 |
                         escape = false;
377 | 20 |
                         result.append(c);
378 |  |
                     }
379 |  |
                     else
380 |  |
                     {
381 | 310 |
                         if (escape)
382 |  |
                         {
383 | 5 |
                             escape = false;
384 | 5 |
                             result.append('\\');
385 |  |
                         }
386 |  |
387 | 310 |
                         result.append(c);
388 |  |
                     }
389 |  |
                 }
390 |  |
                 else
391 |  |
                 {
392 | 107 |
                     if (COMMENT_CHARS.indexOf(c) == -1)
393 |  |
                     {
394 | 102 |
                         result.append(c);
395 |  |
                     }
396 |  |
                     else
397 |  |
                     {
398 | 5 |
                         stop = true;
399 |  |
                     }
400 |  |
                 }
401 |  |
402 | 485 |
                 i++;
403 |  |
             }
404 |  |
405 | 44 |
             String v = result.toString();
406 | 44 |
             if (!quoted)
407 |  |
             {
408 | 21 |
                 v = v.trim();
409 |  |
             }
410 | 44 |
             return v;
411 |  |
         }
412 |  |
413 |  |
         /**
414 |  |
          * Add quotes around the specified value if it contains a comment character.
415 |  |
          */
416 |  |
         private String formatValue(String value)
417 |  |
         {
418 | 9 |
             boolean quoted = false;
419 |  |
420 | 27 |
             for (int i = 0; i < COMMENT_CHARS.length() && !quoted; i++)
421 |  |
             {
422 | 18 |
                 char c = COMMENT_CHARS.charAt(i);
423 | 18 |
                 if (value.indexOf(c) != -1)
424 |  |
                 {
425 | 1 |
                     quoted = true;
426 |  |
                 }
427 |  |
             }
428 |  |
429 | 9 |
             if (quoted)
430 |  |
             {
431 | 1 |
                 return '"' + StringUtils.replace(value, "\"", "\\\"") + '"';
432 |  |
             }
433 |  |
             else
434 |  |
             {
435 | 8 |
                 return value;
436 |  |
             }
437 |  |
         }
438 |  |
439 |  |
         /**
440 |  |
          * Determine if the given line is a comment line.
441 |  |
          *
442 |  |
          * @param line The line to check.
443 |  |
          * @return true if the line is empty or starts with one of the comment
444 |  |
          * characters
445 |  |
          */
446 |  |
         protected boolean isCommentLine(String line)
447 |  |
         {
448 | 67 |
             if (line == null)
449 |  |
             {
450 | 1 |
                 return false;
451 |  |
             }
452 |  |
             // blank lines are also treated as comment lines
453 | 66 |
             return line.length() < 1 || COMMENT_CHARS.indexOf(line.charAt(0)) >= 0;
454 |  |
         }
455 |  |
456 |  |
         /**
457 |  |
          * Determine if the given line is a section.
458 |  |
          *
459 |  |
          * @param line The line to check.
460 |  |
          * @return true if the line contains a secion
461 |  |
          */
462 |  |
         protected boolean isSectionLine(String line)
463 |  |
         {
464 | 59 |
             if (line == null)
465 |  |
             {
466 | 1 |
                 return false;
467 |  |
             }
468 | 58 |
             return line.startsWith("[") && line.endsWith("]");
469 |  |
         }
470 |  |
471 |  |
         /**
472 |  |
          * Return a set containing the sections in this ini configuration. Note that
473 |  |
          * changes to this set do not affect the configuration.
474 |  |
          *
475 |  |
          * @return a set containing the sections.
476 |  |
          */
477 |  |
         public Set getSections()
478 |  |
         {
479 | 5 |
             Set sections = new TreeSet();
480 |  |
481 | 5 |
             Iterator keys = getKeys();
482 | 29 |
             while (keys.hasNext())
483 |  |
             {
484 | 24 |
                 String key = (String) keys.next();
485 | 24 |
                 int index = key.indexOf(".");
486 | 24 |
                 if (index >= 0)
487 |  |
                 {
488 | 24 |
                     sections.add(key.substring(0, index));
489 |  |
                 }
490 |  |
             }
491 |  |
492 | 5 |
             return sections;
493 |  |
         }
494 |  |
     }