    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 *     https://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 */
    017package org.apache.commons.configuration2;
    018
    019import org.apache.commons.configuration2.tree.ImmutableNode;
    020import org.apache.commons.configuration2.tree.InMemoryNodeModel;
    021import org.apache.commons.configuration2.tree.InMemoryNodeModelSupport;
    022import org.apache.commons.configuration2.tree.NodeModel;
    023import org.apache.commons.configuration2.tree.NodeSelector;
    024import org.apache.commons.configuration2.tree.TrackedNodeModel;
    025
    026/**
    027 * <p>
    028 * A specialized hierarchical configuration class with a node model that uses a tracked node of another node model as
    029 * its root node.
    030 * </p>
    031 * <p>
    032 * Configurations of this type are initialized with a special {@link NodeModel} operating on a specific tracked node of
    033 * the parent configuration and the corresponding {@link NodeSelector}. All property accessor methods are evaluated
    034 * relative to this root node. A good use case for a {@code SubnodeConfiguration} is when multiple properties from a
    035 * specific sub tree of the whole configuration need to be accessed. Then a {@code SubnodeConfiguration} can be created
    036 * with the parent node of the affected sub tree as root node. This allows for simpler property keys and is also more
    037 * efficient.
    038 * </p>
    039 * <p>
    040 * By making use of a tracked node as root node, a {@code SubnodeConfiguration} and its parent configuration initially
    041 * operate on the same hierarchy of configuration nodes. So if modifications are performed at the subnode configuration,
    042 * these changes are immediately visible in the parent configuration. Analogously will updates of the parent
    043 * configuration affect the {@code SubnodeConfiguration} if the sub tree spanned by the {@code SubnodeConfiguration}'s
    044 * root node is involved.
    045 * </p>
    046 * <p>
    047 * Note that by making use of a {@code NodeSelector} the {@code SubnodeConfiguration} is not associated with a physical
    048 * node instance, but the selection criteria stored in the selector are evaluated after each change of the nodes
    049 * structure. As an example consider that the selector uses a key with an index into a list element, say index 2. Now if
    050 * an update occurs on the underlying nodes structure which removes the first element in this list structure, the
    051 * {@code SubnodeConfiguration} still references the element with index 2 which is now another one.
    052 * </p>
    053 * <p>
    054 * There are also possible changes of the underlying nodes structure which completely detach the
    055 * {@code SubnodeConfiguration} from its parent configuration. For instance, the key referenced by the
    056 * {@code SubnodeConfiguration} could be removed in the parent configuration. If this happens, the
    057 * {@code SubnodeConfiguration} stays functional; however, it now operates on a separate node model than its parent
    058 * configuration. Changes made by one configuration are no longer visible for the other one (as the node models have no
    059 * longer overlapping nodes, there is no way to have a synchronization here).
    060 * </p>
    061 * <p>
    062 * When a subnode configuration is created, it inherits the settings of its parent configuration, for example some flags like
    063 * the {@code throwExceptionOnMissing} flag or the settings for handling list delimiters) or the expression engine. If
    064 * these settings are changed later in either the subnode or the parent configuration, the changes are not visible for
    065 * each other. So you could create a subnode configuration, and change its expression engine without affecting the
    066 * parent configuration.
    067 * </p>
    068 * <p>
    069 * Because the {@code SubnodeConfiguration} operates on the same nodes structure as its parent it uses the same
    070 * {@code Synchronizer} instance per default. This means that locks held on one {@code SubnodeConfiguration} also impact
    071 * the parent configuration and all of its other {@code SubnodeConfiguration} objects. You should not change this
    072 * without a good reason! Otherwise, there is the risk of data corruption when multiple threads access these
    073 * configuration concurrently.
    074 * </p>
    075 * <p>
    076 * From its purpose this class is quite similar to {@link SubsetConfiguration}. The difference is that a subset
    077 * configuration of a hierarchical configuration may combine multiple configuration nodes from different sub trees of
    078 * the configuration, while all nodes in a subnode configuration belong to the same sub tree. If an application can live
    079 * with this limitation, it is recommended to use this class instead of {@code SubsetConfiguration} because creating a
    080 * subset configuration is more expensive than creating a subnode configuration.
    081 * </p>
    082 * <p>
    083 * It is strongly recommended to create {@code SubnodeConfiguration} instances only through the
    084 * {@code configurationAt()} methods of a hierarchical configuration. These methods ensure that all necessary
    085 * initializations are done. Creating instances manually without doing proper initialization may break some of the
    086 * functionality provided by this class.
    087 * </p>
    088 *
    089 * @since 1.3
    090 */
    091public class SubnodeConfiguration extends BaseHierarchicalConfiguration {
    092
    093    /** Stores the parent configuration. */
    094    private final BaseHierarchicalConfiguration parent;
    095
    096    /** The node selector selecting the root node of this configuration. */
    097    private final NodeSelector rootSelector;
    098
    099    /**
    100     * Creates a new instance of {@code SubnodeConfiguration} and initializes it with all relevant properties.
    101     *
    102     * @param parent the parent configuration
    103     * @param model the {@code TrackedNodeModel} to be used for this configuration
    104     * @throws IllegalArgumentException if a required argument is missing
    105     */
    106    public SubnodeConfiguration(final BaseHierarchicalConfiguration parent, final TrackedNodeModel model) {
    107        super(model);
    108        if (parent == null) {
    109            throw new IllegalArgumentException("Parent configuration must not be null!");
    110        }
    111        if (model == null) {
    112            throw new IllegalArgumentException("Node model must not be null!");
    113        }
    114
    115        this.parent = parent;
    116        rootSelector = model.getSelector();
    117    }
    118
    119    /**
    120     * {@inheritDoc} This implementation returns a copy of the current node model with the same settings. However, it has to
    121     * be ensured that the track count for the node selector is increased.
    122     *
    123     * @return the node model for the clone
    124     */
    125    @Override
    126    protected NodeModel<ImmutableNode> cloneNodeModel() {
    127        final InMemoryNodeModel parentModel = (InMemoryNodeModel) getParent().getModel();
    128        parentModel.trackNode(getRootSelector(), getParent());
    129        return new TrackedNodeModel(getParent(), getRootSelector(), true);
    130    }
    131
    132    /**
    133     * Closes this sub configuration. This method closes the underlying {@link TrackedNodeModel}, thus causing the tracked
    134     * node acting as root node to be released. Per default, this happens automatically when the model is claimed by the
    135     * garbage collector. By calling this method explicitly, it can be indicated that this configuration is no longer used
    136     * and that resources used by it can be freed immediately.
    137     */
    138    public void close() {
    139        getTrackedModel().close();
    140    }
    141
    142    /**
    143     * {@inheritDoc} This implementation makes sure that the correct node model (the one of the parent) is used for the new
    144     * sub configuration.
    145     */
    146    @Override
    147    protected SubnodeConfiguration createSubConfigurationForTrackedNode(final NodeSelector selector, final InMemoryNodeModelSupport parentModelSupport) {
    148        return super.createSubConfigurationForTrackedNode(selector, getParent());
    149    }
    150
    151    /**
    152     * {@inheritDoc} This implementation returns a newly created node model with the correct root node set. Note that this
    153     * model is not used for property access, but only made available to clients that need to operate on the node structure
    154     * of this {@code SubnodeConfiguration}. Be aware that the implementation of this method is not very efficient.
    155     */
    156    @Override
    157    public InMemoryNodeModel getNodeModel() {
    158        final ImmutableNode root = getParent().getNodeModel().getTrackedNode(getRootSelector());
    159        return new InMemoryNodeModel(root);
    160    }
    161
    162    /**
    163     * Gets the parent configuration of this subnode configuration.
    164     *
    165     * @return the parent configuration
    166     */
    167    public BaseHierarchicalConfiguration getParent() {
    168        return parent;
    169    }
    170
    171    /**
    172     * Gets the node model of the root configuration. {@code SubnodeConfiguration} instances created from a hierarchical
    173     * configuration operate on the same node model, using different nodes as their local root nodes. With this method the
    174     * top-level node model can be obtained. It works even in constellations where a {@code SubnodeConfiguration} has been
    175     * created from another {@code SubnodeConfiguration}.
    176     *
    177     * @return the root node model
    178     * @since 2.2
    179     */
    180    public InMemoryNodeModel getRootNodeModel() {
    181        if (getParent() instanceof SubnodeConfiguration) {
    182            return ((SubnodeConfiguration) getParent()).getRootNodeModel();
    183        }
    184        return getParent().getNodeModel();
    185    }
    186
    187    /**
    188     * Gets the selector to the root node of this configuration.
    189     *
    190     * @return the {@code NodeSelector} to the root node
    191     */
    192    public NodeSelector getRootSelector() {
    193        return rootSelector;
    194    }
    195
    196    /**
    197     * {@inheritDoc} This implementation returns a sub selector of the selector of this configuration.
    198     */
    199    @Override
    200    protected NodeSelector getSubConfigurationNodeSelector(final String key) {
    201        return getRootSelector().subSelector(key);
    202    }
    203
    204    /**
    205     * {@inheritDoc} This implementation returns the parent model of the {@link TrackedNodeModel} used by this
    206     * configuration.
    207     */
    208    @Override
    209    protected InMemoryNodeModel getSubConfigurationParentModel() {
    210        return getTrackedModel().getParentModel();
    211    }
    212
    213    /**
    214     * Convenience method that returns the tracked model used by this sub configuration.
    215     *
    216     * @return the {@code TrackedNodeModel}
    217     */
    218    private TrackedNodeModel getTrackedModel() {
    219        return (TrackedNodeModel) getModel();
    220    }
    221}