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