    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 */
    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
    036 * {@link ImmutableNode} structures.
    037 * </p>
    038 * <p>
    039 * This {@code NodeModel} implementation keeps all its data as a tree of
    040 * {@link ImmutableNode} objects in memory. The managed structure can be
    041 * manipulated in a thread-safe, non-blocking way. This is achieved by using
    042 * atomic variables: The root of the tree is stored in an atomic reference
    043 * variable. Each update operation causes a new structure to be constructed
    044 * (which reuses as much from the original structure as possible). The old root
    045 * node is then replaced by the new one using an atomic compare-and-set
    046 * operation. If this fails, the manipulation has to be done anew on the updated
    047 * structure.
    048 * </p>
    049 *
    050 * @since 2.0
    051 */
    052public class InMemoryNodeModel implements NodeModel<ImmutableNode>
    053{
    054    /**
    055     * A dummy node handler instance used in operations which require only a
    056     * limited functionality.
    057     */
    058    private static final NodeHandler<ImmutableNode> DUMMY_HANDLER =
    059            new TreeData(null,
    060                    Collections.<ImmutableNode, ImmutableNode> emptyMap(),
    061                    Collections.<ImmutableNode, ImmutableNode> emptyMap(), null, new ReferenceTracker());
    062
    063    /** Stores information about the current nodes structure. */
    064    private final AtomicReference<TreeData> structure;
    065
    066    /**
    067     * Creates a new instance of {@code InMemoryNodeModel} which is initialized
    068     * with an empty root node.
    069     */
    070    public InMemoryNodeModel()
    071    {
    072        this(null);
    073    }
    074
    075    /**
    076     * Creates a new instance of {@code InMemoryNodeModel} and initializes it
    077     * from the given root node. If the passed in node is <b>null</b>, a new,
    078     * empty root node is created.
    079     *
    080     * @param root the new root node for this model
    081     */
    082    public InMemoryNodeModel(final ImmutableNode root)
    083    {
    084        structure =
    085                new AtomicReference<>(
    086                        createTreeData(initialRootNode(root), null));
    087    }
    088
    089    /**
    090     * Returns the root node of this mode. Note: This method should be used with
    091     * care. The model may be updated concurrently which causes the root node to
    092     * be replaced. If the root node is to be processed further (e.g. by
    093     * executing queries on it), the model should be asked for its
    094     * {@code NodeHandler}, and the root node should be obtained from there. The
    095     * connection between a node handler and its root node remain constant
    096     * because an update of the model causes the whole node handler to be
    097     * replaced.
    098     *
    099     * @return the current root node
    100     */
    101    public ImmutableNode getRootNode()
    102    {
    103        return getTreeData().getRootNode();
    104    }
    105
    106    /**
    107     * {@inheritDoc} {@code InMemoryNodeModel} implements the
    108     * {@code NodeHandler} interface itself. So this implementation just returns
    109     * the <strong>this</strong> reference.
    110     */
    111    @Override
    112    public NodeHandler<ImmutableNode> getNodeHandler()
    113    {
    114        return getReferenceNodeHandler();
    115    }
    116
    117    @Override
    118    public void addProperty(final String key, final Iterable<?> values,
    119            final NodeKeyResolver<ImmutableNode> resolver)
    120    {
    121        addProperty(key, null, values, resolver);
    122    }
    123
    124    /**
    125     * Adds new property values using a tracked node as root node. This method
    126     * works like the normal {@code addProperty()} method, but the origin of the
    127     * operation (also for the interpretation of the passed in key) is a tracked
    128     * node identified by the passed in {@code NodeSelector}. The selector can
    129     * be <b>null</b>, then the root node is assumed.
    130     *
    131     * @param key the key
    132     * @param selector the {@code NodeSelector} defining the root node (or
    133     *        <b>null</b>)
    134     * @param values the values to be added
    135     * @param resolver the {@code NodeKeyResolver}
    136     * @throws ConfigurationRuntimeException if the selector cannot be resolved
    137     */
    138    public void addProperty(final String key, final NodeSelector selector,
    139            final Iterable<?> values,
    140            final NodeKeyResolver<ImmutableNode> resolver)
    141    {
    142        if (valuesNotEmpty(values))
    143        {
    144            updateModel(new TransactionInitializer()
    145            {
    146                @Override
    147                public boolean initTransaction(final ModelTransaction tx)
    148                {
    149                    initializeAddTransaction(tx, key, values, resolver);
    150                    return true;
    151                }
    152            }, selector, resolver);
    153        }
    154    }
    155
    156    @Override
    157    public void addNodes(final String key, final Collection<? extends ImmutableNode> nodes,
    158            final NodeKeyResolver<ImmutableNode> resolver)
    159    {
    160        addNodes(key, null, nodes, resolver);
    161    }
    162
    163    /**
    164     * Adds new nodes using a tracked node as root node. This method works like
    165     * the normal {@code addNodes()} method, but the origin of the operation
    166     * (also for the interpretation of the passed in key) is a tracked node
    167     * identified by the passed in {@code NodeSelector}. The selector can be
    168     * <b>null</b>, then the root node is assumed.
    169     *
    170     * @param key the key
    171     * @param selector the {@code NodeSelector} defining the root node (or
    172     *        <b>null</b>)
    173     * @param nodes the collection of new nodes to be added
    174     * @param resolver the {@code NodeKeyResolver}
    175     * @throws ConfigurationRuntimeException if the selector cannot be resolved
    176     */
    177    public void addNodes(final String key, final NodeSelector selector,
    178            final Collection<? extends ImmutableNode> nodes,
    179            final NodeKeyResolver<ImmutableNode> resolver)
    180    {
    181        if (nodes != null && !nodes.isEmpty())
    182        {
    183            updateModel(new TransactionInitializer()
    184            {
    185                @Override
    186                public boolean initTransaction(final ModelTransaction tx)
    187                {
    188                    final List<QueryResult<ImmutableNode>> results =
    189                            resolver.resolveKey(tx.getQueryRoot(), key,
    190                                    tx.getCurrentData());
    191                    if (results.size() == 1)
    192                    {
    193                        if (results.get(0).isAttributeResult())
    194                        {
    195                            throw attributeKeyException(key);
    196                        }
    197                        tx.addAddNodesOperation(results.get(0).getNode(), nodes);
    198                    }
    199                    else
    200                    {
    201                        final NodeAddData<ImmutableNode> addData =
    202                                resolver.resolveAddKey(tx.getQueryRoot(), key,
    203                                        tx.getCurrentData());
    204                        if (addData.isAttribute())
    205                        {
    206                            throw attributeKeyException(key);
    207                        }
    208                        final ImmutableNode newNode =
    209                                new ImmutableNode.Builder(nodes.size())
    210                                        .name(addData.getNewNodeName())
    211                                        .addChildren(nodes).create();
    212                        addNodesByAddData(tx, addData,
    213                                Collections.singleton(newNode));
    214                    }
    215                    return true;
    216                }
    217            }, selector, resolver);
    218        }
    219    }
    220
    221    @Override
    222    public void setProperty(final String key, final Object value,
    223            final NodeKeyResolver<ImmutableNode> resolver)
    224    {
    225        setProperty(key, null, value, resolver);
    226    }
    227
    228    /**
    229     * Sets the value of a property using a tracked node as root node. This
    230     * method works like the normal {@code setProperty()} method, but the origin
    231     * of the operation (also for the interpretation of the passed in key) is a
    232     * tracked node identified by the passed in {@code NodeSelector}. The
    233     * selector can be <b>null</b>, then the root node is assumed.
    234     *
    235     * @param key the key
    236     * @param selector the {@code NodeSelector} defining the root node (or
    237     *        <b>null</b>)
    238     * @param value the new value for this property
    239     * @param resolver the {@code NodeKeyResolver}
    240     * @throws ConfigurationRuntimeException if the selector cannot be resolved
    241     */
    242    public void setProperty(final String key, final NodeSelector selector,
    243            final Object value, final NodeKeyResolver<ImmutableNode> resolver)
    244    {
    245        updateModel(new TransactionInitializer()
    246        {
    247            @Override
    248            public boolean initTransaction(final ModelTransaction tx)
    249            {
    250                boolean added = false;
    251                final NodeUpdateData<ImmutableNode> updateData =
    252                        resolver.resolveUpdateKey(tx.getQueryRoot(), key,
    253                                value, tx.getCurrentData());
    254                if (!updateData.getNewValues().isEmpty())
    255                {
    256                    initializeAddTransaction(tx, key,
    257                            updateData.getNewValues(), resolver);
    258                    added = true;
    259                }
    260                final boolean cleared =
    261                        initializeClearTransaction(tx,
    262                                updateData.getRemovedNodes());
    263                final boolean updated =
    264                        initializeUpdateTransaction(tx,
    265                                updateData.getChangedValues());
    266                return added || cleared || updated;
    267            }
    268        }, selector, resolver);
    269    }
    270
    271    /**
    272     * {@inheritDoc} This implementation checks whether nodes become undefined
    273     * after subtrees have been removed. If this is the case, such nodes are
    274     * removed, too. Return value is a collection with {@code QueryResult}
    275     * objects for the elements to be removed from the model.
    276     */
    277    @Override
    278    public List<QueryResult<ImmutableNode>> clearTree(final String key,
    279            final NodeKeyResolver<ImmutableNode> resolver)
    280    {
    281        return clearTree(key, null, resolver);
    282    }
    283
    284    /**
    285     * Clears a whole sub tree using a tracked node as root node. This method
    286     * works like the normal {@code clearTree()} method, but the origin of the
    287     * operation (also for the interpretation of the passed in key) is a tracked
    288     * node identified by the passed in {@code NodeSelector}. The selector can
    289     * be <b>null</b>, then the root node is assumed.
    290     *
    291     * @param key the key
    292     * @param selector the {@code NodeSelector} defining the root node (or
    293     *        <b>null</b>)
    294     * @param resolver the {@code NodeKeyResolver}
    295     * @return a list with the results to be removed
    296     * @throws ConfigurationRuntimeException if the selector cannot be resolved
    297     */
    298    public List<QueryResult<ImmutableNode>> clearTree(final String key,
    299            final NodeSelector selector, final NodeKeyResolver<ImmutableNode> resolver)
    300    {
    301        final List<QueryResult<ImmutableNode>> removedElements =
    302                new LinkedList<>();
    303        updateModel(new TransactionInitializer()
    304        {
    305            @Override
    306            public boolean initTransaction(final ModelTransaction tx)
    307            {
    308                boolean changes = false;
    309                final TreeData currentStructure = tx.getCurrentData();
    310                final List<QueryResult<ImmutableNode>> results = resolver.resolveKey(
    311                        tx.getQueryRoot(), key, currentStructure);
    312                removedElements.clear();
    313                removedElements.addAll(results);
    314                for (final QueryResult<ImmutableNode> result : results)
    315                {
    316                    if (result.isAttributeResult())
    317                    {
    318                        tx.addRemoveAttributeOperation(result.getNode(),
    319                                result.getAttributeName());
    320                    }
    321                    else
    322                    {
    323                        if (result.getNode() == currentStructure.getRootNode())
    324                        {
    325                            // the whole model is to be cleared
    326                            clear(resolver);
    327                            return false;
    328                        }
    329                        tx.addRemoveNodeOperation(
    330                                currentStructure.getParent(result.getNode()),
    331                                result.getNode());
    332                    }
    333                    changes = true;
    334                }
    335                return changes;
    336            }
    337        }, selector, resolver);
    338
    339        return removedElements;
    340    }
    341
    342    /**
    343     * {@inheritDoc} If this operation leaves an affected node in an undefined
    344     * state, it is removed from the model.
    345     */
    346    @Override
    347    public void clearProperty(final String key,
    348            final NodeKeyResolver<ImmutableNode> resolver)
    349    {
    350        clearProperty(key, null, resolver);
    351    }
    352
    353    /**
    354     * Clears a property using a tracked node as root node. This method works
    355     * like the normal {@code clearProperty()} method, but the origin of the
    356     * operation (also for the interpretation of the passed in key) is a tracked
    357     * node identified by the passed in {@code NodeSelector}. The selector can
    358     * be <b>null</b>, then the root node is assumed.
    359     *
    360     * @param key the key
    361     * @param selector the {@code NodeSelector} defining the root node (or
    362     *        <b>null</b>)
    363     * @param resolver the {@code NodeKeyResolver}
    364     * @throws ConfigurationRuntimeException if the selector cannot be resolved
    365     */
    366    public void clearProperty(final String key, final NodeSelector selector,
    367            final NodeKeyResolver<ImmutableNode> resolver)
    368    {
    369        updateModel(new TransactionInitializer()
    370        {
    371            @Override
    372            public boolean initTransaction(final ModelTransaction tx)
    373            {
    374                final List<QueryResult<ImmutableNode>> results =
    375                        resolver.resolveKey(tx.getQueryRoot(), key,
    376                                tx.getCurrentData());
    377                return initializeClearTransaction(tx, results);
    378            }
    379        }, selector, resolver);
    380    }
    381
    382    /**
    383     * {@inheritDoc} A new empty root node is created with the same name as the
    384     * current root node. Implementation note: Because this is a hard reset the
    385     * usual dance for dealing with concurrent updates is not required here.
    386     *
    387     * @param resolver the {@code NodeKeyResolver}
    388     */
    389    @Override
    390    public void clear(final NodeKeyResolver<ImmutableNode> resolver)
    391    {
    392        final ImmutableNode newRoot =
    393                new ImmutableNode.Builder().name(getRootNode().getNodeName())
    394                        .create();
    395        setRootNode(newRoot);
    396    }
    397
    398    /**
    399     * {@inheritDoc} This implementation simply returns the current root node of this
    400     * model.
    401     */
    402    @Override
    403    public ImmutableNode getInMemoryRepresentation()
    404    {
    405        return getTreeData().getRootNode();
    406    }
    407
    408    /**
    409     * {@inheritDoc} All tracked nodes and reference objects managed by this
    410     * model are cleared.Care has to be taken when this method is used and the
    411     * model is accessed by multiple threads. It is not deterministic which
    412     * concurrent operations see the old root and which see the new root node.
    413     *
    414     * @param newRoot the new root node to be set (can be <b>null</b>, then an
    415     *        empty root node is set)
    416     */
    417    @Override
    418    public void setRootNode(final ImmutableNode newRoot)
    419    {
    420        structure.set(createTreeData(initialRootNode(newRoot), structure.get()));
    421    }
    422
    423    /**
    424     * Replaces the root node of this model. This method is similar to
    425     * {@link #setRootNode(ImmutableNode)}; however, tracked nodes will not get
    426     * lost. The model applies the selectors of all tracked nodes on the new
    427     * nodes hierarchy, so that corresponding nodes are selected (this may cause
    428     * nodes to become detached if a select operation fails). This operation is
    429     * useful if the new nodes hierarchy to be set is known to be similar to the
    430     * old one. Note that reference objects are lost; there is no way to
    431     * automatically match nodes between the old and the new nodes hierarchy.
    432     *
    433     * @param newRoot the new root node to be set (must not be <b>null</b>)
    434     * @param resolver the {@code NodeKeyResolver}
    435     * @throws IllegalArgumentException if the new root node is <b>null</b>
    436     */
    437    public void replaceRoot(final ImmutableNode newRoot,
    438            final NodeKeyResolver<ImmutableNode> resolver)
    439    {
    440        if (newRoot == null)
    441        {
    442            throw new IllegalArgumentException(
    443                    "Replaced root node must not be null!");
    444        }
    445
    446        final TreeData current = structure.get();
    447        // this step is needed to get a valid NodeHandler
    448        final TreeData temp =
    449                createTreeDataForRootAndTracker(newRoot,
    450                        current.getNodeTracker());
    451        structure.set(temp.updateNodeTracker(temp.getNodeTracker().update(
    452                newRoot, null, resolver, temp)));
    453    }
    454
    455    /**
    456     * Merges the root node of this model with the specified node. This method
    457     * is typically caused by configuration implementations when a configuration
    458     * source is loaded, and its data has to be added to the model. It is
    459     * possible to define the new name of the root node and to pass in a map
    460     * with reference objects.
    461     *
    462     * @param node the node to be merged with the root node
    463     * @param rootName the new name of the root node; can be <b>null</b>, then
    464     *        the name of the root node is not changed unless it is <b>null</b>
    465     * @param references an optional map with reference objects
    466     * @param rootRef an optional reference object for the new root node
    467     * @param resolver the {@code NodeKeyResolver}
    468     */
    469    public void mergeRoot(final ImmutableNode node, final String rootName,
    470            final Map<ImmutableNode, ?> references, final Object rootRef,
    471            final NodeKeyResolver<ImmutableNode> resolver)
    472    {
    473        updateModel(new TransactionInitializer()
    474        {
    475            @Override
    476            public boolean initTransaction(final ModelTransaction tx)
    477            {
    478                final TreeData current = tx.getCurrentData();
    479                final String newRootName =
    480                        determineRootName(current.getRootNode(), node, rootName);
    481                if (newRootName != null)
    482                {
    483                    tx.addChangeNodeNameOperation(current.getRootNode(),
    484                            newRootName);
    485                }
    486                tx.addAddNodesOperation(current.getRootNode(),
    487                        node.getChildren());
    488                tx.addAttributesOperation(current.getRootNode(),
    489                        node.getAttributes());
    490                if (node.getValue() != null)
    491                {
    492                    tx.addChangeNodeValueOperation(current.getRootNode(),
    493                            node.getValue());
    494                }
    495                if (references != null)
    496                {
    497                    tx.addNewReferences(references);
    498                }
    499                if (rootRef != null)
    500                {
    501                    tx.addNewReference(current.getRootNode(), rootRef);
    502                }
    503                return true;
    504            }
    505        }, null, resolver);
    506    }
    507
    508    /**
    509     * Adds a node to be tracked. After this method has been called with a
    510     * specific {@code NodeSelector}, the node associated with this key can be
    511     * always obtained using {@link #getTrackedNode(NodeSelector)} with the same
    512     * selector. This is useful because during updates of a model parts of the
    513     * structure are replaced. Therefore, it is not a good idea to simply hold a
    514     * reference to a node; this might become outdated soon. Rather, the node
    515     * should be tracked. This mechanism ensures that always the correct node
    516     * reference can be obtained.
    517     *
    518     * @param selector the {@code NodeSelector} defining the desired node
    519     * @param resolver the {@code NodeKeyResolver}
    520     * @throws ConfigurationRuntimeException if the selector does not select a
    521     *         single node
    522     */
    523    public void trackNode(final NodeSelector selector,
    524            final NodeKeyResolver<ImmutableNode> resolver)
    525    {
    526        boolean done;
    527        do
    528        {
    529            final TreeData current = structure.get();
    530            final NodeTracker newTracker =
    531                    current.getNodeTracker().trackNode(current.getRootNode(),
    532                            selector, resolver, current);
    533            done =
    534                    structure.compareAndSet(current,
    535                            current.updateNodeTracker(newTracker));
    536        } while (!done);
    537    }
    538
    539    /**
    540     * Allows tracking all nodes selected by a key. This method evaluates the
    541     * specified key on the current nodes structure. For all selected nodes
    542     * corresponding {@code NodeSelector} objects are created, and they are
    543     * tracked. The returned collection of {@code NodeSelector} objects can be
    544     * used for interacting with the selected nodes.
    545     *
    546     * @param key the key for selecting the nodes to track
    547     * @param resolver the {@code NodeKeyResolver}
    548     * @return a collection with the {@code NodeSelector} objects for the new
    549     *         tracked nodes
    550     */
    551    public Collection<NodeSelector> selectAndTrackNodes(final String key,
    552            final NodeKeyResolver<ImmutableNode> resolver)
    553    {
    554        final Mutable<Collection<NodeSelector>> refSelectors =
    555                new MutableObject<>();
    556        boolean done;
    557        do
    558        {
    559            final TreeData current = structure.get();
    560            final List<ImmutableNode> nodes =
    561                    resolver.resolveNodeKey(current.getRootNode(), key, current);
    562            if (nodes.isEmpty())
    563            {
    564                return Collections.emptyList();
    565            }
    566            done =
    567                    structure.compareAndSet(
    568                            current,
    569                            createSelectorsForTrackedNodes(refSelectors, nodes,
    570                                    current, resolver));
    571        } while (!done);
    572        return refSelectors.getValue();
    573    }
    574
    575    /**
    576     * Tracks all nodes which are children of the node selected by the passed in
    577     * key. If the key selects exactly one node, for all children of this node
    578     * {@code NodeSelector} objects are created, and they become tracked nodes.
    579     * The returned collection of {@code NodeSelector} objects can be used for
    580     * interacting with the selected nodes.
    581     *
    582     * @param key the key for selecting the parent node whose children are to be
    583     *        tracked
    584     * @param resolver the {@code NodeKeyResolver}
    585     * @return a collection with the {@code NodeSelector} objects for the new
    586     *         tracked nodes
    587     */
    588    public Collection<NodeSelector> trackChildNodes(final String key,
    589            final NodeKeyResolver<ImmutableNode> resolver)
    590    {
    591        final Mutable<Collection<NodeSelector>> refSelectors =
    592                new MutableObject<>();
    593        boolean done;
    594        do
    595        {
    596            refSelectors.setValue(Collections.<NodeSelector> emptyList());
    597            final TreeData current = structure.get();
    598            final List<ImmutableNode> nodes =
    599                    resolver.resolveNodeKey(current.getRootNode(), key, current);
    600            if (nodes.size() == 1)
    601            {
    602                final ImmutableNode node = nodes.get(0);
    603                done =
    604                        node.getChildren().isEmpty()
    605                                || structure.compareAndSet(
    606                                        current,
    607                                        createSelectorsForTrackedNodes(
    608                                                refSelectors,
    609                                                node.getChildren(), current,
    610                                                resolver));
    611            }
    612            else
    613            {
    614                done = true;
    615            }
    616        } while (!done);
    617        return refSelectors.getValue();
    618    }
    619
    620    /**
    621     * Tracks a node which is a child of another node selected by the passed in
    622     * key. If the selected node has a child node with this name, it is tracked
    623     * and its selector is returned. Otherwise, a new child node with this name
    624     * is created first.
    625     *
    626     * @param key the key for selecting the parent node
    627     * @param childName the name of the child node
    628     * @param resolver the {@code NodeKeyResolver}
    629     * @return the {@code NodeSelector} for the tracked child node
    630     * @throws ConfigurationRuntimeException if the passed in key does not
    631     *         select a single node
    632     */
    633    public NodeSelector trackChildNodeWithCreation(final String key,
    634            final String childName, final NodeKeyResolver<ImmutableNode> resolver)
    635    {
    636        final MutableObject<NodeSelector> refSelector =
    637                new MutableObject<>();
    638        boolean done;
    639
    640        do
    641        {
    642            final TreeData current = structure.get();
    643            final List<ImmutableNode> nodes =
    644                    resolver.resolveNodeKey(current.getRootNode(), key, current);
    645            if (nodes.size() != 1)
    646            {
    647                throw new ConfigurationRuntimeException(
    648                        "Key does not select a single node: " + key);
    649            }
    650
    651            final ImmutableNode parent = nodes.get(0);
    652            final TreeData newData =
    653                    createDataWithTrackedChildNode(current, parent, childName,
    654                            resolver, refSelector);
    655
    656            done = structure.compareAndSet(current, newData);
    657        } while (!done);
    658
    659        return refSelector.getValue();
    660    }
    661
    662    /**
    663     * Returns the current {@code ImmutableNode} instance associated with the
    664     * given {@code NodeSelector}. The node must be a tracked node, i.e.
    665     * {@link #trackNode(NodeSelector, NodeKeyResolver)} must have been called
    666     * before with the given selector.
    667     *
    668     * @param selector the {@code NodeSelector} defining the desired node
    669     * @return the current {@code ImmutableNode} associated with this selector
    670     * @throws ConfigurationRuntimeException if the selector is unknown
    671     */
    672    public ImmutableNode getTrackedNode(final NodeSelector selector)
    673    {
    674        return structure.get().getNodeTracker().getTrackedNode(selector);
    675    }
    676
    677    /**
    678     * Replaces a tracked node by another node. If the tracked node is not yet
    679     * detached, it becomes now detached. The passed in node (which must not be
    680     * <b>null</b>) becomes the new root node of an independent model for this
    681     * tracked node. Further updates of this model do not affect the tracked
    682     * node's model and vice versa.
    683     *
    684     * @param selector the {@code NodeSelector} defining the tracked node
    685     * @param newNode the node replacing the tracked node (must not be
    686     *        <b>null</b>)
    687     * @throws ConfigurationRuntimeException if the selector cannot be resolved
    688     * @throws IllegalArgumentException if the replacement node is <b>null</b>
    689     */
    690    public void replaceTrackedNode(final NodeSelector selector, final ImmutableNode newNode)
    691    {
    692        if (newNode == null)
    693        {
    694            throw new IllegalArgumentException(
    695                    "Replacement node must not be null!");
    696        }
    697
    698        boolean done;
    699        do
    700        {
    701            final TreeData currentData = structure.get();
    702            done =
    703                    replaceDetachedTrackedNode(currentData, selector, newNode)
    704                            || replaceActiveTrackedNode(currentData, selector,
    705                                    newNode);
    706        } while (!done);
    707    }
    708
    709    /**
    710     * Returns a {@code NodeHandler} for a tracked node. Such a handler may be
    711     * required for operations on a sub tree of the model. The handler to be
    712     * returned depends on the current state of the tracked node. If it is still
    713     * active, a handler is used which shares some data (especially the parent
    714     * mapping) with this model. Detached track nodes in contrast have their own
    715     * separate model; in this case a handler associated with this model is
    716     * returned.
    717     *
    718     * @param selector the {@code NodeSelector} defining the tracked node
    719     * @return a {@code NodeHandler} for this tracked node
    720     * @throws ConfigurationRuntimeException if the selector is unknown
    721     */
    722    public NodeHandler<ImmutableNode> getTrackedNodeHandler(
    723            final NodeSelector selector)
    724    {
    725        final TreeData currentData = structure.get();
    726        final InMemoryNodeModel detachedNodeModel =
    727                currentData.getNodeTracker().getDetachedNodeModel(selector);
    728        return (detachedNodeModel != null) ? detachedNodeModel.getNodeHandler()
    729                : new TrackedNodeHandler(currentData.getNodeTracker()
    730                        .getTrackedNode(selector), currentData);
    731    }
    732
    733    /**
    734     * Returns a flag whether the specified tracked node is detached. As long as
    735     * the {@code NodeSelector} associated with that node returns a single
    736     * instance, the tracked node is said to be <em>life</em>. If now an update
    737     * of the model happens which invalidates the selector (maybe the target
    738     * node was removed), the tracked node becomes detached. It is still
    739     * possible to query the node; here the latest valid instance is returned.
    740     * But further changes on the node model are no longer tracked for this
    741     * node. So even if there are further changes which would make the
    742     * {@code NodeSelector} valid again, the tracked node stays in detached
    743     * state.
    744     *
    745     * @param selector the {@code NodeSelector} defining the desired node
    746     * @return a flag whether this tracked node is in detached state
    747     * @throws ConfigurationRuntimeException if the selector is unknown
    748     */
    749    public boolean isTrackedNodeDetached(final NodeSelector selector)
    750    {
    751        return structure.get().getNodeTracker().isTrackedNodeDetached(selector);
    752    }
    753
    754    /**
    755     * Removes a tracked node. This method is the opposite of
    756     * {@code trackNode()}. It has to be called if there is no longer the need
    757     * to track a specific node. Note that for each call of {@code trackNode()}
    758     * there has to be a corresponding {@code untrackNode()} call. This ensures
    759     * that multiple observers can track the same node.
    760     *
    761     * @param selector the {@code NodeSelector} defining the desired node
    762     * @throws ConfigurationRuntimeException if the specified node is not
    763     *         tracked
    764     */
    765    public void untrackNode(final NodeSelector selector)
    766    {
    767        boolean done;
    768        do
    769        {
    770            final TreeData current = structure.get();
    771            final NodeTracker newTracker =
    772                    current.getNodeTracker().untrackNode(selector);
    773            done =
    774                    structure.compareAndSet(current,
    775                            current.updateNodeTracker(newTracker));
    776        } while (!done);
    777    }
    778
    779    /**
    780     * Returns a {@code ReferenceNodeHandler} object for this model. This
    781     * extended node handler can be used to query references objects stored for
    782     * this model.
    783     *
    784     * @return the {@code ReferenceNodeHandler}
    785     */
    786    public ReferenceNodeHandler getReferenceNodeHandler()
    787    {
    788        return getTreeData();
    789    }
    790
    791    /**
    792     * Returns the current {@code TreeData} object. This object contains all
    793     * information about the current node structure.
    794     *
    795     * @return the current {@code TreeData} object
    796     */
    797    TreeData getTreeData()
    798    {
    799        return structure.get();
    800    }
    801
    802    /**
    803     * Updates the mapping from nodes to their parents for the passed in
    804     * hierarchy of nodes. This method traverses all children and grand-children
    805     * of the passed in root node. For each node in the subtree the parent
    806     * relation is added to the map.
    807     *
    808     * @param parents the map with parent nodes
    809     * @param root the root node of the current tree
    810     */
    811    static void updateParentMapping(final Map<ImmutableNode, ImmutableNode> parents,
    812            final ImmutableNode root)
    813    {
    814        NodeTreeWalker.INSTANCE.walkBFS(root,
    815                new ConfigurationNodeVisitorAdapter<ImmutableNode>()
    816                {
    817                    @Override
    818                    public void visitBeforeChildren(final ImmutableNode node,
    819                            final NodeHandler<ImmutableNode> handler)
    820                    {
    821                        for (final ImmutableNode c : node.getChildren())
    822                        {
    823                            parents.put(c, node);
    824                        }
    825                    }
    826                }, DUMMY_HANDLER);
    827    }
    828
    829    /**
    830     * Checks if the passed in node is defined. Result is <b>true</b> if the
    831     * node contains any data.
    832     *
    833     * @param node the node in question
    834     * @return <b>true</b> if the node is defined, <b>false</b> otherwise
    835     */
    836    static boolean checkIfNodeDefined(final ImmutableNode node)
    837    {
    838        return node.getValue() != null || !node.getChildren().isEmpty()
    839                || !node.getAttributes().isEmpty();
    840    }
    841
    842    /**
    843     * Initializes a transaction for an add operation.
    844     *
    845     * @param tx the transaction to be initialized
    846     * @param key the key
    847     * @param values the collection with node values
    848     * @param resolver the {@code NodeKeyResolver}
    849     */
    850    private void initializeAddTransaction(final ModelTransaction tx, final String key,
    851            final Iterable<?> values, final NodeKeyResolver<ImmutableNode> resolver)
    852    {
    853        final NodeAddData<ImmutableNode> addData =
    854                resolver.resolveAddKey(tx.getQueryRoot(), key,
    855                        tx.getCurrentData());
    856        if (addData.isAttribute())
    857        {
    858            addAttributeProperty(tx, addData, values);
    859        }
    860        else
    861        {
    862            addNodeProperty(tx, addData, values);
    863        }
    864    }
    865
    866    /**
    867     * Creates a {@code TreeData} object for the specified root node.
    868     *
    869     * @param root the root node of the current tree
    870     * @param current the current {@code TreeData} object (may be <b>null</b>)
    871     * @return the {@code TreeData} describing the current tree
    872     */
    873    private TreeData createTreeData(final ImmutableNode root, final TreeData current)
    874    {
    875        final NodeTracker newTracker =
    876                (current != null) ? current.getNodeTracker()
    877                        .detachAllTrackedNodes() : new NodeTracker();
    878        return createTreeDataForRootAndTracker(root, newTracker);
    879    }
    880
    881    /**
    882     * Creates a {@code TreeData} object for the specified root node and
    883     * {@code NodeTracker}. Other parameters are set to default values.
    884     *
    885     * @param root the new root node for this model
    886     * @param newTracker the new {@code NodeTracker}
    887     * @return the new {@code TreeData} object
    888     */
    889    private TreeData createTreeDataForRootAndTracker(final ImmutableNode root,
    890            final NodeTracker newTracker)
    891    {
    892        return new TreeData(root, createParentMapping(root),
    893                Collections.<ImmutableNode, ImmutableNode> emptyMap(),
    894                newTracker, new ReferenceTracker());
    895    }
    896
    897    /**
    898     * Handles an add property operation if the property to be added is a node.
    899     *
    900     * @param tx the transaction
    901     * @param addData the {@code NodeAddData}
    902     * @param values the collection with node values
    903     */
    904    private static void addNodeProperty(final ModelTransaction tx,
    905            final NodeAddData<ImmutableNode> addData, final Iterable<?> values)
    906    {
    907        final Collection<ImmutableNode> newNodes =
    908                createNodesToAdd(addData.getNewNodeName(), values);
    909        addNodesByAddData(tx, addData, newNodes);
    910    }
    911
    912    /**
    913     * Initializes a transaction to add a collection of nodes as described by a
    914     * {@code NodeAddData} object. If necessary, new path nodes are created.
    915     * Eventually, the new nodes are added as children to the specified target
    916     * node.
    917     *
    918     * @param tx the transaction
    919     * @param addData the {@code NodeAddData}
    920     * @param newNodes the collection of new child nodes
    921     */
    922    private static void addNodesByAddData(final ModelTransaction tx,
    923            final NodeAddData<ImmutableNode> addData,
    924            final Collection<ImmutableNode> newNodes)
    925    {
    926        if (addData.getPathNodes().isEmpty())
    927        {
    928            tx.addAddNodesOperation(addData.getParent(), newNodes);
    929        }
    930        else
    931        {
    932            final ImmutableNode newChild = createNodeToAddWithPath(addData, newNodes);
    933            tx.addAddNodeOperation(addData.getParent(), newChild);
    934        }
    935    }
    936
    937    /**
    938     * Handles an add property operation if the property to be added is an
    939     * attribute.
    940     *
    941     * @param tx the transaction
    942     * @param addData the {@code NodeAddData}
    943     * @param values the collection with node values
    944     */
    945    private static void addAttributeProperty(final ModelTransaction tx,
    946            final NodeAddData<ImmutableNode> addData, final Iterable<?> values)
    947    {
    948        if (addData.getPathNodes().isEmpty())
    949        {
    950            tx.addAttributeOperation(addData.getParent(),
    951                    addData.getNewNodeName(), values.iterator().next());
    952        }
    953        else
    954        {
    955            final int pathNodeCount = addData.getPathNodes().size();
    956            final ImmutableNode childWithAttribute =
    957                    new ImmutableNode.Builder()
    958                            .name(addData.getPathNodes().get(pathNodeCount - 1))
    959                            .addAttribute(addData.getNewNodeName(),
    960                                    values.iterator().next()).create();
    961            final ImmutableNode newChild =
    962                    (pathNodeCount > 1) ? createNodeOnPath(addData
    963                            .getPathNodes().subList(0, pathNodeCount - 1)
    964                            .iterator(),
    965                            Collections.singleton(childWithAttribute))
    966                            : childWithAttribute;
    967            tx.addAddNodeOperation(addData.getParent(), newChild);
    968        }
    969    }
    970
    971    /**
    972     * Creates a collection with new nodes with a given name and a value from a
    973     * given collection.
    974     *
    975     * @param newNodeName the name of the new nodes
    976     * @param values the collection with node values
    977     * @return the newly created collection
    978     */
    979    private static Collection<ImmutableNode> createNodesToAdd(
    980            final String newNodeName, final Iterable<?> values)
    981    {
    982        final Collection<ImmutableNode> nodes = new LinkedList<>();
    983        for (final Object value : values)
    984        {
    985            nodes.add(new ImmutableNode.Builder().name(newNodeName)
    986                    .value(value).create());
    987        }
    988        return nodes;
    989    }
    990
    991    /**
    992     * Creates a node structure consisting of the path nodes defined by the
    993     * passed in {@code NodeAddData} instance and all new child nodes.
    994     *
    995     * @param addData the {@code NodeAddData}
    996     * @param newNodes the collection of new child nodes
    997     * @return the parent node of the newly created hierarchy
    998     */
    999    private static ImmutableNode createNodeToAddWithPath(
    1000            final NodeAddData<ImmutableNode> addData,
    1001            final Collection<ImmutableNode> newNodes)
    1002    {
    1003        return createNodeOnPath(addData.getPathNodes().iterator(), newNodes);
    1004    }
    1005
    1006    /**
    1007     * Recursive helper method for creating a path node for an add operation.
    1008     * All path nodes except for the last have a single child. The last path
    1009     * node has the new nodes as children.
    1010     *
    1011     * @param it the iterator over the names of the path nodes
    1012     * @param newNodes the collection of new child nodes
    1013     * @return the newly created path node
    1014     */
    1015    private static ImmutableNode createNodeOnPath(final Iterator<String> it,
    1016            final Collection<ImmutableNode> newNodes)
    1017    {
    1018        final String nodeName = it.next();
    1019        ImmutableNode.Builder builder;
    1020        if (it.hasNext())
    1021        {
    1022            builder = new ImmutableNode.Builder(1);
    1023            builder.addChild(createNodeOnPath(it, newNodes));
    1024        }
    1025        else
    1026        {
    1027            builder = new ImmutableNode.Builder(newNodes.size());
    1028            builder.addChildren(newNodes);
    1029        }
    1030        return builder.name(nodeName).create();
    1031    }
    1032
    1033    /**
    1034     * Initializes a transaction to clear the values of a property based on the
    1035     * passed in collection of affected results.
    1036     *
    1037     * @param tx the transaction to be initialized
    1038     * @param results a collection with results pointing to the nodes to be
    1039     *        cleared
    1040     * @return a flag whether there are elements to be cleared
    1041     */
    1042    private static boolean initializeClearTransaction(final ModelTransaction tx,
    1043            final Collection<QueryResult<ImmutableNode>> results)
    1044    {
    1045        for (final QueryResult<ImmutableNode> result : results)
    1046        {
    1047            if (result.isAttributeResult())
    1048            {
    1049                tx.addRemoveAttributeOperation(result.getNode(),
    1050                        result.getAttributeName());
    1051            }
    1052            else
    1053            {
    1054                tx.addClearNodeValueOperation(result.getNode());
    1055            }
    1056        }
    1057
    1058        return !results.isEmpty();
    1059    }
    1060
    1061    /**
    1062     * Initializes a transaction to change the values of some query results
    1063     * based on the passed in map.
    1064     *
    1065     * @param tx the transaction to be initialized
    1066     * @param changedValues the map defining the elements to be changed
    1067     * @return a flag whether there are elements to be updated
    1068     */
    1069    private static boolean initializeUpdateTransaction(final ModelTransaction tx,
    1070            final Map<QueryResult<ImmutableNode>, Object> changedValues)
    1071    {
    1072        for (final Map.Entry<QueryResult<ImmutableNode>, Object> e : changedValues
    1073                .entrySet())
    1074        {
    1075            if (e.getKey().isAttributeResult())
    1076            {
    1077                tx.addAttributeOperation(e.getKey().getNode(), e.getKey()
    1078                        .getAttributeName(), e.getValue());
    1079            }
    1080            else
    1081            {
    1082                tx.addChangeNodeValueOperation(e.getKey().getNode(),
    1083                        e.getValue());
    1084            }
    1085        }
    1086
    1087        return !changedValues.isEmpty();
    1088    }
    1089
    1090    /**
    1091     * Determines the initial root node of this model. If a root node has been
    1092     * provided, it is used. Otherwise, an empty dummy root node is created.
    1093     *
    1094     * @param providedRoot the passed in root node
    1095     * @return the root node to be used
    1096     */
    1097    private static ImmutableNode initialRootNode(final ImmutableNode providedRoot)
    1098    {
    1099        return (providedRoot != null) ? providedRoot
    1100                : new ImmutableNode.Builder().create();
    1101    }
    1102
    1103    /**
    1104     * Determines the name of the root node for a merge operation. If a root
    1105     * name is provided, it is used. Otherwise, if the current root node has no
    1106     * name, the name of the node to be merged is used. A result of <b>null</b>
    1107     * means that no node name has to be set.
    1108     *
    1109     * @param rootNode the current root node
    1110     * @param node the node to be merged with the root node
    1111     * @param rootName the name of the resulting node
    1112     * @return the new name of the root node
    1113     */
    1114    private static String determineRootName(final ImmutableNode rootNode,
    1115            final ImmutableNode node, final String rootName)
    1116    {
    1117        if (rootName != null)
    1118        {
    1119            return rootName;
    1120        }
    1121        if (rootNode.getNodeName() == null)
    1122        {
    1123            return node.getNodeName();
    1124        }
    1125        return null;
    1126    }
    1127
    1128    /**
    1129     * Creates the mapping to parent nodes for the nodes structured represented
    1130     * by the passed in root node. Each node is assigned its parent node. Here
    1131     * an iterative algorithm is used rather than a recursive one to avoid stack
    1132     * overflow for huge structures.
    1133     *
    1134     * @param root the root node of the structure
    1135     * @return the parent node mapping
    1136     */
    1137    private Map<ImmutableNode, ImmutableNode> createParentMapping(
    1138            final ImmutableNode root)
    1139    {
    1140        final Map<ImmutableNode, ImmutableNode> parents =
    1141                new HashMap<>();
    1142        updateParentMapping(parents, root);
    1143        return parents;
    1144    }
    1145
    1146    /**
    1147     * Performs a non-blocking, thread-safe update of this model based on a
    1148     * transaction initialized by the passed in initializer. This method uses
    1149     * the atomic reference for the model's current data to ensure that an
    1150     * update was successful even if the model is concurrently accessed.
    1151     *
    1152     * @param txInit the {@code TransactionInitializer}
    1153     * @param selector an optional {@code NodeSelector} defining the target node
    1154     *        of the transaction
    1155     * @param resolver the {@code NodeKeyResolver}
    1156     */
    1157    private void updateModel(final TransactionInitializer txInit,
    1158            final NodeSelector selector, final NodeKeyResolver<ImmutableNode> resolver)
    1159    {
    1160        boolean done;
    1161
    1162        do
    1163        {
    1164            final TreeData currentData = getTreeData();
    1165            done =
    1166                    executeTransactionOnDetachedTrackedNode(txInit, selector,
    1167                            currentData, resolver)
    1168                            || executeTransactionOnCurrentStructure(txInit,
    1169                                    selector, currentData, resolver);
    1170        } while (!done);
    1171    }
    1172
    1173    /**
    1174     * Executes a transaction on the current data of this model. This method is
    1175     * called if an operation is to be executed on the model's root node or a
    1176     * tracked node which is not yet detached.
    1177     *
    1178     * @param txInit the {@code TransactionInitializer}
    1179     * @param selector an optional {@code NodeSelector} defining the target node
    1180     * @param currentData the current data of the model
    1181     * @param resolver the {@code NodeKeyResolver}
    1182     * @return a flag whether the operation has been completed successfully
    1183     */
    1184    private boolean executeTransactionOnCurrentStructure(
    1185            final TransactionInitializer txInit, final NodeSelector selector,
    1186            final TreeData currentData, final NodeKeyResolver<ImmutableNode> resolver)
    1187    {
    1188        boolean done;
    1189        final ModelTransaction tx =
    1190                new ModelTransaction(currentData, selector, resolver);
    1191        if (!txInit.initTransaction(tx))
    1192        {
    1193            done = true;
    1194        }
    1195        else
    1196        {
    1197            final TreeData newData = tx.execute();
    1198            done = structure.compareAndSet(tx.getCurrentData(), newData);
    1199        }
    1200        return done;
    1201    }
    1202
    1203    /**
    1204     * Tries to execute a transaction on the model of a detached tracked node.
    1205     * This method checks whether the target node of the transaction is a
    1206     * tracked node and if this node is already detached. If this is the case,
    1207     * the update operation is independent on this model and has to be executed
    1208     * on the specific model for the detached node.
    1209     *
    1210     * @param txInit the {@code TransactionInitializer}
    1211     * @param selector an optional {@code NodeSelector} defining the target node
    1212     * @param currentData the current data of the model
    1213     * @param resolver the {@code NodeKeyResolver} @return a flag whether the
    1214     *        transaction could be executed
    1215     * @throws ConfigurationRuntimeException if the selector cannot be resolved
    1216     */
    1217    private boolean executeTransactionOnDetachedTrackedNode(
    1218            final TransactionInitializer txInit, final NodeSelector selector,
    1219            final TreeData currentData, final NodeKeyResolver<ImmutableNode> resolver)
    1220    {
    1221        if (selector != null)
    1222        {
    1223            final InMemoryNodeModel detachedNodeModel =
    1224                    currentData.getNodeTracker().getDetachedNodeModel(selector);
    1225            if (detachedNodeModel != null)
    1226            {
    1227                detachedNodeModel.updateModel(txInit, null, resolver);
    1228                return true;
    1229            }
    1230        }
    1231
    1232        return false;
    1233    }
    1234
    1235    /**
    1236     * Replaces a tracked node if it is already detached.
    1237     *
    1238     * @param currentData the current data of the model
    1239     * @param selector the {@code NodeSelector} defining the tracked node
    1240     * @param newNode the node replacing the tracked node
    1241     * @return a flag whether the operation was successful
    1242     */
    1243    private boolean replaceDetachedTrackedNode(final TreeData currentData,
    1244            final NodeSelector selector, final ImmutableNode newNode)
    1245    {
    1246        final InMemoryNodeModel detachedNodeModel =
    1247                currentData.getNodeTracker().getDetachedNodeModel(selector);
    1248        if (detachedNodeModel != null)
    1249        {
    1250            detachedNodeModel.setRootNode(newNode);
    1251            return true;
    1252        }
    1253
    1254        return false;
    1255    }
    1256
    1257    /**
    1258     * Replaces an active tracked node. The node then becomes detached.
    1259     *
    1260     * @param currentData the current data of the model
    1261     * @param selector the {@code NodeSelector} defining the tracked node
    1262     * @param newNode the node replacing the tracked node
    1263     * @return a flag whether the operation was successful
    1264     */
    1265    private boolean replaceActiveTrackedNode(final TreeData currentData,
    1266            final NodeSelector selector, final ImmutableNode newNode)
    1267    {
    1268        final NodeTracker newTracker =
    1269                currentData.getNodeTracker().replaceAndDetachTrackedNode(
    1270                        selector, newNode);
    1271        return structure.compareAndSet(currentData,
    1272                currentData.updateNodeTracker(newTracker));
    1273    }
    1274
    1275    /**
    1276     * Creates tracked node entries for the specified nodes and creates the
    1277     * corresponding selectors.
    1278     *
    1279     * @param refSelectors the reference where to store the selectors
    1280     * @param nodes the nodes to be tracked
    1281     * @param current the current {@code TreeData} object
    1282     * @param resolver the {@code NodeKeyResolver}
    1283     * @return the updated {@code TreeData} object
    1284     */
    1285    private static TreeData createSelectorsForTrackedNodes(
    1286            final Mutable<Collection<NodeSelector>> refSelectors,
    1287            final List<ImmutableNode> nodes, final TreeData current,
    1288            final NodeKeyResolver<ImmutableNode> resolver)
    1289    {
    1290        final List<NodeSelector> selectors =
    1291                new ArrayList<>(nodes.size());
    1292        final Map<ImmutableNode, String> cache = new HashMap<>();
    1293        for (final ImmutableNode node : nodes)
    1294        {
    1295            selectors.add(new NodeSelector(resolver.nodeKey(node, cache,
    1296                    current)));
    1297        }
    1298        refSelectors.setValue(selectors);
    1299        final NodeTracker newTracker =
    1300                current.getNodeTracker().trackNodes(selectors, nodes);
    1301        return current.updateNodeTracker(newTracker);
    1302    }
    1303
    1304    /**
    1305     * Adds a tracked node that has already been resolved to the specified data
    1306     * object.
    1307     *
    1308     * @param current the current {@code TreeData} object
    1309     * @param node the node in question
    1310     * @param resolver the {@code NodeKeyResolver}
    1311     * @param refSelector here the newly created {@code NodeSelector} is
    1312     *        returned
    1313     * @return the new {@code TreeData} instance
    1314     */
    1315    private static TreeData updateDataWithNewTrackedNode(final TreeData current,
    1316            final ImmutableNode node, final NodeKeyResolver<ImmutableNode> resolver,
    1317            final MutableObject<NodeSelector> refSelector)
    1318    {
    1319        final NodeSelector selector =
    1320                new NodeSelector(resolver.nodeKey(node,
    1321                        new HashMap<ImmutableNode, String>(), current));
    1322        refSelector.setValue(selector);
    1323        final NodeTracker newTracker =
    1324                current.getNodeTracker().trackNodes(
    1325                        Collections.singleton(selector),
    1326                        Collections.singleton(node));
    1327        return current.updateNodeTracker(newTracker);
    1328    }
    1329
    1330    /**
    1331     * Creates a new data object with a tracked child node of the given parent
    1332     * node. If such a child node already exists, it is used. Otherwise, a new
    1333     * one is created.
    1334     *
    1335     * @param current the current {@code TreeData} object
    1336     * @param parent the parent node
    1337     * @param childName the name of the child node
    1338     * @param resolver the {@code NodeKeyResolver}
    1339     * @param refSelector here the newly created {@code NodeSelector} is
    1340     *        returned
    1341     * @return the new {@code TreeData} instance
    1342     */
    1343    private static TreeData createDataWithTrackedChildNode(final TreeData current,
    1344            final ImmutableNode parent, final String childName,
    1345            final NodeKeyResolver<ImmutableNode> resolver,
    1346            final MutableObject<NodeSelector> refSelector)
    1347    {
    1348        TreeData newData;
    1349        final List<ImmutableNode> namedChildren =
    1350                current.getChildren(parent, childName);
    1351        if (!namedChildren.isEmpty())
    1352        {
    1353            newData =
    1354                    updateDataWithNewTrackedNode(current, namedChildren.get(0),
    1355                            resolver, refSelector);
    1356        }
    1357        else
    1358        {
    1359            final ImmutableNode child =
    1360                    new ImmutableNode.Builder().name(childName).create();
    1361            final ModelTransaction tx = new ModelTransaction(current, null, resolver);
    1362            tx.addAddNodeOperation(parent, child);
    1363            newData =
    1364                    updateDataWithNewTrackedNode(tx.execute(), child, resolver,
    1365                            refSelector);
    1366        }
    1367        return newData;
    1368    }
    1369
    1370    /**
    1371     * Checks whether the specified collection with values is not empty.
    1372     *
    1373     * @param values the collection with node values
    1374     * @return <b>true</b> if values are provided, <b>false</b> otherwise
    1375     */
    1376    private static boolean valuesNotEmpty(final Iterable<?> values)
    1377    {
    1378        return values.iterator().hasNext();
    1379    }
    1380
    1381    /**
    1382     * Creates an exception referring to an invalid key for adding properties.
    1383     * Such an exception is thrown when an operation tries to add something to
    1384     * an attribute.
    1385     *
    1386     * @param key the invalid key causing this exception
    1387     * @return the exception
    1388     */
    1389    private static RuntimeException attributeKeyException(final String key)
    1390    {
    1391        return new IllegalArgumentException(
    1392                "New nodes cannot be added to an attribute key: " + key);
    1393    }
    1394
    1395    /**
    1396     * An interface used internally for handling concurrent updates. An
    1397     * implementation has to populate the passed in {@code ModelTransaction}.
    1398     * The transaction is then executed, and an atomic update of the model's
    1399     * {@code TreeData} is attempted. If this fails - because another update
    1400     * came across -, the whole operation has to be tried anew.
    1401     */
    1402    private interface TransactionInitializer
    1403    {
    1404        /**
    1405         * Initializes the specified transaction for an update operation. The
    1406         * return value indicates whether the transaction should be executed. A
    1407         * result of <b>false</b> means that the update is to be aborted (maybe
    1408         * another update method was called).
    1409         *
    1410         * @param tx the transaction to be initialized
    1411         * @return a flag whether the update should continue
    1412         */
    1413        boolean initTransaction(ModelTransaction tx);
    1414    }
    1415}