| /* |
| * 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 java.util.NoSuchElementException; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.apache.commons.lang3.StringUtils; |
| import org.dbunit.dataset.common.handlers.NoHandler; |
| import org.easymock.EasyMock; |
| |
| /** |
| * A helper class for tests related to hierarchies of {@code ImmutableNode} |
| * objects. This class provides functionality for creating test trees and |
| * accessing single nodes. It can be used by various test classes. |
| * |
| */ |
| public class NodeStructureHelper |
| { |
| /** A pattern for parsing node keys with optional indices. */ |
| private static final Pattern PAT_KEY_WITH_INDEX = Pattern |
| .compile("(\\w+)\\((\\d+)\\)"); |
| |
| /** The character for splitting node path elements. */ |
| private static final String PATH_SEPARATOR = "/"; |
| |
| /** An array with authors. */ |
| private static final String[] AUTHORS = { |
| "Shakespeare", "Homer", "Simmons" |
| }; |
| |
| /** An array with the works of the test authors. */ |
| private static final String[][] WORKS = { |
| { |
| "Troilus and Cressida", "The Tempest", |
| "A Midsummer Night's Dream" |
| }, { |
| "Ilias" |
| }, { |
| "Ilium", "Hyperion" |
| } |
| }; |
| |
| /** An array with the personae in the works. */ |
| private static final String[][][] PERSONAE = { |
| { |
| // Works of Shakespeare |
| { |
| "Troilus", "Cressidia", "Ajax", "Achilles" |
| }, { |
| "Prospero", "Ariel" |
| }, { |
| "Oberon", "Titania", "Puck" |
| } |
| }, { |
| // Works of Homer |
| { |
| "Achilles", "Agamemnon", "Hektor" |
| } |
| }, { |
| // Works of Dan Simmons |
| { |
| "Hockenberry", "Achilles" |
| }, { |
| "Shrike", "Moneta", "Consul", "Weintraub" |
| } |
| } |
| }; |
| |
| /** An array with table names used for the TABLES tree. */ |
| private static final String[] TABLES = { |
| "users", "documents" |
| }; |
| |
| /** |
| * An array with the names of columns to be used for the TABLES tree. |
| */ |
| private static final String[][] FIELDS = { |
| { |
| "uid", "uname", "firstName", "lastName", "email" |
| }, { |
| "docid", "name", "creationDate", "authorID", "version", "length" |
| } |
| }; |
| |
| /** Constant for the author attribute. */ |
| public static final String ATTR_AUTHOR = "author"; |
| |
| /** Constant for the original value element in the personae tree. */ |
| public static final String ELEM_ORG_VALUE = "originalValue"; |
| |
| /** Constant for the tested attribute. */ |
| public static final String ATTR_TESTED = "tested"; |
| |
| /** The root node of the authors tree. */ |
| public static final ImmutableNode ROOT_AUTHORS_TREE = createAuthorsTree(); |
| |
| /** The root node of the personae tree. */ |
| public static final ImmutableNode ROOT_PERSONAE_TREE = createPersonaeTree(); |
| |
| /** The root node of the TABLES tree. */ |
| public static final ImmutableNode ROOT_TABLES_TREE = createTablesTree(); |
| |
| /** |
| * Returns the number of authors. |
| * |
| * @return the number of authors |
| */ |
| public static int authorsLength() |
| { |
| return AUTHORS.length; |
| } |
| |
| /** |
| * Returns the name of the author at the given index. |
| * |
| * @param idx the index |
| * @return the name of this author |
| */ |
| public static String author(final int idx) |
| { |
| return AUTHORS[idx]; |
| } |
| |
| /** |
| * Returns the number of works for the author with the given index. |
| * |
| * @param authorIdx the author index |
| * @return the number of works of this author |
| */ |
| public static int worksLength(final int authorIdx) |
| { |
| return WORKS[authorIdx].length; |
| } |
| |
| /** |
| * Returns the work of an author with a given index. |
| * |
| * @param authorIdx the author index |
| * @param idx the index of the work |
| * @return the desired work |
| */ |
| public static String work(final int authorIdx, final int idx) |
| { |
| return WORKS[authorIdx][idx]; |
| } |
| |
| /** |
| * Returns the number of personae in the given work of the specified author. |
| * |
| * @param authorIdx the author index |
| * @param workIdx the index of the work |
| * @return the number of personae in this work |
| */ |
| public static int personaeLength(final int authorIdx, final int workIdx) |
| { |
| return PERSONAE[authorIdx][workIdx].length; |
| } |
| |
| /** |
| * Returns the name of a persona. |
| * |
| * @param authorIdx the author index |
| * @param workIdx the index of the work |
| * @param personaIdx the index of the persona |
| * @return the name of this persona |
| */ |
| public static String persona(final int authorIdx, final int workIdx, final int personaIdx) |
| { |
| return PERSONAE[authorIdx][workIdx][personaIdx]; |
| } |
| |
| /** |
| * Returns the number of tables in the tables tree. |
| * |
| * @return the number of tables |
| */ |
| public static int tablesLength() |
| { |
| return TABLES.length; |
| } |
| |
| /** |
| * Returns the name of the test table with the given index. |
| * |
| * @param idx the index of the table |
| * @return the name of the test table with this index |
| */ |
| public static String table(final int idx) |
| { |
| return TABLES[idx]; |
| } |
| |
| /** |
| * Returns the number of fields in the test table with the given index. |
| * |
| * @param tabIdx the index of the table |
| * @return the number of fields in this table |
| */ |
| public static int fieldsLength(final int tabIdx) |
| { |
| return FIELDS[tabIdx].length; |
| } |
| |
| /** |
| * Returns the name of the specified field in the tables tree. |
| * |
| * @param tabIdx the index of the table |
| * @param fldIdx the index of the field |
| * @return the name of this field |
| */ |
| public static String field(final int tabIdx, final int fldIdx) |
| { |
| return FIELDS[tabIdx][fldIdx]; |
| } |
| |
| /** |
| * Appends a component to a node path. The component is added separated by a |
| * path separator. |
| * |
| * @param path the path |
| * @param component the component to be added |
| * @return the resulting path |
| */ |
| public static String appendPath(final String path, final String component) |
| { |
| final StringBuilder buf = |
| new StringBuilder(StringUtils.length(path) |
| + StringUtils.length(component) + 1); |
| buf.append(path).append(PATH_SEPARATOR).append(component); |
| return buf.toString(); |
| } |
| |
| /** |
| * Evaluates the given key and finds the corresponding child node of the |
| * specified root. Keys have the form {@code path/to/node}. If there are |
| * multiple sibling nodes with the same name, a numerical index can be |
| * specified in parenthesis. |
| * |
| * @param root the root node |
| * @param key the key to the desired node |
| * @return the node with this key |
| * @throws NoSuchElementException if the key cannot be resolved |
| */ |
| public static ImmutableNode nodeForKey(final ImmutableNode root, final String key) |
| { |
| final String[] components = key.split(PATH_SEPARATOR); |
| return findNode(root, components, 0); |
| } |
| |
| /** |
| * Evaluates the given key and finds the corresponding child node of the |
| * root node of the specified model. This is a convenience method that works |
| * like the method with the same name, but obtains the root node from the |
| * given model. |
| * |
| * @param model the node model |
| * @param key the key to the desired node |
| * @return the found target node |
| * @throws NoSuchElementException if the desired node cannot be found |
| */ |
| public static ImmutableNode nodeForKey(final InMemoryNodeModel model, final String key) |
| { |
| return nodeForKey(model.getRootNode(), key); |
| } |
| |
| /** |
| * Evaluates the given key and finds the corresponding child node of the |
| * root node of the specified {@code NodeHandler} object. This is a |
| * convenience method that works like the method with the same name, but |
| * obtains the root node from the given handler object. |
| * |
| * @param handler the {@code NodeHandler} object |
| * @param key the key to the desired node |
| * @return the found target node |
| * @throws NoSuchElementException if the desired node cannot be found |
| */ |
| public static ImmutableNode nodeForKey(final NodeHandler<ImmutableNode> handler, |
| final String key) |
| { |
| return nodeForKey(handler.getRootNode(), key); |
| } |
| |
| /** |
| * Convenience method for creating a path for accessing a node based on the |
| * node names. |
| * |
| * @param path an array with the expected node names on the path |
| * @return the resulting path as string |
| */ |
| public static String nodePath(final String... path) |
| { |
| return StringUtils.join(path, PATH_SEPARATOR); |
| } |
| |
| /** |
| * Convenience method for creating a node path with a special end node. |
| * |
| * @param endNode the name of the last path component |
| * @param path an array with the expected node names on the path |
| * @return the resulting path as string |
| */ |
| public static String nodePathWithEndNode(final String endNode, final String... path) |
| { |
| return nodePath(path) + PATH_SEPARATOR + endNode; |
| } |
| |
| /** |
| * Helper method for creating an immutable node with a name and a value. |
| * |
| * @param name the node's name |
| * @param value the node's value |
| * @return the new node |
| */ |
| public static ImmutableNode createNode(final String name, final Object value) |
| { |
| return new ImmutableNode.Builder().name(name).value(value).create(); |
| } |
| |
| /** |
| * Helper method for creating a field node with its children. Nodes of this |
| * type are used within the tables tree. They define a single column of a |
| * table. |
| * |
| * @param name the name of the field |
| * @return the field node |
| */ |
| public static ImmutableNode createFieldNode(final String name) |
| { |
| final ImmutableNode.Builder fldBuilder = new ImmutableNode.Builder(1); |
| fldBuilder.addChild(createNode("name", name)); |
| return fldBuilder.name("field").create(); |
| } |
| |
| /** |
| * Creates a mock for a resolver. |
| * |
| * @return the resolver mock |
| */ |
| public static NodeKeyResolver<ImmutableNode> createResolverMock() |
| { |
| @SuppressWarnings("unchecked") |
| final |
| NodeKeyResolver<ImmutableNode> mock = |
| EasyMock.createMock(NodeKeyResolver.class); |
| return mock; |
| } |
| |
| /** |
| * Prepares a mock for a resolver to expect arbitrary resolve operations. |
| * These operations are implemented on top of a default expression engine. |
| * |
| * @param resolver the mock resolver |
| */ |
| @SuppressWarnings("unchecked") |
| public static void expectResolveKeyForQueries( |
| final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| EasyMock.expect( |
| resolver.resolveKey(EasyMock.anyObject(ImmutableNode.class), |
| EasyMock.anyObject(String.class), |
| (NodeHandler<ImmutableNode>) EasyMock |
| .anyObject(NoHandler.class))) |
| .andAnswer(() -> { |
| final ImmutableNode root = |
| (ImmutableNode) EasyMock.getCurrentArguments()[0]; |
| final String key = (String) EasyMock.getCurrentArguments()[1]; |
| final NodeHandler<ImmutableNode> handler = |
| (NodeHandler<ImmutableNode>) EasyMock |
| .getCurrentArguments()[2]; |
| return DefaultExpressionEngine.INSTANCE.query(root, |
| key, handler); |
| }).anyTimes(); |
| } |
| |
| /** |
| * Prepares the passed in resolver mock to resolve add keys. They are |
| * interpreted on a default expression engine. |
| * |
| * @param resolver the {@code NodeKeyResolver} mock |
| */ |
| public static void expectResolveAddKeys( |
| final NodeKeyResolver<ImmutableNode> resolver) |
| { |
| EasyMock.expect( |
| resolver.resolveAddKey(EasyMock.anyObject(ImmutableNode.class), |
| EasyMock.anyString(), |
| 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()[2]; |
| return DefaultExpressionEngine.INSTANCE.prepareAdd( |
| root, key, handler); |
| }).anyTimes(); |
| } |
| |
| /** |
| * Creates as tree with database table data based on the passed in arrays of |
| * table names and fields for tables. Works like the method without |
| * parameters, but allows defining the data of the structure. |
| * |
| * @param tables an array with the names of the tables |
| * @param fields an array with the fields of the single tables |
| * @return the resulting nodes structure |
| */ |
| public static ImmutableNode createTablesTree(final String[] tables, |
| final String[][] fields) |
| { |
| final ImmutableNode.Builder bldTables = |
| new ImmutableNode.Builder(tables.length); |
| bldTables.name("tables"); |
| for (int i = 0; i < tables.length; i++) |
| { |
| final ImmutableNode.Builder bldTable = new ImmutableNode.Builder(2); |
| bldTable.addChild(createNode("name", tables[i])); |
| final ImmutableNode.Builder bldFields = |
| new ImmutableNode.Builder(fields[i].length); |
| bldFields.name("fields"); |
| |
| for (int j = 0; j < fields[i].length; j++) |
| { |
| bldFields.addChild(createFieldNode(fields[i][j])); |
| } |
| bldTable.addChild(bldFields.create()); |
| bldTables.addChild(bldTable.name("table").create()); |
| } |
| return bldTables.create(); |
| } |
| |
| /** |
| * Returns a clone of the array with the table names. This is useful if a |
| * slightly different tree structure should be created. |
| * |
| * @return the cloned table names |
| */ |
| public static String[] getClonedTables() |
| { |
| return TABLES.clone(); |
| } |
| |
| /** |
| * Returns a clone of the array with the table fields. This is useful if a |
| * slightly different tree structure should be created. |
| * |
| * @return the cloned field names |
| */ |
| public static String[][] getClonedFields() |
| { |
| final String[][] fieldNamesNew = new String[FIELDS.length][]; |
| for (int i = 0; i < FIELDS.length; i++) |
| { |
| fieldNamesNew[i] = FIELDS[i].clone(); |
| } |
| return fieldNamesNew; |
| } |
| |
| /** |
| * Creates a tree with a root node whose children are the test authors. Each |
| * other has his works as child nodes. Each work has its personae as |
| * children. |
| * |
| * @return the root node of the authors tree |
| */ |
| private static ImmutableNode createAuthorsTree() |
| { |
| final ImmutableNode.Builder rootBuilder = |
| new ImmutableNode.Builder(AUTHORS.length); |
| for (int author = 0; author < AUTHORS.length; author++) |
| { |
| final ImmutableNode.Builder authorBuilder = new ImmutableNode.Builder(); |
| authorBuilder.name(AUTHORS[author]); |
| for (int work = 0; work < WORKS[author].length; work++) |
| { |
| final ImmutableNode.Builder workBuilder = new ImmutableNode.Builder(); |
| workBuilder.name(WORKS[author][work]); |
| for (final String person : PERSONAE[author][work]) |
| { |
| workBuilder.addChild(new ImmutableNode.Builder().name( |
| person).create()); |
| } |
| authorBuilder.addChild(workBuilder.create()); |
| } |
| rootBuilder.addChild(authorBuilder.create()); |
| } |
| return rootBuilder.name("authorTree").create(); |
| } |
| |
| /** |
| * Creates a tree with a root node whose children are the test personae. |
| * Each node represents a person and has an attribute pointing to the author |
| * who invented this person. There is a single child node for the associated |
| * work which has again a child and an attribute. |
| * |
| * @return the root node of the personae tree |
| */ |
| private static ImmutableNode createPersonaeTree() |
| { |
| final ImmutableNode.Builder rootBuilder = new ImmutableNode.Builder(); |
| for (int author = 0; author < AUTHORS.length; author++) |
| { |
| for (int work = 0; work < WORKS[author].length; work++) |
| { |
| for (final String person : PERSONAE[author][work]) |
| { |
| final ImmutableNode orgValue = |
| new ImmutableNode.Builder().name(ELEM_ORG_VALUE) |
| .value("yes") |
| .addAttribute(ATTR_TESTED, Boolean.FALSE) |
| .create(); |
| final ImmutableNode workNode = |
| new ImmutableNode.Builder(1) |
| .name(WORKS[author][work]) |
| .addChild(orgValue).create(); |
| final ImmutableNode personNode = |
| new ImmutableNode.Builder(1).name(person) |
| .addAttribute(ATTR_AUTHOR, AUTHORS[author]) |
| .addChild(workNode).create(); |
| rootBuilder.addChild(personNode); |
| } |
| } |
| } |
| return rootBuilder.create(); |
| } |
| |
| /** |
| * Creates a tree with database table data with the following structure: |
| * |
| * tables |
| * table |
| * name |
| * fields |
| * field |
| * name |
| * field |
| * name |
| * @return the resulting nodes structure |
| */ |
| private static ImmutableNode createTablesTree() |
| { |
| return createTablesTree(TABLES, FIELDS); |
| } |
| |
| /** |
| * Helper method for evaluating a single component of a node key. |
| * |
| * @param parent the current parent node |
| * @param components the array with the components of the node key |
| * @param currentIdx the index of the current path component |
| * @return the found target node |
| * @throws NoSuchElementException if the desired node cannot be found |
| */ |
| private static ImmutableNode findNode(final ImmutableNode parent, |
| final String[] components, final int currentIdx) |
| { |
| if (currentIdx >= components.length) |
| { |
| return parent; |
| } |
| |
| final Matcher m = PAT_KEY_WITH_INDEX.matcher(components[currentIdx]); |
| String childName; |
| int childIndex; |
| if (m.matches()) |
| { |
| childName = m.group(1); |
| childIndex = Integer.parseInt(m.group(2)); |
| } |
| else |
| { |
| childName = components[currentIdx]; |
| childIndex = 0; |
| } |
| |
| int foundIdx = 0; |
| for (final ImmutableNode node : parent.getChildren()) |
| { |
| if (childName.equals(node.getNodeName())) |
| { |
| if (foundIdx++ == childIndex) |
| { |
| return findNode(node, components, currentIdx + 1); |
| } |
| } |
| } |
| throw new NoSuchElementException("Cannot resolve child " |
| + components[currentIdx]); |
| } |
| } |