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    *     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 }