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