View Javadoc

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