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
50 /** A map with reference data. */
51 private final Map<ImmutableNode, Object> references;
52
53 /** A list with the removed references. */
54 private final List<Object> removedReferences;
55
56 /**
57 * Creates a new instance of {@code ReferenceTracker}. This instance does not yet contain any data about references.
58 */
59 public ReferenceTracker() {
60 this(Collections.<ImmutableNode, Object>emptyMap(), Collections.emptyList());
61 }
62
63 /**
64 * Creates a new instance of {@code ReferenceTracker} and sets the data to be managed. This constructor is used
65 * internally when references are updated.
66 *
67 * @param refs the references
68 * @param removedRefs the removed references
69 */
70 private ReferenceTracker(final Map<ImmutableNode, Object> refs, final List<Object> removedRefs) {
71 references = refs;
72 removedReferences = removedRefs;
73 }
74
75 /**
76 * Adds all references stored in the passed in map to the managed references. A new instance is created managing this
77 * new set of references.
78 *
79 * @param refs the references to be added
80 * @return the new instance
81 */
82 public ReferenceTracker addReferences(final Map<ImmutableNode, ?> refs) {
83 final Map<ImmutableNode, Object> newRefs = new HashMap<>(references);
84 newRefs.putAll(refs);
85 return new ReferenceTracker(newRefs, removedReferences);
86 }
87
88 /**
89 * Gets the reference object associated with the given node.
90 *
91 * @param node the node
92 * @return the reference object for this node or <strong>null</strong>
93 */
94 public Object getReference(final ImmutableNode node) {
95 return references.get(node);
96 }
97
98 /**
99 * Gets the list with removed references. This list is immutable.
100 *
101 * @return the list with removed references
102 */
103 public List<Object> getRemovedReferences() {
104 return Collections.unmodifiableList(removedReferences);
105 }
106
107 /**
108 * Updates the references managed by this object at the end of a model transaction. This method is called by the
109 * transaction with the nodes that have been replaced by others and the nodes that have been removed. The internal data
110 * structures are updated correspondingly.
111 *
112 * @param replacedNodes the map with nodes that have been replaced
113 * @param removedNodes the list with nodes that have been removed
114 * @return the new instance
115 */
116 public ReferenceTracker updateReferences(final Map<ImmutableNode, ImmutableNode> replacedNodes, final Collection<ImmutableNode> removedNodes) {
117 if (!references.isEmpty()) {
118 Map<ImmutableNode, Object> newRefs = null;
119 for (final Map.Entry<ImmutableNode, ImmutableNode> e : replacedNodes.entrySet()) {
120 final Object ref = references.get(e.getKey());
121 if (ref != null) {
122 if (newRefs == null) {
123 newRefs = new HashMap<>(references);
124 }
125 newRefs.put(e.getValue(), ref);
126 newRefs.remove(e.getKey());
127 }
128 }
129
130 List<Object> newRemovedRefs = newRefs != null ? new LinkedList<>(removedReferences) : null;
131 for (final ImmutableNode node : removedNodes) {
132 final Object ref = references.get(node);
133 if (ref != null) {
134 if (newRefs == null) {
135 newRefs = new HashMap<>(references);
136 }
137 newRefs.remove(node);
138 if (newRemovedRefs == null) {
139 newRemovedRefs = new LinkedList<>(removedReferences);
140 }
141 newRemovedRefs.add(ref);
142 }
143 }
144
145 if (newRefs != null) {
146 return new ReferenceTracker(newRefs, newRemovedRefs);
147 }
148 }
149
150 return this;
151 }
152 }