You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by oh...@apache.org on 2014/03/09 21:55:22 UTC
svn commit: r1575755 -
/commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/NodeTracker.java
Author: oheger
Date: Sun Mar 9 20:55:21 2014
New Revision: 1575755
URL: http://svn.apache.org/r1575755
Log:
Added a helper class for tracking nodes.
This class allows access to nodes by key even if the nodes structure was
changed by update operations.
Added:
commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/NodeTracker.java
Added: commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/NodeTracker.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/NodeTracker.java?rev=1575755&view=auto
==============================================================================
--- commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/NodeTracker.java (added)
+++ commons/proper/configuration/branches/immutableNodes/src/main/java/org/apache/commons/configuration/tree/NodeTracker.java Sun Mar 9 20:55:21 2014
@@ -0,0 +1,330 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.configuration.ex.ConfigurationRuntimeException;
+
+/**
+ * <p>
+ * A class which can track specific nodes in an {@link InMemoryNodeModel}.
+ * </p>
+ * <p>
+ * Sometimes it is necessary to keep track on a specific node, for instance when
+ * operating on a subtree of a model. For a model comprised of immutable nodes
+ * this is not trivial because each update of the model may cause the node to be
+ * replaced. So holding a direct pointer onto the target node is not an option;
+ * this instance may become outdated.
+ * </p>
+ * <p>
+ * This class provides an API for selecting a specific node by using a
+ * {@link NodeSelector}. The selector is used to obtain an initial reference to
+ * the target node. It is then applied again after each update of the associated
+ * node model (which is done in the {@code update()} method). At this point of
+ * time two things can happen:
+ * <ul>
+ * <li>The {@code NodeSelector} associated with the tracked node still selects a
+ * single node. Then this node becomes the new tracked node. This may be the
+ * same instance as before or a new one.</li>
+ * <li>The selector does no longer find the target node. This can happen for
+ * instance if it has been removed by an operation. In this case, the previous
+ * node instance is used. It is now detached from the model, but can still be
+ * used for operations on this subtree. It may even become life again after
+ * another update of the model.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * Implementation note: This class is intended to work in a concurrent
+ * environment. Instances are immutable. The represented state can be updated by
+ * creating new instances which are then stored by the owning node model.
+ * </p>
+ *
+ * @version $Id$
+ * @since 2.0
+ */
+class NodeTracker
+{
+ /** A map with data about tracked nodes. */
+ private final Map<NodeSelector, TrackedNodeData> trackedNodes;
+
+ /**
+ * Creates a new instance of {@code NodeTracker}. This instance does not yet
+ * track any nodes.
+ */
+ public NodeTracker()
+ {
+ this(Collections.<NodeSelector, TrackedNodeData> emptyMap());
+ }
+
+ /**
+ * Creates a new instance of {@code NodeTracker} and initializes it with the
+ * given map of tracked nodes. This constructor is used internally when the
+ * state of tracked nodes has changed.
+ *
+ * @param map the map with tracked nodes
+ */
+ private NodeTracker(Map<NodeSelector, TrackedNodeData> map)
+ {
+ trackedNodes = map;
+ }
+
+ /**
+ * Adds a node to be tracked. The passed in selector must select exactly one
+ * target node, otherwise an exception is thrown. A new instance is created
+ * with the updated tracking state.
+ *
+ * @param root the root node
+ * @param selector the {@code NodeSelector}
+ * @param resolver the {@code NodeKeyResolver}
+ * @param handler the {@code NodeHandler}
+ * @return the updated instance
+ * @throws ConfigurationRuntimeException if the selector does not select a
+ * single node
+ */
+ public NodeTracker trackNode(ImmutableNode root, NodeSelector selector,
+ NodeKeyResolver<ImmutableNode> resolver,
+ NodeHandler<ImmutableNode> handler)
+ {
+ Map<NodeSelector, TrackedNodeData> newState =
+ new HashMap<NodeSelector, TrackedNodeData>(trackedNodes);
+ TrackedNodeData trackData = newState.get(selector);
+ newState.put(
+ selector,
+ trackDataForAddedObserver(root, selector, resolver, handler,
+ trackData));
+ return new NodeTracker(newState);
+ }
+
+ /**
+ * Notifies this object that an observer was removed for the specified
+ * tracked node. If this was the last observer, the track data for this
+ * selector can be removed.
+ *
+ * @param selector the {@code NodeSelector}
+ * @return the updated instance
+ * @throws ConfigurationRuntimeException if no information about this node
+ * is available
+ */
+ public NodeTracker untrackNode(NodeSelector selector)
+ {
+ TrackedNodeData trackData = getTrackedNodeData(selector);
+
+ Map<NodeSelector, TrackedNodeData> newState =
+ new HashMap<NodeSelector, TrackedNodeData>(trackedNodes);
+ TrackedNodeData newTrackData = trackData.observerRemoved();
+ if (newTrackData == null)
+ {
+ newState.remove(selector);
+ }
+ else
+ {
+ newState.put(selector, newTrackData);
+ }
+ return new NodeTracker(newState);
+ }
+
+ /**
+ * Returns the current {@code ImmutableNode} instance associated with the
+ * given selector.
+ *
+ * @param selector the {@code NodeSelector}
+ * @return the {@code ImmutableNode} selected by this selector
+ * @throws ConfigurationRuntimeException if no data for this selector is
+ * available
+ */
+ public ImmutableNode getTrackedNode(NodeSelector selector)
+ {
+ return getTrackedNodeData(selector).getNode();
+ }
+
+ /**
+ * Updates tracking information after the node structure has been changed.
+ * This method iterates over all tracked nodes. The selectors are evaluated
+ * again to update the node reference. If this fails for a selector, the
+ * previous node is reused; this tracked node is then detached.
+ *
+ * @param root the root node
+ * @param resolver the {@code NodeKeyResolver}
+ * @param handler the {@code NodeHandler}
+ * @return the updated instance
+ */
+ public NodeTracker update(ImmutableNode root,
+ NodeKeyResolver<ImmutableNode> resolver,
+ NodeHandler<ImmutableNode> handler)
+ {
+ if (trackedNodes.isEmpty())
+ {
+ // there is not state to be updated
+ return this;
+ }
+
+ Map<NodeSelector, TrackedNodeData> newState =
+ new HashMap<NodeSelector, TrackedNodeData>();
+ for (Map.Entry<NodeSelector, TrackedNodeData> e : trackedNodes
+ .entrySet())
+ {
+ ImmutableNode newTarget =
+ e.getKey().select(root, resolver, handler);
+ TrackedNodeData newTrackData =
+ (newTarget != null) ? e.getValue().updateNode(newTarget)
+ : e.getValue();
+ newState.put(e.getKey(), newTrackData);
+ }
+
+ return new NodeTracker(newState);
+ }
+
+ /**
+ * Obtains the {@code TrackedNodeData} object for the specified selector. If
+ * the selector cannot be resolved, an exception is thrown.
+ *
+ * @param selector the {@code NodeSelector}
+ * @return the {@code TrackedNodeData} object for this selector
+ * @throws ConfigurationRuntimeException if the selector cannot be resolved
+ */
+ private TrackedNodeData getTrackedNodeData(NodeSelector selector)
+ {
+ TrackedNodeData trackData = trackedNodes.get(selector);
+ if (trackData == null)
+ {
+ throw new ConfigurationRuntimeException("No tracked node found: "
+ + selector);
+ }
+ return trackData;
+ }
+
+ /**
+ * Creates a {@code TrackedNodeData} object for a newly added observer for
+ * the specified node selector.
+ *
+ * @param root the root node
+ * @param selector the {@code NodeSelector}
+ * @param resolver the {@code NodeKeyResolver}
+ * @param handler the {@code NodeHandler}
+ * @param trackData the current data for this selector
+ * @return the updated {@code TrackedNodeData}
+ * @throws ConfigurationRuntimeException if the selector does not select a
+ * single node
+ */
+ private static TrackedNodeData trackDataForAddedObserver(
+ ImmutableNode root, NodeSelector selector,
+ NodeKeyResolver<ImmutableNode> resolver,
+ NodeHandler<ImmutableNode> handler, TrackedNodeData trackData)
+ {
+ if (trackData != null)
+ {
+ return trackData.observerAdded();
+ }
+ else
+ {
+ ImmutableNode target = selector.select(root, resolver, handler);
+ if (target == null)
+ {
+ throw new ConfigurationRuntimeException(
+ "Selector does not select unique node: " + selector);
+ }
+ return new TrackedNodeData(target);
+ }
+ }
+
+ /**
+ * A simple data class holding information about a tracked node.
+ */
+ private static class TrackedNodeData
+ {
+ /** The current instance of the tracked node. */
+ private final ImmutableNode node;
+
+ /** The number of observers of this tracked node. */
+ private final int observerCount;
+
+ /**
+ * Creates a new instance of {@code TrackedNodeData} and initializes it
+ * with the current reference to the tracked node.
+ *
+ * @param nd the tracked node
+ */
+ public TrackedNodeData(ImmutableNode nd)
+ {
+ this(nd, 1);
+ }
+
+ /**
+ * Creates a new instance of {@code TrackedNodeData} and initializes its
+ * properties.
+ *
+ * @param nd the tracked node
+ * @param obsCount the observer count
+ */
+ private TrackedNodeData(ImmutableNode nd, int obsCount)
+ {
+ node = nd;
+ observerCount = obsCount;
+ }
+
+ /**
+ * Returns the tracked node.
+ *
+ * @return the tracked node
+ */
+ public ImmutableNode getNode()
+ {
+ return node;
+ }
+
+ /**
+ * Another observer was added for this tracked node. This method returns
+ * a new instance with an adjusted observer count.
+ *
+ * @return the updated instance
+ */
+ public TrackedNodeData observerAdded()
+ {
+ return new TrackedNodeData(node, observerCount + 1);
+ }
+
+ /**
+ * An observer for this tracked node was removed. This method returns a
+ * new instance with an adjusted observer count. If there are no more
+ * observers, result is <b>null</b>. This means that this node is no
+ * longer tracked and can be released.
+ *
+ * @return the updated instance or <b>null</b>
+ */
+ public TrackedNodeData observerRemoved()
+ {
+ return (observerCount <= 1) ? null : new TrackedNodeData(node,
+ observerCount - 1);
+ }
+
+ /**
+ * Updates the node reference. This method is called after an update of
+ * the underlying node structure if the tracked node was replaced by
+ * another instance.
+ *
+ * @param newNode the new tracked node instance
+ * @return the updated instance
+ */
+ public TrackedNodeData updateNode(ImmutableNode newNode)
+ {
+ return new TrackedNodeData(newNode, observerCount);
+ }
+ }
+}