ReferenceTracker.java

  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. import java.util.Collection;
  19. import java.util.Collections;
  20. import java.util.HashMap;
  21. import java.util.LinkedList;
  22. import java.util.List;
  23. import java.util.Map;

  24. /**
  25.  * <p>
  26.  * A class which allows an {@link InMemoryNodeModel} to associate arbitrary objects with nodes.
  27.  * </p>
  28.  * <p>
  29.  * Some special configuration implementations need additional data to be stored with their nodes structure. We call such
  30.  * data &quot;references&quot; because objects required by a configuration are referenced. In such constellations, it is
  31.  * necessary to keep track about the nodes associated with references even if they are replaced by others during an
  32.  * update operation of the model. This is the task of this class.
  33.  * </p>
  34.  * <p>
  35.  * Basically, an instance manages a map associating nodes with reference objects. When a node is replaced the map gets
  36.  * updated. References becoming orphans because the nodes pointing to them were removed are tracked, too. They may be of
  37.  * importance for special configuration implementations as they might require additional updates. A concrete use case
  38.  * for this class is {@code XMLConfiguration} which stores the DOM nodes represented by configuration nodes as
  39.  * references.
  40.  * </p>
  41.  * <p>
  42.  * Implementation note: This class is intended to work in a concurrent environment. Instances are immutable. The
  43.  * represented state can be updated by creating new instances which are then stored by the owning node model.
  44.  * </p>
  45.  */
  46. final class ReferenceTracker {
  47.     /** A map with reference data. */
  48.     private final Map<ImmutableNode, Object> references;

  49.     /** A list with the removed references. */
  50.     private final List<Object> removedReferences;

  51.     /**
  52.      * Creates a new instance of {@code ReferenceTracker}. This instance does not yet contain any data about references.
  53.      */
  54.     public ReferenceTracker() {
  55.         this(Collections.<ImmutableNode, Object>emptyMap(), Collections.emptyList());
  56.     }

  57.     /**
  58.      * Creates a new instance of {@code ReferenceTracker} and sets the data to be managed. This constructor is used
  59.      * internally when references are updated.
  60.      *
  61.      * @param refs the references
  62.      * @param removedRefs the removed references
  63.      */
  64.     private ReferenceTracker(final Map<ImmutableNode, Object> refs, final List<Object> removedRefs) {
  65.         references = refs;
  66.         removedReferences = removedRefs;
  67.     }

  68.     /**
  69.      * Adds all references stored in the passed in map to the managed references. A new instance is created managing this
  70.      * new set of references.
  71.      *
  72.      * @param refs the references to be added
  73.      * @return the new instance
  74.      */
  75.     public ReferenceTracker addReferences(final Map<ImmutableNode, ?> refs) {
  76.         final Map<ImmutableNode, Object> newRefs = new HashMap<>(references);
  77.         newRefs.putAll(refs);
  78.         return new ReferenceTracker(newRefs, removedReferences);
  79.     }

  80.     /**
  81.      * Gets the reference object associated with the given node.
  82.      *
  83.      * @param node the node
  84.      * @return the reference object for this node or <strong>null</strong>
  85.      */
  86.     public Object getReference(final ImmutableNode node) {
  87.         return references.get(node);
  88.     }

  89.     /**
  90.      * Gets the list with removed references. This list is immutable.
  91.      *
  92.      * @return the list with removed references
  93.      */
  94.     public List<Object> getRemovedReferences() {
  95.         return Collections.unmodifiableList(removedReferences);
  96.     }

  97.     /**
  98.      * Updates the references managed by this object at the end of a model transaction. This method is called by the
  99.      * transaction with the nodes that have been replaced by others and the nodes that have been removed. The internal data
  100.      * structures are updated correspondingly.
  101.      *
  102.      * @param replacedNodes the map with nodes that have been replaced
  103.      * @param removedNodes the list with nodes that have been removed
  104.      * @return the new instance
  105.      */
  106.     public ReferenceTracker updateReferences(final Map<ImmutableNode, ImmutableNode> replacedNodes, final Collection<ImmutableNode> removedNodes) {
  107.         if (!references.isEmpty()) {
  108.             Map<ImmutableNode, Object> newRefs = null;
  109.             for (final Map.Entry<ImmutableNode, ImmutableNode> e : replacedNodes.entrySet()) {
  110.                 final Object ref = references.get(e.getKey());
  111.                 if (ref != null) {
  112.                     if (newRefs == null) {
  113.                         newRefs = new HashMap<>(references);
  114.                     }
  115.                     newRefs.put(e.getValue(), ref);
  116.                     newRefs.remove(e.getKey());
  117.                 }
  118.             }

  119.             List<Object> newRemovedRefs = newRefs != null ? new LinkedList<>(removedReferences) : null;
  120.             for (final ImmutableNode node : removedNodes) {
  121.                 final Object ref = references.get(node);
  122.                 if (ref != null) {
  123.                     if (newRefs == null) {
  124.                         newRefs = new HashMap<>(references);
  125.                     }
  126.                     newRefs.remove(node);
  127.                     if (newRemovedRefs == null) {
  128.                         newRemovedRefs = new LinkedList<>(removedReferences);
  129.                     }
  130.                     newRemovedRefs.add(ref);
  131.                 }
  132.             }

  133.             if (newRefs != null) {
  134.                 return new ReferenceTracker(newRefs, newRemovedRefs);
  135.             }
  136.         }

  137.         return this;
  138.     }
  139. }