    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *     http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017    package org.apache.commons.configuration;
    018
    019    import java.io.BufferedReader;
    020    import java.io.File;
    021    import java.io.IOException;
    022    import java.io.PrintWriter;
    023    import java.io.Reader;
    024    import java.io.Writer;
    025    import java.net.URL;
    026    import java.util.Collection;
    027    import java.util.Iterator;
    028    import java.util.List;
    029    import java.util.Set;
    030
    031    import org.apache.commons.collections.set.ListOrderedSet;
    032    import org.apache.commons.configuration.tree.ConfigurationNode;
    033    import org.apache.commons.configuration.tree.ViewNode;
    034    import org.apache.commons.lang.StringUtils;
    035
    036    /**
    037     * <p>
    038     * A specialized hierarchical configuration implementation for parsing ini
    039     * files.
    040     * </p>
    041     * <p>
    042     * An initialization or ini file is a configuration file typically found on
    043     * Microsoft's Windows operating system and contains data for Windows based
    044     * applications.
    045     * </p>
    046     * <p>
    047     * Although popularized by Windows, ini files can be used on any system or
    048     * platform due to the fact that they are merely text files that can easily be
    049     * parsed and modified by both humans and computers.
    050     * </p>
    051     * <p>
    052     * A typcial ini file could look something like:
    053     * </p>
    054     * <code>
    055     * [section1]<br>
    056     * ; this is a comment!<br>
    057     * var1 = foo<br>
    058     * var2 = bar<br>
    059     * <br>
    060     * [section2]<br>
    061     * var1 = doo<br>
    062     * </code>
    063     * <p>
    064     * The format of ini files is fairly straight forward and is composed of three
    065     * components:<br>
    066     * <ul>
    067     * <li><b>Sections:</b> Ini files are split into sections, each section starting
    068     * with a section declaration. A section declaration starts with a '[' and ends
    069     * with a ']'. Sections occur on one line only.</li>
    070     * <li><b>Parameters:</b> Items in a section are known as parameters. Parameters
    071     * have a typical <code>key = value</code> format.</li>
    072     * <li><b>Comments:</b> Lines starting with a ';' are assumed to be comments.</li>
    073     * </ul>
    074     * </p>
    075     * <p>
    076     * There are various implementations of the ini file format by various vendors
    077     * which has caused a number of differences to appear. As far as possible this
    078     * configuration tries to be lenient and support most of the differences.
    079     * </p>
    080     * <p>
    081     * Some of the differences supported are as follows:
    082     * <ul>
    083     * <li><b>Comments:</b> The '#' character is also accepted as a comment
    084     * signifier.</li>
    085     * <li><b>Key value separtor:</b> The ':' character is also accepted in place of
    086     * '=' to separate keys and values in parameters, for example
    087     * <code>var1 : foo</code>.</li>
    088     * <li><b>Duplicate sections:</b> Typically duplicate sections are not allowed,
    089     * this configuration does however support this feature. In the event of a duplicate
    090     * section, the two section's values are merged so that there is only a single
    091     * section. <strong>Note</strong>: This also affects the internal data of the
    092     * configuration. If it is saved, only a single section is written!</li>
    093     * <li><b>Duplicate parameters:</b> Typically duplicate parameters are only
    094     * allowed if they are in two different sections, thus they are local to
    095     * sections; this configuration simply merges duplicates; if a section has a
    096     * duplicate parameter the values are then added to the key as a list.</li>
    097     * </ul>
    098     * </p>
    099     * <p>
    100     * Global parameters are also allowed; any parameters declared before a section
    101     * is declared are added to a global section. It is important to note that this
    102     * global section does not have a name.
    103     * </p>
    104     * <p>
    105     * In all instances, a parameter's key is prepended with its section name and a
    106     * '.' (period). Thus a parameter named "var1" in "section1" will have the key
    107     * <code>section1.var1</code> in this configuration. (This is the default
    108     * behavior. Because this is a hierarchical configuration you can change this by
    109     * setting a different {@link org.apache.commons.configuration.tree.ExpressionEngine}.)
    110     * </p>
    111     * <p>
    112     * <h3>Implementation Details:</h3> Consider the following ini file:<br>
    113     * <code>
    114     *  default = ok<br>
    115     *  <br>
    116     *  [section1]<br>
    117     *  var1 = foo<br>
    118     *  var2 = doodle<br>
    119     *   <br>
    120     *  [section2]<br>
    121     *  ; a comment<br>
    122     *  var1 = baz<br>
    123     *  var2 = shoodle<br>
    124     *  bad =<br>
    125     *  = worse<br>
    126     *  <br>
    127     *  [section3]<br>
    128     *  # another comment<br>
    129     *  var1 : foo<br>
    130     *  var2 : bar<br>
    131     *  var5 : test1<br>
    132     *  <br>
    133     *  [section3]<br>
    134     *  var3 = foo<br>
    135     *  var4 = bar<br>
    136     *  var5 = test2<br>
    137     *  <br>
    138     *  [sectionSeparators]<br>
    139     *  passwd : abc=def<br>
    140     *  a:b = "value"
    141     *  </code>
    142     * </p>
    143     * <p>
    144     * This ini file will be parsed without error. Note:
    145     * <ul>
    146     * <li>The parameter named "default" is added to the global section, it's value
    147     * is accessed simply using <code>getProperty("default")</code>.</li>
    148     * <li>Section 1's parameters can be accessed using
    149     * <code>getProperty("section1.var1")</code>.</li>
    150     * <li>The parameter named "bad" simply adds the parameter with an empty value.</li>
    151     * <li>The empty key with value "= worse" is added using a key consisting of a
    152     * single space character. This key is still added to section 2 and the value
    153     * can be accessed using <code>getProperty("section2. ")</code>, notice the
    154     * period '.' and the space following the section name.</li>
    155     * <li>Section three uses both '=' and ':' to separate keys and values.</li>
    156     * <li>Section 3 has a duplicate key named "var5". The value for this key is
    157     * [test1, test2], and is represented as a List.</li>
    158     * <li>The section called <em>sectionSeparators</em> demonstrates how the
    159     * configuration deals with multiple occurrences of separator characters. Per
    160     * default the first separator character in a line is detected and used to
    161     * split the key from the value. Therefore the first property definition in this
    162     * section has the key <code>passwd</code> and the value <code>abc=def</code>.
    163     * This default behavior can be changed by using quotes. If there is a separator
    164     * character before the first quote character (ignoring whitespace), this
    165     * character is used as separator. Thus the second property definition in the
    166     * section has the key <code>a:b</code> and the value <code>value</code>.</li>
    167     * </ul>
    168     * </p>
    169     * <p>
    170     * Internally, this configuration maps the content of the represented ini file
    171     * to its node structure in the following way:
    172     * <ul>
    173     * <li>Sections are represented by direct child nodes of the root node.</li>
    174     * <li>For the content of a section, corresponding nodes are created as children
    175     * of the section node.</li>
    176     * </ul>
    177     * This explains how the keys for the properties can be constructed. You can
    178     * also use other methods of {@link HierarchicalConfiguration} for querying or
    179     * manipulating the hierarchy of configuration nodes, for instance the
    180     * <code>configurationAt()</code> method for obtaining the data of a specific
    181     * section. However, be careful that the storage scheme described above is not
    182     * violated (e.g. by adding multiple levels of nodes or inserting duplicate
    183     * section nodes). Otherwise, the special methods for ini configurations may not
    184     * work correctly!
    185     * </p>
    186     * <p>
    187     * The set of sections in this configuration can be retrieved using the
    188     * <code>getSections()</code> method. For obtaining a
    189     * <code>SubnodeConfiguration</code> with the content of a specific section the
    190     * <code>getSection()</code> method can be used.
    191     * </p>
    192     * <p>
    193     * <em>Note:</em> Configuration objects of this type can be read concurrently by
    194     * multiple threads. However if one of these threads modifies the object,
    195     * synchronization has to be performed manually.
    196     * </p>
    197     *
    198     * @author <a
    199     *         href="http://commons.apache.org/configuration/team-list.html">Commons
    200     *         Configuration team</a>
    201     * @version $Id: HierarchicalINIConfiguration.java 1158123 2011-08-16 06:22:16Z oheger $
    202     * @since 1.6
    203     */
    204    public class HierarchicalINIConfiguration extends
    205            AbstractHierarchicalFileConfiguration
    206    {
    207        /**
    208         * The characters that signal the start of a comment line.
    209         */
    210        protected static final String COMMENT_CHARS = "#;";
    211
    212        /**
    213         * The characters used to separate keys from values.
    214         */
    215        protected static final String SEPARATOR_CHARS = "=:";
    216
    217        /**
    218         * The serial version UID.
    219         */
    220        private static final long serialVersionUID = 2548006161386850670L;
    221
    222        /**
    223         * Constant for the line separator.
    224         */
    225        private static final String LINE_SEPARATOR = System.getProperty("line.separator");
    226
    227        /**
    228         * The characters used for quoting values.
    229         */
    230        private static final String QUOTE_CHARACTERS = "\"'";
    231
    232        /**
    233         * The line continuation character.
    234         */
    235        private static final String LINE_CONT = "\\";
    236
    237        /**
    238         * Create a new empty INI Configuration.
    239         */
    240        public HierarchicalINIConfiguration()
    241        {
    242            super();
    243        }
    244
    245        /**
    246         * Create and load the ini configuration from the given file.
    247         *
    248         * @param filename The name pr path of the ini file to load.
    249         * @throws ConfigurationException If an error occurs while loading the file
    250         */
    251        public HierarchicalINIConfiguration(String filename)
    252                throws ConfigurationException
    253        {
    254            super(filename);
    255        }
    256
    257        /**
    258         * Create and load the ini configuration from the given file.
    259         *
    260         * @param file The ini file to load.
    261         * @throws ConfigurationException If an error occurs while loading the file
    262         */
    263        public HierarchicalINIConfiguration(File file)
    264                throws ConfigurationException
    265        {
    266            super(file);
    267        }
    268
    269        /**
    270         * Create and load the ini configuration from the given url.
    271         *
    272         * @param url The url of the ini file to load.
    273         * @throws ConfigurationException If an error occurs while loading the file
    274         */
    275        public HierarchicalINIConfiguration(URL url) throws ConfigurationException
    276        {
    277            super(url);
    278        }
    279
    280        /**
    281         * Save the configuration to the specified writer.
    282         *
    283         * @param writer - The writer to save the configuration to.
    284         * @throws ConfigurationException If an error occurs while writing the
    285         *         configuration
    286         */
    287        public void save(Writer writer) throws ConfigurationException
    288        {
    289            PrintWriter out = new PrintWriter(writer);
    290            Iterator it = getSections().iterator();
    291            while (it.hasNext())
    292            {
    293                String section = (String) it.next();
    294                Configuration subset;
    295                if (section != null)
    296                {
    297                    out.print("[");
    298                    out.print(section);
    299                    out.print("]");
    300                    out.println();
    301                    subset = createSubnodeConfiguration(getSectionNode(section));
    302                }
    303                else
    304                {
    305                    subset = getSection(null);
    306                }
    307
    308                Iterator keys = subset.getKeys();
    309                while (keys.hasNext())
    310                {
    311                    String key = (String) keys.next();
    312                    Object value = subset.getProperty(key);
    313                    if (value instanceof Collection)
    314                    {
    315                        Iterator values = ((Collection) value).iterator();
    316                        while (values.hasNext())
    317                        {
    318                            value = (Object) values.next();
    319                            out.print(key);
    320                            out.print(" = ");
    321                            out.print(formatValue(value.toString()));
    322                            out.println();
    323                        }
    324                    }
    325                    else
    326                    {
    327                        out.print(key);
    328                        out.print(" = ");
    329                        out.print(formatValue(value.toString()));
    330                        out.println();
    331                    }
    332                }
    333
    334                out.println();
    335            }
    336
    337            out.flush();
    338        }
    339
    340        /**
    341         * Load the configuration from the given reader. Note that the
    342         * <code>clear</code> method is not called so the configuration read in will
    343         * be merged with the current configuration.
    344         *
    345         * @param reader The reader to read the configuration from.
    346         * @throws ConfigurationException If an error occurs while reading the
    347         *         configuration
    348         */
    349        public void load(Reader reader) throws ConfigurationException
    350        {
    351            try
    352            {
    353                BufferedReader bufferedReader = new BufferedReader(reader);
    354                ConfigurationNode sectionNode = getRootNode();
    355
    356                String line = bufferedReader.readLine();
    357                while (line != null)
    358                {
    359                    line = line.trim();
    360                    if (!isCommentLine(line))
    361                    {
    362                        if (isSectionLine(line))
    363                        {
    364                            String section = line.substring(1, line.length() - 1);
    365                            sectionNode = getSectionNode(section);
    366                        }
    367
    368                        else
    369                        {
    370                            String key = "";
    371                            String value = "";
    372                            int index = findSeparator(line);
    373                            if (index >= 0)
    374                            {
    375                                key = line.substring(0, index);
    376                                value = parseValue(line.substring(index + 1), bufferedReader);
    377                            }
    378                            else
    379                            {
    380                                key = line;
    381                            }
    382                            key = key.trim();
    383                            if (key.length() < 1)
    384                            {
    385                                // use space for properties with no key
    386                                key = " ";
    387                            }
    388                            ConfigurationNode node = createNode(key);
    389                            node.setValue(value);
    390                            sectionNode.addChild(node);
    391                        }
    392                    }
    393
    394                    line = bufferedReader.readLine();
    395                }
    396            }
    397            catch (IOException e)
    398            {
    399                throw new ConfigurationException(
    400                        "Unable to load the configuration", e);
    401            }
    402        }
    403
    404        /**
    405         * Parse the value to remove the quotes and ignoring the comment. Example:
    406         *
    407         * <pre>
    408         * &quot;value&quot; ; comment -&gt; value
    409         * </pre>
    410         *
    411         * <pre>
    412         * 'value' ; comment -&gt; value
    413         * </pre>
    414         * Note that a comment character is only recognized if there is at least one
    415         * whitespace character before it. So it can appear in the property value,
    416         * e.g.:
    417         * <pre>
    418         * C:\\Windows;C:\\Windows\\system32
    419         * </pre>
    420         *
    421         * @param val the value to be parsed
    422         * @param reader the reader (needed if multiple lines have to be read)
    423         * @throws IOException if an IO error occurs
    424         */
    425        private static String parseValue(String val, BufferedReader reader) throws IOException
    426        {
    427            StringBuffer propertyValue = new StringBuffer();
    428            boolean lineContinues;
    429            String value = val.trim();
    430
    431            do
    432            {
    433                boolean quoted = value.startsWith("\"") || value.startsWith("'");
    434                boolean stop = false;
    435                boolean escape = false;
    436
    437                char quote = quoted ? value.charAt(0) : 0;
    438
    439                int i = quoted ? 1 : 0;
    440
    441                StringBuffer result = new StringBuffer();
    442                char lastChar = 0;
    443                while (i < value.length() && !stop)
    444                {
    445                    char c = value.charAt(i);
    446
    447                    if (quoted)
    448                    {
    449                        if ('\\' == c && !escape)
    450                        {
    451                            escape = true;
    452                        }
    453                        else if (!escape && quote == c)
    454                        {
    455                            stop = true;
    456                        }
    457                        else if (escape && quote == c)
    458                        {
    459                            escape = false;
    460                            result.append(c);
    461                        }
    462                        else
    463                        {
    464                            if (escape)
    465                            {
    466                                escape = false;
    467                                result.append('\\');
    468                            }
    469
    470                            result.append(c);
    471                        }
    472                    }
    473                    else
    474                    {
    475                        if (isCommentChar(c) && Character.isWhitespace(lastChar))
    476                        {
    477                            stop = true;
    478                        }
    479                        else
    480                        {
    481                            result.append(c);
    482                        }
    483                    }
    484
    485                    i++;
    486                    lastChar = c;
    487                }
    488
    489                String v = result.toString();
    490                if (!quoted)
    491                {
    492                    v = v.trim();
    493                    lineContinues = lineContinues(v);
    494                    if (lineContinues)
    495                    {
    496                        // remove trailing "\"
    497                        v = v.substring(0, v.length() - 1).trim();
    498                    }
    499                }
    500                else
    501                {
    502                    lineContinues = lineContinues(value, i);
    503                }
    504                propertyValue.append(v);
    505
    506                if (lineContinues)
    507                {
    508                    propertyValue.append(LINE_SEPARATOR);
    509                    value = reader.readLine();
    510                }
    511            } while (lineContinues && value != null);
    512
    513            return propertyValue.toString();
    514        }
    515
    516        /**
    517         * Tests whether the specified string contains a line continuation marker.
    518         *
    519         * @param line the string to check
    520         * @return a flag whether this line continues
    521         */
    522        private static boolean lineContinues(String line)
    523        {
    524            String s = line.trim();
    525            return s.equals(LINE_CONT)
    526                    || (s.length() > 2 && s.endsWith(LINE_CONT) && Character
    527                            .isWhitespace(s.charAt(s.length() - 2)));
    528        }
    529
    530        /**
    531         * Tests whether the specified string contains a line continuation marker
    532         * after the specified position. This method parses the string to remove a
    533         * comment that might be present. Then it checks whether a line continuation
    534         * marker can be found at the end.
    535         *
    536         * @param line the line to check
    537         * @param pos the start position
    538         * @return a flag whether this line continues
    539         */
    540        private static boolean lineContinues(String line, int pos)
    541        {
    542            String s;
    543
    544            if (pos >= line.length())
    545            {
    546                s = line;
    547            }
    548            else
    549            {
    550                int end = pos;
    551                while (end < line.length() && !isCommentChar(line.charAt(end)))
    552                {
    553                    end++;
    554                }
    555                s = line.substring(pos, end);
    556            }
    557
    558            return lineContinues(s);
    559        }
    560
    561        /**
    562         * Tests whether the specified character is a comment character.
    563         *
    564         * @param c the character
    565         * @return a flag whether this character starts a comment
    566         */
    567        private static boolean isCommentChar(char c)
    568        {
    569            return COMMENT_CHARS.indexOf(c) >= 0;
    570        }
    571
    572        /**
    573         * Tries to find the index of the separator character in the given string.
    574         * This method checks for the presence of separator characters in the given
    575         * string. If multiple characters are found, the first one is assumed to be
    576         * the correct separator. If there are quoting characters, they are taken
    577         * into account, too.
    578         *
    579         * @param line the line to be checked
    580         * @return the index of the separator character or -1 if none is found
    581         */
    582        private static int findSeparator(String line)
    583        {
    584            int index =
    585                    findSeparatorBeforeQuote(line,
    586                            findFirstOccurrence(line, QUOTE_CHARACTERS));
    587            if (index < 0)
    588            {
    589                index = findFirstOccurrence(line, SEPARATOR_CHARS);
    590            }
    591            return index;
    592        }
    593
    594        /**
    595         * Checks for the occurrence of the specified separators in the given line.
    596         * The index of the first separator is returned.
    597         *
    598         * @param line the line to be investigated
    599         * @param separators a string with the separator characters to look for
    600         * @return the lowest index of a separator character or -1 if no separator
    601         *         is found
    602         */
    603        private static int findFirstOccurrence(String line, String separators)
    604        {
    605            int index = -1;
    606
    607            for (int i = 0; i < separators.length(); i++)
    608            {
    609                char sep = separators.charAt(i);
    610                int pos = line.indexOf(sep);
    611                if (pos >= 0)
    612                {
    613                    if (index < 0 || pos < index)
    614                    {
    615                        index = pos;
    616                    }
    617                }
    618            }
    619
    620            return index;
    621        }
    622
    623        /**
    624         * Searches for a separator character directly before a quoting character.
    625         * If the first non-whitespace character before a quote character is a
    626         * separator, it is considered the "real" separator in this line - even if
    627         * there are other separators before.
    628         *
    629         * @param line the line to be investigated
    630         * @param quoteIndex the index of the quote character
    631         * @return the index of the separator before the quote or &lt; 0 if there is
    632         *         none
    633         */
    634        private static int findSeparatorBeforeQuote(String line, int quoteIndex)
    635        {
    636            int index = quoteIndex - 1;
    637            while (index >= 0 && Character.isWhitespace(line.charAt(index)))
    638            {
    639                index--;
    640            }
    641
    642            if (index >= 0 && SEPARATOR_CHARS.indexOf(line.charAt(index)) < 0)
    643            {
    644                index = -1;
    645            }
    646
    647            return index;
    648        }
    649
    650        /**
    651         * Add quotes around the specified value if it contains a comment character.
    652         */
    653        private String formatValue(String value)
    654        {
    655            boolean quoted = false;
    656
    657            for (int i = 0; i < COMMENT_CHARS.length() && !quoted; i++)
    658            {
    659                char c = COMMENT_CHARS.charAt(i);
    660                if (value.indexOf(c) != -1)
    661                {
    662                    quoted = true;
    663                }
    664            }
    665
    666            if (quoted)
    667            {
    668                return '"' + StringUtils.replace(value, "\"", "\\\"") + '"';
    669            }
    670            else
    671            {
    672                return value;
    673            }
    674        }
    675
    676        /**
    677         * Determine if the given line is a comment line.
    678         *
    679         * @param line The line to check.
    680         * @return true if the line is empty or starts with one of the comment
    681         *         characters
    682         */
    683        protected boolean isCommentLine(String line)
    684        {
    685            if (line == null)
    686            {
    687                return false;
    688            }
    689            // blank lines are also treated as comment lines
    690            return line.length() < 1 || COMMENT_CHARS.indexOf(line.charAt(0)) >= 0;
    691        }
    692
    693        /**
    694         * Determine if the given line is a section.
    695         *
    696         * @param line The line to check.
    697         * @return true if the line contains a secion
    698         */
    699        protected boolean isSectionLine(String line)
    700        {
    701            if (line == null)
    702            {
    703                return false;
    704            }
    705            return line.startsWith("[") && line.endsWith("]");
    706        }
    707
    708        /**
    709         * Return a set containing the sections in this ini configuration. Note that
    710         * changes to this set do not affect the configuration.
    711         *
    712         * @return a set containing the sections.
    713         */
    714        public Set getSections()
    715        {
    716            Set sections = new ListOrderedSet();
    717            boolean globalSection = false;
    718            boolean inSection = false;
    719
    720            for (Iterator it = getRootNode().getChildren().iterator(); it.hasNext();)
    721            {
    722                ConfigurationNode node = (ConfigurationNode) it.next();
    723                if (isSectionNode(node))
    724                {
    725                    inSection = true;
    726                    sections.add(node.getName());
    727                }
    728                else
    729                {
    730                    if (!inSection && !globalSection)
    731                    {
    732                        globalSection = true;
    733                        sections.add(null);
    734                    }
    735                }
    736            }
    737
    738            return sections;
    739        }
    740
    741        /**
    742         * Returns a configuration with the content of the specified section. This
    743         * provides an easy way of working with a single section only. The way this
    744         * configuration is structured internally, this method is very similar to
    745         * calling {@link HierarchicalConfiguration#configurationAt(String)} with
    746         * the name of the section in question. There are the following differences
    747         * however:
    748         * <ul>
    749         * <li>This method never throws an exception. If the section does not exist,
    750         * it is created now. The configuration returned in this case is empty.</li>
    751         * <li>If section is contained multiple times in the configuration, the
    752         * configuration returned by this method is initialized with the first
    753         * occurrence of the section. (This can only happen if
    754         * <code>addProperty()</code> has been used in a way that does not conform
    755         * to the storage scheme used by <code>HierarchicalINIConfiguration</code>.
    756         * If used correctly, there will not be duplicate sections.)</li>
    757         * <li>There is special support for the global section: Passing in
    758         * <b>null</b> as section name returns a configuration with the content of
    759         * the global section (which may also be empty).</li>
    760         * </ul>
    761         *
    762         * @param name the name of the section in question; <b>null</b> represents
    763         *        the global section
    764         * @return a configuration containing only the properties of the specified
    765         *         section
    766         */
    767        public SubnodeConfiguration getSection(String name)
    768        {
    769            if (name == null)
    770            {
    771                return getGlobalSection();
    772            }
    773
    774            else
    775            {
    776                try
    777                {
    778                    return configurationAt(name);
    779                }
    780                catch (IllegalArgumentException iex)
    781                {
    782                    // the passed in key does not map to exactly one node
    783                    // obtain the node for the section, create it on demand
    784                    return new SubnodeConfiguration(this, getSectionNode(name));
    785                }
    786            }
    787        }
    788
    789        /**
    790         * Obtains the node representing the specified section. This method is
    791         * called while the configuration is loaded. If a node for this section
    792         * already exists, it is returned. Otherwise a new node is created.
    793         *
    794         * @param sectionName the name of the section
    795         * @return the node for this section
    796         */
    797        private ConfigurationNode getSectionNode(String sectionName)
    798        {
    799            List nodes = getRootNode().getChildren(sectionName);
    800            if (!nodes.isEmpty())
    801            {
    802                return (ConfigurationNode) nodes.get(0);
    803            }
    804
    805            ConfigurationNode node = createNode(sectionName);
    806            markSectionNode(node);
    807            getRootNode().addChild(node);
    808            return node;
    809        }
    810
    811        /**
    812         * Creates a sub configuration for the global section of the represented INI
    813         * configuration.
    814         *
    815         * @return the sub configuration for the global section
    816         */
    817        private SubnodeConfiguration getGlobalSection()
    818        {
    819            ViewNode parent = new ViewNode();
    820
    821            for (Iterator it = getRootNode().getChildren().iterator(); it.hasNext();)
    822            {
    823                ConfigurationNode node = (ConfigurationNode) it.next();
    824                if (!isSectionNode(node))
    825                {
    826                    synchronized (node)
    827                    {
    828                        parent.addChild(node);
    829                    }
    830                }
    831            }
    832
    833            return createSubnodeConfiguration(parent);
    834        }
    835
    836        /**
    837         * Marks a configuration node as a section node. This means that this node
    838         * represents a section header. This implementation uses the node's
    839         * reference property to store a flag.
    840         *
    841         * @param node the node to be marked
    842         */
    843        private static void markSectionNode(ConfigurationNode node)
    844        {
    845            node.setReference(Boolean.TRUE);
    846        }
    847
    848        /**
    849         * Checks whether the specified configuration node represents a section.
    850         *
    851         * @param node the node in question
    852         * @return a flag whether this node represents a section
    853         */
    854        private static boolean isSectionNode(ConfigurationNode node)
    855        {
    856            return node.getReference() != null || node.getChildrenCount() > 0;
    857        }
    858    }