Coverage Report - org.apache.commons.configuration.INIConfiguration
 
Classes in this File Line Coverage Branch Coverage Complexity
INIConfiguration
92%
103/112
100%
29/29
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  
 }