001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.configuration;
019
020import java.io.File;
021import java.io.InputStream;
022import java.io.OutputStream;
023import java.io.Reader;
024import java.io.Writer;
025import java.net.URL;
026import java.util.Collection;
027import java.util.Iterator;
028import java.util.List;
029
030import org.apache.commons.configuration.event.ConfigurationErrorEvent;
031import org.apache.commons.configuration.event.ConfigurationErrorListener;
032import org.apache.commons.configuration.event.ConfigurationEvent;
033import org.apache.commons.configuration.event.ConfigurationListener;
034import org.apache.commons.configuration.reloading.Reloadable;
035import org.apache.commons.configuration.reloading.ReloadingStrategy;
036import org.apache.commons.configuration.tree.ConfigurationNode;
037
038/**
039 * <p>Base class for implementing file based hierarchical configurations.</p>
040 * <p>This class serves an analogous purpose as the
041 * {@link AbstractFileConfiguration} class for non hierarchical
042 * configurations. It behaves in exactly the same way, so please refer to the
043 * documentation of {@code AbstractFileConfiguration} for further details.</p>
044 *
045 * @since 1.2
046 *
047 * @author Emmanuel Bourg
048 * @version $Id: AbstractHierarchicalFileConfiguration.FileConfigurationDelegate.html 901729 2014-03-15 20:24:09Z oheger $
049 */
050public abstract class AbstractHierarchicalFileConfiguration
051extends HierarchicalConfiguration
052implements FileConfiguration, ConfigurationListener, ConfigurationErrorListener, FileSystemBased,
053        Reloadable
054{
055    /** Stores the delegate used for implementing functionality related to the
056     * {@code FileConfiguration} interface.
057     */
058    private FileConfigurationDelegate delegate;
059
060    /**
061     * Creates a new instance of {@code AbstractHierarchicalFileConfiguration}.
062     */
063    protected AbstractHierarchicalFileConfiguration()
064    {
065        initialize();
066    }
067
068    /**
069     * Creates a new instance of
070     * {@code AbstractHierarchicalFileConfiguration} and copies the
071     * content of the specified configuration into this object.
072     *
073     * @param c the configuration to copy
074     * @since 1.4
075     */
076    protected AbstractHierarchicalFileConfiguration(HierarchicalConfiguration c)
077    {
078        super(c);
079        initialize();
080    }
081
082    /**
083     * Creates and loads the configuration from the specified file.
084     *
085     * @param fileName The name of the plist file to load.
086     * @throws ConfigurationException Error while loading the file
087     */
088    public AbstractHierarchicalFileConfiguration(String fileName) throws ConfigurationException
089    {
090        this();
091        // store the file name
092        delegate.setFileName(fileName);
093
094        // load the file
095        load();
096    }
097
098    /**
099     * Creates and loads the configuration from the specified file.
100     *
101     * @param file The configuration file to load.
102     * @throws ConfigurationException Error while loading the file
103     */
104    public AbstractHierarchicalFileConfiguration(File file) throws ConfigurationException
105    {
106        this();
107        // set the file and update the url, the base path and the file name
108        setFile(file);
109
110        // load the file
111        if (file.exists())
112        {
113            load();
114        }
115    }
116
117    /**
118     * Creates and loads the configuration from the specified URL.
119     *
120     * @param url The location of the configuration file to load.
121     * @throws ConfigurationException Error while loading the file
122     */
123    public AbstractHierarchicalFileConfiguration(URL url) throws ConfigurationException
124    {
125        this();
126        // set the URL and update the base path and the file name
127        setURL(url);
128
129        // load the file
130        load();
131    }
132
133    /**
134     * Initializes this instance, mainly the internally used delegate object.
135     */
136    private void initialize()
137    {
138        delegate = createDelegate();
139        initDelegate(delegate);
140    }
141
142    @Override
143    protected void addPropertyDirect(String key, Object obj)
144    {
145        synchronized (delegate.getReloadLock())
146        {
147            super.addPropertyDirect(key, obj);
148            delegate.possiblySave();
149        }
150    }
151
152    @Override
153    public void clearProperty(String key)
154    {
155        synchronized (delegate.getReloadLock())
156        {
157            super.clearProperty(key);
158            delegate.possiblySave();
159        }
160    }
161
162    @Override
163    public void clearTree(String key)
164    {
165        synchronized (delegate.getReloadLock())
166        {
167            super.clearTree(key);
168            delegate.possiblySave();
169        }
170    }
171
172    @Override
173    public void setProperty(String key, Object value)
174    {
175        synchronized (delegate.getReloadLock())
176        {
177            super.setProperty(key, value);
178            delegate.possiblySave();
179        }
180    }
181
182    public void load() throws ConfigurationException
183    {
184        delegate.load();
185    }
186
187    public void load(String fileName) throws ConfigurationException
188    {
189        delegate.load(fileName);
190    }
191
192    public void load(File file) throws ConfigurationException
193    {
194        delegate.load(file);
195    }
196
197    public void load(URL url) throws ConfigurationException
198    {
199        delegate.load(url);
200    }
201
202    public void load(InputStream in) throws ConfigurationException
203    {
204        delegate.load(in);
205    }
206
207    public void load(InputStream in, String encoding) throws ConfigurationException
208    {
209        delegate.load(in, encoding);
210    }
211
212    public void save() throws ConfigurationException
213    {
214        delegate.save();
215    }
216
217    public void save(String fileName) throws ConfigurationException
218    {
219        delegate.save(fileName);
220    }
221
222    public void save(File file) throws ConfigurationException
223    {
224        delegate.save(file);
225    }
226
227    public void save(URL url) throws ConfigurationException
228    {
229        delegate.save(url);
230    }
231
232    public void save(OutputStream out) throws ConfigurationException
233    {
234        delegate.save(out);
235    }
236
237    public void save(OutputStream out, String encoding) throws ConfigurationException
238    {
239        delegate.save(out, encoding);
240    }
241
242    public String getFileName()
243    {
244        return delegate.getFileName();
245    }
246
247    public void setFileName(String fileName)
248    {
249        delegate.setFileName(fileName);
250    }
251
252    public String getBasePath()
253    {
254        return delegate.getBasePath();
255    }
256
257    public void setBasePath(String basePath)
258    {
259        delegate.setBasePath(basePath);
260    }
261
262    public File getFile()
263    {
264        return delegate.getFile();
265    }
266
267    public void setFile(File file)
268    {
269        delegate.setFile(file);
270    }
271
272    public URL getURL()
273    {
274        return delegate.getURL();
275    }
276
277    public void setURL(URL url)
278    {
279        delegate.setURL(url);
280    }
281
282    public void setAutoSave(boolean autoSave)
283    {
284        delegate.setAutoSave(autoSave);
285    }
286
287    public boolean isAutoSave()
288    {
289        return delegate.isAutoSave();
290    }
291
292    public ReloadingStrategy getReloadingStrategy()
293    {
294        return delegate.getReloadingStrategy();
295    }
296
297    public void setReloadingStrategy(ReloadingStrategy strategy)
298    {
299        delegate.setReloadingStrategy(strategy);
300    }
301
302    public void reload()
303    {
304        reload(false);
305    }
306
307    private boolean reload(boolean checkReload)
308    {
309        synchronized (delegate.getReloadLock())
310        {
311            setDetailEvents(false);
312            try
313            {
314                return delegate.reload(checkReload);
315            }
316            finally
317            {
318                setDetailEvents(true);
319            }
320        }
321    }
322
323    /**
324     * Reloads the associated configuration file. This method first clears the
325     * content of this configuration, then the associated configuration file is
326     * loaded again. Updates on this configuration which have not yet been saved
327     * are lost. Calling this method is like invoking {@code reload()}
328     * without checking the reloading strategy.
329     *
330     * @throws ConfigurationException if an error occurs
331     * @since 1.7
332     */
333    public void refresh() throws ConfigurationException
334    {
335        delegate.refresh();
336    }
337
338    public String getEncoding()
339    {
340        return delegate.getEncoding();
341    }
342
343    public void setEncoding(String encoding)
344    {
345        delegate.setEncoding(encoding);
346    }
347
348    @Override
349    public Object getReloadLock()
350    {
351        return delegate.getReloadLock();
352    }
353
354    @Override
355    public boolean containsKey(String key)
356    {
357        reload();
358        synchronized (delegate.getReloadLock())
359        {
360            return super.containsKey(key);
361        }
362    }
363
364    @Override
365    public Iterator<String> getKeys()
366    {
367        reload();
368        synchronized (delegate.getReloadLock())
369        {
370            return super.getKeys();
371        }
372    }
373
374    @Override
375    public Iterator<String> getKeys(String prefix)
376    {
377        reload();
378        synchronized (delegate.getReloadLock())
379        {
380            return super.getKeys(prefix);
381        }
382    }
383
384    @Override
385    public Object getProperty(String key)
386    {
387        if (reload(true))
388        {
389            // Avoid reloading again and getting the same error.
390            synchronized (delegate.getReloadLock())
391            {
392                return super.getProperty(key);
393            }
394        }
395        return null;
396    }
397
398    @Override
399    public boolean isEmpty()
400    {
401        reload();
402        synchronized (delegate.getReloadLock())
403        {
404            return super.isEmpty();
405        }
406    }
407
408    /**
409     * Directly adds sub nodes to this configuration. This implementation checks
410     * whether auto save is necessary after executing the operation.
411     *
412     * @param key the key where the nodes are to be added
413     * @param nodes a collection with the nodes to be added
414     * @since 1.5
415     */
416    @Override
417    public void addNodes(String key, Collection<? extends ConfigurationNode> nodes)
418    {
419        synchronized (delegate.getReloadLock())
420        {
421            super.addNodes(key, nodes);
422            delegate.possiblySave();
423        }
424    }
425
426    /**
427     * Fetches a list of nodes, which are selected by the specified key. This
428     * implementation will perform a reload if necessary.
429     *
430     * @param key the key
431     * @return a list with the selected nodes
432     */
433    @Override
434    protected List<ConfigurationNode> fetchNodeList(String key)
435    {
436        reload();
437        synchronized (delegate.getReloadLock())
438        {
439            return super.fetchNodeList(key);
440        }
441    }
442
443    /**
444     * Reacts on changes of an associated subnode configuration. If the auto
445     * save mechanism is active, the configuration must be saved.
446     *
447     * @param event the event describing the change
448     * @since 1.5
449     */
450    @Override
451    protected void subnodeConfigurationChanged(ConfigurationEvent event)
452    {
453        delegate.possiblySave();
454        super.subnodeConfigurationChanged(event);
455    }
456
457    /**
458     * Creates the file configuration delegate, i.e. the object that implements
459     * functionality required by the {@code FileConfiguration} interface.
460     * This base implementation will return an instance of the
461     * {@code FileConfigurationDelegate} class. Derived classes may
462     * override it to create a different delegate object.
463     *
464     * @return the file configuration delegate
465     */
466    protected FileConfigurationDelegate createDelegate()
467    {
468        return new FileConfigurationDelegate();
469    }
470
471    /**
472     * Helper method for initializing the file configuration delegate.
473     *
474     * @param del the delegate
475     */
476    private void initDelegate(FileConfigurationDelegate del)
477    {
478        del.addConfigurationListener(this);
479        del.addErrorListener(this);
480        del.setLogger(getLogger());
481    }
482
483    /**
484     * Reacts on configuration change events triggered by the delegate. These
485     * events are passed to the registered configuration listeners.
486     *
487     * @param event the triggered event
488     * @since 1.3
489     */
490    public void configurationChanged(ConfigurationEvent event)
491    {
492        // deliver reload events to registered listeners
493        setDetailEvents(true);
494        try
495        {
496            fireEvent(event.getType(), event.getPropertyName(), event
497                    .getPropertyValue(), event.isBeforeUpdate());
498        }
499        finally
500        {
501            setDetailEvents(false);
502        }
503    }
504
505    public void configurationError(ConfigurationErrorEvent event)
506    {
507        fireError(event.getType(), event.getPropertyName(), event.getPropertyValue(),
508                event.getCause());
509    }
510
511    /**
512     * Returns the file configuration delegate.
513     *
514     * @return the delegate
515     */
516    protected FileConfigurationDelegate getDelegate()
517    {
518        return delegate;
519    }
520
521    /**
522     * Allows to set the file configuration delegate.
523     * @param delegate the new delegate
524     */
525    protected void setDelegate(FileConfigurationDelegate delegate)
526    {
527        this.delegate = delegate;
528    }
529
530    /**
531     * Set the FileSystem to be used for this Configuration.
532     * @param fileSystem The FileSystem to use.
533     */
534    public void setFileSystem(FileSystem fileSystem)
535    {
536        delegate.setFileSystem(fileSystem);
537    }
538
539    /**
540     * Reset the FileSystem to the default;
541     */
542    public void resetFileSystem()
543    {
544        delegate.resetFileSystem();
545    }
546
547    /**
548     * Retrieve the FileSystem being used.
549     * @return The FileSystem.
550     */
551    public FileSystem getFileSystem()
552    {
553        return delegate.getFileSystem();
554    }
555
556    /**
557     * A special implementation of the {@code FileConfiguration} interface that is
558     * used internally to implement the {@code FileConfiguration} methods
559     * for hierarchical configurations.
560     */
561    protected class FileConfigurationDelegate extends AbstractFileConfiguration
562    {
563        public void load(Reader in) throws ConfigurationException
564        {
565            AbstractHierarchicalFileConfiguration.this.load(in);
566        }
567
568        public void save(Writer out) throws ConfigurationException
569        {
570            AbstractHierarchicalFileConfiguration.this.save(out);
571        }
572
573        @Override
574        public void clear()
575        {
576            AbstractHierarchicalFileConfiguration.this.clear();
577        }
578    }
579}