Coverage Report - org.apache.commons.configuration.AbstractFileConfiguration
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractFileConfiguration
90%
234/261
97%
33/34
2,438
 
 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.File;
 21  
 import java.io.FileOutputStream;
 22  
 import java.io.IOException;
 23  
 import java.io.InputStream;
 24  
 import java.io.InputStreamReader;
 25  
 import java.io.OutputStream;
 26  
 import java.io.OutputStreamWriter;
 27  
 import java.io.Reader;
 28  
 import java.io.UnsupportedEncodingException;
 29  
 import java.io.Writer;
 30  
 import java.net.HttpURLConnection;
 31  
 import java.net.MalformedURLException;
 32  
 import java.net.URL;
 33  
 import java.net.URLConnection;
 34  
 import java.util.Iterator;
 35  
 
 36  
 import org.apache.commons.configuration.reloading.InvariantReloadingStrategy;
 37  
 import org.apache.commons.configuration.reloading.ReloadingStrategy;
 38  
 import org.apache.commons.lang.StringUtils;
 39  
 import org.apache.commons.logging.LogFactory;
 40  
 
 41  
 /**
 42  
  * <p>Partial implementation of the <code>FileConfiguration</code> interface.
 43  
  * Developpers of file based configuration may want to extend this class,
 44  
  * the two methods left to implement are <code>{@link FileConfiguration#load(Reader)}</code>
 45  
  * and <code>{@link FileConfiguration#save(Writer)}</code>.</p>
 46  
  * <p>This base class already implements a couple of ways to specify the location
 47  
  * of the file this configuration is based on. The following possibilities
 48  
  * exist:
 49  
  * <ul><li>URLs: With the method <code>setURL()</code> a full URL to the
 50  
  * configuration source can be specified. This is the most flexible way. Note
 51  
  * that the <code>save()</code> methods support only <em>file:</em> URLs.</li>
 52  
  * <li>Files: The <code>setFile()</code> method allows to specify the
 53  
  * configuration source as a file. This can be either a relative or an
 54  
  * absolute file. In the former case the file is resolved based on the current
 55  
  * directory.</li>
 56  
  * <li>As file paths in string form: With the <code>setPath()</code> method a
 57  
  * full path to a configuration file can be provided as a string.</li>
 58  
  * <li>Separated as base path and file name: This is the native form in which
 59  
  * the location is stored. The base path is a string defining either a local
 60  
  * directory or a URL. It can be set using the <code>setBasePath()</code>
 61  
  * method. The file name, non surprisingly, defines the name of the configuration
 62  
  * file.</li></ul></p>
 63  
  * <p>Note that the <code>load()</code> methods do not wipe out the configuration's
 64  
  * content before the new configuration file is loaded. Thus it is very easy to
 65  
  * construct a union configuration by simply loading multiple configuration
 66  
  * files, e.g.</p>
 67  
  * <p><pre>
 68  
  * config.load(configFile1);
 69  
  * config.load(configFile2);
 70  
  * </pre></p>
 71  
  * <p>After executing this code fragment, the resulting configuration will
 72  
  * contain both the properties of configFile1 and configFile2. On the other
 73  
  * hand, if the current configuration file is to be reloaded, <code>clear()</code>
 74  
  * should be called first. Otherwise the properties are doubled. This behavior
 75  
  * is analogous to the behavior of the <code>load(InputStream)</code> method
 76  
  * in <code>java.util.Properties</code>.</p>
 77  
  *
 78  
  * @author Emmanuel Bourg
 79  
  * @version $Revision: 545904 $, $Date: 2007-06-10 18:10:17 +0200 (So, 10 Jun 2007) $
 80  
  * @since 1.0-rc2
 81  
  */
 82  
 public abstract class AbstractFileConfiguration extends BaseConfiguration implements FileConfiguration
 83  
 {
 84  
     /** Constant for the configuration reload event.*/
 85  
     public static final int EVENT_RELOAD = 20;
 86  
 
 87  
     /** Stores the file name.*/
 88  
     protected String fileName;
 89  
 
 90  
     /** Stores the base path.*/
 91  
     protected String basePath;
 92  
 
 93  
     /** The auto save flag.*/
 94  
     protected boolean autoSave;
 95  
 
 96  
     /** Holds a reference to the reloading strategy.*/
 97  
     protected ReloadingStrategy strategy;
 98  
 
 99  
     /** A lock object for protecting reload operations.*/
 100  1226
     private Object reloadLock = new Object();
 101  
 
 102  
     /** Stores the encoding of the configuration file.*/
 103  
     private String encoding;
 104  
 
 105  
     /** Stores the URL from which the configuration file was loaded.*/
 106  
     private URL sourceURL;
 107  
 
 108  
     /** A counter that prohibits reloading.*/
 109  
     private int noReload;
 110  
 
 111  
     /**
 112  
      * Default constructor
 113  
      *
 114  
      * @since 1.1
 115  
      */
 116  
     public AbstractFileConfiguration()
 117  1226
     {
 118  1226
         initReloadingStrategy();
 119  1226
         setLogger(LogFactory.getLog(getClass()));
 120  1226
         addErrorLogListener();
 121  1226
     }
 122  
 
 123  
     /**
 124  
      * Creates and loads the configuration from the specified file. The passed
 125  
      * in string must be a valid file name, either absolute or relativ.
 126  
      *
 127  
      * @param fileName The name of the file to load.
 128  
      *
 129  
      * @throws ConfigurationException Error while loading the file
 130  
      * @since 1.1
 131  
      */
 132  
     public AbstractFileConfiguration(String fileName) throws ConfigurationException
 133  
     {
 134  258
         this();
 135  
 
 136  
         // store the file name
 137  258
         setFileName(fileName);
 138  
 
 139  
         // load the file
 140  258
         load();
 141  256
     }
 142  
 
 143  
     /**
 144  
      * Creates and loads the configuration from the specified file.
 145  
      *
 146  
      * @param file The file to load.
 147  
      * @throws ConfigurationException Error while loading the file
 148  
      * @since 1.1
 149  
      */
 150  
     public AbstractFileConfiguration(File file) throws ConfigurationException
 151  
     {
 152  17
         this();
 153  
 
 154  
         // set the file and update the url, the base path and the file name
 155  17
         setFile(file);
 156  
 
 157  
         // load the file
 158  17
         if (file.exists())
 159  
         {
 160  15
             load();
 161  
         }
 162  16
     }
 163  
 
 164  
     /**
 165  
      * Creates and loads the configuration from the specified URL.
 166  
      *
 167  
      * @param url The location of the file to load.
 168  
      * @throws ConfigurationException Error while loading the file
 169  
      * @since 1.1
 170  
      */
 171  
     public AbstractFileConfiguration(URL url) throws ConfigurationException
 172  
     {
 173  2
         this();
 174  
 
 175  
         // set the URL and update the base path and the file name
 176  2
         setURL(url);
 177  
 
 178  
         // load the file
 179  2
         load();
 180  2
     }
 181  
 
 182  
     /**
 183  
      * Load the configuration from the underlying location.
 184  
      *
 185  
      * @throws ConfigurationException if loading of the configuration fails
 186  
      */
 187  
     public void load() throws ConfigurationException
 188  
     {
 189  925
         if (sourceURL != null)
 190  
         {
 191  235
             load(sourceURL);
 192  235
         }
 193  
         else
 194  
         {
 195  690
             load(getFileName());
 196  
         }
 197  898
     }
 198  
 
 199  
     /**
 200  
      * Locate the specified file and load the configuration. This does not
 201  
      * change the source of the configuration (i.e. the internally maintained file name).
 202  
      * Use one of the setter methods for this purpose.
 203  
      *
 204  
      * @param fileName the name of the file to be loaded
 205  
      * @throws ConfigurationException if an error occurs
 206  
      */
 207  
     public void load(String fileName) throws ConfigurationException
 208  
     {
 209  
         try
 210  
         {
 211  697
             URL url = ConfigurationUtils.locate(basePath, fileName);
 212  
 
 213  697
             if (url == null)
 214  
             {
 215  23
                 throw new ConfigurationException("Cannot locate configuration source " + fileName);
 216  
             }
 217  674
             load(url);
 218  
         }
 219  28
         catch (ConfigurationException e)
 220  
         {
 221  28
             throw e;
 222  
         }
 223  0
         catch (Exception e)
 224  
         {
 225  0
             throw new ConfigurationException(e.getMessage(), e);
 226  669
         }
 227  669
     }
 228  
 
 229  
     /**
 230  
      * Load the configuration from the specified file. This does not change
 231  
      * the source of the configuration (i.e. the internally maintained file
 232  
      * name). Use one of the setter methods for this purpose.
 233  
      *
 234  
      * @param file the file to load
 235  
      * @throws ConfigurationException if an error occurs
 236  
      */
 237  
     public void load(File file) throws ConfigurationException
 238  
     {
 239  
         try
 240  
         {
 241  13
             load(file.toURL());
 242  
         }
 243  3
         catch (ConfigurationException e)
 244  
         {
 245  3
             throw e;
 246  
         }
 247  0
         catch (Exception e)
 248  
         {
 249  0
             throw new ConfigurationException(e.getMessage(), e);
 250  10
         }
 251  10
     }
 252  
 
 253  
     /**
 254  
      * Load the configuration from the specified URL. This does not change the
 255  
      * source of the configuration (i.e. the internally maintained file name).
 256  
      * Use on of the setter methods for this purpose.
 257  
      *
 258  
      * @param url the URL of the file to be loaded
 259  
      * @throws ConfigurationException if an error occurs
 260  
      */
 261  
     public void load(URL url) throws ConfigurationException
 262  
     {
 263  1305
         if (sourceURL == null)
 264  
         {
 265  686
             if (StringUtils.isEmpty(getBasePath()))
 266  
             {
 267  
                 // ensure that we have a valid base path
 268  338
                 setBasePath(url.toString());
 269  
             }
 270  686
             sourceURL = url;
 271  
         }
 272  
 
 273  
         // throw an exception if the target URL is a directory
 274  1305
         File file = ConfigurationUtils.fileFromURL(url);
 275  1305
         if (file != null && file.isDirectory())
 276  
         {
 277  4
             throw new ConfigurationException("Cannot load a configuration from a directory");
 278  
         }
 279  
 
 280  1301
         InputStream in = null;
 281  
 
 282  
         try
 283  
         {
 284  1301
             in = url.openStream();
 285  1301
             load(in);
 286  
         }
 287  4
         catch (ConfigurationException e)
 288  
         {
 289  4
             throw e;
 290  
         }
 291  0
         catch (Exception e)
 292  
         {
 293  0
             throw new ConfigurationException(e.getMessage(), e);
 294  
         }
 295  
         finally
 296  
         {
 297  
             // close the input stream
 298  4
             try
 299  
             {
 300  1301
                 if (in != null)
 301  
                 {
 302  1301
                     in.close();
 303  
                 }
 304  
             }
 305  0
             catch (IOException e)
 306  
             {
 307  0
                 getLogger().warn("Could not close input stream", e);
 308  1301
             }
 309  4
         }
 310  1297
     }
 311  
 
 312  
     /**
 313  
      * Load the configuration from the specified stream, using the encoding
 314  
      * returned by {@link #getEncoding()}.
 315  
      *
 316  
      * @param in the input stream
 317  
      *
 318  
      * @throws ConfigurationException if an error occurs during the load operation
 319  
      */
 320  
     public void load(InputStream in) throws ConfigurationException
 321  
     {
 322  986
         load(in, getEncoding());
 323  985
     }
 324  
 
 325  
     /**
 326  
      * Load the configuration from the specified stream, using the specified
 327  
      * encoding. If the encoding is null the default encoding is used.
 328  
      *
 329  
      * @param in the input stream
 330  
      * @param encoding the encoding used. <code>null</code> to use the default encoding
 331  
      *
 332  
      * @throws ConfigurationException if an error occurs during the load operation
 333  
      */
 334  
     public void load(InputStream in, String encoding) throws ConfigurationException
 335  
     {
 336  987
         Reader reader = null;
 337  
 
 338  987
         if (encoding != null)
 339  
         {
 340  
             try
 341  
             {
 342  480
                 reader = new InputStreamReader(in, encoding);
 343  
             }
 344  0
             catch (UnsupportedEncodingException e)
 345  
             {
 346  0
                 throw new ConfigurationException(
 347  
                         "The requested encoding is not supported, try the default encoding.", e);
 348  480
             }
 349  
         }
 350  
 
 351  987
         if (reader == null)
 352  
         {
 353  507
             reader = new InputStreamReader(in);
 354  
         }
 355  
 
 356  987
         load(reader);
 357  986
     }
 358  
 
 359  
     /**
 360  
      * Save the configuration. Before this method can be called a valid file
 361  
      * name must have been set.
 362  
      *
 363  
      * @throws ConfigurationException if an error occurs or no file name has
 364  
      * been set yet
 365  
      */
 366  
     public void save() throws ConfigurationException
 367  
     {
 368  48
         if (getFileName() == null)
 369  
         {
 370  4
             throw new ConfigurationException("No file name has been set!");
 371  
         }
 372  
 
 373  44
         if (sourceURL != null)
 374  
         {
 375  21
             save(sourceURL);
 376  21
         }
 377  
         else
 378  
         {
 379  23
             save(fileName);
 380  
         }
 381  44
         strategy.init();
 382  44
     }
 383  
 
 384  
     /**
 385  
      * Save the configuration to the specified file. This doesn't change the
 386  
      * source of the configuration, use setFileName() if you need it.
 387  
      *
 388  
      * @param fileName the file name
 389  
      *
 390  
      * @throws ConfigurationException if an error occurs during the save operation
 391  
      */
 392  
     public void save(String fileName) throws ConfigurationException
 393  
     {
 394  
         try
 395  
         {
 396  34
             File file = ConfigurationUtils.getFile(basePath, fileName);
 397  34
             if (file == null)
 398  
             {
 399  1
                 throw new ConfigurationException("Invalid file name for save: " + fileName);
 400  
             }
 401  33
             save(file);
 402  
         }
 403  1
         catch (ConfigurationException e)
 404  
         {
 405  1
             throw e;
 406  
         }
 407  0
         catch (Exception e)
 408  
         {
 409  0
             throw new ConfigurationException(e.getMessage(), e);
 410  33
         }
 411  33
     }
 412  
 
 413  
     /**
 414  
      * Save the configuration to the specified URL.
 415  
      * This doesn't change the source of the configuration, use setURL()
 416  
      * if you need it.
 417  
      *
 418  
      * @param url the URL
 419  
      *
 420  
      * @throws ConfigurationException if an error occurs during the save operation
 421  
      */
 422  
     public void save(URL url) throws ConfigurationException
 423  
     {
 424  
         // file URLs have to be converted to Files since FileURLConnection is
 425  
         // read only (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4191800)
 426  26
         File file = ConfigurationUtils.fileFromURL(url);
 427  26
         if (file != null)
 428  
         {
 429  22
             save(file);
 430  22
         }
 431  
         else
 432  
         {
 433  
             // for non file URLs save through an URLConnection
 434  4
             OutputStream out = null;
 435  
             try
 436  
             {
 437  4
                 URLConnection connection = url.openConnection();
 438  4
                 connection.setDoOutput(true);
 439  
 
 440  
                 // use the PUT method for http URLs
 441  4
                 if (connection instanceof HttpURLConnection)
 442  
                 {
 443  3
                     HttpURLConnection conn = (HttpURLConnection) connection;
 444  3
                     conn.setRequestMethod("PUT");
 445  
                 }
 446  
 
 447  4
                 out = connection.getOutputStream();
 448  4
                 save(out);
 449  
 
 450  
                 // check the response code for http URLs and throw an exception if an error occured
 451  4
                 if (connection instanceof HttpURLConnection)
 452  
                 {
 453  3
                     HttpURLConnection conn = (HttpURLConnection) connection;
 454  3
                     if (conn.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST)
 455  
                     {
 456  2
                         throw new IOException("HTTP Error " + conn.getResponseCode() + " " + conn.getResponseMessage());
 457  
                     }
 458  
                 }
 459  
             }
 460  2
             catch (IOException e)
 461  
             {
 462  2
                 throw new ConfigurationException("Could not save to URL " + url, e);
 463  
             }
 464  
             finally
 465  
             {
 466  4
                 closeSilent(out);
 467  4
             }
 468  
         }
 469  24
     }
 470  
 
 471  
     /**
 472  
      * Save the configuration to the specified file. The file is created
 473  
      * automatically if it doesn't exist. This doesn't change the source
 474  
      * of the configuration, use {@link #setFile} if you need it.
 475  
      *
 476  
      * @param file the target file
 477  
      *
 478  
      * @throws ConfigurationException if an error occurs during the save operation
 479  
      */
 480  
     public void save(File file) throws ConfigurationException
 481  
     {
 482  83
         OutputStream out = null;
 483  
 
 484  
         try
 485  
         {
 486  
             // create the file if necessary
 487  83
             createPath(file);
 488  83
             out = new FileOutputStream(file);
 489  83
             save(out);
 490  
         }
 491  0
         catch (IOException e)
 492  
         {
 493  0
             throw new ConfigurationException(e.getMessage(), e);
 494  
         }
 495  
         finally
 496  
         {
 497  83
             closeSilent(out);
 498  83
         }
 499  82
     }
 500  
 
 501  
     /**
 502  
      * Save the configuration to the specified stream, using the encoding
 503  
      * returned by {@link #getEncoding()}.
 504  
      *
 505  
      * @param out the output stream
 506  
      *
 507  
      * @throws ConfigurationException if an error occurs during the save operation
 508  
      */
 509  
     public void save(OutputStream out) throws ConfigurationException
 510  
     {
 511  88
         save(out, getEncoding());
 512  87
     }
 513  
 
 514  
     /**
 515  
      * Save the configuration to the specified stream, using the specified
 516  
      * encoding. If the encoding is null the default encoding is used.
 517  
      *
 518  
      * @param out the output stream
 519  
      * @param encoding the encoding to use
 520  
      * @throws ConfigurationException if an error occurs during the save operation
 521  
      */
 522  
     public void save(OutputStream out, String encoding) throws ConfigurationException
 523  
     {
 524  89
         Writer writer = null;
 525  
 
 526  89
         if (encoding != null)
 527  
         {
 528  
             try
 529  
             {
 530  34
                 writer = new OutputStreamWriter(out, encoding);
 531  
             }
 532  0
             catch (UnsupportedEncodingException e)
 533  
             {
 534  0
                 throw new ConfigurationException(
 535  
                         "The requested encoding is not supported, try the default encoding.", e);
 536  34
             }
 537  
         }
 538  
 
 539  89
         if (writer == null)
 540  
         {
 541  55
             writer = new OutputStreamWriter(out);
 542  
         }
 543  
 
 544  89
         save(writer);
 545  88
     }
 546  
 
 547  
     /**
 548  
      * Return the name of the file.
 549  
      *
 550  
      * @return the file name
 551  
      */
 552  
     public String getFileName()
 553  
     {
 554  828
         return fileName;
 555  
     }
 556  
 
 557  
     /**
 558  
      * Set the name of the file. The passed in file name can contain a
 559  
      * relative path.
 560  
      * It must be used when referring files with relative paths from classpath.
 561  
      * Use <code>{@link AbstractFileConfiguration#setPath(String)
 562  
      * setPath()}</code> to set a full qualified file name.
 563  
      *
 564  
      * @param fileName the name of the file
 565  
      */
 566  
     public void setFileName(String fileName)
 567  
     {
 568  728
         sourceURL = null;
 569  728
         this.fileName = fileName;
 570  728
     }
 571  
 
 572  
     /**
 573  
      * Return the base path.
 574  
      *
 575  
      * @return the base path
 576  
      * @see FileConfiguration#getBasePath()
 577  
      */
 578  
     public String getBasePath()
 579  
     {
 580  1176
         return basePath;
 581  
     }
 582  
 
 583  
     /**
 584  
      * Sets the base path. The base path is typically either a path to a
 585  
      * directory or a URL. Together with the value passed to the
 586  
      * <code>setFileName()</code> method it defines the location of the
 587  
      * configuration file to be loaded. The strategies for locating the file are
 588  
      * quite tolerant. For instance if the file name is already an absolute path
 589  
      * or a fully defined URL, the base path will be ignored. The base path can
 590  
      * also be a URL, in which case the file name is interpreted in this URL's
 591  
      * context. Because the base path is used by some of the derived classes for
 592  
      * resolving relative file names it should contain a meaningful value. If
 593  
      * other methods are used for determining the location of the configuration
 594  
      * file (e.g. <code>setFile()</code> or <code>setURL()</code>), the
 595  
      * base path is automatically set.
 596  
      *
 597  
      * @param basePath the base path.
 598  
      */
 599  
     public void setBasePath(String basePath)
 600  
     {
 601  742
         sourceURL = null;
 602  742
         this.basePath = basePath;
 603  742
     }
 604  
 
 605  
     /**
 606  
      * Return the file where the configuration is stored. If the base path is a
 607  
      * URL with a protocol different than &quot;file&quot;, or the configuration
 608  
      * file is within a compressed archive, the return value
 609  
      * will not point to a valid file object.
 610  
      *
 611  
      * @return the file where the configuration is stored; this can be <b>null</b>
 612  
      */
 613  
     public File getFile()
 614  
     {
 615  14
         if (getFileName() == null && sourceURL == null)
 616  
         {
 617  1
             return null;
 618  
         }
 619  13
         else if (sourceURL != null)
 620  
         {
 621  4
             return ConfigurationUtils.fileFromURL(sourceURL);
 622  
         }
 623  
         else
 624  
         {
 625  9
             return ConfigurationUtils.getFile(getBasePath(), getFileName());
 626  
         }
 627  
     }
 628  
 
 629  
     /**
 630  
      * Set the file where the configuration is stored. The passed in file is
 631  
      * made absolute if it is not yet. Then the file's path component becomes
 632  
      * the base path and its name component becomes the file name.
 633  
      *
 634  
      * @param file the file where the configuration is stored
 635  
      */
 636  
     public void setFile(File file)
 637  
     {
 638  265
         sourceURL = null;
 639  265
         setFileName(file.getName());
 640  265
         setBasePath((file.getParentFile() != null) ? file.getParentFile()
 641  
                 .getAbsolutePath() : null);
 642  265
     }
 643  
 
 644  
     /**
 645  
      * Returns the full path to the file this configuration is based on. The
 646  
      * return value is a valid File path only if this configuration is based on
 647  
      * a file on the local disk.
 648  
      * If the configuration was loaded from a packed archive the returned value
 649  
      * is the string form of the URL from which the configuration was loaded.
 650  
      *
 651  
      * @return the full path to the configuration file
 652  
      */
 653  
     public String getPath()
 654  
     {
 655  2
         String path = null;
 656  2
         File file = getFile();
 657  
         // if resource was loaded from jar file may be null
 658  2
         if (file != null)
 659  
         {
 660  2
             path = file.getAbsolutePath();
 661  
         }
 662  
 
 663  
         // try to see if file was loaded from a jar
 664  2
         if (path == null)
 665  
         {
 666  0
             if (sourceURL != null)
 667  
             {
 668  0
                 path = sourceURL.getPath();
 669  0
             }
 670  
             else
 671  
             {
 672  
                 try
 673  
                 {
 674  0
                     path = ConfigurationUtils.getURL(getBasePath(), getFileName()).getPath();
 675  
                 }
 676  0
                 catch (MalformedURLException e)
 677  
                 {
 678  
                     // simply ignore it and return null
 679  
                     ;
 680  0
                 }
 681  
             }
 682  
         }
 683  
 
 684  2
         return path;
 685  
     }
 686  
 
 687  
     /**
 688  
      * Sets the location of this configuration as a full or relative path name.
 689  
      * The passed in path should represent a valid file name on the file system.
 690  
      * It must not be used to specify relative paths for files that exist
 691  
      * in classpath, either plain file system or compressed archive,
 692  
      * because this method expands any relative path to an absolute one which
 693  
      * may end in an invalid absolute path for classpath references.
 694  
      *
 695  
      * @param path the full path name of the configuration file
 696  
      */
 697  
     public void setPath(String path)
 698  
     {
 699  1
         setFile(new File(path));
 700  1
     }
 701  
 
 702  
     /**
 703  
      * Return the URL where the configuration is stored.
 704  
      *
 705  
      * @return the configuration's location as URL
 706  
      */
 707  
     public URL getURL()
 708  
     {
 709  924
         return (sourceURL != null) ? sourceURL
 710  
                 : ConfigurationUtils.locate(getBasePath(), getFileName());
 711  
     }
 712  
 
 713  
     /**
 714  
      * Set the location of this configuration as a URL. For loading this can be
 715  
      * an arbitrary URL with a supported protocol. If the configuration is to
 716  
      * be saved, too, a URL with the &quot;file&quot; protocol should be
 717  
      * provided.
 718  
      *
 719  
      * @param url the location of this configuration as URL
 720  
      */
 721  
     public void setURL(URL url)
 722  
     {
 723  15
         setBasePath(ConfigurationUtils.getBasePath(url));
 724  15
         setFileName(ConfigurationUtils.getFileName(url));
 725  15
         sourceURL = url;
 726  15
     }
 727  
 
 728  
     public void setAutoSave(boolean autoSave)
 729  
     {
 730  2271
         this.autoSave = autoSave;
 731  2271
     }
 732  
 
 733  
     public boolean isAutoSave()
 734  
     {
 735  1132
         return autoSave;
 736  
     }
 737  
 
 738  
     /**
 739  
      * Save the configuration if the automatic persistence is enabled
 740  
      * and if a file is specified.
 741  
      */
 742  
     protected void possiblySave()
 743  
     {
 744  25982
         if (autoSave && fileName != null)
 745  
         {
 746  
             try
 747  
             {
 748  19
                 save();
 749  
             }
 750  0
             catch (ConfigurationException e)
 751  
             {
 752  0
                 throw new ConfigurationRuntimeException("Failed to auto-save", e);
 753  19
             }
 754  
         }
 755  25982
     }
 756  
 
 757  
     /**
 758  
      * Adds a new property to this configuration. This implementation checks if
 759  
      * the auto save mode is enabled and saves the configuration if necessary.
 760  
      *
 761  
      * @param key the key of the new property
 762  
      * @param value the value
 763  
      */
 764  
     public void addProperty(String key, Object value)
 765  
     {
 766  21529
         super.addProperty(key, value);
 767  21528
         possiblySave();
 768  21528
     }
 769  
 
 770  
     /**
 771  
      * Sets a new value for the specified property. This implementation checks
 772  
      * if the auto save mode is enabled and saves the configuration if
 773  
      * necessary.
 774  
      *
 775  
      * @param key the key of the affected property
 776  
      * @param value the value
 777  
      */
 778  
     public void setProperty(String key, Object value)
 779  
     {
 780  18
         super.setProperty(key, value);
 781  18
         possiblySave();
 782  18
     }
 783  
 
 784  
     public void clearProperty(String key)
 785  
     {
 786  64
         super.clearProperty(key);
 787  64
         possiblySave();
 788  64
     }
 789  
 
 790  
     public ReloadingStrategy getReloadingStrategy()
 791  
     {
 792  7
         return strategy;
 793  
     }
 794  
 
 795  
     public void setReloadingStrategy(ReloadingStrategy strategy)
 796  
     {
 797  1260
         this.strategy = strategy;
 798  1260
         strategy.setConfiguration(this);
 799  1260
         strategy.init();
 800  1260
     }
 801  
 
 802  
     /**
 803  
      * Performs a reload operation if necessary. This method is called on each
 804  
      * access of this configuration. It asks the associated reloading strategy
 805  
      * whether a reload should be performed. If this is the case, the
 806  
      * configuration is cleared and loaded again from its source. If this
 807  
      * operation causes an exception, the registered error listeners will be
 808  
      * notified. The error event passed to the listeners is of type
 809  
      * <code>EVENT_RELOAD</code> and contains the exception that caused the
 810  
      * event.
 811  
      */
 812  
     public void reload()
 813  
     {
 814  32535
         synchronized (reloadLock)
 815  
         {
 816  32535
             if (noReload == 0)
 817  
             {
 818  
                 try
 819  
                 {
 820  22135
                     enterNoReload(); // avoid reentrant calls
 821  
 
 822  22135
                     if (strategy.reloadingRequired())
 823  
                     {
 824  216
                         if (getLogger().isInfoEnabled())
 825  
                         {
 826  0
                             getLogger().info("Reloading configuration. URL is " + getURL());
 827  
                         }
 828  216
                         fireEvent(EVENT_RELOAD, null, getURL(), true);
 829  216
                         setDetailEvents(false);
 830  216
                         boolean autoSaveBak = this.isAutoSave(); // save the current state
 831  216
                         this.setAutoSave(false); // deactivate autoSave to prevent information loss
 832  
                         try
 833  
                         {
 834  216
                             clear();
 835  216
                             load();
 836  
                         }
 837  
                         finally
 838  
                         {
 839  216
                             this.setAutoSave(autoSaveBak); // set autoSave to previous value
 840  216
                             setDetailEvents(true);
 841  216
                         }
 842  215
                         fireEvent(EVENT_RELOAD, null, getURL(), false);
 843  
 
 844  
                         // notify the strategy
 845  215
                         strategy.reloadingPerformed();
 846  
                     }
 847  
                 }
 848  1
                 catch (Exception e)
 849  
                 {
 850  1
                     fireError(EVENT_RELOAD, null, null, e);
 851  
                     // todo rollback the changes if the file can't be reloaded
 852  
                 }
 853  
                 finally
 854  
                 {
 855  22135
                     exitNoReload();
 856  22135
                 }
 857  
             }
 858  32535
         }
 859  32535
     }
 860  
 
 861  
     /**
 862  
      * Enters the &quot;No reloading mode&quot;. As long as this mode is active
 863  
      * no reloading will be performed. This is necessary for some
 864  
      * implementations of <code>save()</code> in derived classes, which may
 865  
      * cause a reload while accessing the properties to save. This may cause the
 866  
      * whole configuration to be erased. To avoid this, this method can be
 867  
      * called first. After a call to this method there always must be a
 868  
      * corresponding call of <code>{@link #exitNoReload()}</code> later! (If
 869  
      * necessary, <code>finally</code> blocks must be used to ensure this.
 870  
      */
 871  
     protected void enterNoReload()
 872  
     {
 873  66303
         synchronized (reloadLock)
 874  
         {
 875  66303
             noReload++;
 876  66303
         }
 877  66303
     }
 878  
 
 879  
     /**
 880  
      * Leaves the &quot;No reloading mode&quot;.
 881  
      *
 882  
      * @see #enterNoReload()
 883  
      */
 884  
     protected void exitNoReload()
 885  
     {
 886  66303
         synchronized (reloadLock)
 887  
         {
 888  66303
             if (noReload > 0) // paranoia check
 889  
             {
 890  66303
                 noReload--;
 891  
             }
 892  66303
         }
 893  66303
     }
 894  
 
 895  
     /**
 896  
      * Sends an event to all registered listeners. This implementation ensures
 897  
      * that no reloads are performed while the listeners are invoked. So
 898  
      * infinite loops can be avoided that can be caused by event listeners
 899  
      * accessing the configuration's properties when they are invoked.
 900  
      *
 901  
      * @param type the event type
 902  
      * @param propName the name of the property
 903  
      * @param propValue the value of the property
 904  
      * @param before the before update flag
 905  
      */
 906  
     protected void fireEvent(int type, String propName, Object propValue, boolean before)
 907  
     {
 908  44134
         enterNoReload();
 909  
         try
 910  
         {
 911  44134
             super.fireEvent(type, propName, propValue, before);
 912  
         }
 913  
         finally
 914  
         {
 915  44134
             exitNoReload();
 916  44134
         }
 917  44134
     }
 918  
 
 919  
     public Object getProperty(String key)
 920  
     {
 921  24736
         reload();
 922  24736
         return super.getProperty(key);
 923  
     }
 924  
 
 925  
     public boolean isEmpty()
 926  
     {
 927  10
         reload();
 928  10
         return super.isEmpty();
 929  
     }
 930  
 
 931  
     public boolean containsKey(String key)
 932  
     {
 933  1211
         reload();
 934  1211
         return super.containsKey(key);
 935  
     }
 936  
 
 937  
     public Iterator getKeys()
 938  
     {
 939  112
         reload();
 940  112
         return super.getKeys();
 941  
     }
 942  
 
 943  
     /**
 944  
      * Create the path to the specified file.
 945  
      *
 946  
      * @param file the target file
 947  
      */
 948  
     private void createPath(File file)
 949  
     {
 950  83
         if (file != null)
 951  
         {
 952  
             // create the path to the file if the file doesn't exist
 953  83
             if (!file.exists())
 954  
             {
 955  50
                 File parent = file.getParentFile();
 956  50
                 if (parent != null && !parent.exists())
 957  
                 {
 958  3
                     parent.mkdirs();
 959  
                 }
 960  
             }
 961  
         }
 962  83
     }
 963  
 
 964  
     public String getEncoding()
 965  
     {
 966  1137
         return encoding;
 967  
     }
 968  
 
 969  
     public void setEncoding(String encoding)
 970  
     {
 971  468
         this.encoding = encoding;
 972  468
     }
 973  
 
 974  
     /**
 975  
      * Creates a copy of this configuration. The new configuration object will
 976  
      * contain the same properties as the original, but it will lose any
 977  
      * connection to a source file (if one exists); this includes setting the
 978  
      * source URL, base path, and file name to <b>null</b>. This is done to
 979  
      * avoid race conditions if both the original and the copy are modified and
 980  
      * then saved.
 981  
      *
 982  
      * @return the copy
 983  
      * @since 1.3
 984  
      */
 985  
     public Object clone()
 986  
     {
 987  4
         AbstractFileConfiguration copy = (AbstractFileConfiguration) super.clone();
 988  4
         copy.setBasePath(null);
 989  4
         copy.setFileName(null);
 990  4
         copy.initReloadingStrategy();
 991  4
         return copy;
 992  
     }
 993  
 
 994  
     /**
 995  
      * Helper method for initializing the reloading strategy.
 996  
      */
 997  
     private void initReloadingStrategy()
 998  
     {
 999  1230
         setReloadingStrategy(new InvariantReloadingStrategy());
 1000  1230
     }
 1001  
 
 1002  
     /**
 1003  
      * A helper method for closing an output stream. Occurring exceptions will
 1004  
      * be ignored.
 1005  
      *
 1006  
      * @param out the output stream to be closed (may be <b>null</b>)
 1007  
      * @since 1.5
 1008  
      */
 1009  
     private void closeSilent(OutputStream out)
 1010  
     {
 1011  
         try
 1012  
         {
 1013  87
             if (out != null)
 1014  
             {
 1015  87
                 out.close();
 1016  
             }
 1017  
         }
 1018  0
         catch (IOException e)
 1019  
         {
 1020  0
             getLogger().warn("Could not close output stream", e);
 1021  87
         }
 1022  87
     }
 1023  
 }