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