ModelTransaction.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.configuration2.tree;

  18. import java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.Collections;
  21. import java.util.HashMap;
  22. import java.util.HashSet;
  23. import java.util.LinkedList;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.Set;
  27. import java.util.SortedMap;
  28. import java.util.TreeMap;

  29. /**
  30.  * <p>
  31.  * An internal helper class for a atomic updates of an {@link InMemoryNodeModel}.
  32.  * </p>
  33.  * <p>
  34.  * This class performs updates on the node structure of a node model consisting of {@link ImmutableNode} objects.
  35.  * Because the nodes themselves cannot be changed updates are achieved by replacing parts of the structure with new
  36.  * nodes; the new nodes are copies of original nodes with the corresponding manipulations applied. Therefore, each
  37.  * update of a node in the structure results in a new structure in which the affected node is replaced by a new one, and
  38.  * this change bubbles up to the root node (because all parent nodes have to be replaced by instances with an updated
  39.  * child reference).
  40.  * </p>
  41.  * <p>
  42.  * A single update of a model may consist of multiple changes on nodes. For instance, a remove property operation can
  43.  * include many nodes. There are some reasons why such updates should be handled in a single "transaction" rather than
  44.  * executing them on altered node structures one by one:
  45.  * <ul>
  46.  * <li>An operation is typically executed on a set of source nodes from the original node hierarchy. While manipulating
  47.  * nodes, nodes of this set may be replaced by new ones. The handling of these replacements complicates things a
  48.  * lot.</li>
  49.  * <li>Performing all updates one after the other may cause more updates of nodes than necessary. Nodes near to the root
  50.  * node always have to be replaced when a child of them gets manipulated. If all these updates are deferred and handled
  51.  * in a single transaction, the resulting operation is more efficient.</li>
  52.  * </ul>
  53.  * </p>
  54.  */
  55. final class ModelTransaction {

  56.     /**
  57.      * A specialized operation class for adding an attribute to a target node.
  58.      */
  59.     private static final class AddAttributeOperation extends Operation {
  60.         /** The attribute name. */
  61.         private final String attributeName;

  62.         /** The attribute value. */
  63.         private final Object attributeValue;

  64.         /**
  65.          * Creates a new instance of {@code AddAttributeOperation}.
  66.          *
  67.          * @param name the name of the attribute
  68.          * @param value the value of the attribute
  69.          */
  70.         public AddAttributeOperation(final String name, final Object value) {
  71.             attributeName = name;
  72.             attributeValue = value;
  73.         }

  74.         @Override
  75.         protected ImmutableNode apply(final ImmutableNode target, final Operations operations) {
  76.             return target.setAttribute(attributeName, attributeValue);
  77.         }
  78.     }

  79.     /**
  80.      * A specialized operation class for adding multiple attributes to a target node.
  81.      */
  82.     private static final class AddAttributesOperation extends Operation {
  83.         /** The map with attributes. */
  84.         private final Map<String, Object> attributes;

  85.         /**
  86.          * Creates a new instance of {@code AddAttributesOperation}.
  87.          *
  88.          * @param attrs the map with attributes
  89.          */
  90.         public AddAttributesOperation(final Map<String, Object> attrs) {
  91.             attributes = attrs;
  92.         }

  93.         @Override
  94.         protected ImmutableNode apply(final ImmutableNode target, final Operations operations) {
  95.             return target.setAttributes(attributes);
  96.         }
  97.     }

  98.     /**
  99.      * A specialized operation class which changes the name of a node.
  100.      */
  101.     private static final class ChangeNodeNameOperation extends Operation {
  102.         /** The new node name. */
  103.         private final String newName;

  104.         /**
  105.          * Creates a new instance of {@code ChangeNodeNameOperation} and sets the new node name.
  106.          *
  107.          * @param name the new node name
  108.          */
  109.         public ChangeNodeNameOperation(final String name) {
  110.             newName = name;
  111.         }

  112.         @Override
  113.         protected ImmutableNode apply(final ImmutableNode target, final Operations operations) {
  114.             return target.setName(newName);
  115.         }
  116.     }

  117.     /**
  118.      * A specialized operation class which changes the value of a node.
  119.      */
  120.     private static final class ChangeNodeValueOperation extends Operation {
  121.         /** The new value for the affected node. */
  122.         private final Object newValue;

  123.         /**
  124.          * Creates a new instance of {@code ChangeNodeValueOperation} and initializes it with the new value to set for the node.
  125.          *
  126.          * @param value the new node value
  127.          */
  128.         public ChangeNodeValueOperation(final Object value) {
  129.             newValue = value;
  130.         }

  131.         @Override
  132.         protected ImmutableNode apply(final ImmutableNode target, final Operations operations) {
  133.             return target.setValue(newValue);
  134.         }
  135.     }

  136.     /**
  137.      * A specialized {@code Operation} implementation for replacing the children of a target node. All other properties are
  138.      * not touched. With this operation single children of a node can be altered or removed; new children can be added. This
  139.      * operation is frequently used because each update of a node causes updates of the children of all parent nodes.
  140.      * Therefore, it is treated in a special way and allows adding further sub operations dynamically.
  141.      */
  142.     private final class ChildrenUpdateOperation extends Operation {
  143.         /** A collection with new nodes to be added. */
  144.         private Collection<ImmutableNode> newNodes;

  145.         /** A collection with nodes to be removed. */
  146.         private Set<ImmutableNode> nodesToRemove;

  147.         /**
  148.          * A map with nodes to be replaced by others. The keys are the nodes to be replaced, the values the replacements.
  149.          */
  150.         private Map<ImmutableNode, ImmutableNode> nodesToReplace;

  151.         /**
  152.          * Adds a node to be added to the target of the operation.
  153.          *
  154.          * @param node the new node to be added
  155.          */
  156.         public void addNewNode(final ImmutableNode node) {
  157.             newNodes = append(newNodes, node);
  158.         }

  159.         /**
  160.          * Adds a collection of nodes to be added to the target of the operation.
  161.          *
  162.          * @param nodes the collection with new nodes
  163.          */
  164.         public void addNewNodes(final Collection<? extends ImmutableNode> nodes) {
  165.             newNodes = concatenate(newNodes, nodes);
  166.         }

  167.         /**
  168.          * Adds a node for a remove operation. This child node is going to be removed from its parent.
  169.          *
  170.          * @param node the child node to be removed
  171.          */
  172.         public void addNodeToRemove(final ImmutableNode node) {
  173.             nodesToRemove = append(nodesToRemove, node);
  174.         }

  175.         /**
  176.          * Adds a node for a replacement operation. The original node is going to be replaced by its replacement.
  177.          *
  178.          * @param org the original node
  179.          * @param replacement the replacement node
  180.          */
  181.         public void addNodeToReplace(final ImmutableNode org, final ImmutableNode replacement) {
  182.             nodesToReplace = append(nodesToReplace, org, replacement);
  183.         }

  184.         /**
  185.          * {@inheritDoc} This implementation applies changes on the children of the passed in target node according to its
  186.          * configuration: new nodes are added, replacements are performed, and nodes no longer needed are removed.
  187.          */
  188.         @Override
  189.         protected ImmutableNode apply(final ImmutableNode target, final Operations operations) {
  190.             final Map<ImmutableNode, ImmutableNode> replacements = fetchReplacementMap();
  191.             final Set<ImmutableNode> removals = fetchRemovalSet();
  192.             final List<ImmutableNode> resultNodes = new LinkedList<>();

  193.             for (final ImmutableNode nd : target) {
  194.                 final ImmutableNode repl = replacements.get(nd);
  195.                 if (repl != null) {
  196.                     resultNodes.add(repl);
  197.                     replacedNodes.put(nd, repl);
  198.                 } else if (removals.contains(nd)) {
  199.                     removedNodes.add(nd);
  200.                 } else {
  201.                     resultNodes.add(nd);
  202.                 }
  203.             }

  204.             concatenate(resultNodes, newNodes);
  205.             operations.newNodesAdded(newNodes);
  206.             return target.replaceChildren(resultNodes);
  207.         }

  208.         /**
  209.          * Adds all operations defined by the specified object to this instance.
  210.          *
  211.          * @param op the operation to be combined
  212.          */
  213.         public void combine(final ChildrenUpdateOperation op) {
  214.             newNodes = concatenate(newNodes, op.newNodes);
  215.             nodesToReplace = concatenate(nodesToReplace, op.nodesToReplace);
  216.             nodesToRemove = concatenate(nodesToRemove, op.nodesToRemove);
  217.         }

  218.         /**
  219.          * Returns a set with nodes to be removed. If no remove operations are pending, an empty set is returned.
  220.          *
  221.          * @return the set with nodes to be removed
  222.          */
  223.         private Set<ImmutableNode> fetchRemovalSet() {
  224.             return nodesToRemove != null ? nodesToRemove : Collections.<ImmutableNode>emptySet();
  225.         }

  226.         /**
  227.          * Obtains the map with replacement nodes. If no replacements are defined, an empty map is returned.
  228.          *
  229.          * @return the map with replacement nodes
  230.          */
  231.         private Map<ImmutableNode, ImmutableNode> fetchReplacementMap() {
  232.             return nodesToReplace != null ? nodesToReplace : Collections.<ImmutableNode, ImmutableNode>emptyMap();
  233.         }
  234.     }

  235.     /**
  236.      * An abstract base class representing an operation to be performed on a node. Concrete subclasses implement specific
  237.      * update operations.
  238.      */
  239.     private abstract static class Operation {
  240.         /**
  241.          * Executes this operation on the provided target node returning the result.
  242.          *
  243.          * @param target the target node for this operation
  244.          * @param operations the current {@code Operations} instance
  245.          * @return the manipulated node
  246.          */
  247.         protected abstract ImmutableNode apply(ImmutableNode target, Operations operations);
  248.     }

  249.     /**
  250.      * A helper class which collects multiple update operations to be executed on a single node.
  251.      */
  252.     private final class Operations {
  253.         /** An operation for manipulating child nodes. */
  254.         private ChildrenUpdateOperation childrenOperation;

  255.         /**
  256.          * A collection for the other operations to be performed on the target node.
  257.          */
  258.         private Collection<Operation> operations;

  259.         /** A collection with nodes added by an operation. */
  260.         private Collection<ImmutableNode> addedNodesInOperation;

  261.         /**
  262.          * Adds an operation which manipulates children.
  263.          *
  264.          * @param co the operation
  265.          */
  266.         public void addChildrenOperation(final ChildrenUpdateOperation co) {
  267.             if (childrenOperation == null) {
  268.                 childrenOperation = co;
  269.             } else {
  270.                 childrenOperation.combine(co);
  271.             }
  272.         }

  273.         /**
  274.          * Adds an operation.
  275.          *
  276.          * @param op the operation
  277.          */
  278.         public void addOperation(final Operation op) {
  279.             operations = append(operations, op);
  280.         }

  281.         /**
  282.          * Executes all operations stored in this object on the given target node. The resulting node then has to be integrated
  283.          * in the current node hierarchy. Unless the root node is already reached, this causes another updated operation to be
  284.          * created which replaces the manipulated child in the parent node.
  285.          *
  286.          * @param target the target node for this operation
  287.          * @param level the level of the target node
  288.          */
  289.         public void apply(final ImmutableNode target, final int level) {
  290.             ImmutableNode node = target;
  291.             if (childrenOperation != null) {
  292.                 node = childrenOperation.apply(node, this);
  293.             }

  294.             if (operations != null) {
  295.                 for (final Operation op : operations) {
  296.                     node = op.apply(node, this);
  297.                 }
  298.             }

  299.             handleAddedNodes(node);
  300.             if (level == 0) {
  301.                 // reached the root node
  302.                 newRoot = node;
  303.                 replacedNodes.put(target, node);
  304.             } else {
  305.                 // propagate change
  306.                 propagateChange(target, node, level);
  307.             }
  308.         }

  309.         /**
  310.          * Checks whether new nodes have been added during operation execution. If so, the parent mapping has to be updated.
  311.          *
  312.          * @param node the resulting node after applying all operations
  313.          */
  314.         private void handleAddedNodes(final ImmutableNode node) {
  315.             if (addedNodesInOperation != null) {
  316.                 addedNodesInOperation.forEach(child -> {
  317.                     parentMapping.put(child, node);
  318.                     addedNodes.add(child);
  319.                 });
  320.             }
  321.         }

  322.         /**
  323.          * Notifies this object that new nodes have been added by a sub operation. It has to be ensured that these nodes are
  324.          * added to the parent mapping.
  325.          *
  326.          * @param newNodes the collection of newly added nodes
  327.          */
  328.         public void newNodesAdded(final Collection<ImmutableNode> newNodes) {
  329.             addedNodesInOperation = concatenate(addedNodesInOperation, newNodes);
  330.         }

  331.         /**
  332.          * Propagates the changes on the target node to the next level above of the hierarchy. If the updated node is no longer
  333.          * defined, it can even be removed from its parent. Otherwise, it is just replaced.
  334.          *
  335.          * @param target the target node for this operation
  336.          * @param node the resulting node after applying all operations
  337.          * @param level the level of the target node
  338.          */
  339.         private void propagateChange(final ImmutableNode target, final ImmutableNode node, final int level) {
  340.             final ImmutableNode parent = getParent(target);
  341.             final ChildrenUpdateOperation co = new ChildrenUpdateOperation();
  342.             if (InMemoryNodeModel.checkIfNodeDefined(node)) {
  343.                 co.addNodeToReplace(target, node);
  344.             } else {
  345.                 co.addNodeToRemove(target);
  346.             }
  347.             fetchOperations(parent, level - 1).addChildrenOperation(co);
  348.         }
  349.     }

  350.     /**
  351.      * A specialized operation class for removing an attribute from a target node.
  352.      */
  353.     private static final class RemoveAttributeOperation extends Operation {
  354.         /** The attribute name. */
  355.         private final String attributeName;

  356.         /**
  357.          * Creates a new instance of {@code RemoveAttributeOperation}.
  358.          *
  359.          * @param name the name of the attribute
  360.          */
  361.         public RemoveAttributeOperation(final String name) {
  362.             attributeName = name;
  363.         }

  364.         @Override
  365.         protected ImmutableNode apply(final ImmutableNode target, final Operations operations) {
  366.             return target.removeAttribute(attributeName);
  367.         }
  368.     }

  369.     /**
  370.      * Constant for the maximum number of entries in the replacement mapping. If this number is exceeded, the parent mapping
  371.      * is reconstructed. The number is a bit arbitrary. If it is too low, updates - especially on large node structures -
  372.      * are expensive because the parent mapping is often rebuild. If it is too big, read access to the model is slowed down
  373.      * because looking up the parent of a node is more complicated.
  374.      */
  375.     private static final int MAX_REPLACEMENTS = 200;

  376.     /** Constant for an unknown level. */
  377.     private static final int LEVEL_UNKNOWN = -1;

  378.     /**
  379.      * Appends a single element to a collection. The collection may be null, then it is created.
  380.      *
  381.      * @param col the collection
  382.      * @param node the element to be added
  383.      * @param <E> the type of elements involved
  384.      * @return the resulting collection
  385.      */
  386.     private static <E> Collection<E> append(final Collection<E> col, final E node) {
  387.         final Collection<E> result = col != null ? col : new LinkedList<>();
  388.         result.add(node);
  389.         return result;
  390.     }

  391.     /**
  392.      * Adds a single key-value pair to a map. The map may be null, then it is created.
  393.      *
  394.      * @param map the map
  395.      * @param key the key
  396.      * @param value the value
  397.      * @param <K> the type of the key
  398.      * @param <V> the type of the value
  399.      * @return the resulting map
  400.      */
  401.     private static <K, V> Map<K, V> append(final Map<K, V> map, final K key, final V value) {
  402.         final Map<K, V> result = map != null ? map : new HashMap<>();
  403.         result.put(key, value);
  404.         return result;
  405.     }

  406.     /**
  407.      * Appends a single element to a set. The set may be null then it is created.
  408.      *
  409.      * @param col the set
  410.      * @param elem the element to be added
  411.      * @param <E> the type of the elements involved
  412.      * @return the resulting set
  413.      */
  414.     private static <E> Set<E> append(final Set<E> col, final E elem) {
  415.         final Set<E> result = col != null ? col : new HashSet<>();
  416.         result.add(elem);
  417.         return result;
  418.     }

  419.     /**
  420.      * Constructs the concatenation of two collections. Both can be null.
  421.      *
  422.      * @param col1 the first collection
  423.      * @param col2 the second collection
  424.      * @param <E> the type of the elements involved
  425.      * @return the resulting collection
  426.      */
  427.     private static <E> Collection<E> concatenate(final Collection<E> col1, final Collection<? extends E> col2) {
  428.         if (col2 == null) {
  429.             return col1;
  430.         }

  431.         final Collection<E> result = col1 != null ? col1 : new ArrayList<>(col2.size());
  432.         result.addAll(col2);
  433.         return result;
  434.     }

  435.     /**
  436.      * Constructs the concatenation of two maps. Both can be null.
  437.      *
  438.      * @param map1 the first map
  439.      * @param map2 the second map
  440.      * @param <K> the type of the keys
  441.      * @param <V> the type of the values
  442.      * @return the resulting map
  443.      */
  444.     private static <K, V> Map<K, V> concatenate(final Map<K, V> map1, final Map<? extends K, ? extends V> map2) {
  445.         if (map2 == null) {
  446.             return map1;
  447.         }

  448.         final Map<K, V> result = map1 != null ? map1 : new HashMap<>();
  449.         result.putAll(map2);
  450.         return result;
  451.     }

  452.     /**
  453.      * Constructs the concatenation of two sets. Both can be null.
  454.      *
  455.      * @param set1 the first set
  456.      * @param set2 the second set
  457.      * @param <E> the type of the elements involved
  458.      * @return the resulting set
  459.      */
  460.     private static <E> Set<E> concatenate(final Set<E> set1, final Set<? extends E> set2) {
  461.         if (set2 == null) {
  462.             return set1;
  463.         }

  464.         final Set<E> result = set1 != null ? set1 : new HashSet<>();
  465.         result.addAll(set2);
  466.         return result;
  467.     }

  468.     /** Stores the current tree data of the calling node model. */
  469.     private final TreeData currentData;

  470.     /** The root node for query operations. */
  471.     private final ImmutableNode queryRoot;

  472.     /** The selector to the root node of this transaction. */
  473.     private final NodeSelector rootNodeSelector;

  474.     /** The {@code NodeKeyResolver} to be used for this transaction. */
  475.     private final NodeKeyResolver<ImmutableNode> resolver;

  476.     /** A new replacement mapping. */
  477.     private final Map<ImmutableNode, ImmutableNode> replacementMapping;

  478.     /** The nodes replaced in this transaction. */
  479.     private final Map<ImmutableNode, ImmutableNode> replacedNodes;

  480.     /** A new parent mapping. */
  481.     private final Map<ImmutableNode, ImmutableNode> parentMapping;

  482.     /** A collection with nodes which have been added. */
  483.     private final Collection<ImmutableNode> addedNodes;

  484.     /** A collection with nodes which have been removed. */
  485.     private final Collection<ImmutableNode> removedNodes;

  486.     /**
  487.      * Stores all nodes which have been removed in this transaction (not only the root nodes of removed trees).
  488.      */
  489.     private final Collection<ImmutableNode> allRemovedNodes;

  490.     /**
  491.      * Stores the operations to be executed during this transaction. The map is sorted by the levels of the nodes to be
  492.      * manipulated: Operations on nodes down in the hierarchy are executed first because they affect the nodes closer to the
  493.      * root.
  494.      */
  495.     private final SortedMap<Integer, Map<ImmutableNode, Operations>> operations;

  496.     /** A map with reference objects to be added during this transaction. */
  497.     private Map<ImmutableNode, Object> newReferences;

  498.     /** The new root node. */
  499.     private ImmutableNode newRoot;

  500.     /**
  501.      * Creates a new instance of {@code ModelTransaction} for the current tree data.
  502.      *
  503.      * @param treeData the current {@code TreeData} structure to operate on
  504.      * @param selector an optional {@code NodeSelector} defining the target root node for this transaction; this can be used
  505.      *        to perform operations on tracked nodes
  506.      * @param resolver the {@code NodeKeyResolver}
  507.      */
  508.     public ModelTransaction(final TreeData treeData, final NodeSelector selector, final NodeKeyResolver<ImmutableNode> resolver) {
  509.         currentData = treeData;
  510.         this.resolver = resolver;
  511.         replacementMapping = getCurrentData().copyReplacementMapping();
  512.         replacedNodes = new HashMap<>();
  513.         parentMapping = getCurrentData().copyParentMapping();
  514.         operations = new TreeMap<>();
  515.         addedNodes = new LinkedList<>();
  516.         removedNodes = new LinkedList<>();
  517.         allRemovedNodes = new LinkedList<>();
  518.         queryRoot = initQueryRoot(treeData, selector);
  519.         rootNodeSelector = selector;
  520.     }

  521.     /**
  522.      * Adds an operation for adding a new child to a given parent node.
  523.      *
  524.      * @param parent the parent node
  525.      * @param newChild the new child to be added
  526.      */
  527.     public void addAddNodeOperation(final ImmutableNode parent, final ImmutableNode newChild) {
  528.         final ChildrenUpdateOperation op = new ChildrenUpdateOperation();
  529.         op.addNewNode(newChild);
  530.         fetchOperations(parent, LEVEL_UNKNOWN).addChildrenOperation(op);
  531.     }

  532.     /**
  533.      * Adds an operation for adding a number of new children to a given parent node.
  534.      *
  535.      * @param parent the parent node
  536.      * @param newNodes the collection of new child nodes
  537.      */
  538.     public void addAddNodesOperation(final ImmutableNode parent, final Collection<? extends ImmutableNode> newNodes) {
  539.         final ChildrenUpdateOperation op = new ChildrenUpdateOperation();
  540.         op.addNewNodes(newNodes);
  541.         fetchOperations(parent, LEVEL_UNKNOWN).addChildrenOperation(op);
  542.     }

  543.     /**
  544.      * Adds an operation for adding an attribute to a target node.
  545.      *
  546.      * @param target the target node
  547.      * @param name the name of the attribute
  548.      * @param value the value of the attribute
  549.      */
  550.     public void addAttributeOperation(final ImmutableNode target, final String name, final Object value) {
  551.         fetchOperations(target, LEVEL_UNKNOWN).addOperation(new AddAttributeOperation(name, value));
  552.     }

  553.     /**
  554.      * Adds an operation for adding multiple attributes to a target node.
  555.      *
  556.      * @param target the target node
  557.      * @param attributes the map with attributes to be set
  558.      */
  559.     public void addAttributesOperation(final ImmutableNode target, final Map<String, Object> attributes) {
  560.         fetchOperations(target, LEVEL_UNKNOWN).addOperation(new AddAttributesOperation(attributes));
  561.     }

  562.     /**
  563.      * Adds an operation for changing the name of a target node.
  564.      *
  565.      * @param target the target node
  566.      * @param newName the new name for this node
  567.      */
  568.     public void addChangeNodeNameOperation(final ImmutableNode target, final String newName) {
  569.         fetchOperations(target, LEVEL_UNKNOWN).addOperation(new ChangeNodeNameOperation(newName));
  570.     }

  571.     /**
  572.      * Adds an operation for changing the value of a target node.
  573.      *
  574.      * @param target the target node
  575.      * @param newValue the new value for this node
  576.      */
  577.     public void addChangeNodeValueOperation(final ImmutableNode target, final Object newValue) {
  578.         fetchOperations(target, LEVEL_UNKNOWN).addOperation(new ChangeNodeValueOperation(newValue));
  579.     }

  580.     /**
  581.      * Adds an operation for clearing the value of a target node.
  582.      *
  583.      * @param target the target node
  584.      */
  585.     public void addClearNodeValueOperation(final ImmutableNode target) {
  586.         addChangeNodeValueOperation(target, null);
  587.     }

  588.     /**
  589.      * Adds a new reference object for the given node.
  590.      *
  591.      * @param node the affected node
  592.      * @param ref the reference object for this node
  593.      */
  594.     public void addNewReference(final ImmutableNode node, final Object ref) {
  595.         fetchReferenceMap().put(node, ref);
  596.     }

  597.     /**
  598.      * Adds a map with new reference objects. The entries in this map are passed to the {@code ReferenceTracker} during
  599.      * execution of this transaction.
  600.      *
  601.      * @param refs the map with new reference objects
  602.      */
  603.     public void addNewReferences(final Map<ImmutableNode, ?> refs) {
  604.         fetchReferenceMap().putAll(refs);
  605.     }

  606.     /**
  607.      * Adds an operation for removing an attribute from a target node.
  608.      *
  609.      * @param target the target node
  610.      * @param name the name of the attribute
  611.      */
  612.     public void addRemoveAttributeOperation(final ImmutableNode target, final String name) {
  613.         fetchOperations(target, LEVEL_UNKNOWN).addOperation(new RemoveAttributeOperation(name));
  614.     }

  615.     /**
  616.      * Adds an operation for removing a child node of a given node.
  617.      *
  618.      * @param parent the parent node
  619.      * @param node the child node to be removed
  620.      */
  621.     public void addRemoveNodeOperation(final ImmutableNode parent, final ImmutableNode node) {
  622.         final ChildrenUpdateOperation op = new ChildrenUpdateOperation();
  623.         op.addNodeToRemove(node);
  624.         fetchOperations(parent, LEVEL_UNKNOWN).addChildrenOperation(op);
  625.     }

  626.     /**
  627.      * Executes this transaction resulting in a new {@code TreeData} object. The object returned by this method serves as
  628.      * the definition of a new node structure for the calling model.
  629.      *
  630.      * @return the updated {@code TreeData}
  631.      */
  632.     public TreeData execute() {
  633.         executeOperations();
  634.         updateParentMapping();
  635.         return new TreeData(newRoot, parentMapping, replacementMapping,
  636.             currentData.getNodeTracker().update(newRoot, rootNodeSelector, getResolver(), getCurrentData()), updateReferenceTracker());
  637.     }

  638.     /**
  639.      * Executes all operations in this transaction.
  640.      */
  641.     private void executeOperations() {
  642.         while (!operations.isEmpty()) {
  643.             final Integer level = operations.lastKey(); // start down in hierarchy
  644.             operations.remove(level).forEach((k, v) -> v.apply(k, level));
  645.         }
  646.     }

  647.     /**
  648.      * Obtains the {@code Operations} object for manipulating the specified node. If no such object exists yet, it is
  649.      * created. The level can be undefined, then it is determined based on the target node.
  650.      *
  651.      * @param target the target node
  652.      * @param level the level of the target node (may be undefined)
  653.      * @return the {@code Operations} object for this node
  654.      */
  655.     Operations fetchOperations(final ImmutableNode target, final int level) {
  656.         final Integer nodeLevel = Integer.valueOf(level == LEVEL_UNKNOWN ? level(target) : level);
  657.         final Map<ImmutableNode, Operations> levelOperations = operations.computeIfAbsent(nodeLevel, k -> new HashMap<>());
  658.         return levelOperations.computeIfAbsent(target, k -> new Operations());
  659.     }

  660.     /**
  661.      * Returns the map with new reference objects. It is created if necessary.
  662.      *
  663.      * @return the map with reference objects
  664.      */
  665.     private Map<ImmutableNode, Object> fetchReferenceMap() {
  666.         if (newReferences == null) {
  667.             newReferences = new HashMap<>();
  668.         }
  669.         return newReferences;
  670.     }

  671.     /**
  672.      * Gets the current {@code TreeData} object this transaction operates on.
  673.      *
  674.      * @return the associated {@code TreeData} object
  675.      */
  676.     public TreeData getCurrentData() {
  677.         return currentData;
  678.     }

  679.     /**
  680.      * Gets the parent node of the given node.
  681.      *
  682.      * @param node the node in question
  683.      * @return the parent of this node
  684.      */
  685.     ImmutableNode getParent(final ImmutableNode node) {
  686.         return getCurrentData().getParent(node);
  687.     }

  688.     /**
  689.      * Gets the root node to be used within queries. This is not necessarily the current root node of the model. If the
  690.      * operation is executed on a tracked node, this node has to be passed as root nodes to the expression engine.
  691.      *
  692.      * @return the root node for queries and calls to the expression engine
  693.      */
  694.     public ImmutableNode getQueryRoot() {
  695.         return queryRoot;
  696.     }

  697.     /**
  698.      * Gets the {@code NodeKeyResolver} used by this transaction.
  699.      *
  700.      * @return the {@code NodeKeyResolver}
  701.      */
  702.     public NodeKeyResolver<ImmutableNode> getResolver() {
  703.         return resolver;
  704.     }

  705.     /**
  706.      * Initializes the root node to be used within queries. If a tracked node selector is provided, this node becomes the
  707.      * root node. Otherwise, the actual root node is used.
  708.      *
  709.      * @param treeData the current data of the model
  710.      * @param selector an optional {@code NodeSelector} defining the target root
  711.      * @return the query root node for this transaction
  712.      */
  713.     private ImmutableNode initQueryRoot(final TreeData treeData, final NodeSelector selector) {
  714.         return selector == null ? treeData.getRootNode() : treeData.getNodeTracker().getTrackedNode(selector);
  715.     }

  716.     /**
  717.      * Determines the level of the specified node in the current hierarchy. The level of the root node is 0, the children of
  718.      * the root have level 1 and so on.
  719.      *
  720.      * @param node the node in question
  721.      * @return the level of this node
  722.      */
  723.     private int level(final ImmutableNode node) {
  724.         ImmutableNode current = getCurrentData().getParent(node);
  725.         int level = 0;
  726.         while (current != null) {
  727.             level++;
  728.             current = getCurrentData().getParent(current);
  729.         }
  730.         return level;
  731.     }

  732.     /**
  733.      * Rebuilds the parent mapping from scratch. This method is called if the replacement mapping exceeds its maximum size.
  734.      * In this case, it is cleared, and a new parent mapping is constructed for the new root node.
  735.      */
  736.     private void rebuildParentMapping() {
  737.         replacementMapping.clear();
  738.         parentMapping.clear();
  739.         InMemoryNodeModel.updateParentMapping(parentMapping, newRoot);
  740.     }

  741.     /**
  742.      * Removes the specified node completely from the replacement mapping. This also includes the nodes that replace the
  743.      * given one.
  744.      *
  745.      * @param node the node to be removed
  746.      */
  747.     private void removeNodeFromReplacementMapping(final ImmutableNode node) {
  748.         ImmutableNode replacement = node;
  749.         do {
  750.             replacement = replacementMapping.remove(replacement);
  751.         } while (replacement != null);
  752.     }

  753.     /**
  754.      * Removes a node and its children (recursively) from the parent and the replacement mappings.
  755.      *
  756.      * @param root the root of the subtree to be removed
  757.      */
  758.     private void removeNodesFromParentAndReplacementMapping(final ImmutableNode root) {
  759.         NodeTreeWalker.INSTANCE.walkBFS(root, new ConfigurationNodeVisitorAdapter<ImmutableNode>() {
  760.             @Override
  761.             public void visitBeforeChildren(final ImmutableNode node, final NodeHandler<ImmutableNode> handler) {
  762.                 allRemovedNodes.add(node);
  763.                 parentMapping.remove(node);
  764.                 removeNodeFromReplacementMapping(node);
  765.             }
  766.         }, getCurrentData());
  767.     }

  768.     /**
  769.      * Updates the parent mapping for the resulting {@code TreeData} instance. This method is called after all update
  770.      * operations have been executed. It ensures that the parent mapping is updated for the changes on the nodes structure.
  771.      */
  772.     private void updateParentMapping() {
  773.         replacementMapping.putAll(replacedNodes);
  774.         if (replacementMapping.size() > MAX_REPLACEMENTS) {
  775.             rebuildParentMapping();
  776.         } else {
  777.             updateParentMappingForAddedNodes();
  778.             updateParentMappingForRemovedNodes();
  779.         }
  780.     }

  781.     /**
  782.      * Adds newly added nodes and their children to the parent mapping.
  783.      */
  784.     private void updateParentMappingForAddedNodes() {
  785.         addedNodes.forEach(node -> InMemoryNodeModel.updateParentMapping(parentMapping, node));
  786.     }

  787.     /**
  788.      * Removes nodes that have been removed during this transaction from the parent and replacement mappings.
  789.      */
  790.     private void updateParentMappingForRemovedNodes() {
  791.         removedNodes.forEach(this::removeNodesFromParentAndReplacementMapping);
  792.     }

  793.     /**
  794.      * Returns an updated {@code ReferenceTracker} instance. The changes performed during this transaction are applied to
  795.      * the tracker.
  796.      *
  797.      * @return the updated tracker instance
  798.      */
  799.     private ReferenceTracker updateReferenceTracker() {
  800.         ReferenceTracker tracker = currentData.getReferenceTracker();
  801.         if (newReferences != null) {
  802.             tracker = tracker.addReferences(newReferences);
  803.         }
  804.         return tracker.updateReferences(replacedNodes, allRemovedNodes);
  805.     }
  806. }