View Javadoc
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 }