DefaultExpressionEngine.java
- /*
- * 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.Collection;
- import java.util.LinkedList;
- import java.util.List;
- import org.apache.commons.lang3.StringUtils;
- /**
- * <p>
- * A default implementation of the {@code ExpressionEngine} interface providing the "native" expression
- * language for hierarchical configurations.
- * </p>
- * <p>
- * This class implements a rather simple expression language for navigating through a hierarchy of configuration nodes.
- * It supports the following operations:
- * </p>
- * <ul>
- * <li>Navigating from a node to one of its children using the child node delimiter, which is by the default a dot
- * (".").</li>
- * <li>Navigating from a node to one of its attributes using the attribute node delimiter, which by default follows the
- * XPATH like syntax {@code [@<attributeName>]}.</li>
- * <li>If there are multiple child or attribute nodes with the same name, a specific node can be selected using a
- * numerical index. By default indices are written in parenthesis.</li>
- * </ul>
- * <p>
- * As an example consider the following XML document:
- * </p>
- *
- * <pre>
- * <database>
- * <tables>
- * <table type="system">
- * <name>users</name>
- * <fields>
- * <field>
- * <name>lid</name>
- * <type>long</name>
- * </field>
- * <field>
- * <name>usrName</name>
- * <type>java.lang.String</type>
- * </field>
- * ...
- * </fields>
- * </table>
- * <table>
- * <name>documents</name>
- * <fields>
- * <field>
- * <name>docid</name>
- * <type>long</type>
- * </field>
- * ...
- * </fields>
- * </table>
- * ...
- * </tables>
- * </database>
- * </pre>
- *
- * <p>
- * If this document is parsed and stored in a hierarchical configuration object, for instance the key
- * {@code tables.table(0).name} can be used to find out the name of the first table. In opposite
- * {@code tables.table.name} would return a collection with the names of all available tables. Similarly the key
- * {@code tables.table(1).fields.field.name} returns a collection with the names of all fields of the second table. If
- * another index is added after the {@code field} element, a single field can be accessed:
- * {@code tables.table(1).fields.field(0).name}. The key {@code tables.table(0)[@type]} would select the type attribute
- * of the first table.
- * </p>
- * <p>
- * This example works with the default values for delimiters and index markers. It is also possible to set custom values
- * for these properties so that you can adapt a {@code DefaultExpressionEngine} to your personal needs.
- * </p>
- * <p>
- * The concrete symbols used by an instance are determined by a {@link DefaultExpressionEngineSymbols} object passed to
- * the constructor. By providing a custom symbols object the syntax for querying properties in a hierarchical
- * configuration can be altered.
- * </p>
- * <p>
- * Instances of this class are thread-safe and can be shared between multiple hierarchical configuration objects.
- * </p>
- *
- * @since 1.3
- */
- public class DefaultExpressionEngine implements ExpressionEngine {
- /**
- * A default instance of this class that is used as expression engine for hierarchical configurations per default.
- */
- public static final DefaultExpressionEngine INSTANCE = new DefaultExpressionEngine(DefaultExpressionEngineSymbols.DEFAULT_SYMBOLS);
- /** The symbols used by this instance. */
- private final DefaultExpressionEngineSymbols symbols;
- /** The matcher for node names. */
- private final NodeMatcher<String> nameMatcher;
- /**
- * Creates a new instance of {@code DefaultExpressionEngine} and initializes its symbols.
- *
- * @param syms the object with the symbols (must not be <strong>null</strong>)
- * @throws IllegalArgumentException if the symbols are <strong>null</strong>
- */
- public DefaultExpressionEngine(final DefaultExpressionEngineSymbols syms) {
- this(syms, null);
- }
- /**
- * Creates a new instance of {@code DefaultExpressionEngine} and initializes its symbols and the matcher for comparing
- * node names. The passed in matcher is always used when the names of nodes have to be matched against parts of
- * configuration keys.
- *
- * @param syms the object with the symbols (must not be <strong>null</strong>)
- * @param nodeNameMatcher the matcher for node names; can be <strong>null</strong>, then a default matcher is used
- * @throws IllegalArgumentException if the symbols are <strong>null</strong>
- */
- public DefaultExpressionEngine(final DefaultExpressionEngineSymbols syms, final NodeMatcher<String> nodeNameMatcher) {
- if (syms == null) {
- throw new IllegalArgumentException("Symbols must not be null!");
- }
- symbols = syms;
- nameMatcher = nodeNameMatcher != null ? nodeNameMatcher : NodeNameMatchers.EQUALS;
- }
- @Override
- public String attributeKey(final String parentKey, final String attributeName) {
- final DefaultConfigurationKey key = new DefaultConfigurationKey(this, parentKey);
- key.appendAttribute(attributeName);
- return key.toString();
- }
- /**
- * {@inheritDoc} This implementation works similar to {@code nodeKey()}; however, each key returned by this method has
- * an index (except for the root node). The parent key is prepended to the name of the current node in any case and
- * without further checks. If it is <strong>null</strong>, only the name of the current node with its index is returned.
- */
- @Override
- public <T> String canonicalKey(final T node, final String parentKey, final NodeHandler<T> handler) {
- final String nodeName = handler.nodeName(node);
- final T parent = handler.getParent(node);
- final DefaultConfigurationKey key = new DefaultConfigurationKey(this, parentKey);
- key.append(StringUtils.defaultString(nodeName));
- if (parent != null) {
- // this is not the root key
- key.appendIndex(determineIndex(node, parent, nodeName, handler));
- }
- return key.toString();
- }
- /**
- * Determines the index of the given node based on its parent node.
- *
- * @param node the current node
- * @param parent the parent node
- * @param nodeName the name of the current node
- * @param handler the node handler
- * @param <T> the type of the nodes to be dealt with
- * @return the index of this node
- */
- private <T> int determineIndex(final T node, final T parent, final String nodeName, final NodeHandler<T> handler) {
- return findChildNodesByName(handler, parent, nodeName).indexOf(node);
- }
- /**
- * Returns a list with all child nodes of the given parent node which match the specified node name. The match is done
- * using the current node name matcher.
- *
- * @param handler the {@code NodeHandler}
- * @param parent the parent node
- * @param nodeName the name of the current node
- * @param <T> the type of the nodes to be dealt with
- * @return a list with all matching child nodes
- */
- private <T> List<T> findChildNodesByName(final NodeHandler<T> handler, final T parent, final String nodeName) {
- return handler.getMatchingChildren(parent, nameMatcher, nodeName);
- }
- /**
- * Finds the last existing node for an add operation. This method traverses the node tree along the specified key. The
- * last existing node on this path is returned.
- *
- * @param <T> the type of the nodes to be dealt with
- * @param keyIt the key iterator
- * @param node the current node
- * @param handler the node handler
- * @return the last existing node on the given path
- */
- protected <T> T findLastPathNode(final DefaultConfigurationKey.KeyIterator keyIt, final T node, final NodeHandler<T> handler) {
- final String keyPart = keyIt.nextKey(false);
- if (keyIt.hasNext()) {
- if (!keyIt.isPropertyKey()) {
- // Attribute keys can only appear as last elements of the path
- throw new IllegalArgumentException("Invalid path for add operation: " + "Attribute key in the middle!");
- }
- final int idx = keyIt.hasIndex() ? keyIt.getIndex() : handler.getMatchingChildrenCount(node, nameMatcher, keyPart) - 1;
- if (idx < 0 || idx >= handler.getMatchingChildrenCount(node, nameMatcher, keyPart)) {
- return node;
- }
- return findLastPathNode(keyIt, findChildNodesByName(handler, node, keyPart).get(idx), handler);
- }
- return node;
- }
- /**
- * Recursive helper method for evaluating a key. This method processes all facets of a configuration key, traverses the
- * tree of properties and fetches the results of all matching properties.
- *
- * @param <T> the type of nodes to be dealt with
- * @param keyPart the configuration key iterator
- * @param node the current node
- * @param results here the found results are stored
- * @param handler the node handler
- */
- protected <T> void findNodesForKey(final DefaultConfigurationKey.KeyIterator keyPart, final T node, final Collection<QueryResult<T>> results,
- final NodeHandler<T> handler) {
- if (!keyPart.hasNext()) {
- results.add(QueryResult.createNodeResult(node));
- } else {
- final String key = keyPart.nextKey(false);
- if (keyPart.isPropertyKey()) {
- processSubNodes(keyPart, findChildNodesByName(handler, node, key), results, handler);
- }
- if (keyPart.isAttribute() && !keyPart.hasNext() && handler.getAttributeValue(node, key) != null) {
- results.add(QueryResult.createAttributeResult(node, key));
- }
- }
- }
- /**
- * Gets the {@code DefaultExpressionEngineSymbols} object associated with this instance.
- *
- * @return the {@code DefaultExpressionEngineSymbols} used by this engine
- * @since 2.0
- */
- public DefaultExpressionEngineSymbols getSymbols() {
- return symbols;
- }
- /**
- * {@inheritDoc} This implementation takes the given parent key, adds a property delimiter, and then adds the node's
- * name. The name of the root node is a blank string. Note that no indices are returned.
- */
- @Override
- public <T> String nodeKey(final T node, final String parentKey, final NodeHandler<T> handler) {
- if (parentKey == null) {
- // this is the root node
- return StringUtils.EMPTY;
- }
- final DefaultConfigurationKey key = new DefaultConfigurationKey(this, parentKey);
- key.append(handler.nodeName(node), true);
- return key.toString();
- }
- /**
- * <p>
- * Prepares Adding the property with the specified key.
- * </p>
- * <p>
- * To be able to deal with the structure supported by hierarchical configuration implementations the passed in key is of
- * importance, especially the indices it might contain. The following example should clarify this: Suppose the current
- * node structure looks like the following:
- * </p>
- *
- * <pre>
- * tables
- * +-- table
- * +-- name = user
- * +-- fields
- * +-- field
- * +-- name = uid
- * +-- field
- * +-- name = firstName
- * ...
- * +-- table
- * +-- name = documents
- * +-- fields
- * ...
- * </pre>
- * <p>
- * In this example a database structure is defined, for example all fields of the first table could be accessed using the key
- * {@code tables.table(0).fields.field.name}. If now properties are to be added, it must be exactly specified at which
- * position in the hierarchy the new property is to be inserted. So to add a new field name to a table it is not enough
- * to say just
- * </p>
- *
- * <pre>
- * config.addProperty("tables.table.fields.field.name", "newField");
- * </pre>
- * <p>
- * The statement given above contains some ambiguity. For instance it is not clear, to which table the new field should
- * be added. If this method finds such an ambiguity, it is resolved by following the last valid path. Here this would be
- * the last table. The same is true for the {@code field}; because there are multiple fields and no explicit index is
- * provided, a new {@code name} property would be added to the last field - which is probably not what was desired.
- * </p>
- * <p>
- * To make things clear explicit indices should be provided whenever possible. In the example above the exact table
- * could be specified by providing an index for the {@code table} element as in {@code tables.table(1).fields}. By
- * specifying an index it can also be expressed that at a given position in the configuration tree a new branch should
- * be added. In the example above we did not want to add an additional {@code name} element to the last field of the
- * table, but we want a complete new {@code field} element. This can be achieved by specifying an invalid index (like
- * -1) after the element where a new branch should be created. Given this our example would run:
- * </p>
- *
- * <pre>
- * config.addProperty("tables.table(1).fields.field(-1).name", "newField");
- * </pre>
- * <p>
- * With this notation it is possible to add new branches everywhere. We could for instance create a new {@code table}
- * element by specifying
- * </p>
- *
- * <pre>
- * config.addProperty("tables.table(-1).fields.field.name", "newField2");
- * </pre>
- * <p>
- * (Note that because after the {@code table} element a new branch is created indices in following elements are not
- * relevant; the branch is new so there cannot be any ambiguities.)
- * </p>
- *
- * @param <T> the type of the nodes to be dealt with
- * @param root the root node of the nodes hierarchy
- * @param key the key of the new property
- * @param handler the node handler
- * @return a data object with information needed for the add operation
- */
- @Override
- public <T> NodeAddData<T> prepareAdd(final T root, final String key, final NodeHandler<T> handler) {
- final DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(this, key).iterator();
- if (!it.hasNext()) {
- throw new IllegalArgumentException("Key for add operation must be defined!");
- }
- final T parent = findLastPathNode(it, root, handler);
- final List<String> pathNodes = new LinkedList<>();
- while (it.hasNext()) {
- if (!it.isPropertyKey()) {
- throw new IllegalArgumentException("Invalid key for add operation: " + key + " (Attribute key in the middle.)");
- }
- pathNodes.add(it.currentKey());
- it.next();
- }
- return new NodeAddData<>(parent, it.currentKey(), !it.isPropertyKey(), pathNodes);
- }
- /**
- * Called by {@code findNodesForKey()} to process the sub nodes of the current node depending on the type of the current
- * key part (children, attributes, or both).
- *
- * @param <T> the type of the nodes to be dealt with
- * @param keyPart the key part
- * @param subNodes a list with the sub nodes to process
- * @param nodes the target collection
- * @param handler the node handler
- */
- private <T> void processSubNodes(final DefaultConfigurationKey.KeyIterator keyPart, final List<T> subNodes, final Collection<QueryResult<T>> nodes,
- final NodeHandler<T> handler) {
- if (keyPart.hasIndex()) {
- if (keyPart.getIndex() >= 0 && keyPart.getIndex() < subNodes.size()) {
- findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart.clone(), subNodes.get(keyPart.getIndex()), nodes, handler);
- }
- } else {
- subNodes.forEach(node -> findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart.clone(), node, nodes, handler));
- }
- }
- /**
- * {@inheritDoc} This method supports the syntax as described in the class comment.
- */
- @Override
- public <T> List<QueryResult<T>> query(final T root, final String key, final NodeHandler<T> handler) {
- final List<QueryResult<T>> results = new LinkedList<>();
- findNodesForKey(new DefaultConfigurationKey(this, key).iterator(), root, results, handler);
- return results;
- }
- }