Coverage report

  %line %branch
org.apache.commons.configuration.PropertiesConfiguration
85% 
100% 

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

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