Coverage report

  %line %branch
org.apache.commons.configuration.PropertiesConfiguration
89% 
97% 

 1  
 /*
 2  
  * Copyright 2001-2005 The Apache Software Foundation.
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License")
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *     http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 
 17  
 package org.apache.commons.configuration;
 18  
 
 19  
 import java.io.File;
 20  
 import java.io.FilterWriter;
 21  
 import java.io.IOException;
 22  
 import java.io.LineNumberReader;
 23  
 import java.io.Reader;
 24  
 import java.io.Writer;
 25  
 import java.io.StringReader;
 26  
 import java.io.BufferedReader;
 27  
 import java.net.URL;
 28  
 import java.util.Date;
 29  
 import java.util.Iterator;
 30  
 import java.util.List;
 31  
 
 32  
 import org.apache.commons.lang.StringEscapeUtils;
 33  
 import org.apache.commons.lang.StringUtils;
 34  
 
 35  
 /**
 36  
  * This is the "classic" Properties loader which loads the values from
 37  
  * a single or multiple files (which can be chained with "include =".
 38  
  * All given path references are either absolute or relative to the
 39  
  * file name supplied in the constructor.
 40  
  * <p>
 41  
  * In this class, empty PropertyConfigurations can be built, properties
 42  
  * added and later saved. include statements are (obviously) not supported
 43  
  * if you don't construct a PropertyConfiguration from a file.
 44  
  *
 45  
  * <p>The properties file syntax is explained here:
 46  
  *
 47  
  * <ul>
 48  
  *  <li>
 49  
  *   Each property has the syntax <code>key = value</code>
 50  
  *  </li>
 51  
  *  <li>
 52  
  *   The <i>key</i> may use any character but the equal sign '='.
 53  
  *  </li>
 54  
  *  <li>
 55  
  *   <i>value</i> may be separated on different lines if a backslash
 56  
  *   is placed at the end of the line that continues below.
 57  
  *  </li>
 58  
  *  <li>
 59  
  *   If <i>value</i> is a list of strings, each token is separated
 60  
  *   by a comma ',' by default.
 61  
  *  </li>
 62  
  *  <li>
 63  
  *   Commas in each token are escaped placing a backslash right before
 64  
  *   the comma.
 65  
  *  </li>
 66  
  *  <li>
 67  
  *   If a <i>key</i> is used more than once, the values are appended
 68  
  *   like if they were on the same line separated with commas.
 69  
  *  </li>
 70  
  *  <li>
 71  
  *   Blank lines and lines starting with character '#' are skipped.
 72  
  *  </li>
 73  
  *  <li>
 74  
  *   If a property is named "include" (or whatever is defined by
 75  
  *   setInclude() and getInclude() and the value of that property is
 76  
  *   the full path to a file on disk, that file will be included into
 77  
  *   the configuration. You can also pull in files relative to the parent
 78  
  *   configuration file. So if you have something like the following:
 79  
  *
 80  
  *   include = additional.properties
 81  
  *
 82  
  *   Then "additional.properties" is expected to be in the same
 83  
  *   directory as the parent configuration file.
 84  
  *
 85  
  *   The properties in the included file are added to the parent configuration,
 86  
  *   they do not replace existing properties with the same key.
 87  
  *
 88  
  *  </li>
 89  
  * </ul>
 90  
  *
 91  
  * <p>Here is an example of a valid extended properties file:
 92  
  *
 93  
  * <p><pre>
 94  
  *      # lines starting with # are comments
 95  
  *
 96  
  *      # This is the simplest property
 97  
  *      key = value
 98  
  *
 99  
  *      # A long property may be separated on multiple lines
 100  
  *      longvalue = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
 101  
  *                  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
 102  
  *
 103  
  *      # This is a property with many tokens
 104  
  *      tokens_on_a_line = first token, second token
 105  
  *
 106  
  *      # This sequence generates exactly the same result
 107  
  *      tokens_on_multiple_lines = first token
 108  
  *      tokens_on_multiple_lines = second token
 109  
  *
 110  
  *      # commas may be escaped in tokens
 111  
  *      commas.excaped = Hi\, what'up?
 112  
  *
 113  
  *      # properties can reference other properties
 114  
  *      base.prop = /base
 115  
  *      first.prop = ${base.prop}/first
 116  
  *      second.prop = ${first.prop}/second
 117  
  * </pre>
 118  
  *
 119  
  * @author <a href="mailto:e.bourg@cross-systems.com">Emmanuel Bourg</a>
 120  
  * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
 121  
  * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
 122  
  * @author <a href="mailto:daveb@miceda-data">Dave Bryson</a>
 123  
  * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
 124  
  * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a>
 125  
  * @author <a href="mailto:kjohnson@transparent.com">Kent Johnson</a>
 126  
  * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
 127  
  * @author <a href="mailto:ipriha@surfeu.fi">Ilkka Priha</a>
 128  
  * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
 129  
  * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
 130  
  * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
 131  
  * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
 132  
  * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
 133  
  * @version $Id: PropertiesConfiguration.java 156237 2005-03-05 10:26:22Z oheger $
 134  
  */
 135  
 public class PropertiesConfiguration extends AbstractFileConfiguration
 136  
 {
 137  
     /**
 138  
      * This is the name of the property that can point to other
 139  
      * properties file for including other properties files.
 140  
      */
 141  322
     static String include = "include";
 142  
 
 143  
     /** Allow file inclusion or not */
 144  5286
     private boolean includesAllowed = true;
 145  
 
 146  
     /** Comment header of the .properties file */
 147  
     private String header;
 148  
 
 149  
     /**
 150  
      * Creates an empty PropertyConfiguration object which can be
 151  
      * used to synthesize a new Properties file by adding values and
 152  
      * then saving(). An object constructed by this C'tor can not be
 153  
      * tickled into loading included files because it cannot supply a
 154  
      * base for relative includes.
 155  
      */
 156  
     public PropertiesConfiguration()
 157  1086
     {
 158  1086
         setIncludesAllowed(false);
 159  1086
     }
 160  
 
 161  
     /**
 162  
      * Creates and loads the extended properties from the specified file.
 163  
      * The specified file can contain "include = " properties which then
 164  
      * are loaded and merged into the properties.
 165  
      *
 166  
      * @param fileName The name of the properties file to load.
 167  
      * @throws ConfigurationException Error while loading the properties file
 168  
      */
 169  
     public PropertiesConfiguration(String fileName) throws ConfigurationException
 170  
     {
 171  4177
         super(fileName);
 172  4154
     }
 173  
 
 174  
     /**
 175  
      * Creates and loads the extended properties from the specified file.
 176  
      * The specified file can contain "include = " properties which then
 177  
      * are loaded and merged into the properties.
 178  
      *
 179  
      * @param file The properties file to load.
 180  
      * @throws ConfigurationException Error while loading the properties file
 181  
      */
 182  
     public PropertiesConfiguration(File file) throws ConfigurationException
 183  
     {
 184  46
         super(file);
 185  46
     }
 186  
 
 187  
     /**
 188  
      * Creates and loads the extended properties from the specified URL.
 189  
      * The specified file can contain "include = " properties which then
 190  
      * are loaded and merged into the properties.
 191  
      *
 192  
      * @param url The location of the properties file to load.
 193  
      * @throws ConfigurationException Error while loading the properties file
 194  
      */
 195  
     public PropertiesConfiguration(URL url) throws ConfigurationException
 196  
     {
 197  0
         super(url);
 198  0
     }
 199  
 
 200  
     /**
 201  
      * Gets the property value for including other properties files.
 202  
      * By default it is "include".
 203  
      *
 204  
      * @return A String.
 205  
      */
 206  
     public static String getInclude()
 207  
     {
 208  303514
         return PropertiesConfiguration.include;
 209  
     }
 210  
 
 211  
     /**
 212  
      * Sets the property value for including other properties files.
 213  
      * By default it is "include".
 214  
      *
 215  
      * @param inc A String.
 216  
      */
 217  
     public static void setInclude(String inc)
 218  
     {
 219  46
         PropertiesConfiguration.include = inc;
 220  46
     }
 221  
 
 222  
     /**
 223  
      * Controls whether additional files can be loaded by the include = <xxx>
 224  
      * statement or not. Base rule is, that objects created by the empty
 225  
      * C'tor can not have included files.
 226  
      *
 227  
      * @param includesAllowed includesAllowed True if Includes are allowed.
 228  
      */
 229  
     protected void setIncludesAllowed(boolean includesAllowed)
 230  
     {
 231  6303
         this.includesAllowed = includesAllowed;
 232  6303
     }
 233  
 
 234  
     /**
 235  
      * Reports the status of file inclusion.
 236  
      *
 237  
      * @return True if include files are loaded.
 238  
      */
 239  
     public boolean getIncludesAllowed()
 240  
     {
 241  3018
         return this.includesAllowed;
 242  
     }
 243  
 
 244  
     /**
 245  
      * Return the comment header.
 246  
      *
 247  
      * @since 1.1
 248  
      */
 249  
     public String getHeader()
 250  
     {
 251  92
         return header;
 252  
     }
 253  
 
 254  
     /**
 255  
      * Set the comment header.
 256  
      *
 257  
      * @since 1.1
 258  
      */
 259  
     public void setHeader(String header)
 260  
     {
 261  92
         this.header = header;
 262  92
     }
 263  
 
 264  
     /**
 265  
      * Load the properties from the given reader.
 266  
      * Note that the <code>clear()</code> method is not called, so
 267  
      * the properties contained in the loaded file will be added to the
 268  
      * actual set of properties.
 269  
      *
 270  
      * @param in An InputStream.
 271  
      *
 272  
      * @throws ConfigurationException
 273  
      */
 274  3078
     public synchronized void load(Reader in) throws ConfigurationException
 275  
     {
 276  4858
         PropertiesReader reader = new PropertiesReader(in);
 277  
 
 278  
         try
 279  
         {
 280  62001
             while (true)
 281  
             {
 282  159693
                 String line = reader.readProperty();
 283  
 
 284  100770
                 if (line == null)
 285  
                 {
 286  4858
                     break; // EOF
 287  58923
                 }
 288  58923
 
 289  92834
                 int equalSign = line.indexOf('=');
 290  151757
                 if (equalSign > 0)
 291  58923
                 {
 292  92834
                     String key = line.substring(0, equalSign).trim();
 293  92834
                     String value = line.substring(equalSign + 1).trim();
 294  
 
 295  
                     // Though some software (e.g. autoconf) may produce
 296  
                     // empty values like foo=\n, emulate the behavior of
 297  
                     // java.util.Properties by setting the value to the
 298  58923
                     // empty string.
 299  
 
 300  92834
                     if (StringUtils.isNotEmpty(getInclude())
 301  1170
                         && key.equalsIgnoreCase(getInclude()))
 302  
                     {
 303  3000
                         if (getIncludesAllowed())
 304  2304
                         {
 305  1820
                             String [] files = StringUtils.split(value, getDelimiter());
 306  4792
                             for (int i = 0; i < files.length; i++)
 307  
                             {
 308  1820
                                 load(ConfigurationUtils.locate(getBasePath(), files[i].trim()));
 309  
                             }
 310  
                         }
 311  
                     }
 312  57753
                     else
 313  
                     {
 314  90986
                         addProperty(key, unescapeJava(value, getDelimiter()));
 315  
                     }
 316  
                 }
 317  
             }
 318  
         }
 319  0
         catch (IOException ioe)
 320  3078
         {
 321  3078
             throw new ConfigurationException("Could not load configuration from input stream.", ioe);
 322  4858
         }
 323  4858
     }
 324  
 
 325  
     /**
 326  
      * Save the configuration to the specified stream.
 327  
      *
 328  
      * @param writer the output stream used to save the configuration
 329  
      */
 330  
     public void save(Writer writer) throws ConfigurationException
 331  
     {
 332  63
         try
 333  
         {
 334  161
             PropertiesWriter out = new PropertiesWriter(writer, getDelimiter());
 335  
 
 336  98
             if (header != null)
 337  
             {
 338  0
                 BufferedReader reader = new BufferedReader(class="keyword">new StringReader(header));
 339  
                 String line;
 340  0
                 while ((line = reader.readLine()) != null)
 341  
                 {
 342  0
                     out.writeComment(line);
 343  
                 }
 344  0
                 out.write("\n");
 345  63
             }
 346  63
 
 347  161
             out.writeComment("written by PropertiesConfiguration");
 348  98
             out.writeComment(new Date().toString());
 349  161
             out.write("\n");
 350  900
 
 351  98
             Iterator keys = getKeys();
 352  2237
             while (keys.hasNext())
 353  837
             {
 354  1302
                 String key = (String) keys.next();
 355  2139
                 Object value = getProperty(key);
 356  
 
 357  1590
                 if (value instanceof List)
 358  
                 {
 359  448
                     out.writeProperty(key, (List) value);
 360  
                 }
 361  549
                 else
 362  
                 {
 363  854
                     out.writeProperty(key, value);
 364  
                 }
 365  63
             }
 366  
 
 367  98
             out.flush();
 368  
         }
 369  0
         catch (IOException e)
 370  63
         {
 371  63
             throw new ConfigurationException(e.getMessage(), e);
 372  98
         }
 373  98
     }
 374  
 
 375  
     /**
 376  
      * Extend the setBasePath method to turn includes
 377  
      * on and off based on the existence of a base path.
 378  
      *
 379  
      * @param basePath The new basePath to set.
 380  
      */
 381  2016
     public void setBasePath(String basePath)
 382  2016
     {
 383  5194
         super.setBasePath(basePath);
 384  3178
         setIncludesAllowed(StringUtils.isNotEmpty(basePath));
 385  3178
     }
 386  
 
 387  
     /**
 388  
      * This class is used to read properties lines.  These lines do
 389  
      * not terminate with new-line chars but rather when there is no
 390  
      * backslash sign a the end of the line.  This is used to
 391  
      * concatenate multiple lines for readability.
 392  
      */
 393  
     public static class PropertiesReader extends LineNumberReader
 394  
     {
 395  
         /**
 396  
          * Constructor.
 397  
          *
 398  
          * @param reader A Reader.
 399  
          */
 400  
         public PropertiesReader(Reader reader)
 401  
         {
 402  
             super(reader);
 403  
         }
 404  
 
 405  
         /**
 406  
          * Read a property. Returns null if Stream is
 407  
          * at EOF. Concatenates lines ending with "\".
 408  
          * Skips lines beginning with "#" and empty lines.
 409  
          *
 410  
          * @return A string containing a property value or null
 411  
          *
 412  
          * @throws IOException
 413  
          */
 414  
         public String readProperty() throws IOException
 415  
         {
 416  
             StringBuffer buffer = new StringBuffer();
 417  
 
 418  
             while (true)
 419  
             {
 420  
                 String line = readLine();
 421  
                 if (line == null)
 422  
                 {
 423  
                     // EOF
 424  
                     return null;
 425  
                 }
 426  
 
 427  
                 line = line.trim();
 428  
 
 429  
                 // skip comments and empty lines
 430  
                 if (StringUtils.isEmpty(line) || (line.charAt(0) == '#'))
 431  
                 {
 432  
                     continue;
 433  
                 }
 434  
 
 435  
                 if (line.endsWith("\\"))
 436  
                 {
 437  
                     line = line.substring(0, line.length() - 1);
 438  
                     buffer.append(line);
 439  
                 }
 440  
                 else
 441  
                 {
 442  
                     buffer.append(line);
 443  
                     break;
 444  
                 }
 445  
             }
 446  
             return buffer.toString();
 447  
         }
 448  
     } // class PropertiesReader
 449  
 
 450  
     /**
 451  
      * This class is used to write properties lines.
 452  
      */
 453  
     public static class PropertiesWriter extends FilterWriter
 454  
     {
 455  
         private char delimiter;
 456  
 
 457  
         /**
 458  
          * Constructor.
 459  
          *
 460  
          * @param writer a Writer object providing the underlying stream
 461  
          */
 462  
         public PropertiesWriter(Writer writer, char delimiter)
 463  
         {
 464  
             super(writer);
 465  
             this.delimiter = delimiter;
 466  
         }
 467  
 
 468  
         /**
 469  
          * Write a property.
 470  
          *
 471  
          * @param key
 472  
          * @param value
 473  
          * @throws IOException
 474  
          */
 475  
         public void writeProperty(String key, Object value) throws IOException
 476  
         {
 477  
             write(key);
 478  
             write(" = ");
 479  
             if (value != null)
 480  
             {
 481  
                 String v = StringEscapeUtils.escapeJava(String.valueOf(value));
 482  
                 v = StringUtils.replace(v, String.valueOf(delimiter), "\\" + delimiter);
 483  
                 write(v);
 484  
             }
 485  
 
 486  
             write('\n');
 487  
         }
 488  
 
 489  
         /**
 490  
          * Write a property.
 491  
          *
 492  
          * @param key The key of the property
 493  
          * @param values The array of values of the property
 494  
          */
 495  
         public void writeProperty(String key, List values) throws IOException
 496  
         {
 497  
             for (int i = 0; i < values.size(); i++)
 498  
             {
 499  
                 writeProperty(key, values.get(i));
 500  
             }
 501  
         }
 502  
 
 503  
         /**
 504  
          * Write a comment.
 505  
          *
 506  
          * @param comment
 507  
          * @throws IOException
 508  
          */
 509  
         public void writeComment(String comment) throws IOException
 510  
         {
 511  
             write("# " + comment + "\n");
 512  
         }
 513  
     } // class PropertiesWriter
 514  
 
 515  
     /**
 516  
      * <p>Unescapes any Java literals found in the <code>String</code> to a
 517  
      * <code>Writer</code>.</p> This is a slightly modified version of the
 518  
      * StringEscapeUtils.unescapeJava() function in commons-lang that doesn't
 519  
      * drop escaped commas (i.e '\,').
 520  
      *
 521  
      * @param str  the <code>String</code> to unescape, may be null
 522  
      *
 523  
      * @throws IllegalArgumentException if the Writer is <code>null</code>
 524  
      */
 525  57762
     protected static String unescapeJava(String str, char delimiter)
 526  
     {
 527  91000
         if (str == null)
 528  
         {
 529  57762
             return null;
 530  57762
         }
 531  148762
         int sz = str.length();
 532  148762
         StringBuffer out = new StringBuffer(sz);
 533  148762
         StringBuffer unicode = new StringBuffer(4);
 534  705376
         boolean hadSlash = false;
 535  91000
         boolean inUnicode = false;
 536  1525540
         for (int i = 0; i < sz; i++)
 537  556614
         {
 538  877926
             char ch = str.class="keyword">charAt(i);
 539  877926
             if (inUnicode)
 540  
             {
 541  4716
                 // if in unicode, then we're reading unicode
 542  4716
                 // values in somehow
 543  7448
                 unicode.append(ch);
 544  7448
                 if (unicode.length() == 4)
 545  
                 {
 546  
                     // unicode now contains the four hex digits
 547  
                     // which represents our unicode character
 548  1179
                     try
 549  1179
                     {
 550  3041
                         int value = Integer.parseInt(unicode.toString(), 16);
 551  3041
                         out.append((char) value);
 552  3041
                         unicode.setLength(0);
 553  1862
                         inUnicode = false;
 554  1862
                         hadSlash = false;
 555  
                     }
 556  0
                     catch (NumberFormatException nfe)
 557  1179
                     {
 558  0
                         throw new ConfigurationRuntimeException("Unable to parse unicode value: " + unicode, nfe);
 559  1862
                     }
 560  
                 }
 561  
                 continue;
 562  551898
             }
 563  
 
 564  870478
             if (hadSlash)
 565  7128
             {
 566  
                 // handle an escaped value
 567  18384
                 hadSlash = false;
 568  1188
 
 569  11256
                 if (ch=='\\'){
 570  7816
                     out.append('\\');
 571  
                 }
 572  9380
                 else if (ch=='\''){
 573  5940
                     out.append('\'');
 574  1188
                 }
 575  9380
                 else if (ch=='\"'){
 576  6628
                     out.append('"');
 577  
                 }
 578  7504
                 else if (ch=='r'){
 579  4752
                     out.append('\r');
 580  
                 }
 581  7504
                 else if (ch=='f'){
 582  4752
                     out.append('\f');
 583  1188
                 }
 584  7504
                 else if (ch=='t'){
 585  5440
                     out.append('\t');
 586  1188
                 }
 587  5628
                 else if (ch=='n'){
 588  4252
                     out.append('\n');
 589  
                 }
 590  3752
                 else if (ch=='b'){
 591  2376
                     out.append('\b');
 592  1188
                 }
 593  4940
                 else if (ch==delimiter){
 594  1876
                     out.append('\\');
 595  3064
                     out.append(delimiter);
 596  
                 }
 597  3055
                 else if (ch=='u'){
 598  
                     //                  uh-oh, we're in unicode country....
 599  1862
                     inUnicode = true;
 600  9
                 }
 601  
                 else {
 602  14
                     out.append(ch);
 603  9
                 }
 604  
 
 605  544784
                 continue;
 606  
             }
 607  866350
             else if (ch == '\\')
 608  7128
             {
 609  11256
                 hadSlash = true;
 610  548898
                 continue;
 611  
             }
 612  847966
             out.append(ch);
 613  57762
         }
 614  
 
 615  91000
         if (hadSlash)
 616  
         {
 617  
             // then we're in the weird case of a \ at the end of the
 618  
             // string, let's output it anyway.
 619  0
             out.append('\\');
 620  57762
         }
 621  
 
 622  91000
         return out.toString();
 623  
     }
 624  
 
 625  
 }

This report is generated by jcoverage, Maven and Maven JCoverage Plugin.