TreeData.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.HashMap;
  19. import java.util.List;
  20. import java.util.Map;
  21. import java.util.Map.Entry;
  22. import java.util.stream.Collectors;

  23. /**
  24.  * An internally used helper class for storing information about the managed node structure. An instance of this class
  25.  * represents the current tree. It stores the current root node and additional information which is not part of the
  26.  * {@code ImmutableNode} class.
  27.  *
  28.  * @since 2.0
  29.  */
  30. final class TreeData extends AbstractImmutableNodeHandler implements ReferenceNodeHandler {
  31.     /**
  32.      * Checks whether the passed in node is subject of a replacement by another one. If so, the other node is returned. This
  33.      * is done until a node is found which had not been replaced. Updating the parent mapping may be expensive for large
  34.      * node structures. Therefore, it initially remains constant, and a map with replacements is used. When querying a
  35.      * parent node, the replacement map has to be consulted whether the parent node is still valid.
  36.      *
  37.      * @param replace the replacement node
  38.      * @param mapping the replacement mapping
  39.      * @return the corresponding node according to the mapping
  40.      */
  41.     private static ImmutableNode handleReplacements(final ImmutableNode replace, final Map<ImmutableNode, ImmutableNode> mapping) {
  42.         ImmutableNode node = replace;
  43.         ImmutableNode org;
  44.         do {
  45.             org = mapping.get(node);
  46.             if (org != null) {
  47.                 node = org;
  48.             }
  49.         } while (org != null);
  50.         return node;
  51.     }

  52.     /** The root node of the tree. */
  53.     private final ImmutableNode root;

  54.     /** A map that associates the parent node to each node. */
  55.     private final Map<ImmutableNode, ImmutableNode> parentMapping;

  56.     /**
  57.      * Stores information about nodes which have been replaced by manipulations of the structure. This map is used to avoid
  58.      * that the parent mapping has to be updated after each change.
  59.      */
  60.     private final Map<ImmutableNode, ImmutableNode> replacementMapping;

  61.     /** An inverse replacement mapping. */
  62.     private final Map<ImmutableNode, ImmutableNode> inverseReplacementMapping;

  63.     /** The node tracker. */
  64.     private final NodeTracker nodeTracker;

  65.     /** The reference tracker. */
  66.     private final ReferenceTracker referenceTracker;

  67.     /**
  68.      * Creates a new instance of {@code TreeData} and initializes it with all data to be stored.
  69.      *
  70.      * @param root the root node of the current tree
  71.      * @param parentMapping the mapping to parent nodes
  72.      * @param replacements the map with the nodes that have been replaced
  73.      * @param tracker the {@code NodeTracker}
  74.      * @param refTracker the {@code ReferenceTracker}
  75.      */
  76.     public TreeData(final ImmutableNode root, final Map<ImmutableNode, ImmutableNode> parentMapping, final Map<ImmutableNode, ImmutableNode> replacements,
  77.         final NodeTracker tracker, final ReferenceTracker refTracker) {
  78.         this.root = root;
  79.         this.parentMapping = parentMapping;
  80.         replacementMapping = replacements;
  81.         inverseReplacementMapping = createInverseMapping(replacements);
  82.         nodeTracker = tracker;
  83.         referenceTracker = refTracker;
  84.     }

  85.     /**
  86.      * Returns a copy of the mapping from nodes to their parents.
  87.      *
  88.      * @return the copy of the parent mapping
  89.      */
  90.     public Map<ImmutableNode, ImmutableNode> copyParentMapping() {
  91.         return new HashMap<>(parentMapping);
  92.     }

  93.     /**
  94.      * Returns a copy of the map storing the replaced nodes.
  95.      *
  96.      * @return the copy of the replacement mapping
  97.      */
  98.     public Map<ImmutableNode, ImmutableNode> copyReplacementMapping() {
  99.         return new HashMap<>(replacementMapping);
  100.     }

  101.     /**
  102.      * Creates the inverse replacement mapping.
  103.      *
  104.      * @param replacements the original replacement mapping
  105.      * @return the inverse replacement mapping
  106.      */
  107.     private Map<ImmutableNode, ImmutableNode> createInverseMapping(final Map<ImmutableNode, ImmutableNode> replacements) {
  108.         return replacements.entrySet().stream().collect(Collectors.toMap(Entry::getValue, Entry::getKey));
  109.     }

  110.     /**
  111.      * Gets the {@code NodeTracker}
  112.      *
  113.      * @return the {@code NodeTracker}
  114.      */
  115.     public NodeTracker getNodeTracker() {
  116.         return nodeTracker;
  117.     }

  118.     /**
  119.      * Gets the parent node of the specified node. Result is <strong>null</strong> for the root node. If the passed in node cannot
  120.      * be resolved, an exception is thrown.
  121.      *
  122.      * @param node the node in question
  123.      * @return the parent node for this node
  124.      * @throws IllegalArgumentException if the node cannot be resolved
  125.      */
  126.     @Override
  127.     public ImmutableNode getParent(final ImmutableNode node) {
  128.         if (node == getRootNode()) {
  129.             return null;
  130.         }
  131.         final ImmutableNode org = handleReplacements(node, inverseReplacementMapping);

  132.         final ImmutableNode parent = parentMapping.get(org);
  133.         if (parent == null) {
  134.             throw new IllegalArgumentException("Cannot determine parent! " + node + " is not part of this model.");
  135.         }
  136.         return handleReplacements(parent, replacementMapping);
  137.     }

  138.     /**
  139.      * {@inheritDoc} This implementation delegates to the reference tracker.
  140.      */
  141.     @Override
  142.     public Object getReference(final ImmutableNode node) {
  143.         return getReferenceTracker().getReference(node);
  144.     }

  145.     /**
  146.      * Gets the {@code ReferenceTracker}.
  147.      *
  148.      * @return the {@code ReferenceTracker}
  149.      */
  150.     public ReferenceTracker getReferenceTracker() {
  151.         return referenceTracker;
  152.     }

  153.     @Override
  154.     public ImmutableNode getRootNode() {
  155.         return root;
  156.     }

  157.     /**
  158.      * {@inheritDoc} This implementation delegates to the reference tracker.
  159.      */
  160.     @Override
  161.     public List<Object> removedReferences() {
  162.         return getReferenceTracker().getRemovedReferences();
  163.     }

  164.     /**
  165.      * Creates a new instance which uses the specified {@code NodeTracker}. This method is called when there are updates of
  166.      * the state of tracked nodes.
  167.      *
  168.      * @param newTracker the new {@code NodeTracker}
  169.      * @return the updated instance
  170.      */
  171.     public TreeData updateNodeTracker(final NodeTracker newTracker) {
  172.         return new TreeData(root, parentMapping, replacementMapping, newTracker, referenceTracker);
  173.     }

  174.     /**
  175.      * Creates a new instance which uses the specified {@code ReferenceTracker}. All other information are unchanged. This
  176.      * method is called when there updates for references.
  177.      *
  178.      * @param newTracker the new {@code ReferenceTracker}
  179.      * @return the updated instance
  180.      */
  181.     public TreeData updateReferenceTracker(final ReferenceTracker newTracker) {
  182.         return new TreeData(root, parentMapping, replacementMapping, nodeTracker, newTracker);
  183.     }
  184. }