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 19 import java.util.HashMap; 20 import java.util.List; 21 import java.util.Map; 22 import java.util.Map.Entry; 23 import java.util.stream.Collectors; 24 25 /** 26 * An internally used helper class for storing information about the managed node structure. An instance of this class 27 * represents the current tree. It stores the current root node and additional information which is not part of the 28 * {@code ImmutableNode} class. 29 * 30 * @since 2.0 31 */ 32 final class TreeData extends AbstractImmutableNodeHandler implements ReferenceNodeHandler { 33 /** 34 * Checks whether the passed in node is subject of a replacement by another one. If so, the other node is returned. This 35 * is done until a node is found which had not been replaced. Updating the parent mapping may be expensive for large 36 * node structures. Therefore, it initially remains constant, and a map with replacements is used. When querying a 37 * parent node, the replacement map has to be consulted whether the parent node is still valid. 38 * 39 * @param replace the replacement node 40 * @param mapping the replacement mapping 41 * @return the corresponding node according to the mapping 42 */ 43 private static ImmutableNode handleReplacements(final ImmutableNode replace, final Map<ImmutableNode, ImmutableNode> mapping) { 44 ImmutableNode node = replace; 45 ImmutableNode org; 46 do { 47 org = mapping.get(node); 48 if (org != null) { 49 node = org; 50 } 51 } while (org != null); 52 return node; 53 } 54 55 /** The root node of the tree. */ 56 private final ImmutableNode root; 57 58 /** A map that associates the parent node to each node. */ 59 private final Map<ImmutableNode, ImmutableNode> parentMapping; 60 61 /** 62 * Stores information about nodes which have been replaced by manipulations of the structure. This map is used to avoid 63 * that the parent mapping has to be updated after each change. 64 */ 65 private final Map<ImmutableNode, ImmutableNode> replacementMapping; 66 67 /** An inverse replacement mapping. */ 68 private final Map<ImmutableNode, ImmutableNode> inverseReplacementMapping; 69 70 /** The node tracker. */ 71 private final NodeTracker nodeTracker; 72 73 /** The reference tracker. */ 74 private final ReferenceTracker referenceTracker; 75 76 /** 77 * Creates a new instance of {@code TreeData} and initializes it with all data to be stored. 78 * 79 * @param root the root node of the current tree 80 * @param parentMapping the mapping to parent nodes 81 * @param replacements the map with the nodes that have been replaced 82 * @param tracker the {@code NodeTracker} 83 * @param refTracker the {@code ReferenceTracker} 84 */ 85 public TreeData(final ImmutableNode root, final Map<ImmutableNode, ImmutableNode> parentMapping, final Map<ImmutableNode, ImmutableNode> replacements, 86 final NodeTracker tracker, final ReferenceTracker refTracker) { 87 this.root = root; 88 this.parentMapping = parentMapping; 89 replacementMapping = replacements; 90 inverseReplacementMapping = createInverseMapping(replacements); 91 nodeTracker = tracker; 92 referenceTracker = refTracker; 93 } 94 95 /** 96 * Returns a copy of the mapping from nodes to their parents. 97 * 98 * @return the copy of the parent mapping 99 */ 100 public Map<ImmutableNode, ImmutableNode> copyParentMapping() { 101 return new HashMap<>(parentMapping); 102 } 103 104 /** 105 * Returns a copy of the map storing the replaced nodes. 106 * 107 * @return the copy of the replacement mapping 108 */ 109 public Map<ImmutableNode, ImmutableNode> copyReplacementMapping() { 110 return new HashMap<>(replacementMapping); 111 } 112 113 /** 114 * Creates the inverse replacement mapping. 115 * 116 * @param replacements the original replacement mapping 117 * @return the inverse replacement mapping 118 */ 119 private Map<ImmutableNode, ImmutableNode> createInverseMapping(final Map<ImmutableNode, ImmutableNode> replacements) { 120 return replacements.entrySet().stream().collect(Collectors.toMap(Entry::getValue, Entry::getKey)); 121 } 122 123 /** 124 * Gets the {@code NodeTracker} 125 * 126 * @return the {@code NodeTracker} 127 */ 128 public NodeTracker getNodeTracker() { 129 return nodeTracker; 130 } 131 132 /** 133 * Gets the parent node of the specified node. Result is <strong>null</strong> for the root node. If the passed in node cannot 134 * be resolved, an exception is thrown. 135 * 136 * @param node the node in question 137 * @return the parent node for this node 138 * @throws IllegalArgumentException if the node cannot be resolved 139 */ 140 @Override 141 public ImmutableNode getParent(final ImmutableNode node) { 142 if (node == getRootNode()) { 143 return null; 144 } 145 final ImmutableNode org = handleReplacements(node, inverseReplacementMapping); 146 147 final ImmutableNode parent = parentMapping.get(org); 148 if (parent == null) { 149 throw new IllegalArgumentException("Cannot determine parent! " + node + " is not part of this model."); 150 } 151 return handleReplacements(parent, replacementMapping); 152 } 153 154 /** 155 * {@inheritDoc} This implementation delegates to the reference tracker. 156 */ 157 @Override 158 public Object getReference(final ImmutableNode node) { 159 return getReferenceTracker().getReference(node); 160 } 161 162 /** 163 * Gets the {@code ReferenceTracker}. 164 * 165 * @return the {@code ReferenceTracker} 166 */ 167 public ReferenceTracker getReferenceTracker() { 168 return referenceTracker; 169 } 170 171 @Override 172 public ImmutableNode getRootNode() { 173 return root; 174 } 175 176 /** 177 * {@inheritDoc} This implementation delegates to the reference tracker. 178 */ 179 @Override 180 public List<Object> removedReferences() { 181 return getReferenceTracker().getRemovedReferences(); 182 } 183 184 /** 185 * Creates a new instance which uses the specified {@code NodeTracker}. This method is called when there are updates of 186 * the state of tracked nodes. 187 * 188 * @param newTracker the new {@code NodeTracker} 189 * @return the updated instance 190 */ 191 public TreeData updateNodeTracker(final NodeTracker newTracker) { 192 return new TreeData(root, parentMapping, replacementMapping, newTracker, referenceTracker); 193 } 194 195 /** 196 * Creates a new instance which uses the specified {@code ReferenceTracker}. All other information are unchanged. This 197 * method is called when there updates for references. 198 * 199 * @param newTracker the new {@code ReferenceTracker} 200 * @return the updated instance 201 */ 202 public TreeData updateReferenceTracker(final ReferenceTracker newTracker) { 203 return new TreeData(root, parentMapping, replacementMapping, nodeTracker, newTracker); 204 } 205 }