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