| /* |
| * 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.apache.commons.configuration2.tree.NodeStructureHelper.ROOT_AUTHORS_TREE; |
| import static org.apache.commons.configuration2.tree.NodeStructureHelper.ROOT_PERSONAE_TREE; |
| import static org.apache.commons.configuration2.tree.NodeStructureHelper.nodeForKey; |
| import static org.apache.commons.configuration2.tree.NodeStructureHelper.nodePathWithEndNode; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotEquals; |
| import static org.junit.Assert.assertNotNull; |
| 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.List; |
| import java.util.Map; |
| import java.util.NoSuchElementException; |
| import java.util.Set; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.easymock.EasyMock; |
| import org.junit.Test; |
| |
| /** |
| * Test class for {@code InMemoryNodeModel}. |
| * |
| */ |
| public class TestInMemoryNodeModel |
| { |
| /** Constant for a test key. */ |
| private static final String KEY = "aTestKey"; |
| |
| /** |
| * Tests whether an undefined default root node is created if none is |
| * specified. |
| */ |
| @Test |
| public void testInitDefaultRoot() |
| { |
| final InMemoryNodeModel model = new InMemoryNodeModel(); |
| final ImmutableNode root = model.getRootNode(); |
| assertNull("Got a name", root.getNodeName()); |
| assertNull("Got a value", root.getValue()); |
| assertTrue("Got children", root.getChildren().isEmpty()); |
| assertTrue("Got attributes", root.getAttributes().isEmpty()); |
| } |
| |
| /** |
| * Tests whether the correct root node is returned if a tree was passed at |
| * construction time. |
| */ |
| @Test |
| public void testGetRootNodeFromConstructor() |
| { |
| final InMemoryNodeModel model = new InMemoryNodeModel(ROOT_AUTHORS_TREE); |
| assertSame("Wrong root node", ROOT_AUTHORS_TREE, model.getRootNode()); |
| } |
| |
| /** |
| * Tests whether the correct node handler is returned. |
| */ |
| @Test |
| public void testGetNodeHandler() |
| { |
| final InMemoryNodeModel model = new InMemoryNodeModel(ROOT_PERSONAE_TREE); |
| assertSame("Wrong node handler", model.getTreeData(), model.getNodeHandler()); |
| } |
| |
| /** |
| * Creates a mock for a {@code NodeKeyResolver}. |
| * |
| * @return the mock for the resolver |
| */ |
| private static NodeKeyResolver<ImmutableNode> createResolver() |
| { |
| @SuppressWarnings("unchecked") |
| final |
| NodeKeyResolver<ImmutableNode> resolver = |
| EasyMock.createMock(NodeKeyResolver.class); |
| return resolver; |
| } |
| |
| /** |
| * Tests whether a property can be added to the node model if there are some |
| * additional path nodes to be created. |
| */ |
| @Test |
| public void testAddPropertyWithPathNodes() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final NodeAddData<ImmutableNode> addData = |
| new NodeAddData<>(nodeForKey(ROOT_AUTHORS_TREE, |
| "Homer/Ilias"), "location", false, |
| Collections.singleton("locations")); |
| final InMemoryNodeModel model = new InMemoryNodeModel(ROOT_AUTHORS_TREE); |
| EasyMock.expect(resolver.resolveAddKey(ROOT_AUTHORS_TREE, KEY, model.getNodeHandler())) |
| .andReturn(addData); |
| EasyMock.replay(resolver); |
| final String[] locations = { |
| "Troja", "Beach", "Olympos" |
| }; |
| |
| model.addProperty(KEY, Arrays.asList(locations), resolver); |
| final ImmutableNode nodeLocs = nodeForKey(model, "Homer/Ilias/locations"); |
| assertEquals("Wrong number of children", locations.length, nodeLocs |
| .getChildren().size()); |
| int idx = 0; |
| for (final ImmutableNode c : nodeLocs.getChildren()) |
| { |
| assertEquals("Wrong node name", "location", c.getNodeName()); |
| assertEquals("Wrong value", locations[idx], c.getValue()); |
| assertTrue("Got children", c.getChildren().isEmpty()); |
| assertTrue("Got attributes", c.getAttributes().isEmpty()); |
| idx++; |
| } |
| assertNotNull("Could not find other nodes", |
| nodeForKey(model, "Homer/Ilias/Hektor")); |
| } |
| |
| /** |
| * Tests whether a property can be added if there are no intermediate path |
| * nodes. |
| */ |
| @Test |
| public void testAddPropertyNoPathNodes() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final NodeAddData<ImmutableNode> addData = |
| new NodeAddData<>(nodeForKey(ROOT_AUTHORS_TREE, |
| "Homer"), "work", false, null); |
| final InMemoryNodeModel model = new InMemoryNodeModel(ROOT_AUTHORS_TREE); |
| EasyMock.expect( |
| resolver.resolveAddKey(ROOT_AUTHORS_TREE, KEY, |
| model.getNodeHandler())).andReturn(addData); |
| EasyMock.replay(resolver); |
| |
| model.addProperty(KEY, Collections.singleton("Odyssee"), resolver); |
| final ImmutableNode node = nodeForKey(model, "Homer/work"); |
| assertEquals("Wrong node value", "Odyssee", node.getValue()); |
| assertNotNull("Could not find other nodes", |
| nodeForKey(model, "Homer/Ilias/Hektor")); |
| } |
| |
| /** |
| * Tests whether the parent node references are updated when nodes are |
| * added. |
| */ |
| @Test |
| public void testAddPropertyUpdateParentReferences() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final NodeAddData<ImmutableNode> addData = |
| new NodeAddData<>(nodeForKey(ROOT_AUTHORS_TREE, |
| "Homer/Ilias"), "location", false, |
| Collections.singleton("locations")); |
| final InMemoryNodeModel model = new InMemoryNodeModel(ROOT_AUTHORS_TREE); |
| EasyMock.expect( |
| resolver.resolveAddKey(ROOT_AUTHORS_TREE, KEY, |
| model.getNodeHandler())).andReturn(addData); |
| EasyMock.replay(resolver); |
| final String[] locations = { |
| "Troja", "Beach", "Olympos" |
| }; |
| |
| model.addProperty(KEY, Arrays.asList(locations), resolver); |
| final String[] path = { |
| "Homer", "Ilias", "locations" |
| }; |
| final ImmutableNode node = |
| nodeForKey(model, nodePathWithEndNode("location(1)", path)); |
| checkPathToRoot(model, node, path); |
| } |
| |
| /** |
| * Helper method for checking whether the expected nodes are encountered on |
| * a path from a start node to the root node. |
| * |
| * @param model the node model |
| * @param node the start node in the path |
| * @param path an array with the expected node names on the path |
| */ |
| private static void checkPathToRoot(final InMemoryNodeModel model, |
| ImmutableNode node, final String... path) |
| { |
| final NodeHandler<ImmutableNode> handler = model.getNodeHandler(); |
| for (int i = path.length - 1; i >= 0; i--) |
| { |
| node = handler.getParent(node); |
| assertEquals("Wrong node name", path[i], node.getNodeName()); |
| } |
| assertSame("Wrong root node", model.getRootNode(), |
| handler.getParent(node)); |
| } |
| |
| /** |
| * Tests whether an attribute can be added if there are some path nodes. |
| */ |
| @Test |
| public void testAddPropertyAttributeWithPathNodes() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final NodeAddData<ImmutableNode> addData = |
| new NodeAddData<>(nodeForKey(ROOT_AUTHORS_TREE, |
| "Homer/Ilias"), "number", true, Arrays.asList("scenes", |
| "scene")); |
| final InMemoryNodeModel model = new InMemoryNodeModel(ROOT_AUTHORS_TREE); |
| EasyMock.expect( |
| resolver.resolveAddKey(ROOT_AUTHORS_TREE, KEY, |
| model.getNodeHandler())).andReturn(addData); |
| EasyMock.replay(resolver); |
| |
| model.addProperty(KEY, Collections.singleton(1), resolver); |
| final ImmutableNode node = nodeForKey(model, "Homer/Ilias/scenes/scene"); |
| assertEquals("Attribute not set", 1, node.getAttributes().get("number")); |
| } |
| |
| /** |
| * Tests the special case that an attribute is added with a single path |
| * node. |
| */ |
| @Test |
| public void testAddPropertyAttributeWithSinglePathNode() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final NodeAddData<ImmutableNode> addData = |
| new NodeAddData<>(nodeForKey(ROOT_AUTHORS_TREE, |
| NodeStructureHelper.author(0)), "year", true, |
| Arrays.asList("dateOfBirth")); |
| final InMemoryNodeModel model = new InMemoryNodeModel(ROOT_AUTHORS_TREE); |
| EasyMock.expect( |
| resolver.resolveAddKey(ROOT_AUTHORS_TREE, KEY, |
| model.getNodeHandler())).andReturn(addData); |
| EasyMock.replay(resolver); |
| |
| final Integer year = 1564; |
| model.addProperty(KEY, Collections.singleton(year), resolver); |
| final ImmutableNode node = nodeForKey(model, "Shakespeare/dateOfBirth"); |
| assertEquals("Attribute not set", year, node.getAttributes() |
| .get("year")); |
| } |
| |
| /** |
| * Tests whether an attribute property can be added if there are no path |
| * nodes. |
| */ |
| @Test |
| public void testAddPropertyAttributeNoPathNodes() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final NodeAddData<ImmutableNode> addData = |
| new NodeAddData<>(nodeForKey(ROOT_AUTHORS_TREE, |
| "Shakespeare/The Tempest"), "year", true, null); |
| final InMemoryNodeModel model = new InMemoryNodeModel(ROOT_AUTHORS_TREE); |
| EasyMock.expect( |
| resolver.resolveAddKey(ROOT_AUTHORS_TREE, KEY, |
| model.getNodeHandler())).andReturn(addData); |
| EasyMock.replay(resolver); |
| |
| model.addProperty(KEY, Collections.singleton(1611), resolver); |
| final ImmutableNode node = nodeForKey(model, "Shakespeare/The Tempest"); |
| assertEquals("Attribute not set", 1611, node.getAttributes() |
| .get("year")); |
| } |
| |
| /** |
| * Tests an addProperty() operation if no values are provided. |
| */ |
| @Test |
| public void testAddPropertyNoValues() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| EasyMock.replay(resolver); |
| final InMemoryNodeModel model = new InMemoryNodeModel(ROOT_AUTHORS_TREE); |
| |
| model.addProperty(KEY, Collections.emptySet(), resolver); |
| assertSame("Root node was changed", ROOT_AUTHORS_TREE, |
| model.getRootNode()); |
| } |
| |
| /** |
| * Tests whether a clearTree() operation can be performed if only nodes are |
| * involved. |
| */ |
| @Test |
| public void testClearTreeNodes() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final InMemoryNodeModel model = new InMemoryNodeModel(ROOT_AUTHORS_TREE); |
| final QueryResult<ImmutableNode> result = |
| QueryResult.createNodeResult(nodeForKey(model, |
| "Homer/Ilias/Achilles")); |
| EasyMock.expect( |
| resolver.resolveKey(ROOT_AUTHORS_TREE, KEY, |
| model.getNodeHandler())).andReturn( |
| Collections.singletonList(result)); |
| EasyMock.replay(resolver); |
| |
| final List<QueryResult<ImmutableNode>> removed = model.clearTree(KEY, resolver); |
| final ImmutableNode node = nodeForKey(model, "Homer/Ilias"); |
| assertEquals("Wrong number of children", 2, node.getChildren().size()); |
| for (final ImmutableNode c : node.getChildren()) |
| { |
| assertNotEquals("Node still found", result.getNode().getNodeName(), |
| c.getNodeName()); |
| } |
| assertEquals("Wrong number of removed elements", 1, removed.size()); |
| assertTrue("Wrong removed element", removed.contains(result)); |
| } |
| |
| /** |
| * Helper method for testing whether nodes removed from the model can no |
| * longer be looked up in the parent mapping. |
| * |
| * @param pathToRemove the path to the node to be removed |
| * @param nodeToCheck the node to check in the parent mapping |
| */ |
| private void checkClearTreeUpdatedParentMapping(final String pathToRemove, |
| final ImmutableNode nodeToCheck) |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final InMemoryNodeModel model = new InMemoryNodeModel(ROOT_AUTHORS_TREE); |
| final QueryResult<ImmutableNode> result = |
| QueryResult.createNodeResult(nodeForKey(model, pathToRemove)); |
| EasyMock.expect( |
| resolver.resolveKey(ROOT_AUTHORS_TREE, KEY, |
| model.getNodeHandler())).andReturn( |
| Collections.singletonList(result)); |
| EasyMock.replay(resolver); |
| |
| model.clearTree(KEY, resolver); |
| try |
| { |
| model.getNodeHandler().getParent(nodeToCheck); |
| fail("Removed node still in parent mapping!"); |
| } |
| catch (final IllegalArgumentException iaex) |
| { |
| // expected result |
| } |
| } |
| |
| /** |
| * Tests whether a removed node can no longer be passed to getParent(). |
| */ |
| @Test |
| public void testClearTreeNodeRemovedFromParentMapping() |
| { |
| final String path = "Homer/Ilias/Achilles"; |
| checkClearTreeUpdatedParentMapping(path, |
| nodeForKey(ROOT_AUTHORS_TREE, path)); |
| } |
| |
| /** |
| * Tests whether the children of removed nodes are also removed from the |
| * parent mapping. |
| */ |
| @Test |
| public void testClearTreeChildrenRemovedFromParentMapping() |
| { |
| final String path = "Homer/Ilias"; |
| checkClearTreeUpdatedParentMapping(path, |
| nodeForKey(ROOT_AUTHORS_TREE, path + "/Achilles")); |
| } |
| |
| /** |
| * Tests whether references to parent nodes are updated correctly when |
| * clearing properties. |
| */ |
| @Test |
| public void testClearTreeUpdateParentReferences() |
| { |
| final String[] path = { |
| "Homer", "Ilias" |
| }; |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final InMemoryNodeModel model = new InMemoryNodeModel(ROOT_AUTHORS_TREE); |
| final QueryResult<ImmutableNode> result = |
| QueryResult.createNodeResult(nodeForKey(model, |
| nodePathWithEndNode("Achilles", path))); |
| EasyMock.expect( |
| resolver.resolveKey(ROOT_AUTHORS_TREE, KEY, |
| model.getNodeHandler())).andReturn( |
| Collections.singletonList(result)); |
| EasyMock.replay(resolver); |
| |
| model.clearTree(KEY, resolver); |
| checkPathToRoot(model, |
| nodeForKey(model, nodePathWithEndNode("Hektor", path)), path); |
| } |
| |
| /** |
| * Tests whether undefined nodes are removed from the hierarchy when |
| * clearing properties. |
| */ |
| @Test |
| public void testClearTreeRemoveUndefinedNodes() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final InMemoryNodeModel model = new InMemoryNodeModel(ROOT_AUTHORS_TREE); |
| final ImmutableNode node = nodeForKey(model, "Homer/Ilias"); |
| final List<QueryResult<ImmutableNode>> results = |
| new ArrayList<>(node.getChildren() |
| .size()); |
| for (final ImmutableNode child : node.getChildren()) |
| { |
| results.add(QueryResult.createNodeResult(child)); |
| } |
| EasyMock.expect( |
| resolver.resolveKey(ROOT_AUTHORS_TREE, KEY, |
| model.getNodeHandler())).andReturn(results); |
| EasyMock.replay(resolver); |
| |
| model.clearTree(KEY, resolver); |
| assertEquals("Child of root not removed", |
| NodeStructureHelper.authorsLength() - 1, model.getRootNode() |
| .getChildren().size()); |
| for (final ImmutableNode child : model.getRootNode().getChildren()) |
| { |
| assertNotEquals("Child still found", "Homer", child.getNodeName()); |
| } |
| } |
| |
| /** |
| * Tests a clearTree() operation which should yield an empty tree structure. |
| */ |
| @Test |
| public void testClearTreeResultIsEmpty() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final ImmutableNode child = |
| new ImmutableNode.Builder().name("child").value("test") |
| .create(); |
| final ImmutableNode root = |
| new ImmutableNode.Builder(1).addChild(child).create(); |
| final InMemoryNodeModel model = new InMemoryNodeModel(root); |
| EasyMock.expect(resolver.resolveKey(root, KEY, model.getNodeHandler())) |
| .andReturn( |
| Collections.singletonList(QueryResult |
| .createNodeResult(child))); |
| EasyMock.replay(resolver); |
| |
| model.clearTree(KEY, resolver); |
| assertFalse("Root node still defined", |
| model.getNodeHandler().isDefined(model.getRootNode())); |
| } |
| |
| /** |
| * Tests whether attributes can be cleared with clearTree(). |
| */ |
| @Test |
| public void testClearTreeAttribute() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final InMemoryNodeModel model = new InMemoryNodeModel(ROOT_PERSONAE_TREE); |
| final String nodeName = "Puck"; |
| final QueryResult<ImmutableNode> result = QueryResult.createAttributeResult( |
| nodeForKey(model, nodeName), |
| NodeStructureHelper.ATTR_AUTHOR); |
| EasyMock.expect( |
| resolver.resolveKey(ROOT_PERSONAE_TREE, KEY, |
| model.getNodeHandler())).andReturn( |
| Collections.singletonList(result)); |
| EasyMock.replay(resolver); |
| |
| final List<QueryResult<ImmutableNode>> removed = model.clearTree(KEY, resolver); |
| final ImmutableNode node = nodeForKey(model, nodeName); |
| assertTrue("Got still attributes", node.getAttributes().isEmpty()); |
| assertEquals("Wrong number of removed elements", 1, removed.size()); |
| assertTrue("Wrong removed element", removed.contains(result)); |
| } |
| |
| /** |
| * Tests whether both nodes and attributes can be removed by a clearTree() |
| * operation. We remove all attributes and children from a node. The node |
| * becomes undefined and should be removed. |
| */ |
| @Test |
| public void testClearTreeNodesAndAttributes() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final InMemoryNodeModel model = new InMemoryNodeModel(ROOT_PERSONAE_TREE); |
| final String nodeName = "Puck"; |
| final ImmutableNode orgNode = nodeForKey(model, nodeName); |
| final List<QueryResult<ImmutableNode>> results = |
| new ArrayList<>(2); |
| results.add(QueryResult.createAttributeResult(orgNode, |
| NodeStructureHelper.ATTR_AUTHOR)); |
| results.add(QueryResult.createNodeResult(orgNode.getChildren().get(0))); |
| EasyMock.expect( |
| resolver.resolveKey(ROOT_PERSONAE_TREE, KEY, |
| model.getNodeHandler())).andReturn(results); |
| EasyMock.replay(resolver); |
| |
| model.clearTree(KEY, resolver); |
| try |
| { |
| nodeForKey(model, nodeName); |
| fail("Node still present!"); |
| } |
| catch (final NoSuchElementException nex) |
| { |
| // expected |
| } |
| } |
| |
| /** |
| * Tests clearTree() if the passed in key does not exist. |
| */ |
| @Test |
| public void testClearTreeNonExistingKey() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final InMemoryNodeModel model = new InMemoryNodeModel(ROOT_PERSONAE_TREE); |
| EasyMock.expect( |
| resolver.resolveKey(ROOT_PERSONAE_TREE, KEY, |
| model.getNodeHandler())).andReturn( |
| Collections.<QueryResult<ImmutableNode>> emptyList()); |
| EasyMock.replay(resolver); |
| |
| final TreeData treeDataOld = model.getTreeData(); |
| assertTrue("Elements removed", model.clearTree(KEY, resolver).isEmpty()); |
| assertNotNull("No root node", model.getNodeHandler().getRootNode()); |
| assertSame("Data was changed", treeDataOld, model.getTreeData()); |
| } |
| |
| /** |
| * Tests whether the whole node structure can be cleared. |
| */ |
| @Test |
| public void testClear() |
| { |
| final InMemoryNodeModel model = new InMemoryNodeModel(ROOT_AUTHORS_TREE); |
| model.clear(createResolver()); |
| assertFalse("Got still data", |
| model.getNodeHandler().isDefined(model.getRootNode())); |
| assertEquals("Root name was changed", ROOT_AUTHORS_TREE.getNodeName(), |
| model.getRootNode().getNodeName()); |
| } |
| |
| /** |
| * Tests whether clearTree() handles the root node in a special way. |
| */ |
| @Test |
| public void testClearTreeRootNode() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final InMemoryNodeModel model = new InMemoryNodeModel(ROOT_AUTHORS_TREE); |
| final List<QueryResult<ImmutableNode>> results = |
| new ArrayList<>(2); |
| results.add(QueryResult.createNodeResult(nodeForKey(model, |
| NodeStructureHelper.author(0)))); |
| results.add(QueryResult.createNodeResult(ROOT_AUTHORS_TREE)); |
| EasyMock.expect( |
| resolver.resolveKey(ROOT_AUTHORS_TREE, KEY, |
| model.getNodeHandler())).andReturn(results); |
| EasyMock.replay(resolver); |
| |
| model.clearTree(KEY, resolver); |
| assertFalse("Got still data", |
| model.getNodeHandler().isDefined(model.getRootNode())); |
| } |
| |
| /** |
| * Tests whether the replacement mapping is automatically compacted if it |
| * gets too large. |
| */ |
| @Test |
| public void testCompactReplacementMapping() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final InMemoryNodeModel model = new InMemoryNodeModel(ROOT_AUTHORS_TREE); |
| final int numberOfOperations = 200; |
| final String key = "Homer/Ilias"; |
| for (int i = 0; i < numberOfOperations; i++) |
| { |
| final int index = i; |
| EasyMock.expect( |
| resolver.resolveAddKey( |
| EasyMock.anyObject(ImmutableNode.class), |
| EasyMock.eq(KEY), |
| EasyMock.anyObject(TreeData.class))).andAnswer( |
| () -> { |
| assertSame("Wrong root node", model.getRootNode(), |
| EasyMock.getCurrentArguments()[0]); |
| final ImmutableNode addParent = nodeForKey(model, key); |
| return new NodeAddData<>(addParent, |
| "Warrior" + index, false, null); |
| }); |
| } |
| EasyMock.replay(resolver); |
| |
| for (int i = 0; i < numberOfOperations; i++) |
| { |
| model.addProperty(KEY, Collections.singleton(i), resolver); |
| } |
| final ImmutableNode orgNode = nodeForKey(ROOT_AUTHORS_TREE, key); |
| final ImmutableNode changedNode = nodeForKey(model, key); |
| assertEquals("Wrong number of children", orgNode.getChildren().size() |
| + numberOfOperations, changedNode.getChildren().size()); |
| final Map<ImmutableNode, ImmutableNode> replacementMapping = |
| model.getTreeData().copyReplacementMapping(); |
| assertTrue("Replacement mapping too big: " + replacementMapping.size(), |
| replacementMapping.size() < numberOfOperations); |
| } |
| |
| /** |
| * Tests whether concurrent updates of the model are handled correctly. This |
| * test adds a number of authors in parallel. Then it is checked whether all |
| * authors have been added correctly. |
| */ |
| @Test |
| public void testConcurrentUpdate() throws InterruptedException |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final InMemoryNodeModel model = |
| new InMemoryNodeModel(NodeStructureHelper.ROOT_AUTHORS_TREE); |
| EasyMock.expect( |
| resolver.resolveAddKey(EasyMock.anyObject(ImmutableNode.class), |
| EasyMock.eq(KEY), EasyMock.anyObject(TreeData.class))) |
| .andAnswer(() -> { |
| final ImmutableNode addParent = |
| (ImmutableNode) EasyMock.getCurrentArguments()[0]; |
| return new NodeAddData<>(addParent, |
| "name", false, Collections.singleton("author")); |
| }).anyTimes(); |
| EasyMock.replay(resolver); |
| |
| final CountDownLatch latch = new CountDownLatch(1); |
| final String authorPrefix = "newAuthor"; |
| final int threadCount = 32; |
| final Thread[] threads = new Thread[threadCount]; |
| for (int i = 0; i < threadCount; i++) |
| { |
| final String authorName = authorPrefix + i; |
| threads[i] = new Thread() |
| { |
| @Override |
| public void run() |
| { |
| try |
| { |
| latch.await(); |
| model.addProperty(KEY, |
| Collections.singleton(authorName), resolver); |
| } |
| catch (final InterruptedException iex) |
| { |
| // ignore |
| } |
| } |
| }; |
| threads[i].start(); |
| } |
| latch.countDown(); |
| for (final Thread t : threads) |
| { |
| t.join(); |
| } |
| |
| final Pattern patternAuthorName = |
| Pattern.compile(Pattern.quote(authorPrefix) + "(\\d+)"); |
| final Set<Integer> indices = new HashSet<>(); |
| for (int i = 0; i < threadCount; i++) |
| { |
| final ImmutableNode node = nodeForKey(model, "author(" + i + ")/name"); |
| final Matcher m = |
| patternAuthorName.matcher(String.valueOf(node.getValue())); |
| assertTrue("Wrong value: " + node.getValue(), m.matches()); |
| final int idx = Integer.parseInt(m.group(1)); |
| assertTrue("Invalid index: " + idx, idx >= 0 && idx < threadCount); |
| indices.add(idx); |
| } |
| assertEquals("Not all authors were created", threadCount, |
| indices.size()); |
| } |
| |
| /** |
| * Tests whether a property value can be cleared on a node. |
| */ |
| @Test |
| public void testClearPropertyNode() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final InMemoryNodeModel model = |
| new InMemoryNodeModel(NodeStructureHelper.ROOT_PERSONAE_TREE); |
| final String nodeKey = |
| "Ariel/The Tempest/" + NodeStructureHelper.ELEM_ORG_VALUE; |
| EasyMock.expect( |
| resolver.resolveKey(model.getRootNode(), KEY, |
| model.getNodeHandler())).andReturn( |
| Collections.singletonList(QueryResult |
| .createNodeResult(nodeForKey(model, nodeKey)))); |
| EasyMock.replay(resolver); |
| |
| model.clearProperty(KEY, resolver); |
| final ImmutableNode node = nodeForKey(model, nodeKey); |
| assertNull("Value not cleared", node.getValue()); |
| } |
| |
| /** |
| * Tests whether a property value stored as an attribute can be cleared. |
| */ |
| @Test |
| public void testClearPropertyAttribute() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final InMemoryNodeModel model = |
| new InMemoryNodeModel(NodeStructureHelper.ROOT_PERSONAE_TREE); |
| final String nodeKey = |
| "Prospero/The Tempest/" + NodeStructureHelper.ELEM_ORG_VALUE; |
| EasyMock.expect( |
| resolver.resolveKey(model.getRootNode(), KEY, |
| model.getNodeHandler())).andReturn( |
| Collections.singletonList(QueryResult.createAttributeResult( |
| nodeForKey(model, nodeKey), |
| NodeStructureHelper.ATTR_TESTED))); |
| EasyMock.replay(resolver); |
| |
| model.clearProperty(KEY, resolver); |
| final ImmutableNode node = nodeForKey(model, nodeKey); |
| assertTrue("Attribute not removed", node.getAttributes().isEmpty()); |
| } |
| |
| /** |
| * Tests clearProperty() for a non existing property. |
| */ |
| @Test |
| public void testClearPropertyNonExisting() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final InMemoryNodeModel model = |
| new InMemoryNodeModel(NodeStructureHelper.ROOT_PERSONAE_TREE); |
| EasyMock.expect( |
| resolver.resolveKey(model.getRootNode(), KEY, |
| model.getNodeHandler())).andReturn( |
| Collections.<QueryResult<ImmutableNode>> emptyList()); |
| EasyMock.replay(resolver); |
| |
| final TreeData treeDataOld = model.getTreeData(); |
| model.clearProperty(KEY, resolver); |
| assertNotNull("No root node", model.getNodeHandler().getRootNode()); |
| assertSame("Data was changed", treeDataOld, model.getTreeData()); |
| } |
| |
| /** |
| * Tests whether setProperty() can handle newly added values. |
| */ |
| @Test |
| public void testSetPropertyNewValues() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final NodeAddData<ImmutableNode> addData = |
| new NodeAddData<>(nodeForKey(ROOT_AUTHORS_TREE, |
| "Homer"), "work", false, null); |
| final NodeUpdateData<ImmutableNode> updateData = |
| new NodeUpdateData<>(null, |
| Collections.<Object> singleton("Odyssee"), null, KEY); |
| final InMemoryNodeModel model = new InMemoryNodeModel(ROOT_AUTHORS_TREE); |
| EasyMock.expect( |
| resolver.resolveUpdateKey(ROOT_AUTHORS_TREE, KEY, this, |
| model.getNodeHandler())).andReturn(updateData); |
| EasyMock.expect( |
| resolver.resolveAddKey(ROOT_AUTHORS_TREE, KEY, |
| model.getNodeHandler())).andReturn(addData); |
| EasyMock.replay(resolver); |
| |
| model.setProperty(KEY, this, resolver); |
| final ImmutableNode node = nodeForKey(model, "Homer/work"); |
| assertEquals("Wrong node value", "Odyssee", node.getValue()); |
| assertNotNull("Could not find other nodes", |
| nodeForKey(model, "Homer/Ilias/Hektor")); |
| } |
| |
| /** |
| * Tests whether setProperty() can handle nodes to be cleared. |
| */ |
| @Test |
| public void testSetPropertyClearValues() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final InMemoryNodeModel model = |
| new InMemoryNodeModel(NodeStructureHelper.ROOT_PERSONAE_TREE); |
| final String nodeKey = |
| "Ariel/The Tempest/" + NodeStructureHelper.ELEM_ORG_VALUE; |
| final NodeUpdateData<ImmutableNode> updateData = |
| new NodeUpdateData<>(null, null, |
| Collections.singletonList(QueryResult |
| .createNodeResult(nodeForKey(model, nodeKey))), |
| null); |
| EasyMock.expect( |
| resolver.resolveUpdateKey( |
| NodeStructureHelper.ROOT_PERSONAE_TREE, KEY, this, |
| model.getNodeHandler())).andReturn(updateData); |
| EasyMock.replay(resolver); |
| |
| model.setProperty(KEY, this, resolver); |
| final ImmutableNode node = nodeForKey(model, nodeKey); |
| assertNull("Value not cleared", node.getValue()); |
| } |
| |
| /** |
| * Tests whether setProperty() can handle changes in node values. |
| */ |
| @Test |
| public void testSetPropertyChangedValues() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final InMemoryNodeModel model = |
| new InMemoryNodeModel(NodeStructureHelper.ROOT_PERSONAE_TREE); |
| final String nodeKey = |
| "Ariel/The Tempest/" + NodeStructureHelper.ELEM_ORG_VALUE; |
| final Map<QueryResult<ImmutableNode>, Object> changedValues = |
| new HashMap<>(); |
| final String newValue = "of course"; |
| final ImmutableNode changedNode = nodeForKey(model, nodeKey); |
| changedValues.put(QueryResult.createAttributeResult(changedNode, |
| NodeStructureHelper.ATTR_TESTED), newValue); |
| changedValues.put(QueryResult.createNodeResult(changedNode), newValue); |
| final NodeUpdateData<ImmutableNode> updateData = |
| new NodeUpdateData<>(changedValues, null, null, |
| null); |
| EasyMock.expect( |
| resolver.resolveUpdateKey( |
| NodeStructureHelper.ROOT_PERSONAE_TREE, KEY, this, |
| model.getNodeHandler())).andReturn(updateData); |
| EasyMock.replay(resolver); |
| |
| model.setProperty(KEY, this, resolver); |
| final ImmutableNode node = nodeForKey(model, nodeKey); |
| assertEquals("Attribute value not changed", newValue, node |
| .getAttributes().get(NodeStructureHelper.ATTR_TESTED)); |
| assertEquals("Node value not changed", newValue, node.getValue()); |
| } |
| |
| /** |
| * Tests a set property operation which is a no-op. |
| */ |
| @Test |
| public void testSetPropertyNoChanges() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final InMemoryNodeModel model = |
| new InMemoryNodeModel(NodeStructureHelper.ROOT_PERSONAE_TREE); |
| EasyMock.expect( |
| resolver.resolveUpdateKey( |
| NodeStructureHelper.ROOT_PERSONAE_TREE, KEY, this, |
| model.getNodeHandler())).andReturn( |
| new NodeUpdateData<ImmutableNode>(null, null, null, null)); |
| EasyMock.replay(resolver); |
| |
| model.setProperty(KEY, this, resolver); |
| assertSame("Model was changed", NodeStructureHelper.ROOT_PERSONAE_TREE, |
| model.getRootNode()); |
| } |
| |
| /** |
| * Tests whether new nodes can be added to an existing node in the model. |
| */ |
| @Test |
| public void testAddNodesToExistingNode() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final InMemoryNodeModel model = |
| new InMemoryNodeModel(NodeStructureHelper.ROOT_AUTHORS_TREE); |
| final String key = NodeStructureHelper.author(0); |
| final ImmutableNode newWork1 = |
| new ImmutableNode.Builder().name("King Lear").create(); |
| final ImmutableNode newWork2 = |
| new ImmutableNode.Builder().name("The Taming of the Shrew") |
| .create(); |
| EasyMock.expect( |
| resolver.resolveKey(NodeStructureHelper.ROOT_AUTHORS_TREE, KEY, |
| model.getNodeHandler())).andReturn( |
| Collections.singletonList(QueryResult |
| .createNodeResult(nodeForKey(model, key)))); |
| EasyMock.replay(resolver); |
| |
| model.addNodes(KEY, Arrays.asList(newWork1, newWork2), resolver); |
| final ImmutableNode node = nodeForKey(model, key); |
| final int size = node.getChildren().size(); |
| assertSame("New child 1 not added", newWork1, |
| node.getChildren().get(size - 2)); |
| assertSame("New child 2 not added", newWork2, |
| node.getChildren().get(size - 1)); |
| } |
| |
| /** |
| * Tests whether nodes can be added to a node which has to be created. |
| */ |
| @Test |
| public void testAddNodesToNewNode() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final InMemoryNodeModel model = |
| new InMemoryNodeModel(NodeStructureHelper.ROOT_AUTHORS_TREE); |
| final String newAuthor = "Goethe"; |
| final String newWork = "Faust"; |
| final String newPersona = "Mephisto"; |
| EasyMock.expect( |
| resolver.resolveKey(NodeStructureHelper.ROOT_AUTHORS_TREE, KEY, |
| model.getNodeHandler())).andReturn( |
| new ArrayList<QueryResult<ImmutableNode>>(0)); |
| EasyMock.expect( |
| resolver.resolveAddKey(NodeStructureHelper.ROOT_AUTHORS_TREE, |
| KEY, model.getNodeHandler())).andReturn( |
| new NodeAddData<>( |
| NodeStructureHelper.ROOT_AUTHORS_TREE, newWork, false, |
| Arrays.asList(newAuthor))); |
| EasyMock.replay(resolver); |
| |
| final ImmutableNode personaNode = |
| new ImmutableNode.Builder().name(newPersona).create(); |
| model.addNodes(KEY, Collections.singleton(personaNode), resolver); |
| assertSame("Wrong added node", personaNode, |
| nodeForKey(model, newAuthor + "/" + newWork + "/" + newPersona)); |
| } |
| |
| /** |
| * Tries to add new nodes if the key references an attribute. |
| */ |
| @Test(expected = IllegalArgumentException.class) |
| public void testAddNodesToAttribute() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final InMemoryNodeModel model = |
| new InMemoryNodeModel(NodeStructureHelper.ROOT_AUTHORS_TREE); |
| EasyMock.expect( |
| resolver.resolveKey(NodeStructureHelper.ROOT_AUTHORS_TREE, KEY, |
| model.getNodeHandler())).andReturn( |
| Collections.singletonList(QueryResult.createAttributeResult( |
| nodeForKey(model, NodeStructureHelper.author(1)), |
| "test"))); |
| EasyMock.replay(resolver); |
| |
| final ImmutableNode newNode = |
| new ImmutableNode.Builder().name("newNode").create(); |
| model.addNodes(KEY, Collections.singleton(newNode), resolver); |
| } |
| |
| /** |
| * Tries to add new nodes to an non-existing key pointing to an attribute. |
| */ |
| @Test(expected = IllegalArgumentException.class) |
| public void testAddNodesToNewAttributeKey() |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final InMemoryNodeModel model = |
| new InMemoryNodeModel(NodeStructureHelper.ROOT_AUTHORS_TREE); |
| EasyMock.expect( |
| resolver.resolveKey(NodeStructureHelper.ROOT_AUTHORS_TREE, KEY, |
| model.getNodeHandler())).andReturn( |
| Collections.<QueryResult<ImmutableNode>> emptyList()); |
| EasyMock.expect( |
| resolver.resolveAddKey(NodeStructureHelper.ROOT_AUTHORS_TREE, |
| KEY, model.getNodeHandler())).andReturn( |
| new NodeAddData<>( |
| NodeStructureHelper.ROOT_AUTHORS_TREE, "test", true, |
| null)); |
| EasyMock.replay(resolver); |
| |
| final ImmutableNode newNode = |
| new ImmutableNode.Builder().name("newNode").create(); |
| model.addNodes(KEY, Collections.singleton(newNode), resolver); |
| } |
| |
| /** |
| * Helper method for testing the behavior of addNodes() if no nodes to be |
| * added are provided. |
| * |
| * @param newNodes the collection with new nodes |
| */ |
| private void checkAddNodesNoNodes(final Collection<ImmutableNode> newNodes) |
| { |
| final NodeKeyResolver<ImmutableNode> resolver = createResolver(); |
| final InMemoryNodeModel model = |
| new InMemoryNodeModel(NodeStructureHelper.ROOT_AUTHORS_TREE); |
| EasyMock.replay(resolver); |
| |
| model.addNodes(KEY, newNodes, resolver); |
| assertSame("Model was changed", NodeStructureHelper.ROOT_AUTHORS_TREE, |
| model.getRootNode()); |
| } |
| |
| /** |
| * Tests an add nodes operation if a null collection is passed in. |
| */ |
| @Test |
| public void testAddNodesNullCollection() |
| { |
| checkAddNodesNoNodes(null); |
| } |
| |
| /** |
| * Tests an add nodes operation if an empty collection is passed in. |
| */ |
| @Test |
| public void testAddNodesEmptyCollection() |
| { |
| checkAddNodesNoNodes(Collections.<ImmutableNode> emptySet()); |
| } |
| |
| /** |
| * Tests whether a new root node can be set. |
| */ |
| @Test |
| public void testSetRoot() |
| { |
| final InMemoryNodeModel model = |
| new InMemoryNodeModel(NodeStructureHelper.ROOT_PERSONAE_TREE); |
| model.setRootNode(NodeStructureHelper.ROOT_AUTHORS_TREE); |
| assertSame("Root node not changed", |
| NodeStructureHelper.ROOT_AUTHORS_TREE, model.getRootNode()); |
| final ImmutableNode node = nodeForKey(model, "Homer/Ilias"); |
| assertEquals("Wrong parent mapping", nodeForKey(model, "Homer"), |
| model.getNodeHandler().getParent(node)); |
| } |
| |
| /** |
| * Tests whether the root node can be set to null. |
| */ |
| @Test |
| public void testSetRootNull() |
| { |
| final InMemoryNodeModel model = |
| new InMemoryNodeModel(NodeStructureHelper.ROOT_PERSONAE_TREE); |
| model.setRootNode(null); |
| final ImmutableNode rootNode = model.getRootNode(); |
| assertTrue("Got children", rootNode.getChildren().isEmpty()); |
| } |
| |
| /** |
| * Tests whether the model's data can be represented as immutable node |
| * objects (which is trivial in this case). |
| */ |
| @Test |
| public void testGetInMemoryRepresentation() |
| { |
| final InMemoryNodeModel model = |
| new InMemoryNodeModel(NodeStructureHelper.ROOT_AUTHORS_TREE); |
| assertSame("Wrong in-memory representation", |
| NodeStructureHelper.ROOT_AUTHORS_TREE, |
| model.getInMemoryRepresentation()); |
| } |
| } |