View Javadoc

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