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