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