blob: af2b376c42611316e4d599f30f77abb90b73458a [file] [log] [blame]
/*
* 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.configuration2.tree;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
/**
* A special test class for {@code InMemoryNodeModel} which tests the facilities
* for tracking nodes.
*
*/
public class TestInMemoryNodeModelTrackedNodes
{
/** Constant for the name of a new table field. */
private static final String NEW_FIELD = "newTableField";
/** Constant for a test key. */
private static final String TEST_KEY = "someTestKey";
/** Constant for the key used by the test selector. */
private static final String SELECTOR_KEY = "tables.table(1)";
/** The root node for the test hierarchy. */
private static ImmutableNode root;
/** A default node selector initialized with a test key. */
private static NodeSelector selector;
/** The model to be tested. */
private InMemoryNodeModel model;
@BeforeClass
public static void setUpBeforeClass() throws Exception
{
root =
new ImmutableNode.Builder(1).addChild(
NodeStructureHelper.ROOT_TABLES_TREE).create();
selector = new NodeSelector(SELECTOR_KEY);
}
@Before
public void setUp() throws Exception
{
model = new InMemoryNodeModel(root);
}
/**
* Creates a default resolver which supports arbitrary queries on a target
* node.
*
* @return the resolver
*/
private static NodeKeyResolver<ImmutableNode> createResolver()
{
return createResolver(true);
}
/**
* Creates a default resolver which supports arbitrary queries on a target
* node and allows specifying the replay flag. If the boolean parameter is
* false, the mock is not replayed; so additional behaviors can be defined.
*
* @param replay the replay flag
* @return the resolver mock
*/
private static NodeKeyResolver<ImmutableNode> createResolver(final boolean replay)
{
final NodeKeyResolver<ImmutableNode> resolver =
NodeStructureHelper.createResolverMock();
NodeStructureHelper.expectResolveKeyForQueries(resolver);
if (replay)
{
EasyMock.replay(resolver);
}
return resolver;
}
/**
* Tries to call trackNode() with a key that does not yield any results.
*/
@Test(expected = ConfigurationRuntimeException.class)
public void testTrackNodeKeyNoResults()
{
model.trackNode(new NodeSelector("tables.unknown"), createResolver());
}
/**
* Tries to call trackNode() with a key that selects multiple results.
*/
@Test(expected = ConfigurationRuntimeException.class)
public void testTrackNodeKeyMultipleResults()
{
model.trackNode(new NodeSelector("tables.table.fields.field.name"),
createResolver());
}
/**
* Tests whether a tracked node can be queried.
*/
@Test
public void testGetTrackedNodeExisting()
{
final ImmutableNode node =
NodeStructureHelper.nodeForKey(model, "tables/table(1)");
model.trackNode(selector, createResolver());
assertSame("Wrong node", node, model.getTrackedNode(selector));
}
/**
* Tries to obtain a tracked node which is unknown.
*/
@Test(expected = ConfigurationRuntimeException.class)
public void testGetTrackedNodeNonExisting()
{
model.getTrackedNode(selector);
}
/**
* Tests whether a tracked node survives updates of the node model.
*/
@Test
public void testGetTrackedNodeAfterUpdate()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
model.clearProperty("tables.table(1).fields.field(1).name", resolver);
final ImmutableNode node = model.getTrackedNode(selector);
assertEquals("Wrong node", NodeStructureHelper.table(1), node
.getChildren().get(0).getValue());
}
/**
* Tests whether a tracked node can be queried even if it was removed from
* the structure.
*/
@Test
public void testGetTrackedNodeAfterUpdateNoLongerExisting()
{
final ImmutableNode node =
NodeStructureHelper.nodeForKey(model, "tables/table(1)");
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
initDetachedNode(resolver);
assertSame("Wrong node", node, model.getTrackedNode(selector));
}
/**
* Produces a tracked node with the default selector and executes an
* operation which detaches this node.
*
* @param resolver the {@code NodeKeyResolver}
*/
private void initDetachedNode(final NodeKeyResolver<ImmutableNode> resolver)
{
model.trackNode(selector, resolver);
model.clearTree("tables.table(0)", resolver);
}
/**
* Tests whether a tracked node can be queried even after the model was
* cleared.
*/
@Test
public void testGetTrackedNodeAfterClear()
{
final ImmutableNode node =
NodeStructureHelper.nodeForKey(model, "tables/table(1)");
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
model.clear(resolver);
assertSame("Wrong node", node, model.getTrackedNode(selector));
}
/**
* Tests whether a tracked node can be queried after the root node was
* changed.
*/
@Test
public void testGetTrackedNodeAfterSetRootNode()
{
final ImmutableNode node =
NodeStructureHelper.nodeForKey(model, "tables/table(1)");
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
model.setRootNode(root);
assertSame("Wrong node", node, model.getTrackedNode(selector));
}
/**
* Tries to stop tracking of a node which is not tracked.
*/
@Test(expected = ConfigurationRuntimeException.class)
public void testUntrackNodeNonExisting()
{
model.untrackNode(selector);
}
/**
* Tests whether tracking of a node can be stopped.
*/
@Test
public void testUntrackNode()
{
model.trackNode(selector, createResolver());
model.untrackNode(selector);
try
{
model.getTrackedNode(selector);
fail("Could get untracked node!");
}
catch (final ConfigurationRuntimeException crex)
{
// expected
}
}
/**
* Tests whether a single node can be tracked multiple times.
*/
@Test
public void testTrackNodeMultipleTimes()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
model.trackNode(selector, resolver);
model.untrackNode(selector);
assertNotNull("No tracked node", model.getTrackedNode(selector));
}
/**
* Tests isDetached() for a node which has just been tracked.
*/
@Test
public void testIsDetachedFalseNoUpdates()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
assertFalse("Node is detached", model.isTrackedNodeDetached(selector));
}
/**
* Tests isDetached() for a life node.
*/
@Test
public void testIsDetachedFalseAfterUpdate()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
model.clearProperty("tables.table(1).fields.field(1).name", resolver);
assertFalse("Node is detached", model.isTrackedNodeDetached(selector));
}
/**
* Tests isDetached() for an actually detached node.
*/
@Test
public void testIsDetachedTrue()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
initDetachedNode(resolver);
assertTrue("Node is not detached",
model.isTrackedNodeDetached(selector));
}
/**
* Tests whether a clear() operation causes nodes to be detached.
*/
@Test
public void testIsDetachedAfterClear()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
model.clear(resolver);
assertTrue("Node is not detached",
model.isTrackedNodeDetached(selector));
}
/**
* Tests whether tracked nodes become detached when a new root node is set.
*/
@Test
public void testIsDetachedAfterSetRoot()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
model.clearProperty("tables.table(1).fields.field(1).name", resolver);
model.setRootNode(root);
assertTrue("Node is not detached",
model.isTrackedNodeDetached(selector));
}
/**
* Returns the fields node from the model.
*
* @return the fields node
*/
private ImmutableNode fieldsNodeFromModel()
{
return NodeStructureHelper.nodeForKey(model, "tables/table(1)/fields");
}
/**
* Returns the fields node from a tracked node.
*
* @return the fields node
*/
private ImmutableNode fieldsNodeFromTrackedNode()
{
return NodeStructureHelper.nodeForKey(model.getTrackedNode(selector),
"fields");
}
/**
* Helper method for checking whether the expected field node was removed.
*
* @param nodeFields the fields node
* @param idx the index of the removed field
*/
private static void checkForRemovedField(final ImmutableNode nodeFields, final int idx)
{
assertEquals("Field not removed",
NodeStructureHelper.fieldsLength(1) - 1, nodeFields
.getChildren().size());
final Set<String> expectedNames = new HashSet<>();
final Set<String> actualNames = new HashSet<>();
for (int i = 0; i < NodeStructureHelper.fieldsLength(1); i++)
{
if (idx != i)
{
expectedNames.add(NodeStructureHelper.field(1, i));
}
}
for (final ImmutableNode field : nodeFields)
{
final ImmutableNode nodeName = field.getChildren().get(0);
actualNames.add(String.valueOf(nodeName.getValue()));
}
assertEquals("Wrong field names", expectedNames, actualNames);
}
/**
* Tests whether clearProperty() can operate on a tracked node.
*/
@Test
public void testClearPropertyOnTrackedNode()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
model.clearProperty("fields.field(0).name", selector, resolver);
final ImmutableNode nodeFields = fieldsNodeFromModel();
checkForRemovedField(nodeFields, 0);
}
/**
* Tests a clearProperty() operation on a tracked node which is detached.
*/
@Test
public void testClearPropertyOnDetachedNode()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
initDetachedNode(resolver);
final ImmutableNode rootNode = model.getRootNode();
model.clearProperty("fields.field(0).name", selector, resolver);
assertSame("Model root was changed", rootNode, model.getRootNode());
final ImmutableNode nodeFields = fieldsNodeFromTrackedNode();
checkForRemovedField(nodeFields, 0);
}
/**
* Tests whether clearTree() can operate on a tracked node.
*/
@Test
public void testClearTreeOnTrackedNode()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
model.clearTree("fields.field(1)", selector, resolver);
final ImmutableNode nodeFields = fieldsNodeFromModel();
checkForRemovedField(nodeFields, 1);
}
/**
* Tests a clearTree() operation on a tracked node which is detached.
*/
@Test
public void testClearTreeOnDetachedNode()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
initDetachedNode(resolver);
final ImmutableNode rootNode = model.getRootNode();
model.clearTree("fields.field(1)", selector, resolver);
assertSame("Model root was changed", rootNode, model.getRootNode());
final ImmutableNode nodeFields = fieldsNodeFromTrackedNode();
checkForRemovedField(nodeFields, 1);
}
/**
* Tests whether a field node was added.
*
* @param nodeFields the fields node
*/
private static void checkForAddedField(final ImmutableNode nodeFields)
{
assertEquals("Wrong number of children",
NodeStructureHelper.fieldsLength(1) + 1, nodeFields
.getChildren().size());
final ImmutableNode nodeField =
nodeFields.getChildren().get(
NodeStructureHelper.fieldsLength(1));
checkFieldNode(nodeField, NEW_FIELD);
}
/**
* Checks whether a field node has the expected content.
*
* @param nodeField the field node to be checked
* @param name the expected name of this field
*/
private static void checkFieldNode(final ImmutableNode nodeField, final String name)
{
assertEquals("Wrong node name", "field", nodeField.getNodeName());
assertEquals("Wrong number of children of field node", 1, nodeField
.getChildren().size());
final ImmutableNode nodeName = nodeField.getChildren().get(0);
assertEquals("Wrong name of name node", "name", nodeName.getNodeName());
assertEquals("Wrong node value", name, nodeName.getValue());
}
/**
* Tests whether an addProperty() operation works on a tracked node.
*/
@Test
public void testAddPropertyOnTrackedNode()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver(false);
NodeStructureHelper.expectResolveAddKeys(resolver);
EasyMock.replay(resolver);
model.trackNode(selector, resolver);
model.addProperty("fields.field(-1).name", selector,
Collections.singleton(NEW_FIELD), resolver);
checkForAddedField(fieldsNodeFromModel());
checkForAddedField(fieldsNodeFromTrackedNode());
}
/**
* Tests an addProperty() operation on a tracked node that is detached.
*/
@Test
public void testAddPropertyOnDetachedNode()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver(false);
NodeStructureHelper.expectResolveAddKeys(resolver);
EasyMock.replay(resolver);
model.trackNode(selector, resolver);
initDetachedNode(resolver);
final ImmutableNode rootNode = model.getRootNode();
model.addProperty("fields.field(-1).name", selector,
Collections.singleton(NEW_FIELD), resolver);
assertSame("Root node was changed", rootNode, model.getRootNode());
checkForAddedField(fieldsNodeFromTrackedNode());
}
/**
* Tests whether an addNodes() operation works on a tracked node.
*/
@Test
public void testAddNodesOnTrackedNode()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver(false);
NodeStructureHelper.expectResolveAddKeys(resolver);
EasyMock.replay(resolver);
model.trackNode(selector, resolver);
model.addNodes("fields", selector, Collections
.singleton(NodeStructureHelper.createFieldNode(NEW_FIELD)),
resolver);
checkForAddedField(fieldsNodeFromModel());
checkForAddedField(fieldsNodeFromTrackedNode());
}
/**
* Tests an addNodes() operation on a tracked node that is detached.
*/
@Test
public void testAddNodesOnDetachedNode()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver(false);
NodeStructureHelper.expectResolveAddKeys(resolver);
EasyMock.replay(resolver);
model.trackNode(selector, resolver);
initDetachedNode(resolver);
final ImmutableNode rootNode = model.getRootNode();
model.addNodes("fields", selector, Collections
.singleton(NodeStructureHelper.createFieldNode(NEW_FIELD)),
resolver);
assertSame("Root node was changed", rootNode, model.getRootNode());
checkForAddedField(fieldsNodeFromTrackedNode());
}
/**
* Prepares a mock for a resolver to handle keys for update operations.
* Support is limited. It is expected that only a single value is changed.
*
* @param resolver the {@code NodeKeyResolver} mock
*/
private static void prepareResolverForUpdateKeys(
final NodeKeyResolver<ImmutableNode> resolver)
{
EasyMock.expect(
resolver.resolveUpdateKey(
EasyMock.anyObject(ImmutableNode.class),
EasyMock.anyString(), EasyMock.anyObject(),
EasyMock.anyObject(TreeData.class)))
.andAnswer(() -> {
final ImmutableNode root =
(ImmutableNode) EasyMock.getCurrentArguments()[0];
final String key = (String) EasyMock.getCurrentArguments()[1];
final TreeData handler =
(TreeData) EasyMock.getCurrentArguments()[3];
final List<QueryResult<ImmutableNode>> results =
DefaultExpressionEngine.INSTANCE.query(root,
key, handler);
assertEquals("Wrong number of query results", 1,
results.size());
return new NodeUpdateData<>(Collections
.singletonMap(results.get(0),
EasyMock.getCurrentArguments()[2]),
null, null, null);
}).anyTimes();
}
/**
* Checks whether a fields node was correctly changed by an update
* operation.
*
* @param nodeFields the fields node
* @param idx the index of the changed node
*/
private static void checkedForChangedField(final ImmutableNode nodeFields, final int idx)
{
assertEquals("Wrong number of field nodes",
NodeStructureHelper.fieldsLength(1), nodeFields.getChildren()
.size());
int childIndex = 0;
for (final ImmutableNode field : nodeFields)
{
final String expName =
childIndex == idx ? NEW_FIELD : NodeStructureHelper
.field(1, childIndex);
checkFieldNode(field, expName);
childIndex++;
}
}
/**
* Tests whether a setProperty() operation works on a tracked node.
*/
@Test
public void testSetPropertyOnTrackedNode()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver(false);
prepareResolverForUpdateKeys(resolver);
EasyMock.replay(resolver);
model.trackNode(selector, resolver);
model.setProperty("fields.field(0).name", selector, NEW_FIELD, resolver);
checkedForChangedField(fieldsNodeFromModel(), 0);
checkedForChangedField(fieldsNodeFromTrackedNode(), 0);
}
/**
* Tests a setProperty() operation on a tracked node that is detached.
*/
@Test
public void testSetPropertyOnDetachedNode()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver(false);
prepareResolverForUpdateKeys(resolver);
EasyMock.replay(resolver);
model.trackNode(selector, resolver);
initDetachedNode(resolver);
final ImmutableNode rootNode = model.getRootNode();
model.setProperty("fields.field(0).name", selector, NEW_FIELD, resolver);
assertSame("Root node of model was changed", rootNode,
model.getRootNode());
checkedForChangedField(fieldsNodeFromTrackedNode(), 0);
}
/**
* Tests whether a tracked node is handled correctly if an operation is
* executed on this node which causes the node to be detached. In this case,
* the node should be cleared (it makes no sense to use the last defined
* node instance).
*/
@Test
public void testTrackedNodeClearedInOperation()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
model.clearTree(null, selector, resolver);
assertTrue("Node not detached", model.isTrackedNodeDetached(selector));
final ImmutableNode node = model.getTrackedNode(selector);
assertEquals("Name was changed", "table", node.getNodeName());
assertFalse("Node is defined", model.getNodeHandler().isDefined(node));
}
/**
* Tests whether a node handler for a tracked node can be queried which is
* still active.
*/
@Test
public void testGetTrackedNodeHandlerActive()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
final NodeHandler<ImmutableNode> handler =
model.getTrackedNodeHandler(selector);
assertTrue("Wrong node handler: " + handler,
handler instanceof TrackedNodeHandler);
assertSame("Wrong root node", model.getTrackedNode(selector),
handler.getRootNode());
final TrackedNodeHandler tnh = (TrackedNodeHandler) handler;
assertSame("Wrong parent handler", model.getTreeData(),
tnh.getParentHandler());
}
/**
* Tests whether a node handler for a detached tracked node can be queried.
*/
@Test
public void testGetTrackedNodeHandlerDetached()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
initDetachedNode(resolver);
final NodeHandler<ImmutableNode> handler =
model.getTrackedNodeHandler(selector);
assertSame("Wrong root node", model.getTrackedNode(selector),
handler.getRootNode());
assertTrue("Wrong handler: " + handler, handler instanceof TreeData);
assertNotSame("Shared handler", model.getNodeHandler(), handler);
}
/**
* Helper method for testing whether a tracked node can be replaced.
*/
private void checkReplaceTrackedNode()
{
final ImmutableNode newNode =
new ImmutableNode.Builder().name("newNode").create();
model.replaceTrackedNode(selector, newNode);
assertSame("Node not changed", newNode, model.getTrackedNode(selector));
assertTrue("Node not detached", model.isTrackedNodeDetached(selector));
}
/**
* Tests whether an active tracked node can be replaced.
*/
@Test
public void testReplaceTrackedNodeForActiveTrackedNode()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
checkReplaceTrackedNode();
}
/**
* Tests whether a detached tracked node can be replaced.
*/
@Test
public void testReplaceTrackedNodeForDetachedNode()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
initDetachedNode(resolver);
checkReplaceTrackedNode();
}
/**
* Tries to replace a tracked node with a null node.
*/
@Test(expected = IllegalArgumentException.class)
public void testReplaceTrackedNodeNull()
{
model.trackNode(selector, createResolver());
model.replaceTrackedNode(selector, null);
}
/**
* Prepares the resolver mock to expect a nodeKey() request.
*
* @param resolver the {@code NodeKeyResolver}
* @param node the node whose name is to be resolved
* @param key the key to be returned for this node
*/
private void expectNodeKey(final NodeKeyResolver<ImmutableNode> resolver,
final ImmutableNode node, final String key)
{
final Map<ImmutableNode, String> cache = new HashMap<>();
EasyMock.expect(resolver.nodeKey(node, cache, model.getNodeHandler()))
.andReturn(key);
}
/**
* Tests whether tracked nodes can be created from a key.
*/
@Test
public void testSelectAndTrackNodes()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver(false);
final String nodeKey1 = "tables/table(0)";
final String nodeKey2 = "tables/table(1)";
final ImmutableNode node1 = NodeStructureHelper.nodeForKey(root, nodeKey1);
final ImmutableNode node2 = NodeStructureHelper.nodeForKey(root, nodeKey2);
EasyMock.expect(
resolver.resolveNodeKey(root, TEST_KEY, model.getNodeHandler()))
.andReturn(Arrays.asList(node1, node2));
expectNodeKey(resolver, node1, nodeKey1);
expectNodeKey(resolver, node2, nodeKey2);
EasyMock.replay(resolver);
final Collection<NodeSelector> selectors =
model.selectAndTrackNodes(TEST_KEY, resolver);
final Iterator<NodeSelector> it = selectors.iterator();
NodeSelector sel = it.next();
assertEquals("Wrong selector 1", new NodeSelector(nodeKey1), sel);
assertSame("Wrong tracked node 1", node1, model.getTrackedNode(sel));
sel = it.next();
assertEquals("Wrong selector 2", new NodeSelector(nodeKey2), sel);
assertSame("Wrong tracked node 2", node2, model.getTrackedNode(sel));
assertFalse("Too many selectors", it.hasNext());
}
/**
* Tests selectAndTrackNodes() if the key does not select any nodes.
*/
@Test
public void testSelectAndTrackNodesNoSelection()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver(false);
EasyMock.expect(
resolver.resolveNodeKey(root, TEST_KEY, model.getNodeHandler()))
.andReturn(Collections.<ImmutableNode>emptyList());
EasyMock.replay(resolver);
assertTrue("Got selectors",
model.selectAndTrackNodes(TEST_KEY, resolver).isEmpty());
}
/**
* Tests whether selectAndTrackNodes() works for nodes that are already
* tracked.
*/
@Test
public void testSelectAndTrackNodesNodeAlreadyTracked()
{
NodeKeyResolver<ImmutableNode> resolver = createResolver();
model.trackNode(selector, resolver);
resolver = createResolver(false);
final ImmutableNode node = model.getTrackedNode(selector);
EasyMock.expect(
resolver.resolveNodeKey(root, TEST_KEY, model.getNodeHandler()))
.andReturn(Collections.singletonList(node));
expectNodeKey(resolver, node, SELECTOR_KEY);
EasyMock.replay(resolver);
final Collection<NodeSelector> selectors =
model.selectAndTrackNodes(TEST_KEY, resolver);
assertEquals("Wrong number of selectors", 1, selectors.size());
assertEquals("Wrong selector", selector, selectors.iterator().next());
model.untrackNode(selector);
assertSame("Node not tracked", node, model.getTrackedNode(selector));
}
/**
* Tests whether all children of a node can be tracked at once.
*/
@Test
public void testTrackChildNodes()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver(false);
final ImmutableNode node = NodeStructureHelper.nodeForKey(root, "tables");
final String[] keys = new String[node.getChildren().size()];
for (int i = 0; i < keys.length; i++)
{
final ImmutableNode child = node.getChildren().get(i);
keys[i] =
String.format("%s.%s(%d)", node.getNodeName(),
child.getNodeName(), i);
expectNodeKey(resolver, child, keys[i]);
}
EasyMock.expect(
resolver.resolveNodeKey(root, TEST_KEY, model.getNodeHandler()))
.andReturn(Collections.singletonList(node));
EasyMock.replay(resolver);
final Collection<NodeSelector> selectors =
model.trackChildNodes(TEST_KEY, resolver);
assertEquals("Wrong number of selectors", node.getChildren().size(),
selectors.size());
int idx = 0;
for (final NodeSelector sel : selectors)
{
assertEquals("Wrong selector", new NodeSelector(keys[idx]), sel);
assertEquals("Wrong tracked node for " + sel, node.getChildren()
.get(idx), model.getTrackedNode(sel));
idx++;
}
}
/**
* Checks trackChildNodes() if the passed in key has a result set which
* causes the operation to be aborted.
*
* @param queryResult the result set of the key
*/
private void checkTrackChildNodesNoResult(final List<ImmutableNode> queryResult)
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver(false);
EasyMock.expect(
resolver.resolveNodeKey(root, TEST_KEY, model.getNodeHandler()))
.andReturn(queryResult);
EasyMock.replay(resolver);
final TreeData oldData = model.getTreeData();
assertTrue("Got selectors", model.trackChildNodes(TEST_KEY, resolver)
.isEmpty());
assertSame("Model was changed", oldData, model.getTreeData());
}
/**
* Tests trackChildNodes() for a key that does not return any results.
*/
@Test
public void testTrackChildNodesNoResults()
{
checkTrackChildNodesNoResult(Collections.<ImmutableNode> emptyList());
}
/**
* Tests trackChildNodes() for a key that returns more than a single result.
*/
@Test
public void testTrackChildNodesMultipleResults()
{
checkTrackChildNodesNoResult(Arrays.asList(
NodeStructureHelper.nodeForKey(root, "tables/table(0)"),
NodeStructureHelper.nodeForKey(root, "tables/table(1)")));
}
/**
* Tests trackChildNodes() for a key pointing to a node with no children.
*/
@Test
public void testTrackChildNodesNodeWithNoChildren()
{
checkTrackChildNodesNoResult(Collections
.singletonList(NodeStructureHelper.nodeForKey(root,
"tables/table(0)/name")));
}
/**
* Tests whether an existing child of a selected node can be tracked.
*/
@Test
public void testTrackChildNodeWithCreationExisting()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver(false);
final String childName = "name";
final String parentKey = "tables/table(0)";
final String childKey = parentKey + "/" + childName;
final ImmutableNode node = NodeStructureHelper.nodeForKey(model, parentKey);
final ImmutableNode child = NodeStructureHelper.nodeForKey(node, childName);
EasyMock.expect(
resolver.resolveNodeKey(root, TEST_KEY, model.getNodeHandler()))
.andReturn(Collections.singletonList(node));
expectNodeKey(resolver, child, childKey);
EasyMock.replay(resolver);
final NodeSelector childSelector =
model.trackChildNodeWithCreation(TEST_KEY, childName, resolver);
assertEquals("Wrong selector", new NodeSelector(childKey),
childSelector);
assertSame("Wrong tracked node", child,
model.getTrackedNode(childSelector));
}
/**
* Tests whether a child node to be tracked is created if necessary.
*/
@Test
public void testTrackChildNodeWithCreationNonExisting()
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver(false);
final String childName = "space";
final String parentKey = "tables/table(0)";
final String childKey = parentKey + "/" + childName;
final ImmutableNode node = NodeStructureHelper.nodeForKey(model, parentKey);
EasyMock.expect(
resolver.resolveNodeKey(root, TEST_KEY, model.getNodeHandler()))
.andReturn(Collections.singletonList(node));
EasyMock.expect(
resolver.nodeKey(EasyMock.anyObject(ImmutableNode.class),
EasyMock.eq(new HashMap<>()),
EasyMock.anyObject(TreeData.class)))
.andReturn(childKey);
EasyMock.replay(resolver);
final NodeSelector childSelector =
model.trackChildNodeWithCreation(TEST_KEY, childName, resolver);
assertEquals("Wrong selector", new NodeSelector(childKey),
childSelector);
final ImmutableNode child = model.getTrackedNode(childSelector);
assertEquals("Wrong child name", childName, child.getNodeName());
assertNull("Got a value", child.getValue());
final ImmutableNode parent = model.getNodeHandler().getParent(child);
assertEquals("Wrong parent node", "table", parent.getNodeName());
assertEquals("Wrong node path", child,
NodeStructureHelper.nodeForKey(model, childKey));
}
/**
* Helper method for testing trackChildNodeWithCreation() if invalid query
* results are generated.
*
* @param queryResult the result set of the key
*/
private void checkTrackChildNodeWithCreationInvalidKey(
final List<ImmutableNode> queryResult)
{
final NodeKeyResolver<ImmutableNode> resolver = createResolver(false);
EasyMock.expect(
resolver.resolveNodeKey(model.getRootNode(), TEST_KEY,
model.getNodeHandler())).andReturn(queryResult);
EasyMock.replay(resolver);
model.trackChildNodeWithCreation(TEST_KEY, "someChild", resolver);
}
/**
* Tests trackChildNodeWithCreation() if the passed in key does not select a
* node.
*/
@Test(expected = ConfigurationRuntimeException.class)
public void testTrackChildNodeWithCreationNoResults()
{
checkTrackChildNodeWithCreationInvalidKey(new ArrayList<>());
}
/**
* Tests trackChildNodeWithCreation() if the passed in key selects multiple
* nodes.
*/
@Test(expected = ConfigurationRuntimeException.class)
public void testTrackChildNodeWithCreationMultipleResults()
{
final List<ImmutableNode> nodes =
Arrays.asList(
NodeStructureHelper.nodeForKey(root, "tables/table(0)"),
NodeStructureHelper.nodeForKey(root, "tables/table(1)"));
checkTrackChildNodeWithCreationInvalidKey(nodes);
}
}