001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.configuration;
019
020import java.io.File;
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.InputStreamReader;
024import java.io.OutputStream;
025import java.io.OutputStreamWriter;
026import java.io.Reader;
027import java.io.UnsupportedEncodingException;
028import java.io.Writer;
029import java.net.URL;
030import java.util.Iterator;
031import java.util.LinkedList;
032import java.util.List;
033
034import org.apache.commons.configuration.reloading.InvariantReloadingStrategy;
035import org.apache.commons.configuration.reloading.ReloadingStrategy;
036import org.apache.commons.lang.StringUtils;
037import org.apache.commons.logging.LogFactory;
038
039/**
040 * <p>Partial implementation of the {@code FileConfiguration} interface.
041 * Developers of file based configuration may want to extend this class,
042 * the two methods left to implement are {@link FileConfiguration#load(Reader)}
043 * and {@link FileConfiguration#save(Writer)}.</p>
044 * <p>This base class already implements a couple of ways to specify the location
045 * of the file this configuration is based on. The following possibilities
046 * exist:
047 * <ul><li>URLs: With the method {@code setURL()} a full URL to the
048 * configuration source can be specified. This is the most flexible way. Note
049 * that the {@code save()} methods support only <em>file:</em> URLs.</li>
050 * <li>Files: The {@code setFile()} method allows to specify the
051 * configuration source as a file. This can be either a relative or an
052 * absolute file. In the former case the file is resolved based on the current
053 * directory.</li>
054 * <li>As file paths in string form: With the {@code setPath()} method a
055 * full path to a configuration file can be provided as a string.</li>
056 * <li>Separated as base path and file name: This is the native form in which
057 * the location is stored. The base path is a string defining either a local
058 * directory or a URL. It can be set using the {@code setBasePath()}
059 * method. The file name, non surprisingly, defines the name of the configuration
060 * file.</li></ul></p>
061 * <p>The configuration source to be loaded can be specified using one of the
062 * methods described above. Then the parameterless {@code load()} method can be
063 * called. Alternatively, one of the {@code load()} methods can be used which is
064 * passed the source directly. These methods typically do not change the
065 * internally stored file; however, if the configuration is not yet associated
066 * with a configuration source, the first call to one of the {@code load()}
067 * methods sets the base path and the source URL. This fact has to be taken
068 * into account when calling {@code load()} multiple times with different file
069 * paths.</p>
070 * <p>Note that the {@code load()} methods do not wipe out the configuration's
071 * content before the new configuration file is loaded. Thus it is very easy to
072 * construct a union configuration by simply loading multiple configuration
073 * files, e.g.</p>
074 * <p><pre>
075 * config.load(configFile1);
076 * config.load(configFile2);
077 * </pre></p>
078 * <p>After executing this code fragment, the resulting configuration will
079 * contain both the properties of configFile1 and configFile2. On the other
080 * hand, if the current configuration file is to be reloaded, {@code clear()}
081 * should be called first. Otherwise the properties are doubled. This behavior
082 * is analogous to the behavior of the {@code load(InputStream)} method
083 * in {@code java.util.Properties}.</p>
084 *
085 * @author Emmanuel Bourg
086 * @version $Id: AbstractFileConfiguration.java 1234118 2012-01-20 20:36:04Z oheger $
087 * @since 1.0-rc2
088 */
089public abstract class AbstractFileConfiguration
090extends BaseConfiguration
091implements FileConfiguration, FileSystemBased
092{
093    /** Constant for the configuration reload event.*/
094    public static final int EVENT_RELOAD = 20;
095
096    /** Constant fro the configuration changed event. */
097    public static final int EVENT_CONFIG_CHANGED = 21;
098
099    /** 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    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    private FileSystem fileSystem = FileSystem.getDefaultFileSystem();
128
129    /**
130     * Default constructor
131     *
132     * @since 1.1
133     */
134    public AbstractFileConfiguration()
135    {
136        initReloadingStrategy();
137        setLogger(LogFactory.getLog(getClass()));
138        addErrorLogListener();
139    }
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        this();
153
154        // store the file name
155        setFileName(fileName);
156
157        // load the file
158        load();
159    }
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        this();
171
172        // set the file and update the url, the base path and the file name
173        setFile(file);
174
175        // load the file
176        if (file.exists())
177        {
178            load();
179        }
180    }
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        this();
192
193        // set the URL and update the base path and the file name
194        setURL(url);
195
196        // load the file
197        load();
198    }
199
200    public void setFileSystem(FileSystem fileSystem)
201    {
202        if (fileSystem == null)
203        {
204            throw new NullPointerException("A valid FileSystem must be specified");
205        }
206        this.fileSystem = fileSystem;
207    }
208
209    public void resetFileSystem()
210    {
211        this.fileSystem = FileSystem.getDefaultFileSystem();
212    }
213
214    public FileSystem getFileSystem()
215    {
216        return this.fileSystem;
217    }
218
219    public Object getReloadLock()
220    {
221        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        if (sourceURL != null)
233        {
234            load(sourceURL);
235        }
236        else
237        {
238            load(getFileName());
239        }
240    }
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            URL url = ConfigurationUtils.locate(this.fileSystem, basePath, fileName);
256
257            if (url == null)
258            {
259                throw new ConfigurationException("Cannot locate configuration source " + fileName);
260            }
261            load(url);
262        }
263        catch (ConfigurationException e)
264        {
265            throw e;
266        }
267        catch (Exception e)
268        {
269            throw new ConfigurationException("Unable to load the configuration file " + fileName, e);
270        }
271    }
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            load(ConfigurationUtils.toURL(file));
287        }
288        catch (ConfigurationException e)
289        {
290            throw e;
291        }
292        catch (Exception e)
293        {
294            throw new ConfigurationException("Unable to load the configuration file " + file, e);
295        }
296    }
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        if (sourceURL == null)
310        {
311            if (StringUtils.isEmpty(getBasePath()))
312            {
313                // ensure that we have a valid base path
314                setBasePath(url.toString());
315            }
316            sourceURL = url;
317        }
318
319        InputStream in = null;
320
321        try
322        {
323            in = fileSystem.getInputStream(url);
324            load(in);
325        }
326        catch (ConfigurationException e)
327        {
328            throw e;
329        }
330        catch (Exception e)
331        {
332            throw new ConfigurationException("Unable to load the configuration from the URL " + url, e);
333        }
334        finally
335        {
336            // close the input stream
337            try
338            {
339                if (in != null)
340                {
341                    in.close();
342                }
343            }
344            catch (IOException e)
345            {
346                getLogger().warn("Could not close input stream", e);
347            }
348        }
349    }
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        load(in, getEncoding());
362    }
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        Reader reader = null;
376
377        if (encoding != null)
378        {
379            try
380            {
381                reader = new InputStreamReader(in, encoding);
382            }
383            catch (UnsupportedEncodingException e)
384            {
385                throw new ConfigurationException(
386                        "The requested encoding is not supported, try the default encoding.", e);
387            }
388        }
389
390        if (reader == null)
391        {
392            reader = new InputStreamReader(in);
393        }
394
395        load(reader);
396    }
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        if (getFileName() == null)
408        {
409            throw new ConfigurationException("No file name has been set!");
410        }
411
412        if (sourceURL != null)
413        {
414            save(sourceURL);
415        }
416        else
417        {
418            save(fileName);
419        }
420        strategy.init();
421    }
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            URL url = this.fileSystem.getURL(basePath, fileName);
436
437            if (url == null)
438            {
439                throw new ConfigurationException("Cannot locate configuration source " + fileName);
440            }
441            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        catch (ConfigurationException e)
450        {
451            throw e;
452        }
453        catch (Exception e)
454        {
455            throw new ConfigurationException("Unable to save the configuration to the file " + fileName, e);
456        }
457    }
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        OutputStream out = null;
471        try
472        {
473            out = fileSystem.getOutputStream(url);
474            save(out);
475            if (out instanceof VerifiableOutputStream)
476            {
477                ((VerifiableOutputStream) out).verify();
478            }
479        }
480        catch (IOException e)
481        {
482            throw new ConfigurationException("Could not save to URL " + url, e);
483        }
484        finally
485        {
486            closeSilent(out);
487        }
488    }
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        OutputStream out = null;
502
503        try
504        {
505            out = fileSystem.getOutputStream(file);
506            save(out);
507        }
508        finally
509        {
510            closeSilent(out);
511        }
512    }
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        save(out, getEncoding());
525    }
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        Writer writer = null;
538
539        if (encoding != null)
540        {
541            try
542            {
543                writer = new OutputStreamWriter(out, encoding);
544            }
545            catch (UnsupportedEncodingException e)
546            {
547                throw new ConfigurationException(
548                        "The requested encoding is not supported, try the default encoding.", e);
549            }
550        }
551
552        if (writer == null)
553        {
554            writer = new OutputStreamWriter(out);
555        }
556
557        save(writer);
558    }
559
560    /**
561     * Return the name of the file.
562     *
563     * @return the file name
564     */
565    public String getFileName()
566    {
567        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        if (fileName != null && fileName.startsWith(FILE_SCHEME) && !fileName.startsWith("file://"))
582        {
583            fileName = "file://" + fileName.substring(FILE_SCHEME.length());
584        }
585
586        sourceURL = null;
587        this.fileName = fileName;
588        getLogger().debug("FileName set to " + fileName);
589    }
590
591    /**
592     * Return the base path.
593     *
594     * @return the base path
595     * @see FileConfiguration#getBasePath()
596     */
597    public String getBasePath()
598    {
599        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        if (basePath != null && basePath.startsWith(FILE_SCHEME) && !basePath.startsWith("file://"))
621        {
622            basePath = "file://" + basePath.substring(FILE_SCHEME.length());
623        }
624        sourceURL = null;
625        this.basePath = basePath;
626        getLogger().debug("Base path set to " + basePath);
627    }
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        if (getFileName() == null && sourceURL == null)
640        {
641            return null;
642        }
643        else if (sourceURL != null)
644        {
645            return ConfigurationUtils.fileFromURL(sourceURL);
646        }
647        else
648        {
649            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        sourceURL = null;
663        setFileName(file.getName());
664        setBasePath((file.getParentFile() != null) ? file.getParentFile()
665                .getAbsolutePath() : null);
666    }
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        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        setFile(new File(path));
695    }
696
697    URL getSourceURL()
698    {
699        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        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        setBasePath(ConfigurationUtils.getBasePath(url));
724        setFileName(ConfigurationUtils.getFileName(url));
725        sourceURL = url;
726        getLogger().debug("URL set to " + url);
727    }
728
729    public void setAutoSave(boolean autoSave)
730    {
731        this.autoSave = autoSave;
732    }
733
734    public boolean isAutoSave()
735    {
736        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        if (autoSave && fileName != null)
746        {
747            try
748            {
749                save();
750            }
751            catch (ConfigurationException e)
752            {
753                throw new ConfigurationRuntimeException("Failed to auto-save", e);
754            }
755        }
756    }
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        synchronized (reloadLock)
769        {
770            super.addProperty(key, value);
771            possiblySave();
772        }
773    }
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        synchronized (reloadLock)
787        {
788            super.setProperty(key, value);
789            possiblySave();
790        }
791    }
792
793    @Override
794    public void clearProperty(String key)
795    {
796        synchronized (reloadLock)
797        {
798            super.clearProperty(key);
799            possiblySave();
800        }
801    }
802
803    public ReloadingStrategy getReloadingStrategy()
804    {
805        return strategy;
806    }
807
808    public void setReloadingStrategy(ReloadingStrategy strategy)
809    {
810        this.strategy = strategy;
811        strategy.setConfiguration(this);
812        strategy.init();
813    }
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        reload(false);
828    }
829
830    public boolean reload(boolean checkReload)
831    {
832        synchronized (reloadLock)
833        {
834            if (noReload == 0)
835            {
836                try
837                {
838                    enterNoReload(); // avoid reentrant calls
839
840                    if (strategy.reloadingRequired())
841                    {
842                        if (getLogger().isInfoEnabled())
843                        {
844                            getLogger().info("Reloading configuration. URL is " + getURL());
845                        }
846                        refresh();
847
848                        // notify the strategy
849                        strategy.reloadingPerformed();
850                    }
851                }
852                catch (Exception e)
853                {
854                    fireError(EVENT_RELOAD, null, null, e);
855                    // todo rollback the changes if the file can't be reloaded
856                    if (checkReload)
857                    {
858                        return false;
859                    }
860                }
861                finally
862                {
863                    exitNoReload();
864                }
865            }
866        }
867        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        fireEvent(EVENT_RELOAD, null, getURL(), true);
883        setDetailEvents(false);
884        boolean autoSaveBak = this.isAutoSave(); // save the current state
885        this.setAutoSave(false); // deactivate autoSave to prevent information loss
886        try
887        {
888            clear();
889            load();
890        }
891        finally
892        {
893            this.setAutoSave(autoSaveBak); // set autoSave to previous value
894            setDetailEvents(true);
895        }
896        fireEvent(EVENT_RELOAD, null, getURL(), false);
897    }
898
899    /**
900     * Send notification that the configuration has changed.
901     */
902    public void configurationChanged()
903    {
904        fireEvent(EVENT_CONFIG_CHANGED, null, getURL(), true);
905    }
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        synchronized (reloadLock)
920        {
921            noReload++;
922        }
923    }
924
925    /**
926     * Leaves the &quot;No reloading mode&quot;.
927     *
928     * @see #enterNoReload()
929     */
930    protected void exitNoReload()
931    {
932        synchronized (reloadLock)
933        {
934            if (noReload > 0) // paranoia check
935            {
936                noReload--;
937            }
938        }
939    }
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        enterNoReload();
956        try
957        {
958            super.fireEvent(type, propName, propValue, before);
959        }
960        finally
961        {
962            exitNoReload();
963        }
964    }
965
966    @Override
967    public Object getProperty(String key)
968    {
969        synchronized (reloadLock)
970        {
971            reload();
972            return super.getProperty(key);
973        }
974    }
975
976    @Override
977    public boolean isEmpty()
978    {
979        reload();
980        synchronized (reloadLock)
981        {
982            return super.isEmpty();
983        }
984    }
985
986    @Override
987    public boolean containsKey(String key)
988    {
989        reload();
990        synchronized (reloadLock)
991        {
992            return super.containsKey(key);
993        }
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        reload();
1015        List<String> keyList = new LinkedList<String>();
1016        enterNoReload();
1017        try
1018        {
1019            for (Iterator<String> it = super.getKeys(); it.hasNext();)
1020            {
1021                keyList.add(it.next());
1022            }
1023
1024            return keyList.iterator();
1025        }
1026        finally
1027        {
1028            exitNoReload();
1029        }
1030    }
1031
1032    public String getEncoding()
1033    {
1034        return encoding;
1035    }
1036
1037    public void setEncoding(String encoding)
1038    {
1039        this.encoding = encoding;
1040    }
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        AbstractFileConfiguration copy = (AbstractFileConfiguration) super.clone();
1057        copy.setBasePath(null);
1058        copy.setFileName(null);
1059        copy.initReloadingStrategy();
1060        return copy;
1061    }
1062
1063    /**
1064     * Helper method for initializing the reloading strategy.
1065     */
1066    private void initReloadingStrategy()
1067    {
1068        setReloadingStrategy(new InvariantReloadingStrategy());
1069    }
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            if (out != null)
1083            {
1084                out.close();
1085            }
1086        }
1087        catch (IOException e)
1088        {
1089            getLogger().warn("Could not close output stream", e);
1090        }
1091    }
1092}