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    
018    package org.apache.commons.configuration;
019    
020    import java.io.File;
021    import java.io.IOException;
022    import java.io.InputStream;
023    import java.io.InputStreamReader;
024    import java.io.OutputStream;
025    import java.io.OutputStreamWriter;
026    import java.io.Reader;
027    import java.io.UnsupportedEncodingException;
028    import java.io.Writer;
029    import java.net.URL;
030    import java.util.Iterator;
031    import java.util.LinkedList;
032    import java.util.List;
033    
034    import org.apache.commons.configuration.reloading.InvariantReloadingStrategy;
035    import org.apache.commons.configuration.reloading.ReloadingStrategy;
036    import org.apache.commons.lang.StringUtils;
037    import 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     */
089    public abstract class AbstractFileConfiguration
090    extends BaseConfiguration
091    implements 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    }