Source code
Description: source: package: org.apache.commons.configuration2.tree, class: InMemoryNodeModel

    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.tree;
    018
    019import java.util.ArrayList;
    020import java.util.Collection;
    021import java.util.Collections;
    022import java.util.HashMap;
    023import java.util.Iterator;
    024import java.util.LinkedList;
    025import java.util.List;
    026import java.util.Map;
    027import java.util.concurrent.atomic.AtomicReference;
    028
    029import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
    030import org.apache.commons.lang3.mutable.Mutable;
    031import org.apache.commons.lang3.mutable.MutableObject;
    032
    033/**
    034 * <p>
    035 * A specialized node model implementation which operates on {@link ImmutableNode} structures.
    036 * </p>
    037 * <p>
    038 * This {@code NodeModel} implementation keeps all its data as a tree of {@link ImmutableNode} objects in memory. The
    039 * managed structure can be manipulated in a thread-safe, non-blocking way. This is achieved by using atomic variables:
    040 * The root of the tree is stored in an atomic reference variable. Each update operation causes a new structure to be
    041 * constructed (which reuses as much from the original structure as possible). The old root node is then replaced by the
    042 * new one using an atomic compare-and-set operation. If this fails, the manipulation has to be done anew on the updated
    043 * structure.
    044 * </p>
    045 *
    046 * @since 2.0
    047 */
    048public class InMemoryNodeModel implements NodeModel<ImmutableNode> {
    049
    050    /**
    051     * An interface used internally for handling concurrent updates. An implementation has to populate the passed in
    052     * {@code ModelTransaction}. The transaction is then executed, and an atomic update of the model's {@code TreeData} is
    053     * attempted. If this fails - because another update came across -, the whole operation has to be tried anew.
    054     */
    055    private interface TransactionInitializer {
    056
    057        /**
    058         * Initializes the specified transaction for an update operation. The return value indicates whether the transaction
    059         * should be executed. A result of <strong>false</strong> means that the update is to be aborted (maybe another update method was
    060         * called).
    061         *
    062         * @param tx the transaction to be initialized
    063         * @return a flag whether the update should continue
    064         */
    065        boolean initTransaction(ModelTransaction tx);
    066    }
    067
    068    /**
    069     * A dummy node handler instance used in operations which require only a limited functionality.
    070     */
    071    private static final NodeHandler<ImmutableNode> DUMMY_HANDLER = new TreeData(null, Collections.<ImmutableNode, ImmutableNode>emptyMap(),
    072        Collections.<ImmutableNode, ImmutableNode>emptyMap(), null, new ReferenceTracker());
    073
    074    /**
    075     * Handles an add property operation if the property to be added is an attribute.
    076     *
    077     * @param tx the transaction
    078     * @param addData the {@code NodeAddData}
    079     * @param values the collection with node values
    080     */
    081    private static void addAttributeProperty(final ModelTransaction tx, final NodeAddData<ImmutableNode> addData, final Iterable<?> values) {
    082        if (addData.getPathNodes().isEmpty()) {
    083            tx.addAttributeOperation(addData.getParent(), addData.getNewNodeName(), values.iterator().next());
    084        } else {
    085            final int pathNodeCount = addData.getPathNodes().size();
    086            final ImmutableNode childWithAttribute = new ImmutableNode.Builder().name(addData.getPathNodes().get(pathNodeCount - 1))
    087                .addAttribute(addData.getNewNodeName(), values.iterator().next()).create();
    088            final ImmutableNode newChild = pathNodeCount > 1
    089                ? createNodeOnPath(addData.getPathNodes().subList(0, pathNodeCount - 1).iterator(), Collections.singleton(childWithAttribute))
    090                : childWithAttribute;
    091            tx.addAddNodeOperation(addData.getParent(), newChild);
    092        }
    093    }
    094
    095    /**
    096     * Handles an add property operation if the property to be added is a node.
    097     *
    098     * @param tx the transaction
    099     * @param addData the {@code NodeAddData}
    100     * @param values the collection with node values
    101     */
    102    private static void addNodeProperty(final ModelTransaction tx, final NodeAddData<ImmutableNode> addData, final Iterable<?> values) {
    103        final Collection<ImmutableNode> newNodes = createNodesToAdd(addData.getNewNodeName(), values);
    104        addNodesByAddData(tx, addData, newNodes);
    105    }
    106
    107    /**
    108     * Initializes a transaction to add a collection of nodes as described by a {@code NodeAddData} object. If necessary,
    109     * new path nodes are created. Eventually, the new nodes are added as children to the specified target node.
    110     *
    111     * @param tx the transaction
    112     * @param addData the {@code NodeAddData}
    113     * @param newNodes the collection of new child nodes
    114     */
    115    private static void addNodesByAddData(final ModelTransaction tx, final NodeAddData<ImmutableNode> addData, final Collection<ImmutableNode> newNodes) {
    116        if (addData.getPathNodes().isEmpty()) {
    117            tx.addAddNodesOperation(addData.getParent(), newNodes);
    118        } else {
    119            final ImmutableNode newChild = createNodeToAddWithPath(addData, newNodes);
    120            tx.addAddNodeOperation(addData.getParent(), newChild);
    121        }
    122    }
    123
    124    /**
    125     * Creates an exception referring to an invalid key for adding properties. Such an exception is thrown when an operation
    126     * tries to add something to an attribute.
    127     *
    128     * @param key the invalid key causing this exception
    129     * @return the exception
    130     */
    131    private static IllegalArgumentException attributeKeyException(final String key) {
    132        return new IllegalArgumentException("New nodes cannot be added to an attribute key: " + key);
    133    }
    134
    135    /**
    136     * Checks if the passed in node is defined. Result is <strong>true</strong> if the node contains any data.
    137     *
    138     * @param node the node in question
    139     * @return <strong>true</strong> if the node is defined, <strong>false</strong> otherwise
    140     */
    141    static boolean checkIfNodeDefined(final ImmutableNode node) {
    142        return node.getValue() != null || !node.getChildren().isEmpty() || !node.getAttributes().isEmpty();
    143    }
    144
    145    /**
    146     * Creates a new data object with a tracked child node of the given parent node. If such a child node already exists, it
    147     * is used. Otherwise, a new one is created.
    148     *
    149     * @param current the current {@code TreeData} object
    150     * @param parent the parent node
    151     * @param childName the name of the child node
    152     * @param resolver the {@code NodeKeyResolver}
    153     * @param refSelector here the newly created {@code NodeSelector} is returned
    154     * @return the new {@code TreeData} instance
    155     */
    156    private static TreeData createDataWithTrackedChildNode(final TreeData current, final ImmutableNode parent, final String childName,
    157        final NodeKeyResolver<ImmutableNode> resolver, final MutableObject<NodeSelector> refSelector) {
    158        final TreeData newData;
    159        final List<ImmutableNode> namedChildren = current.getChildren(parent, childName);
    160        if (!namedChildren.isEmpty()) {
    161            newData = updateDataWithNewTrackedNode(current, namedChildren.get(0), resolver, refSelector);
    162        } else {
    163            final ImmutableNode child = new ImmutableNode.Builder().name(childName).create();
    164            final ModelTransaction tx = new ModelTransaction(current, null, resolver);
    165            tx.addAddNodeOperation(parent, child);
    166            newData = updateDataWithNewTrackedNode(tx.execute(), child, resolver, refSelector);
    167        }
    168        return newData;
    169    }
    170
    171    /**
    172     * Recursive helper method for creating a path node for an add operation. All path nodes except for the last have a
    173     * single child. The last path node has the new nodes as children.
    174     *
    175     * @param it the iterator over the names of the path nodes
    176     * @param newNodes the collection of new child nodes
    177     * @return the newly created path node
    178     */
    179    private static ImmutableNode createNodeOnPath(final Iterator<String> it, final Collection<ImmutableNode> newNodes) {
    180        final String nodeName = it.next();
    181        final ImmutableNode.Builder builder;
    182        if (it.hasNext()) {
    183            builder = new ImmutableNode.Builder(1);
    184            builder.addChild(createNodeOnPath(it, newNodes));
    185        } else {
    186            builder = new ImmutableNode.Builder(newNodes.size());
    187            builder.addChildren(newNodes);
    188        }
    189        return builder.name(nodeName).create();
    190    }
    191
    192    /**
    193     * Creates a collection with new nodes with a given name and a value from a given collection.
    194     *
    195     * @param newNodeName the name of the new nodes
    196     * @param values the collection with node values
    197     * @return the newly created collection
    198     */
    199    private static Collection<ImmutableNode> createNodesToAdd(final String newNodeName, final Iterable<?> values) {
    200        final Collection<ImmutableNode> nodes = new LinkedList<>();
    201        values.forEach(value -> nodes.add(new ImmutableNode.Builder().name(newNodeName).value(value).create()));
    202        return nodes;
    203    }
    204
    205    /**
    206     * Creates a node structure consisting of the path nodes defined by the passed in {@code NodeAddData} instance and all
    207     * new child nodes.
    208     *
    209     * @param addData the {@code NodeAddData}
    210     * @param newNodes the collection of new child nodes
    211     * @return the parent node of the newly created hierarchy
    212     */
    213    private static ImmutableNode createNodeToAddWithPath(final NodeAddData<ImmutableNode> addData, final Collection<ImmutableNode> newNodes) {
    214        return createNodeOnPath(addData.getPathNodes().iterator(), newNodes);
    215    }
    216
    217    /**
    218     * Creates tracked node entries for the specified nodes and creates the corresponding selectors.
    219     *
    220     * @param refSelectors the reference where to store the selectors
    221     * @param nodes the nodes to be tracked
    222     * @param current the current {@code TreeData} object
    223     * @param resolver the {@code NodeKeyResolver}
    224     * @return the updated {@code TreeData} object
    225     */
    226    private static TreeData createSelectorsForTrackedNodes(final Mutable<Collection<NodeSelector>> refSelectors, final List<ImmutableNode> nodes,
    227            final TreeData current, final NodeKeyResolver<ImmutableNode> resolver) {
    228        final List<NodeSelector> selectors = new ArrayList<>(nodes.size());
    229        final Map<ImmutableNode, String> cache = new HashMap<>();
    230        nodes.forEach(node -> selectors.add(new NodeSelector(resolver.nodeKey(node, cache, current))));
    231        refSelectors.setValue(selectors);
    232        final NodeTracker newTracker = current.getNodeTracker().trackNodes(selectors, nodes);
    233        return current.updateNodeTracker(newTracker);
    234    }
    235
    236    /**
    237     * Determines the name of the root node for a merge operation. If a root name is provided, it is used. Otherwise, if the
    238     * current root node has no name, the name of the node to be merged is used. A result of <strong>null</strong> means that no node
    239     * name has to be set.
    240     *
    241     * @param rootNode the current root node
    242     * @param node the node to be merged with the root node
    243     * @param rootName the name of the resulting node
    244     * @return the new name of the root node
    245     */
    246    private static String determineRootName(final ImmutableNode rootNode, final ImmutableNode node, final String rootName) {
    247        if (rootName != null) {
    248            return rootName;
    249        }
    250        if (rootNode.getNodeName() == null) {
    251            return node.getNodeName();
    252        }
    253        return null;
    254    }
    255
    256    /**
    257     * Initializes a transaction to clear the values of a property based on the passed in collection of affected results.
    258     *
    259     * @param tx the transaction to be initialized
    260     * @param results a collection with results pointing to the nodes to be cleared
    261     * @return a flag whether there are elements to be cleared
    262     */
    263    private static boolean initializeClearTransaction(final ModelTransaction tx, final Collection<QueryResult<ImmutableNode>> results) {
    264        results.forEach(result -> {
    265            if (result.isAttributeResult()) {
    266                tx.addRemoveAttributeOperation(result.getNode(), result.getAttributeName());
    267            } else {
    268                tx.addClearNodeValueOperation(result.getNode());
    269            }
    270        });
    271
    272        return !results.isEmpty();
    273    }
    274
    275    /**
    276     * Initializes a transaction to change the values of some query results based on the passed in map.
    277     *
    278     * @param tx the transaction to be initialized
    279     * @param changedValues the map defining the elements to be changed
    280     * @return a flag whether there are elements to be updated
    281     */
    282    private static boolean initializeUpdateTransaction(final ModelTransaction tx, final Map<QueryResult<ImmutableNode>, Object> changedValues) {
    283        changedValues.forEach((k, v) -> {
    284            final ImmutableNode node = k.getNode();
    285            if (k.isAttributeResult()) {
    286                tx.addAttributeOperation(node, k.getAttributeName(), v);
    287            } else {
    288                tx.addChangeNodeValueOperation(node, v);
    289            }
    290        });
    291
    292        return !changedValues.isEmpty();
    293    }
    294
    295    /**
    296     * Determines the initial root node of this model. If a root node has been provided, it is used. Otherwise, an empty
    297     * dummy root node is created.
    298     *
    299     * @param providedRoot the passed in root node
    300     * @return the root node to be used
    301     */
    302    private static ImmutableNode initialRootNode(final ImmutableNode providedRoot) {
    303        return providedRoot != null ? providedRoot : new ImmutableNode.Builder().create();
    304    }
    305
    306    /**
    307     * Adds a tracked node that has already been resolved to the specified data object.
    308     *
    309     * @param current the current {@code TreeData} object
    310     * @param node the node in question
    311     * @param resolver the {@code NodeKeyResolver}
    312     * @param refSelector here the newly created {@code NodeSelector} is returned
    313     * @return the new {@code TreeData} instance
    314     */
    315    private static TreeData updateDataWithNewTrackedNode(final TreeData current, final ImmutableNode node, final NodeKeyResolver<ImmutableNode> resolver,
    316        final MutableObject<NodeSelector> refSelector) {
    317        final NodeSelector selector = new NodeSelector(resolver.nodeKey(node, new HashMap<>(), current));
    318        refSelector.setValue(selector);
    319        final NodeTracker newTracker = current.getNodeTracker().trackNodes(Collections.singleton(selector), Collections.singleton(node));
    320        return current.updateNodeTracker(newTracker);
    321    }
    322
    323    /**
    324     * Updates the mapping from nodes to their parents for the passed in hierarchy of nodes. This method traverses all
    325     * children and grand-children of the passed in root node. For each node in the subtree the parent relation is added to
    326     * the map.
    327     *
    328     * @param parents the map with parent nodes
    329     * @param root the root node of the current tree
    330     */
    331    static void updateParentMapping(final Map<ImmutableNode, ImmutableNode> parents, final ImmutableNode root) {
    332        NodeTreeWalker.INSTANCE.walkBFS(root, new ConfigurationNodeVisitorAdapter<ImmutableNode>() {
    333            @Override
    334            public void visitBeforeChildren(final ImmutableNode node, final NodeHandler<ImmutableNode> handler) {
    335                node.forEach(c -> parents.put(c, node));
    336            }
    337        }, DUMMY_HANDLER);
    338    }
    339
    340    /**
    341     * Checks whether the specified collection with values is not empty.
    342     *
    343     * @param values the collection with node values
    344     * @return <strong>true</strong> if values are provided, <strong>false</strong> otherwise
    345     */
    346    private static boolean valuesNotEmpty(final Iterable<?> values) {
    347        return values.iterator().hasNext();
    348    }
    349
    350    /** Stores information about the current nodes structure. */
    351    private final AtomicReference<TreeData> structure;
    352
    353    /**
    354     * Creates a new instance of {@code InMemoryNodeModel} which is initialized with an empty root node.
    355     */
    356    public InMemoryNodeModel() {
    357        this(null);
    358    }
    359
    360    /**
    361     * Creates a new instance of {@code InMemoryNodeModel} and initializes it from the given root node. If the passed in
    362     * node is <strong>null</strong>, a new, empty root node is created.
    363     *
    364     * @param root the new root node for this model
    365     */
    366    public InMemoryNodeModel(final ImmutableNode root) {
    367        structure = new AtomicReference<>(createTreeData(initialRootNode(root), null));
    368    }
    369
    370    @Override
    371    public void addNodes(final String key, final Collection<? extends ImmutableNode> nodes, final NodeKeyResolver<ImmutableNode> resolver) {
    372        addNodes(key, null, nodes, resolver);
    373    }
    374
    375    /**
    376     * Adds new nodes using a tracked node as root node. This method works like the normal {@code addNodes()} method, but
    377     * the origin of the operation (also for the interpretation of the passed in key) is a tracked node identified by the
    378     * passed in {@code NodeSelector}. The selector can be <strong>null</strong>, then the root node is assumed.
    379     *
    380     * @param key the key
    381     * @param selector the {@code NodeSelector} defining the root node (or <strong>null</strong>)
    382     * @param nodes the collection of new nodes to be added
    383     * @param resolver the {@code NodeKeyResolver}
    384     * @throws ConfigurationRuntimeException if the selector cannot be resolved
    385     */
    386    public void addNodes(final String key, final NodeSelector selector, final Collection<? extends ImmutableNode> nodes,
    387        final NodeKeyResolver<ImmutableNode> resolver) {
    388        if (nodes != null && !nodes.isEmpty()) {
    389            updateModel(tx -> {
    390                final List<QueryResult<ImmutableNode>> results = resolver.resolveKey(tx.getQueryRoot(), key, tx.getCurrentData());
    391                if (results.size() == 1) {
    392                    if (results.get(0).isAttributeResult()) {
    393                        throw attributeKeyException(key);
    394                    }
    395                    tx.addAddNodesOperation(results.get(0).getNode(), nodes);
    396                } else {
    397                    final NodeAddData<ImmutableNode> addData = resolver.resolveAddKey(tx.getQueryRoot(), key, tx.getCurrentData());
    398                    if (addData.isAttribute()) {
    399                        throw attributeKeyException(key);
    400                    }
    401                    final ImmutableNode newNode = new ImmutableNode.Builder(nodes.size()).name(addData.getNewNodeName()).addChildren(nodes).create();
    402                    addNodesByAddData(tx, addData, Collections.singleton(newNode));
    403                }
    404                return true;
    405            }, selector, resolver);
    406        }
    407    }
    408
    409    @Override
    410    public void addProperty(final String key, final Iterable<?> values, final NodeKeyResolver<ImmutableNode> resolver) {
    411        addProperty(key, null, values, resolver);
    412    }
    413
    414    /**
    415     * Adds new property values using a tracked node as root node. This method works like the normal {@code addProperty()}
    416     * method, but the origin of the operation (also for the interpretation of the passed in key) is a tracked node
    417     * identified by the passed in {@code NodeSelector}. The selector can be <strong>null</strong>, then the root node is assumed.
    418     *
    419     * @param key the key
    420     * @param selector the {@code NodeSelector} defining the root node (or <strong>null</strong>)
    421     * @param values the values to be added
    422     * @param resolver the {@code NodeKeyResolver}
    423     * @throws ConfigurationRuntimeException if the selector cannot be resolved
    424     */
    425    public void addProperty(final String key, final NodeSelector selector, final Iterable<?> values, final NodeKeyResolver<ImmutableNode> resolver) {
    426        if (valuesNotEmpty(values)) {
    427            updateModel(tx -> {
    428                initializeAddTransaction(tx, key, values, resolver);
    429                return true;
    430            }, selector, resolver);
    431        }
    432    }
    433
    434    /**
    435     * {@inheritDoc} A new empty root node is created with the same name as the current root node. Implementation note:
    436     * Because this is a hard reset the usual dance for dealing with concurrent updates is not required here.
    437     *
    438     * @param resolver the {@code NodeKeyResolver}
    439     */
    440    @Override
    441    public void clear(final NodeKeyResolver<ImmutableNode> resolver) {
    442        final ImmutableNode newRoot = new ImmutableNode.Builder().name(getRootNode().getNodeName()).create();
    443        setRootNode(newRoot);
    444    }
    445
    446    /**
    447     * {@inheritDoc} If this operation leaves an affected node in an undefined state, it is removed from the model.
    448     */
    449    @Override
    450    public void clearProperty(final String key, final NodeKeyResolver<ImmutableNode> resolver) {
    451        clearProperty(key, null, resolver);
    452    }
    453
    454    /**
    455     * Clears a property using a tracked node as root node. This method works like the normal {@code clearProperty()}
    456     * method, but the origin of the operation (also for the interpretation of the passed in key) is a tracked node
    457     * identified by the passed in {@code NodeSelector}. The selector can be <strong>null</strong>, then the root node is assumed.
    458     *
    459     * @param key the key
    460     * @param selector the {@code NodeSelector} defining the root node (or <strong>null</strong>)
    461     * @param resolver the {@code NodeKeyResolver}
    462     * @throws ConfigurationRuntimeException if the selector cannot be resolved
    463     */
    464    public void clearProperty(final String key, final NodeSelector selector, final NodeKeyResolver<ImmutableNode> resolver) {
    465        updateModel(tx -> {
    466            final List<QueryResult<ImmutableNode>> results = resolver.resolveKey(tx.getQueryRoot(), key, tx.getCurrentData());
    467            return initializeClearTransaction(tx, results);
    468        }, selector, resolver);
    469    }
    470
    471    /**
    472     * {@inheritDoc} This implementation checks whether nodes become undefined after subtrees have been removed. If this is
    473     * the case, such nodes are removed, too. Return value is a collection with {@code QueryResult} objects for the elements
    474     * to be removed from the model.
    475     */
    476    @Override
    477    public List<QueryResult<ImmutableNode>> clearTree(final String key, final NodeKeyResolver<ImmutableNode> resolver) {
    478        return clearTree(key, null, resolver);
    479    }
    480
    481    /**
    482     * Clears a whole sub tree using a tracked node as root node. This method works like the normal {@code clearTree()}
    483     * method, but the origin of the operation (also for the interpretation of the passed in key) is a tracked node
    484     * identified by the passed in {@code NodeSelector}. The selector can be <strong>null</strong>, then the root node is assumed.
    485     *
    486     * @param key the key
    487     * @param selector the {@code NodeSelector} defining the root node (or <strong>null</strong>)
    488     * @param resolver the {@code NodeKeyResolver}
    489     * @return a list with the results to be removed
    490     * @throws ConfigurationRuntimeException if the selector cannot be resolved
    491     */
    492    public List<QueryResult<ImmutableNode>> clearTree(final String key, final NodeSelector selector, final NodeKeyResolver<ImmutableNode> resolver) {
    493        final List<QueryResult<ImmutableNode>> removedElements = new LinkedList<>();
    494        updateModel(tx -> {
    495            boolean changes = false;
    496            final TreeData currentStructure = tx.getCurrentData();
    497            final List<QueryResult<ImmutableNode>> results = resolver.resolveKey(tx.getQueryRoot(), key, currentStructure);
    498            removedElements.clear();
    499            removedElements.addAll(results);
    500            for (final QueryResult<ImmutableNode> result : results) {
    501                if (result.isAttributeResult()) {
    502                    tx.addRemoveAttributeOperation(result.getNode(), result.getAttributeName());
    503                } else {
    504                    if (result.getNode() == currentStructure.getRootNode()) {
    505                        // the whole model is to be cleared
    506                        clear(resolver);
    507                        return false;
    508                    }
    509                    tx.addRemoveNodeOperation(currentStructure.getParent(result.getNode()), result.getNode());
    510                }
    511                changes = true;
    512            }
    513            return changes;
    514        }, selector, resolver);
    515
    516        return removedElements;
    517    }
    518
    519    /**
    520     * Creates the mapping to parent nodes for the nodes structured represented by the passed in root node. Each node is
    521     * assigned its parent node. Here an iterative algorithm is used rather than a recursive one to avoid stack overflow for
    522     * huge structures.
    523     *
    524     * @param root the root node of the structure
    525     * @return the parent node mapping
    526     */
    527    private Map<ImmutableNode, ImmutableNode> createParentMapping(final ImmutableNode root) {
    528        final Map<ImmutableNode, ImmutableNode> parents = new HashMap<>();
    529        updateParentMapping(parents, root);
    530        return parents;
    531    }
    532
    533    /**
    534     * Creates a {@code TreeData} object for the specified root node.
    535     *
    536     * @param root the root node of the current tree
    537     * @param current the current {@code TreeData} object (may be <strong>null</strong>)
    538     * @return the {@code TreeData} describing the current tree
    539     */
    540    private TreeData createTreeData(final ImmutableNode root, final TreeData current) {
    541        final NodeTracker newTracker = current != null ? current.getNodeTracker().detachAllTrackedNodes() : new NodeTracker();
    542        return createTreeDataForRootAndTracker(root, newTracker);
    543    }
    544
    545    /**
    546     * Creates a {@code TreeData} object for the specified root node and {@code NodeTracker}. Other parameters are set to
    547     * default values.
    548     *
    549     * @param root the new root node for this model
    550     * @param newTracker the new {@code NodeTracker}
    551     * @return the new {@code TreeData} object
    552     */
    553    private TreeData createTreeDataForRootAndTracker(final ImmutableNode root, final NodeTracker newTracker) {
    554        return new TreeData(root, createParentMapping(root), Collections.<ImmutableNode, ImmutableNode>emptyMap(), newTracker, new ReferenceTracker());
    555    }
    556
    557    /**
    558     * Executes a transaction on the current data of this model. This method is called if an operation is to be executed on
    559     * the model's root node or a tracked node which is not yet detached.
    560     *
    561     * @param txInit the {@code TransactionInitializer}
    562     * @param selector an optional {@code NodeSelector} defining the target node
    563     * @param currentData the current data of the model
    564     * @param resolver the {@code NodeKeyResolver}
    565     * @return a flag whether the operation has been completed successfully
    566     */
    567    private boolean executeTransactionOnCurrentStructure(final TransactionInitializer txInit, final NodeSelector selector, final TreeData currentData,
    568        final NodeKeyResolver<ImmutableNode> resolver) {
    569        final boolean done;
    570        final ModelTransaction tx = new ModelTransaction(currentData, selector, resolver);
    571        if (!txInit.initTransaction(tx)) {
    572            done = true;
    573        } else {
    574            final TreeData newData = tx.execute();
    575            done = structure.compareAndSet(tx.getCurrentData(), newData);
    576        }
    577        return done;
    578    }
    579
    580    /**
    581     * Tries to execute a transaction on the model of a detached tracked node. This method checks whether the target node of
    582     * the transaction is a tracked node and if this node is already detached. If this is the case, the update operation is
    583     * independent on this model and has to be executed on the specific model for the detached node.
    584     *
    585     * @param txInit the {@code TransactionInitializer}
    586     * @param selector an optional {@code NodeSelector} defining the target node
    587     * @param currentData the current data of the model
    588     * @param resolver the {@code NodeKeyResolver} @return a flag whether the transaction could be executed
    589     * @throws ConfigurationRuntimeException if the selector cannot be resolved
    590     */
    591    private boolean executeTransactionOnDetachedTrackedNode(final TransactionInitializer txInit, final NodeSelector selector, final TreeData currentData,
    592        final NodeKeyResolver<ImmutableNode> resolver) {
    593        if (selector != null) {
    594            final InMemoryNodeModel detachedNodeModel = currentData.getNodeTracker().getDetachedNodeModel(selector);
    595            if (detachedNodeModel != null) {
    596                detachedNodeModel.updateModel(txInit, null, resolver);
    597                return true;
    598            }
    599        }
    600
    601        return false;
    602    }
    603
    604    /**
    605     * {@inheritDoc} This implementation simply returns the current root node of this model.
    606     */
    607    @Override
    608    public ImmutableNode getInMemoryRepresentation() {
    609        return getTreeData().getRootNode();
    610    }
    611
    612    /**
    613     * {@inheritDoc} {@code InMemoryNodeModel} implements the {@code NodeHandler} interface itself. So this implementation
    614     * just returns the <strong>this</strong> reference.
    615     */
    616    @Override
    617    public NodeHandler<ImmutableNode> getNodeHandler() {
    618        return getReferenceNodeHandler();
    619    }
    620
    621    /**
    622     * Gets a {@code ReferenceNodeHandler} object for this model. This extended node handler can be used to query
    623     * references objects stored for this model.
    624     *
    625     * @return the {@code ReferenceNodeHandler}
    626     */
    627    public ReferenceNodeHandler getReferenceNodeHandler() {
    628        return getTreeData();
    629    }
    630
    631    /**
    632     * Gets the root node of this mode. Note: This method should be used with care. The model may be updated concurrently
    633     * which causes the root node to be replaced. If the root node is to be processed further (for example by executing queries on
    634     * it), the model should be asked for its {@code NodeHandler}, and the root node should be obtained from there. The
    635     * connection between a node handler and its root node remain constant because an update of the model causes the whole
    636     * node handler to be replaced.
    637     *
    638     * @return the current root node
    639     */
    640    public ImmutableNode getRootNode() {
    641        return getTreeData().getRootNode();
    642    }
    643
    644    /**
    645     * Gets the current {@code ImmutableNode} instance associated with the given {@code NodeSelector}. The node must be a
    646     * tracked node, i.e. {@link #trackNode(NodeSelector, NodeKeyResolver)} must have been called before with the given
    647     * selector.
    648     *
    649     * @param selector the {@code NodeSelector} defining the desired node
    650     * @return the current {@code ImmutableNode} associated with this selector
    651     * @throws ConfigurationRuntimeException if the selector is unknown
    652     */
    653    public ImmutableNode getTrackedNode(final NodeSelector selector) {
    654        return structure.get().getNodeTracker().getTrackedNode(selector);
    655    }
    656
    657    /**
    658     * Gets a {@code NodeHandler} for a tracked node. Such a handler may be required for operations on a sub tree of the
    659     * model. The handler to be returned depends on the current state of the tracked node. If it is still active, a handler
    660     * is used which shares some data (especially the parent mapping) with this model. Detached track nodes in contrast have
    661     * their own separate model; in this case a handler associated with this model is returned.
    662     *
    663     * @param selector the {@code NodeSelector} defining the tracked node
    664     * @return a {@code NodeHandler} for this tracked node
    665     * @throws ConfigurationRuntimeException if the selector is unknown
    666     */
    667    public NodeHandler<ImmutableNode> getTrackedNodeHandler(final NodeSelector selector) {
    668        final TreeData currentData = structure.get();
    669        final InMemoryNodeModel detachedNodeModel = currentData.getNodeTracker().getDetachedNodeModel(selector);
    670        return detachedNodeModel != null ? detachedNodeModel.getNodeHandler()
    671            : new TrackedNodeHandler(currentData.getNodeTracker().getTrackedNode(selector), currentData);
    672    }
    673
    674    /**
    675     * Gets the current {@code TreeData} object. This object contains all information about the current node structure.
    676     *
    677     * @return the current {@code TreeData} object
    678     */
    679    TreeData getTreeData() {
    680        return structure.get();
    681    }
    682
    683    /**
    684     * Initializes a transaction for an add operation.
    685     *
    686     * @param tx the transaction to be initialized
    687     * @param key the key
    688     * @param values the collection with node values
    689     * @param resolver the {@code NodeKeyResolver}
    690     */
    691    private void initializeAddTransaction(final ModelTransaction tx, final String key, final Iterable<?> values,
    692        final NodeKeyResolver<ImmutableNode> resolver) {
    693        final NodeAddData<ImmutableNode> addData = resolver.resolveAddKey(tx.getQueryRoot(), key, tx.getCurrentData());
    694        if (addData.isAttribute()) {
    695            addAttributeProperty(tx, addData, values);
    696        } else {
    697            addNodeProperty(tx, addData, values);
    698        }
    699    }
    700
    701    /**
    702     * Returns a flag whether the specified tracked node is detached. As long as the {@code NodeSelector} associated with
    703     * that node returns a single instance, the tracked node is said to be <em>life</em>. If now an update of the model
    704     * happens which invalidates the selector (maybe the target node was removed), the tracked node becomes detached. It is
    705     * still possible to query the node; here the latest valid instance is returned. But further changes on the node model
    706     * are no longer tracked for this node. So even if there are further changes which would make the {@code NodeSelector}
    707     * valid again, the tracked node stays in detached state.
    708     *
    709     * @param selector the {@code NodeSelector} defining the desired node
    710     * @return a flag whether this tracked node is in detached state
    711     * @throws ConfigurationRuntimeException if the selector is unknown
    712     */
    713    public boolean isTrackedNodeDetached(final NodeSelector selector) {
    714        return structure.get().getNodeTracker().isTrackedNodeDetached(selector);
    715    }
    716
    717    /**
    718     * Merges the root node of this model with the specified node. This method is typically caused by configuration
    719     * implementations when a configuration source is loaded, and its data has to be added to the model. It is possible to
    720     * define the new name of the root node and to pass in a map with reference objects.
    721     *
    722     * @param node the node to be merged with the root node
    723     * @param rootName the new name of the root node; can be <strong>null</strong>, then the name of the root node is not changed
    724     *        unless it is <strong>null</strong>
    725     * @param references an optional map with reference objects
    726     * @param rootRef an optional reference object for the new root node
    727     * @param resolver the {@code NodeKeyResolver}
    728     */
    729    public void mergeRoot(final ImmutableNode node, final String rootName, final Map<ImmutableNode, ?> references, final Object rootRef,
    730        final NodeKeyResolver<ImmutableNode> resolver) {
    731        updateModel(tx -> {
    732            final TreeData current = tx.getCurrentData();
    733            final String newRootName = determineRootName(current.getRootNode(), node, rootName);
    734            if (newRootName != null) {
    735                tx.addChangeNodeNameOperation(current.getRootNode(), newRootName);
    736            }
    737            tx.addAddNodesOperation(current.getRootNode(), node.getChildren());
    738            tx.addAttributesOperation(current.getRootNode(), node.getAttributes());
    739            if (node.getValue() != null) {
    740                tx.addChangeNodeValueOperation(current.getRootNode(), node.getValue());
    741            }
    742            if (references != null) {
    743                tx.addNewReferences(references);
    744            }
    745            if (rootRef != null) {
    746                tx.addNewReference(current.getRootNode(), rootRef);
    747            }
    748            return true;
    749        }, null, resolver);
    750    }
    751
    752    /**
    753     * Replaces an active tracked node. The node then becomes detached.
    754     *
    755     * @param currentData the current data of the model
    756     * @param selector the {@code NodeSelector} defining the tracked node
    757     * @param newNode the node replacing the tracked node
    758     * @return a flag whether the operation was successful
    759     */
    760    private boolean replaceActiveTrackedNode(final TreeData currentData, final NodeSelector selector, final ImmutableNode newNode) {
    761        final NodeTracker newTracker = currentData.getNodeTracker().replaceAndDetachTrackedNode(selector, newNode);
    762        return structure.compareAndSet(currentData, currentData.updateNodeTracker(newTracker));
    763    }
    764
    765    /**
    766     * Replaces a tracked node if it is already detached.
    767     *
    768     * @param currentData the current data of the model
    769     * @param selector the {@code NodeSelector} defining the tracked node
    770     * @param newNode the node replacing the tracked node
    771     * @return a flag whether the operation was successful
    772     */
    773    private boolean replaceDetachedTrackedNode(final TreeData currentData, final NodeSelector selector, final ImmutableNode newNode) {
    774        final InMemoryNodeModel detachedNodeModel = currentData.getNodeTracker().getDetachedNodeModel(selector);
    775        if (detachedNodeModel != null) {
    776            detachedNodeModel.setRootNode(newNode);
    777            return true;
    778        }
    779
    780        return false;
    781    }
    782
    783    /**
    784     * Replaces the root node of this model. This method is similar to {@link #setRootNode(ImmutableNode)}; however, tracked
    785     * nodes will not get lost. The model applies the selectors of all tracked nodes on the new nodes hierarchy, so that
    786     * corresponding nodes are selected (this may cause nodes to become detached if a select operation fails). This
    787     * operation is useful if the new nodes hierarchy to be set is known to be similar to the old one. Note that reference
    788     * objects are lost; there is no way to automatically match nodes between the old and the new nodes hierarchy.
    789     *
    790     * @param newRoot the new root node to be set (must not be <strong>null</strong>)
    791     * @param resolver the {@code NodeKeyResolver}
    792     * @throws IllegalArgumentException if the new root node is <strong>null</strong>
    793     */
    794    public void replaceRoot(final ImmutableNode newRoot, final NodeKeyResolver<ImmutableNode> resolver) {
    795        if (newRoot == null) {
    796            throw new IllegalArgumentException("Replaced root node must not be null.");
    797        }
    798
    799        final TreeData current = structure.get();
    800        // this step is needed to get a valid NodeHandler
    801        final TreeData temp = createTreeDataForRootAndTracker(newRoot, current.getNodeTracker());
    802        structure.set(temp.updateNodeTracker(temp.getNodeTracker().update(newRoot, null, resolver, temp)));
    803    }
    804
    805    /**
    806     * Replaces a tracked node by another node. If the tracked node is not yet detached, it becomes now detached. The passed
    807     * in node (which must not be <strong>null</strong>) becomes the new root node of an independent model for this tracked node.
    808     * Further updates of this model do not affect the tracked node's model and vice versa.
    809     *
    810     * @param selector the {@code NodeSelector} defining the tracked node
    811     * @param newNode the node replacing the tracked node (must not be <strong>null</strong>)
    812     * @throws ConfigurationRuntimeException if the selector cannot be resolved
    813     * @throws IllegalArgumentException if the replacement node is <strong>null</strong>
    814     */
    815    public void replaceTrackedNode(final NodeSelector selector, final ImmutableNode newNode) {
    816        if (newNode == null) {
    817            throw new IllegalArgumentException("Replacement node must not be null.");
    818        }
    819
    820        boolean done;
    821        do {
    822            final TreeData currentData = structure.get();
    823            done = replaceDetachedTrackedNode(currentData, selector, newNode) || replaceActiveTrackedNode(currentData, selector, newNode);
    824        } while (!done);
    825    }
    826
    827    /**
    828     * Allows tracking all nodes selected by a key. This method evaluates the specified key on the current nodes structure.
    829     * For all selected nodes corresponding {@code NodeSelector} objects are created, and they are tracked. The returned
    830     * collection of {@code NodeSelector} objects can be used for interacting with the selected nodes.
    831     *
    832     * @param key the key for selecting the nodes to track
    833     * @param resolver the {@code NodeKeyResolver}
    834     * @return a collection with the {@code NodeSelector} objects for the new tracked nodes
    835     */
    836    public Collection<NodeSelector> selectAndTrackNodes(final String key, final NodeKeyResolver<ImmutableNode> resolver) {
    837        final Mutable<Collection<NodeSelector>> refSelectors = new MutableObject<>();
    838        boolean done;
    839        do {
    840            final TreeData current = structure.get();
    841            final List<ImmutableNode> nodes = resolver.resolveNodeKey(current.getRootNode(), key, current);
    842            if (nodes.isEmpty()) {
    843                return Collections.emptyList();
    844            }
    845            done = structure.compareAndSet(current, createSelectorsForTrackedNodes(refSelectors, nodes, current, resolver));
    846        } while (!done);
    847        return refSelectors.get();
    848    }
    849
    850    /**
    851     * Sets the value of a property using a tracked node as root node. This method works like the normal
    852     * {@code setProperty()} method, but the origin of the operation (also for the interpretation of the passed in key) is a
    853     * tracked node identified by the passed in {@code NodeSelector}. The selector can be <strong>null</strong>, then the root node is
    854     * assumed.
    855     *
    856     * @param key the key
    857     * @param selector the {@code NodeSelector} defining the root node (or <strong>null</strong>)
    858     * @param value the new value for this property
    859     * @param resolver the {@code NodeKeyResolver}
    860     * @throws ConfigurationRuntimeException if the selector cannot be resolved
    861     */
    862    public void setProperty(final String key, final NodeSelector selector, final Object value, final NodeKeyResolver<ImmutableNode> resolver) {
    863        updateModel(tx -> {
    864            boolean added = false;
    865            final NodeUpdateData<ImmutableNode> updateData = resolver.resolveUpdateKey(tx.getQueryRoot(), key, value, tx.getCurrentData());
    866            if (!updateData.getNewValues().isEmpty()) {
    867                initializeAddTransaction(tx, key, updateData.getNewValues(), resolver);
    868                added = true;
    869            }
    870            final boolean cleared = initializeClearTransaction(tx, updateData.getRemovedNodes());
    871            final boolean updated = initializeUpdateTransaction(tx, updateData.getChangedValues());
    872            return added || cleared || updated;
    873        }, selector, resolver);
    874    }
    875
    876    @Override
    877    public void setProperty(final String key, final Object value, final NodeKeyResolver<ImmutableNode> resolver) {
    878        setProperty(key, null, value, resolver);
    879    }
    880
    881    /**
    882     * {@inheritDoc} All tracked nodes and reference objects managed by this model are cleared.Care has to be taken when
    883     * this method is used and the model is accessed by multiple threads. It is not deterministic which concurrent
    884     * operations see the old root and which see the new root node.
    885     *
    886     * @param newRoot the new root node to be set (can be <strong>null</strong>, then an empty root node is set)
    887     */
    888    @Override
    889    public void setRootNode(final ImmutableNode newRoot) {
    890        structure.set(createTreeData(initialRootNode(newRoot), structure.get()));
    891    }
    892
    893    /**
    894     * Tracks all nodes which are children of the node selected by the passed in key. If the key selects exactly one node,
    895     * for all children of this node {@code NodeSelector} objects are created, and they become tracked nodes. The returned
    896     * collection of {@code NodeSelector} objects can be used for interacting with the selected nodes.
    897     *
    898     * @param key the key for selecting the parent node whose children are to be tracked
    899     * @param resolver the {@code NodeKeyResolver}
    900     * @return a collection with the {@code NodeSelector} objects for the new tracked nodes
    901     */
    902    public Collection<NodeSelector> trackChildNodes(final String key, final NodeKeyResolver<ImmutableNode> resolver) {
    903        final Mutable<Collection<NodeSelector>> refSelectors = new MutableObject<>();
    904        boolean done;
    905        do {
    906            refSelectors.setValue(Collections.<NodeSelector>emptyList());
    907            final TreeData current = structure.get();
    908            final List<ImmutableNode> nodes = resolver.resolveNodeKey(current.getRootNode(), key, current);
    909            if (nodes.size() == 1) {
    910                final ImmutableNode node = nodes.get(0);
    911                done = node.getChildren().isEmpty()
    912                    || structure.compareAndSet(current, createSelectorsForTrackedNodes(refSelectors, node.getChildren(), current, resolver));
    913            } else {
    914                done = true;
    915            }
    916        } while (!done);
    917        return refSelectors.get();
    918    }
    919
    920    /**
    921     * Tracks a node which is a child of another node selected by the passed in key. If the selected node has a child node
    922     * with this name, it is tracked and its selector is returned. Otherwise, a new child node with this name is created
    923     * first.
    924     *
    925     * @param key the key for selecting the parent node
    926     * @param childName the name of the child node
    927     * @param resolver the {@code NodeKeyResolver}
    928     * @return the {@code NodeSelector} for the tracked child node
    929     * @throws ConfigurationRuntimeException if the passed in key does not select a single node
    930     */
    931    public NodeSelector trackChildNodeWithCreation(final String key, final String childName, final NodeKeyResolver<ImmutableNode> resolver) {
    932        final MutableObject<NodeSelector> refSelector = new MutableObject<>();
    933        boolean done;
    934
    935        do {
    936            final TreeData current = structure.get();
    937            final List<ImmutableNode> nodes = resolver.resolveNodeKey(current.getRootNode(), key, current);
    938            if (nodes.size() != 1) {
    939                throw new ConfigurationRuntimeException("Key does not select a single node: " + key);
    940            }
    941
    942            final ImmutableNode parent = nodes.get(0);
    943            final TreeData newData = createDataWithTrackedChildNode(current, parent, childName, resolver, refSelector);
    944
    945            done = structure.compareAndSet(current, newData);
    946        } while (!done);
    947
    948        return refSelector.get();
    949    }
    950
    951    /**
    952     * Adds a node to be tracked. After this method has been called with a specific {@code NodeSelector}, the node
    953     * associated with this key can be always obtained using {@link #getTrackedNode(NodeSelector)} with the same selector.
    954     * This is useful because during updates of a model parts of the structure are replaced. Therefore, it is not a good
    955     * idea to simply hold a reference to a node; this might become outdated soon. Rather, the node should be tracked. This
    956     * mechanism ensures that always the correct node reference can be obtained.
    957     *
    958     * @param selector the {@code NodeSelector} defining the desired node
    959     * @param resolver the {@code NodeKeyResolver}
    960     * @throws ConfigurationRuntimeException if the selector does not select a single node
    961     */
    962    public void trackNode(final NodeSelector selector, final NodeKeyResolver<ImmutableNode> resolver) {
    963        boolean done;
    964        do {
    965            final TreeData current = structure.get();
    966            final NodeTracker newTracker = current.getNodeTracker().trackNode(current.getRootNode(), selector, resolver, current);
    967            done = structure.compareAndSet(current, current.updateNodeTracker(newTracker));
    968        } while (!done);
    969    }
    970
    971    /**
    972     * Removes a tracked node. This method is the opposite of {@code trackNode()}. It has to be called if there is no longer
    973     * the need to track a specific node. Note that for each call of {@code trackNode()} there has to be a corresponding
    974     * {@code untrackNode()} call. This ensures that multiple observers can track the same node.
    975     *
    976     * @param selector the {@code NodeSelector} defining the desired node
    977     * @throws ConfigurationRuntimeException if the specified node is not tracked
    978     */
    979    public void untrackNode(final NodeSelector selector) {
    980        boolean done;
    981        do {
    982            final TreeData current = structure.get();
    983            final NodeTracker newTracker = current.getNodeTracker().untrackNode(selector);
    984            done = structure.compareAndSet(current, current.updateNodeTracker(newTracker));
    985        } while (!done);
    986    }
    987
    988    /**
    989     * Performs a non-blocking, thread-safe update of this model based on a transaction initialized by the passed in
    990     * initializer. This method uses the atomic reference for the model's current data to ensure that an update was
    991     * successful even if the model is concurrently accessed.
    992     *
    993     * @param txInit the {@code TransactionInitializer}
    994     * @param selector an optional {@code NodeSelector} defining the target node of the transaction
    995     * @param resolver the {@code NodeKeyResolver}
    996     */
    997    private void updateModel(final TransactionInitializer txInit, final NodeSelector selector, final NodeKeyResolver<ImmutableNode> resolver) {
    998        boolean done;
    999
    1000        do {
    1001            final TreeData currentData = getTreeData();
    1002            done = executeTransactionOnDetachedTrackedNode(txInit, selector, currentData, resolver)
    1003                || executeTransactionOnCurrentStructure(txInit, selector, currentData, resolver);
    1004        } while (!done);
    1005    }
    1006}