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

Classes in this File | Line Coverage | Branch Coverage | Complexity
HierarchicalINIConfiguration |
98% | 161/164
100% | 44/44
| 3.3684210526315788;3,368

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 |  |
     package org.apache.commons.configuration;
18 |  |
19 |  |
     import java.io.BufferedReader;
20 |  |
     import java.io.File;
21 |  |
     import java.io.IOException;
22 |  |
     import java.io.PrintWriter;
23 |  |
     import java.io.Reader;
24 |  |
     import java.io.Writer;
25 |  |
     import java.net.URL;
26 |  |
     import java.util.Collection;
27 |  |
     import java.util.Iterator;
28 |  |
     import java.util.List;
29 |  |
     import java.util.Set;
30 |  |
31 |  |
     import org.apache.commons.collections.set.ListOrderedSet;
32 |  |
     import org.apache.commons.configuration.tree.ConfigurationNode;
33 |  |
     import org.apache.commons.configuration.tree.DefaultConfigurationNode;
34 |  |
     import org.apache.commons.configuration.tree.ViewNode;
35 |  |
     import org.apache.commons.lang.StringUtils;
36 |  |
37 |  |
     /**
38 |  |
      * <p>
39 |  |
      * A specialized hierarchical configuration implementation for parsing ini
40 |  |
      * files.
41 |  |
      * </p>
42 |  |
      * <p>
43 |  |
      * An initialization or ini file is a configuration file typically found on
44 |  |
      * Microsoft's Windows operating system and contains data for Windows based
45 |  |
      * applications.
46 |  |
      * </p>
47 |  |
      * <p>
48 |  |
      * Although popularized by Windows, ini files can be used on any system or
49 |  |
      * platform due to the fact that they are merely text files that can easily be
50 |  |
      * parsed and modified by both humans and computers.
51 |  |
      * </p>
52 |  |
      * <p>
53 |  |
      * A typcial ini file could look something like:
54 |  |
      * </p>
55 |  |
      * <code>
56 |  |
      * [section1]<br>
57 |  |
      * ; this is a comment!<br>
58 |  |
      * var1 = foo<br>
59 |  |
      * var2 = bar<br>
60 |  |
      * <br>
61 |  |
      * [section2]<br>
62 |  |
      * var1 = doo<br>
63 |  |
      * </code>
64 |  |
      * <p>
65 |  |
      * The format of ini files is fairly straight forward and is composed of three
66 |  |
      * components:<br>
67 |  |
      * <ul>
68 |  |
      * <li><b>Sections:</b> Ini files are split into sections, each section starting
69 |  |
      * with a section declaration. A section declaration starts with a '[' and ends
70 |  |
      * with a ']'. Sections occur on one line only.</li>
71 |  |
      * <li><b>Parameters:</b> Items in a section are known as parameters. Parameters
72 |  |
      * have a typical <code>key = value</code> format.</li>
73 |  |
      * <li><b>Comments:</b> Lines starting with a ';' are assumed to be comments.</li>
74 |  |
      * </ul>
75 |  |
      * </p>
76 |  |
      * <p>
77 |  |
      * There are various implementations of the ini file format by various vendors
78 |  |
      * which has caused a number of differences to appear. As far as possible this
79 |  |
      * configuration tries to be lenient and support most of the differences.
80 |  |
      * </p>
81 |  |
      * <p>
82 |  |
      * Some of the differences supported are as follows:
83 |  |
      * <ul>
84 |  |
      * <li><b>Comments:</b> The '#' character is also accepted as a comment
85 |  |
      * signifier.</li>
86 |  |
      * <li><b>Key value separtor:</b> The ':' character is also accepted in place of
87 |  |
      * '=' to separate keys and values in parameters, for example
88 |  |
      * <code>var1 : foo</code>.</li>
89 |  |
      * <li><b>Duplicate sections:</b> Typically duplicate sections are not allowed,
90 |  |
      * this configuration does however support it. In the event of a duplicate
91 |  |
      * section, the two section's values are merged.</li>
92 |  |
      * <li><b>Duplicate parameters:</b> Typically duplicate parameters are only
93 |  |
      * allowed if they are in two different sections, thus they are local to
94 |  |
      * sections; this configuration simply merges duplicates; if a section has a
95 |  |
      * duplicate parameter the values are then added to the key as a list.</li>
96 |  |
      * </ul>
97 |  |
      * </p>
98 |  |
      * <p>
99 |  |
      * Global parameters are also allowed; any parameters declared before a section
100 |  |
      * is declared are added to a global section. It is important to note that this
101 |  |
      * global section does not have a name.
102 |  |
      * </p>
103 |  |
      * <p>
104 |  |
      * In all instances, a parameter's key is prepended with its section name and a
105 |  |
      * '.' (period). Thus a parameter named "var1" in "section1" will have the key
106 |  |
      * <code>section1.var1</code> in this configuration. (This is the default
107 |  |
      * behavior. Because this is a hierarchical configuration you can change this by
108 |  |
      * setting a different {@link org.apache.commons.configuration.tree.ExpressionEngine}.)
109 |  |
      * </p>
110 |  |
      * <p>
111 |  |
      * <h3>Implementation Details:</h3> 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.</li>
146 |  |
      * <li>The empty key with value "= worse" is added using a key consisting of a
147 |  |
      * single space character. This key is still added to section 2 and the value
148 |  |
      * can be accessed using <code>getProperty("section2. ")</code>, notice the
149 |  |
      * period '.' and the space following the section name.</li>
150 |  |
      * <li>Section three uses both '=' and ':' to separate keys and values.</li>
151 |  |
      * <li>Section 3 has a duplicate key named "var5". The value for this key is
152 |  |
      * [test1, test2], and is represented as a List.</li>
153 |  |
      * </ul>
154 |  |
      * </p>
155 |  |
      * <p>
156 |  |
      * Internally, this configuration maps the content of the represented ini file
157 |  |
      * to its node structure in the following way:
158 |  |
      * <ul>
159 |  |
      * <li>Sections are represented by direct child nodes of the root node.</li>
160 |  |
      * <li>For the content of a section, corresponding nodes are created as children
161 |  |
      * of the section node.</li>
162 |  |
      * </ul>
163 |  |
      * This explains how the keys for the properties can be constructed. You can
164 |  |
      * also use other methods of {@link HierarchicalConfiguration} for querying or
165 |  |
      * manipulating the hierarchy of configuration nodes, for instance the
166 |  |
      * <code>configurationAt()</code> method for obtaining the data of a specific
167 |  |
      * section.
168 |  |
      * </p>
169 |  |
      * <p>
170 |  |
      * The set of sections in this configuration can be retrieved using the
171 |  |
      * <code>getSections()</code> method. For obtaining a
172 |  |
      * <code>SubnodeConfiguration</code> with the content of a specific section the
173 |  |
      * <code>getSection()</code> method can be used.
174 |  |
      * </p>
175 |  |
      * <p>
176 |  |
      * <em>Note:</em> Configuration objects of this type can be read concurrently by
177 |  |
      * multiple threads. However if one of these threads modifies the object,
178 |  |
      * synchronization has to be performed manually.
179 |  |
      * </p>
180 |  |
      *
181 |  |
      * @author <a
182 |  |
      *         href="http://commons.apache.org/configuration/team-list.html">Commons
183 |  |
      *         Configuration team</a>
184 |  |
      * @version $Id: HierarchicalINIConfiguration.java 720295 2008-11-24 21:29:42Z oheger $
185 |  |
      * @since 1.6
186 |  |
      */
