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 * https://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 }