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.Collection;
20 import java.util.Collections;
21 import java.util.HashMap;
22 import java.util.LinkedList;
23 import java.util.List;
24 import java.util.Map;
25
26 /**
27 * <p>
28 * A class which allows an {@link InMemoryNodeModel} to associate arbitrary objects with nodes.
29 * </p>
30 * <p>
31 * Some special configuration implementations need additional data to be stored with their nodes structure. We call such
32 * data "references" because objects required by a configuration are referenced. In such constellations, it is
33 * necessary to keep track about the nodes associated with references even if they are replaced by others during an
34 * update operation of the model. This is the task of this class.
35 * </p>
36 * <p>
37 * Basically, an instance manages a map associating nodes with reference objects. When a node is replaced the map gets
38 * updated. References becoming orphans because the nodes pointing to them were removed are tracked, too. They may be of
39 * importance for special configuration implementations as they might require additional updates. A concrete use case
40 * for this class is {@code XMLConfiguration} which stores the DOM nodes represented by configuration nodes as
41 * references.
42 * </p>
43 * <p>
44 * Implementation note: This class is intended to work in a concurrent environment. Instances are immutable. The
45 * represented state can be updated by creating new instances which are then stored by the owning node model.
46 * </p>
47 */
48 final class ReferenceTracker {
49 /** A map with reference data. */
50 private final Map<ImmutableNode, Object> references;
51
52 /** A list with the removed references. */
53 private final List<Object> removedReferences;
54
55 /**
56 * Creates a new instance of {@code ReferenceTracker}. This instance does not yet contain any data about references.
57 */
58 public ReferenceTracker() {
59 this(Collections.<ImmutableNode, Object>emptyMap(), Collections.emptyList());
60 }
61
62 /**
63 * Creates a new instance of {@code ReferenceTracker} and sets the data to be managed. This constructor is used
64 * internally when references are updated.
65 *
66 * @param refs the references
67 * @param removedRefs the removed references
68 */
69 private ReferenceTracker(final Map<ImmutableNode, Object> refs, final List<Object> removedRefs) {
70 references = refs;
71 removedReferences = removedRefs;
72 }
73
74 /**
75 * Adds all references stored in the passed in map to the managed references. A new instance is created managing this
76 * new set of references.
77 *
78 * @param refs the references to be added
79 * @return the new instance
80 */
81 public ReferenceTracker addReferences(final Map<ImmutableNode, ?> refs) {
82 final Map<ImmutableNode, Object> newRefs = new HashMap<>(references);
83 newRefs.putAll(refs);
84 return new ReferenceTracker(newRefs, removedReferences);
85 }
86
87 /**
88 * Gets the reference object associated with the given node.
89 *
90 * @param node the node
91 * @return the reference object for this node or <strong>null</strong>
92 */
93 public Object getReference(final ImmutableNode node) {
94 return references.get(node);
95 }
96
97 /**
98 * Gets the list with removed references. This list is immutable.
99 *
100 * @return the list with removed references
101 */
102 public List<Object> getRemovedReferences() {
103 return Collections.unmodifiableList(removedReferences);
104 }
105
106 /**
107 * Updates the references managed by this object at the end of a model transaction. This method is called by the
108 * transaction with the nodes that have been replaced by others and the nodes that have been removed. The internal data
109 * structures are updated correspondingly.
110 *
111 * @param replacedNodes the map with nodes that have been replaced
112 * @param removedNodes the list with nodes that have been removed
113 * @return the new instance
114 */
115 public ReferenceTracker updateReferences(final Map<ImmutableNode, ImmutableNode> replacedNodes, final Collection<ImmutableNode> removedNodes) {
116 if (!references.isEmpty()) {
117 Map<ImmutableNode, Object> newRefs = null;
118 for (final Map.Entry<ImmutableNode, ImmutableNode> e : replacedNodes.entrySet()) {
119 final Object ref = references.get(e.getKey());
120 if (ref != null) {
121 if (newRefs == null) {
122 newRefs = new HashMap<>(references);
123 }
124 newRefs.put(e.getValue(), ref);
125 newRefs.remove(e.getKey());
126 }
127 }
128
129 List<Object> newRemovedRefs = newRefs != null ? new LinkedList<>(removedReferences) : null;
130 for (final ImmutableNode node : removedNodes) {
131 final Object ref = references.get(node);
132 if (ref != null) {
133 if (newRefs == null) {
134 newRefs = new HashMap<>(references);
135 }
136 newRefs.remove(node);
137 if (newRemovedRefs == null) {
138 newRemovedRefs = new LinkedList<>(removedReferences);
139 }
140 newRemovedRefs.add(ref);
141 }
142 }
143
144 if (newRefs != null) {
145 return new ReferenceTracker(newRefs, newRemovedRefs);
146 }
147 }
148
149 return this;
150 }
151 }