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