187 |  |
     public class HierarchicalINIConfiguration extends
188 |  |
             AbstractHierarchicalFileConfiguration
189 |  |
     {
190 |  |
         /**
191 |  |
          * The characters that signal the start of a comment line.
192 |  |
          */
193 |  |
         protected static final String COMMENT_CHARS = "#;";
194 |  |
195 |  |
         /**
196 |  |
          * The characters used to separate keys from values.
197 |  |
          */
198 |  |
         protected static final String SEPARATOR_CHARS = "=:";
199 |  |
200 |  |
         /**
201 |  |
          * The serial version UID.
202 |  |
          */
203 |  |
         private static final long serialVersionUID = 2548006161386850670L;
204 |  |
205 |  |
         /**
206 |  |
          * Constant for the line separator.
207 |  |
          */
208 | 1 |
         private static final String LINE_SEPARATOR = System.getProperty("line.separator");
209 |  |
210 |  |
         /**
211 |  |
          * The line continuation character.
212 |  |
          */
213 |  |
         private static final String LINE_CONT = "\\";
214 |  |
215 |  |
         /**
216 |  |
          * Create a new empty INI Configuration.
217 |  |
          */
218 |  |
         public HierarchicalINIConfiguration()
219 |  |
         {
220 | 36 |
             super();
221 | 36 |
         }
222 |  |
223 |  |
         /**
224 |  |
          * Create and load the ini configuration from the given file.
225 |  |
          *
226 |  |
          * @param filename The name pr path of the ini file to load.
227 |  |
          * @throws ConfigurationException If an error occurs while loading the file
228 |  |
          */
229 |  |
         public HierarchicalINIConfiguration(String filename)
230 |  |
                 throws ConfigurationException
231 |  |
         {
232 | 1 |
             super(filename);
233 | 1 |
         }
234 |  |
235 |  |
         /**
236 |  |
          * Create and load the ini configuration from the given file.
237 |  |
          *
238 |  |
          * @param file The ini file to load.
239 |  |
          * @throws ConfigurationException If an error occurs while loading the file
240 |  |
          */
241 |  |
         public HierarchicalINIConfiguration(File file)
242 |  |
                 throws ConfigurationException
243 |  |
         {
244 | 1 |
             super(file);
245 | 1 |
         }
246 |  |
247 |  |
         /**
248 |  |
          * Create and load the ini configuration from the given url.
249 |  |
          *
250 |  |
          * @param url The url of the ini file to load.
251 |  |
          * @throws ConfigurationException If an error occurs while loading the file
252 |  |
          */
253 |  |
         public HierarchicalINIConfiguration(URL url) throws ConfigurationException
254 |  |
         {
255 | 1 |
             super(url);
256 | 1 |
         }
257 |  |
258 |  |
         /**
259 |  |
          * Save the configuration to the specified writer.
260 |  |
          *
261 |  |
          * @param writer - The writer to save the configuration to.
262 |  |
          * @throws ConfigurationException If an error occurs while writing the
263 |  |
          *         configuration
264 |  |
          */
265 |  |
         public void save(Writer writer) throws ConfigurationException
266 |  |
         {
267 | 3 |
             PrintWriter out = new PrintWriter(writer);
268 | 3 |
             Iterator it = getSections().iterator();
269 | 11 |
             while (it.hasNext())
270 |  |
             {
271 | 8 |
                 String section = (String) it.next();
272 | 8 |
                 if (section != null)
273 |  |
                 {
274 | 7 |
                     out.print("[");
275 | 7 |
                     out.print(section);
276 | 7 |
                     out.print("]");
277 | 7 |
                     out.println();
278 |  |
                 }
279 |  |
280 | 8 |
                 Configuration subset = getSection(section);
281 | 8 |
                 Iterator keys = subset.getKeys();
282 | 24 |
                 while (keys.hasNext())
283 |  |
                 {
284 | 16 |
                     String key = (String) keys.next();
285 | 16 |
                     Object value = subset.getProperty(key);
286 | 16 |
                     if (value instanceof Collection)
287 |  |
                     {
288 | 2 |
                         Iterator values = ((Collection) value).iterator();
289 | 6 |
                         while (values.hasNext())
290 |  |
                         {
291 | 4 |
                             value = (Object) values.next();
292 | 4 |
                             out.print(key);
293 | 4 |
                             out.print(" = ");
294 | 4 |
                             out.print(formatValue(value.toString()));
295 | 4 |
                             out.println();
296 |  |
                         }
297 |  |
                     }
298 |  |
                     else
299 |  |
                     {
300 | 14 |
                         out.print(key);
301 | 14 |
                         out.print(" = ");
302 | 14 |
                         out.print(formatValue(value.toString()));
303 | 14 |
                         out.println();
304 |  |
                     }
305 |  |
                 }
306 |  |
307 | 8 |
                 out.println();
308 |  |
             }
309 |  |
310 | 3 |
             out.flush();
311 | 3 |
         }
312 |  |
313 |  |
         /**
314 |  |
          * Load the configuration from the given reader. Note that the
315 |  |
          * <code>clear</code> method is not called so the configuration read in will
316 |  |
          * be merged with the current configuration.
317 |  |
          *
318 |  |
          * @param reader The reader to read the configuration from.
319 |  |
          * @throws ConfigurationException If an error occurs while reading the
320 |  |
          *         configuration
321 |  |
          */
322 |  |
         public void load(Reader reader) throws ConfigurationException
323 |  |
         {
324 |  |
             try
325 |  |
             {
326 | 34 |
                 BufferedReader bufferedReader = new BufferedReader(reader);
327 | 34 |
                 ConfigurationNode sectionNode = getRootNode();
328 |  |
329 | 34 |
                 String line = bufferedReader.readLine();
330 | 388 |
                 while (line != null)
331 |  |
                 {
332 | 354 |
                     line = line.trim();
333 | 354 |
                     if (!isCommentLine(line))
334 |  |
                     {
335 | 298 |
                         if (isSectionLine(line))
336 |  |
                         {
337 | 63 |
                             String section = line.substring(1, line.length() - 1);
338 | 63 |
                             sectionNode = getSectionNode(section);
339 |  |
                         }
340 |  |
341 |  |
                         else
342 |  |
                         {
343 | 235 |
                             String key = "";
344 | 235 |
                             String value = "";
345 | 235 |
                             int index = line.indexOf("=");
346 | 235 |
                             if (index >= 0)
347 |  |
                             {
348 | 227 |
                                 key = line.substring(0, index);
349 | 227 |
                                 value = parseValue(line.substring(index + 1), bufferedReader);
350 |  |
                             }
351 |  |
                             else
352 |  |
                             {
353 | 8 |
                                 index = line.indexOf(":");
354 | 8 |
                                 if (index >= 0)
355 |  |
                                 {
356 | 8 |
                                     key = line.substring(0, index);
357 | 8 |
                                     value = parseValue(line.substring(index + 1), bufferedReader);
358 |  |
                                 }
359 |  |
                                 else
360 |  |
                                 {
361 | 0 |
                                     key = line;
362 |  |
                                 }
363 |  |
                             }
364 | 235 |
                             key = key.trim();
365 | 235 |
                             if (key.length() < 1)
366 |  |
                             {
367 |  |
                                 // use space for properties with no key
368 | 1 |
                                 key = " ";
369 |  |
                             }
370 | 235 |
                             ConfigurationNode node = createNode(key);
371 | 235 |
                             node.setValue(value);
372 | 235 |
                             sectionNode.addChild(node);
373 |  |
                         }
374 |  |
                     }
375 |  |
376 | 354 |
                     line = bufferedReader.readLine();
377 |  |
                 }
378 |  |
             }
379 | 0 |
             catch (IOException e)
380 |  |
             {
381 | 0 |
                 throw new ConfigurationException(
382 |  |
                         "Unable to load the configuration", e);
383 | 34 |
             }
384 | 34 |
         }
385 |  |
386 |  |
         /**
387 |  |
          * Parse the value to remove the quotes and ignoring the comment. Example:
388 |  |
          *
389 |  |
          * <pre>
390 |  |
          * &quot;value&quot; ; comment -&gt; value
391 |  |
          * </pre>
392 |  |
          *
393 |  |
          * <pre>
394 |  |
          * 'value' ; comment -&gt; value
395 |  |
          * </pre>
396 |  |
          *
397 |  |
          * @param val the value to be parsed
398 |  |
          * @param reader the reader (needed if multiple lines have to be read)
399 |  |
          * @throws IOException if an IO error occurs
400 |  |
          */
401 |  |
         private static String parseValue(String val, BufferedReader reader) throws IOException
402 |  |
         {
403 | 235 |
             StringBuffer propertyValue = new StringBuffer();
404 |  |
             boolean lineContinues;
405 | 235 |
             String value = val.trim();
406 |  |
407 |  |
             do
408 |  |
             {
409 | 284 |
                 boolean quoted = value.startsWith("\"") || value.startsWith("'");
410 | 284 |
                 boolean stop = false;
411 | 284 |
                 boolean escape = false;
412 |  |
413 | 284 |
                 char quote = quoted ? value.charAt(0) : 0;
414 |  |
415 | 284 |
                 int i = quoted ? 1 : 0;
416 |  |
417 | 284 |
                 StringBuffer result = new StringBuffer();
418 | 2267 |
                 while (i < value.length() && !stop)
419 |  |
                 {
420 | 1983 |
                     char c = value.charAt(i);
421 |  |
422 | 1983 |
                     if (quoted)
423 |  |
                     {
424 | 757 |
                         if ('\\' == c && !escape)
425 |  |
                         {
426 | 45 |
                             escape = true;
427 |  |
                         }
428 | 712 |
                         else if (!escape && quote == c)
429 |  |
                         {
430 | 62 |
                             stop = true;
431 |  |
                         }
432 | 650 |
                         else if (escape && quote == c)
433 |  |
                         {
434 | 36 |
                             escape = false;
435 | 36 |
                             result.append(c);
436 |  |
                         }
437 |  |
                         else
438 |  |
                         {
439 | 614 |
                             if (escape)
440 |  |
                             {
441 | 9 |
                                 escape = false;
442 | 9 |
                                 result.append('\\');
443 |  |
                             }
444 |  |
445 | 614 |
                             result.append(c);
446 |  |
                         }
447 |  |
                     }
448 |  |
                     else
449 |  |
                     {
450 | 1226 |
                         if (!isCommentChar(c))
451 |  |
                         {
452 | 1210 |
                             result.append(c);
453 |  |
                         }
454 |  |
                         else
455 |  |
                         {
456 | 16 |
                             stop = true;
457 |  |
                         }
458 |  |
                     }
459 |  |
460 | 1983 |
                     i++;
461 |  |
                 }
462 |  |
463 | 284 |
                 String v = result.toString();
464 | 284 |
                 if (!quoted)
465 |  |
                 {
466 | 222 |
                     v = v.trim();
467 | 222 |
                     lineContinues = lineContinues(v);
468 | 222 |
                     if (lineContinues)
469 |  |
                     {
470 |  |
                         // remove trailing "\"
471 | 42 |
                         v = v.substring(0, v.length() - 1).trim();
472 |  |
                     }
473 |  |
                 }
474 |  |
                 else
475 |  |
                 {
476 | 62 |
                     lineContinues = lineContinues(value, i);
477 |  |
                 }
478 | 284 |
                 propertyValue.append(v);
479 |  |
480 | 284 |
                 if (lineContinues)
481 |  |
                 {
482 | 56 |
                     propertyValue.append(LINE_SEPARATOR);
483 | 56 |
                     value = reader.readLine();
484 |  |
                 }
485 | 284 |
             } while (lineContinues && value != null);
486 |  |
487 | 235 |
             return propertyValue.toString();
488 |  |
         }
489 |  |
490 |  |
         /**
491 |  |
          * Tests whether the specified string contains a line continuation marker.
492 |  |
          *
493 |  |
          * @param line the string to check
494 |  |
          * @return a flag whether this line continues
495 |  |
          */
496 |  |
         private static boolean lineContinues(String line)
497 |  |
         {
498 | 284 |
             String s = line.trim();
499 | 284 |
             return s.equals(LINE_CONT)
500 |  |
                     || (s.length() > 2 && s.endsWith(LINE_CONT) && Character
501 |  |
                             .isWhitespace(s.charAt(s.length() - 2)));
502 |  |
         }
503 |  |
504 |  |
         /**
505 |  |
          * Tests whether the specified string contains a line continuation marker
506 |  |
          * after the specified position. This method parses the string to remove a
507 |  |
          * comment that might be present. Then it checks whether a line continuation
508 |  |
          * marker can be found at the end.
509 |  |
          *
510 |  |
          * @param line the line to check
511 |  |
          * @param pos the start position
512 |  |
          * @return a flag whether this line continues
513 |  |
          */
514 |  |
         private static boolean lineContinues(String line, int pos)
515 |  |
         {
516 |  |
             String s;
517 |  |
518 | 62 |
             if (pos >= line.length())
519 |  |
             {
520 | 29 |
                 s = line;
521 |  |
             }
522 |  |
             else
523 |  |
             {
524 | 33 |
                 int end = pos;
525 | 87 |
                 while (end < line.length() && !isCommentChar(line.charAt(end)))
526 |  |
                 {
527 | 54 |
                     end++;
528 |  |
                 }
529 | 33 |
                 s = line.substring(pos, end);
530 |  |
             }
531 |  |
532 | 62 |
             return lineContinues(s);
533 |  |
         }
534 |  |
535 |  |
         /**
536 |  |
          * Tests whether the specified character is a comment character.
537 |  |
          *
538 |  |
          * @param c the character
539 |  |
          * @return a flag whether this character starts a comment
540 |  |
          */
541 |  |
         private static boolean isCommentChar(char c)
542 |  |
         {
543 | 1306 |
             return COMMENT_CHARS.indexOf(c) >= 0;
544 |  |
         }
545 |  |
546 |  |
         /**
547 |  |
          * Add quotes around the specified value if it contains a comment character.
548 |  |
          */
549 |  |
         private String formatValue(String value)
550 |  |
         {
551 | 18 |
             boolean quoted = false;
552 |  |
553 | 54 |
             for (int i = 0; i < COMMENT_CHARS.length() && !quoted; i++)
554 |  |
             {
555 | 36 |
                 char c = COMMENT_CHARS.charAt(i);
556 | 36 |
                 if (value.indexOf(c) != -1)
557 |  |
                 {
558 | 1 |
                     quoted = true;
559 |  |
                 }
560 |  |
             }
561 |  |
562 | 18 |
             if (quoted)
563 |  |
             {
564 | 1 |
                 return '"' + StringUtils.replace(value, "\"", "\\\"") + '"';
565 |  |
             }
566 |  |
             else
567 |  |
             {
568 | 17 |
                 return value;
569 |  |
             }
570 |  |
         }
571 |  |
572 |  |
         /**
573 |  |
          * Determine if the given line is a comment line.
574 |  |
          *
575 |  |
          * @param line The line to check.
576 |  |
          * @return true if the line is empty or starts with one of the comment
577 |  |
          *         characters
578 |  |
          */
579 |  |
         protected boolean isCommentLine(String line)
580 |  |
         {
581 | 358 |
             if (line == null)
582 |  |
             {
583 | 1 |
                 return false;
584 |  |
             }
585 |  |
             // blank lines are also treated as comment lines
586 | 357 |
             return line.length() < 1 || COMMENT_CHARS.indexOf(line.charAt(0)) >= 0;
587 |  |
         }
588 |  |
589 |  |
         /**
590 |  |
          * Determine if the given line is a section.
591 |  |
          *
592 |  |
          * @param line The line to check.
593 |  |
          * @return true if the line contains a secion
594 |  |
          */
595 |  |
         protected boolean isSectionLine(String line)
596 |  |
         {
597 | 301 |
             if (line == null)
598 |  |
             {
599 | 1 |
                 return false;
600 |  |
             }
601 | 300 |
             return line.startsWith("[") && line.endsWith("]");
602 |  |
         }
603 |  |
604 |  |
         /**
605 |  |
          * Return a set containing the sections in this ini configuration. Note that
606 |  |
          * changes to this set do not affect the configuration.
607 |  |
          *
608 |  |
          * @return a set containing the sections.
609 |  |
          */
610 |  |
         public Set getSections()
611 |  |
         {
612 | 13 |
             Set sections = new ListOrderedSet();
613 | 13 |
             boolean globalSection = false;
614 |  |
615 | 13 |
             for (Iterator it = getRootNode().getChildren().iterator(); it.hasNext();)
616 |  |
             {
617 | 39 |
                 ConfigurationNode node = (ConfigurationNode) it.next();
618 | 39 |
                 if (isSectionNode(node))
619 |  |
                 {
620 | 35 |
                     if (globalSection)
621 |  |
                     {
622 | 3 |
                         sections.add(null);
623 | 3 |
                         globalSection = false;
624 |  |
                     }
625 | 35 |
                     sections.add(node.getName());
626 |  |
                 }
627 |  |
                 else
628 |  |
                 {
629 | 4 |
                     globalSection = true;
630 |  |
                 }
631 |  |
             }
632 |  |
633 | 13 |
             return sections;
634 |  |
         }
635 |  |
636 |  |
         /**
637 |  |
          * Returns a configuration with the content of the specified section. This
638 |  |
          * provides an easy way of working with a single section only. The way this
639 |  |
          * configuration is structured internally, this method is very similar to
640 |  |
          * calling
641 |  |
          * <code>{@link HierarchicalConfiguration#configurationAt(String)}</code>
642 |  |
          * with the name of the section in question. There are the following
643 |  |
          * differences however:
644 |  |
          * <ul>
645 |  |
          * <li>This method never throws an exception. If the section does not exist,
646 |  |
          * an empty configuration is returned.</li>
647 |  |
          * <li>There is special support for the global section: Passing in
648 |  |
          * <b>null</b> as section name returns a configuration with the content of
649 |  |
          * the global section (which may also be empty).</li>
650 |  |
          * </ul>
651 |  |
          *
652 |  |
          * @param name the name of the section in question; <b>null</b> represents
653 |  |
          *        the global section
654 |  |
          * @return a configuration containing only the properties of the specified
655 |  |
          *         section
656 |  |
          */
657 |  |
         public SubnodeConfiguration getSection(String name)
658 |  |
         {
659 | 13 |
             if (name == null)
660 |  |
             {
661 | 3 |
                 return getGlobalSection();
662 |  |
             }
663 |  |
664 |  |
             else
665 |  |
             {
666 |  |
                 try
667 |  |
                 {
668 | 10 |
                     return configurationAt(name);
669 |  |
                 }
670 | 1 |
                 catch (IllegalArgumentException iex)
671 |  |
                 {
672 |  |
                     // the passed in key does not map to exactly one node
673 |  |
                     // return an empty configuration
674 | 1 |
                     return new SubnodeConfiguration(this,
675 |  |
                             new DefaultConfigurationNode());
676 |  |
                 }
677 |  |
             }
678 |  |
         }
679 |  |
680 |  |
         /**
681 |  |
          * Obtains the node representing the specified section. This method is
682 |  |
          * called while the configuration is loaded. If a node for this section
683 |  |
          * already exists, it is returned. Otherwise a new node is created.
684 |  |
          *
685 |  |
          * @param sectionName the name of the section
686 |  |
          * @return the node for this section
687 |  |
          */
688 |  |
         private ConfigurationNode getSectionNode(String sectionName)
689 |  |
         {
690 | 63 |
             List nodes = getRootNode().getChildren(sectionName);
691 | 63 |
             if (!nodes.isEmpty())
692 |  |
             {
693 | 1 |
                 return (ConfigurationNode) nodes.get(0);
694 |  |
             }
695 |  |
696 | 62 |
             ConfigurationNode node = createNode(sectionName);
697 | 62 |
             markSectionNode(node);
698 | 62 |
             getRootNode().addChild(node);
699 | 62 |
             return node;
700 |  |
         }
701 |  |
702 |  |
         /**
703 |  |
          * Creates a sub configuration for the global section of the represented INI
704 |  |
          * configuration.
705 |  |
          *
706 |  |
          * @return the sub configuration for the global section
707 |  |
          */
708 |  |
         private SubnodeConfiguration getGlobalSection()
709 |  |
         {
710 | 3 |
             ViewNode parent = new ViewNode();
711 |  |
712 | 3 |
             for (Iterator it = getRootNode().getChildren().iterator(); it.hasNext();)
713 |  |
             {
714 | 11 |
                 ConfigurationNode node = (ConfigurationNode) it.next();
715 | 11 |
                 if (!isSectionNode(node))
716 |  |
                 {
717 | 2 |
                     parent.addChild(node);
718 |  |
                 }
719 |  |
             }
720 |  |
721 | 3 |
             return createSubnodeConfiguration(parent);
722 |  |
         }
723 |  |
724 |  |
         /**
725 |  |
          * Marks a configuration node as a section node. This means that this node
726 |  |
          * represents a section header. This implementation uses the node's
727 |  |
          * reference property to store a flag.
728 |  |
          *
729 |  |
          * @param node the node to be marked
730 |  |
          */
731 |  |
         private static void markSectionNode(ConfigurationNode node)
732 |  |
         {
733 | 62 |
             node.setReference(Boolean.TRUE);
734 | 62 |
         }
735 |  |
736 |  |
         /**
737 |  |
          * Checks whether the specified configuration node represents a section.
738 |  |
          *
739 |  |
          * @param node the node in question
740 |  |
          * @return a flag whether this node represents a section
741 |  |
          */
742 |  |
         private static boolean isSectionNode(ConfigurationNode node)
743 |  |
         {
744 | 50 |
             return node.getReference() != null || node.getChildrenCount() > 0;
745 |  |
         }
746 |  |
     }