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