    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.InputStream;
    022    import java.io.OutputStream;
    023    import java.io.Reader;
    024    import java.io.Writer;
    025    import java.net.URL;
    026    import java.util.Collection;
    027    import java.util.Iterator;
    028    import java.util.List;
    029
    030    import org.apache.commons.configuration.event.ConfigurationErrorEvent;
    031    import org.apache.commons.configuration.event.ConfigurationErrorListener;
    032    import org.apache.commons.configuration.event.ConfigurationEvent;
    033    import org.apache.commons.configuration.event.ConfigurationListener;
    034    import org.apache.commons.configuration.reloading.Reloadable;
    035    import org.apache.commons.configuration.reloading.ReloadingStrategy;
    036    import 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.java 1206575 2011-11-26 20:07:52Z oheger $
    049     */
    050    public abstract class AbstractHierarchicalFileConfiguration
    051    extends HierarchicalConfiguration
    052    implements 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    }