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