DefaultExpressionEngine.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.configuration2.tree;

  18. import java.util.Collection;
  19. import java.util.LinkedList;
  20. import java.util.List;

  21. import org.apache.commons.lang3.StringUtils;

  22. /**
  23.  * <p>
  24.  * A default implementation of the {@code ExpressionEngine} interface providing the &quot;native&quot; expression
  25.  * language for hierarchical configurations.
  26.  * </p>
  27.  * <p>
  28.  * This class implements a rather simple expression language for navigating through a hierarchy of configuration nodes.
  29.  * It supports the following operations:
  30.  * </p>
  31.  * <ul>
  32.  * <li>Navigating from a node to one of its children using the child node delimiter, which is by the default a dot
  33.  * (&quot;.&quot;).</li>
  34.  * <li>Navigating from a node to one of its attributes using the attribute node delimiter, which by default follows the
  35.  * XPATH like syntax {@code [@&lt;attributeName&gt;]}.</li>
  36.  * <li>If there are multiple child or attribute nodes with the same name, a specific node can be selected using a
  37.  * numerical index. By default indices are written in parenthesis.</li>
  38.  * </ul>
  39.  * <p>
  40.  * As an example consider the following XML document:
  41.  * </p>
  42.  *
  43.  * <pre>
  44.  *  &lt;database&gt;
  45.  *    &lt;tables&gt;
  46.  *      &lt;table type=&quot;system&quot;&gt;
  47.  *        &lt;name&gt;users&lt;/name&gt;
  48.  *        &lt;fields&gt;
  49.  *          &lt;field&gt;
  50.  *            &lt;name&gt;lid&lt;/name&gt;
  51.  *            &lt;type&gt;long&lt;/name&gt;
  52.  *          &lt;/field&gt;
  53.  *          &lt;field&gt;
  54.  *            &lt;name&gt;usrName&lt;/name&gt;
  55.  *            &lt;type&gt;java.lang.String&lt;/type&gt;
  56.  *          &lt;/field&gt;
  57.  *         ...
  58.  *        &lt;/fields&gt;
  59.  *      &lt;/table&gt;
  60.  *      &lt;table&gt;
  61.  *        &lt;name&gt;documents&lt;/name&gt;
  62.  *        &lt;fields&gt;
  63.  *          &lt;field&gt;
  64.  *            &lt;name&gt;docid&lt;/name&gt;
  65.  *            &lt;type&gt;long&lt;/type&gt;
  66.  *          &lt;/field&gt;
  67.  *          ...
  68.  *        &lt;/fields&gt;
  69.  *      &lt;/table&gt;
  70.  *      ...
  71.  *    &lt;/tables&gt;
  72.  *  &lt;/database&gt;
  73.  * </pre>
  74.  *
  75.  * <p>
  76.  * If this document is parsed and stored in a hierarchical configuration object, for instance the key
  77.  * {@code tables.table(0).name} can be used to find out the name of the first table. In opposite
  78.  * {@code tables.table.name} would return a collection with the names of all available tables. Similarly the key
  79.  * {@code tables.table(1).fields.field.name} returns a collection with the names of all fields of the second table. If
  80.  * another index is added after the {@code field} element, a single field can be accessed:
  81.  * {@code tables.table(1).fields.field(0).name}. The key {@code tables.table(0)[@type]} would select the type attribute
  82.  * of the first table.
  83.  * </p>
  84.  * <p>
  85.  * This example works with the default values for delimiters and index markers. It is also possible to set custom values
  86.  * for these properties so that you can adapt a {@code DefaultExpressionEngine} to your personal needs.
  87.  * </p>
  88.  * <p>
  89.  * The concrete symbols used by an instance are determined by a {@link DefaultExpressionEngineSymbols} object passed to
  90.  * the constructor. By providing a custom symbols object the syntax for querying properties in a hierarchical
  91.  * configuration can be altered.
  92.  * </p>
  93.  * <p>
  94.  * Instances of this class are thread-safe and can be shared between multiple hierarchical configuration objects.
  95.  * </p>
  96.  *
  97.  * @since 1.3
  98.  */
  99. public class DefaultExpressionEngine implements ExpressionEngine {
  100.     /**
  101.      * A default instance of this class that is used as expression engine for hierarchical configurations per default.
  102.      */
  103.     public static final DefaultExpressionEngine INSTANCE = new DefaultExpressionEngine(DefaultExpressionEngineSymbols.DEFAULT_SYMBOLS);

  104.     /** The symbols used by this instance. */
  105.     private final DefaultExpressionEngineSymbols symbols;

  106.     /** The matcher for node names. */
  107.     private final NodeMatcher<String> nameMatcher;

  108.     /**
  109.      * Creates a new instance of {@code DefaultExpressionEngine} and initializes its symbols.
  110.      *
  111.      * @param syms the object with the symbols (must not be <strong>null</strong>)
  112.      * @throws IllegalArgumentException if the symbols are <strong>null</strong>
  113.      */
  114.     public DefaultExpressionEngine(final DefaultExpressionEngineSymbols syms) {
  115.         this(syms, null);
  116.     }

  117.     /**
  118.      * Creates a new instance of {@code DefaultExpressionEngine} and initializes its symbols and the matcher for comparing
  119.      * node names. The passed in matcher is always used when the names of nodes have to be matched against parts of
  120.      * configuration keys.
  121.      *
  122.      * @param syms the object with the symbols (must not be <strong>null</strong>)
  123.      * @param nodeNameMatcher the matcher for node names; can be <strong>null</strong>, then a default matcher is used
  124.      * @throws IllegalArgumentException if the symbols are <strong>null</strong>
  125.      */
  126.     public DefaultExpressionEngine(final DefaultExpressionEngineSymbols syms, final NodeMatcher<String> nodeNameMatcher) {
  127.         if (syms == null) {
  128.             throw new IllegalArgumentException("Symbols must not be null!");
  129.         }

  130.         symbols = syms;
  131.         nameMatcher = nodeNameMatcher != null ? nodeNameMatcher : NodeNameMatchers.EQUALS;
  132.     }

  133.     @Override
  134.     public String attributeKey(final String parentKey, final String attributeName) {
  135.         final DefaultConfigurationKey key = new DefaultConfigurationKey(this, parentKey);
  136.         key.appendAttribute(attributeName);
  137.         return key.toString();
  138.     }

  139.     /**
  140.      * {@inheritDoc} This implementation works similar to {@code nodeKey()}; however, each key returned by this method has
  141.      * an index (except for the root node). The parent key is prepended to the name of the current node in any case and
  142.      * without further checks. If it is <strong>null</strong>, only the name of the current node with its index is returned.
  143.      */
  144.     @Override
  145.     public <T> String canonicalKey(final T node, final String parentKey, final NodeHandler<T> handler) {
  146.         final String nodeName = handler.nodeName(node);
  147.         final T parent = handler.getParent(node);
  148.         final DefaultConfigurationKey key = new DefaultConfigurationKey(this, parentKey);
  149.         key.append(StringUtils.defaultString(nodeName));

  150.         if (parent != null) {
  151.             // this is not the root key
  152.             key.appendIndex(determineIndex(node, parent, nodeName, handler));
  153.         }
  154.         return key.toString();
  155.     }

  156.     /**
  157.      * Determines the index of the given node based on its parent node.
  158.      *
  159.      * @param node the current node
  160.      * @param parent the parent node
  161.      * @param nodeName the name of the current node
  162.      * @param handler the node handler
  163.      * @param <T> the type of the nodes to be dealt with
  164.      * @return the index of this node
  165.      */
  166.     private <T> int determineIndex(final T node, final T parent, final String nodeName, final NodeHandler<T> handler) {
  167.         return findChildNodesByName(handler, parent, nodeName).indexOf(node);
  168.     }

  169.     /**
  170.      * Returns a list with all child nodes of the given parent node which match the specified node name. The match is done
  171.      * using the current node name matcher.
  172.      *
  173.      * @param handler the {@code NodeHandler}
  174.      * @param parent the parent node
  175.      * @param nodeName the name of the current node
  176.      * @param <T> the type of the nodes to be dealt with
  177.      * @return a list with all matching child nodes
  178.      */
  179.     private <T> List<T> findChildNodesByName(final NodeHandler<T> handler, final T parent, final String nodeName) {
  180.         return handler.getMatchingChildren(parent, nameMatcher, nodeName);
  181.     }

  182.     /**
  183.      * Finds the last existing node for an add operation. This method traverses the node tree along the specified key. The
  184.      * last existing node on this path is returned.
  185.      *
  186.      * @param <T> the type of the nodes to be dealt with
  187.      * @param keyIt the key iterator
  188.      * @param node the current node
  189.      * @param handler the node handler
  190.      * @return the last existing node on the given path
  191.      */
  192.     protected <T> T findLastPathNode(final DefaultConfigurationKey.KeyIterator keyIt, final T node, final NodeHandler<T> handler) {
  193.         final String keyPart = keyIt.nextKey(false);

  194.         if (keyIt.hasNext()) {
  195.             if (!keyIt.isPropertyKey()) {
  196.                 // Attribute keys can only appear as last elements of the path
  197.                 throw new IllegalArgumentException("Invalid path for add operation: " + "Attribute key in the middle!");
  198.             }
  199.             final int idx = keyIt.hasIndex() ? keyIt.getIndex() : handler.getMatchingChildrenCount(node, nameMatcher, keyPart) - 1;
  200.             if (idx < 0 || idx >= handler.getMatchingChildrenCount(node, nameMatcher, keyPart)) {
  201.                 return node;
  202.             }
  203.             return findLastPathNode(keyIt, findChildNodesByName(handler, node, keyPart).get(idx), handler);
  204.         }
  205.         return node;
  206.     }

  207.     /**
  208.      * Recursive helper method for evaluating a key. This method processes all facets of a configuration key, traverses the
  209.      * tree of properties and fetches the results of all matching properties.
  210.      *
  211.      * @param <T> the type of nodes to be dealt with
  212.      * @param keyPart the configuration key iterator
  213.      * @param node the current node
  214.      * @param results here the found results are stored
  215.      * @param handler the node handler
  216.      */
  217.     protected <T> void findNodesForKey(final DefaultConfigurationKey.KeyIterator keyPart, final T node, final Collection<QueryResult<T>> results,
  218.         final NodeHandler<T> handler) {
  219.         if (!keyPart.hasNext()) {
  220.             results.add(QueryResult.createNodeResult(node));
  221.         } else {
  222.             final String key = keyPart.nextKey(false);
  223.             if (keyPart.isPropertyKey()) {
  224.                 processSubNodes(keyPart, findChildNodesByName(handler, node, key), results, handler);
  225.             }
  226.             if (keyPart.isAttribute() && !keyPart.hasNext() && handler.getAttributeValue(node, key) != null) {
  227.                 results.add(QueryResult.createAttributeResult(node, key));
  228.             }
  229.         }
  230.     }

  231.     /**
  232.      * Gets the {@code DefaultExpressionEngineSymbols} object associated with this instance.
  233.      *
  234.      * @return the {@code DefaultExpressionEngineSymbols} used by this engine
  235.      * @since 2.0
  236.      */
  237.     public DefaultExpressionEngineSymbols getSymbols() {
  238.         return symbols;
  239.     }

  240.     /**
  241.      * {@inheritDoc} This implementation takes the given parent key, adds a property delimiter, and then adds the node's
  242.      * name. The name of the root node is a blank string. Note that no indices are returned.
  243.      */
  244.     @Override
  245.     public <T> String nodeKey(final T node, final String parentKey, final NodeHandler<T> handler) {
  246.         if (parentKey == null) {
  247.             // this is the root node
  248.             return StringUtils.EMPTY;
  249.         }
  250.         final DefaultConfigurationKey key = new DefaultConfigurationKey(this, parentKey);
  251.         key.append(handler.nodeName(node), true);
  252.         return key.toString();
  253.     }

  254.     /**
  255.      * <p>
  256.      * Prepares Adding the property with the specified key.
  257.      * </p>
  258.      * <p>
  259.      * To be able to deal with the structure supported by hierarchical configuration implementations the passed in key is of
  260.      * importance, especially the indices it might contain. The following example should clarify this: Suppose the current
  261.      * node structure looks like the following:
  262.      * </p>
  263.      *
  264.      * <pre>
  265.      *  tables
  266.      *     +-- table
  267.      *             +-- name = user
  268.      *             +-- fields
  269.      *                     +-- field
  270.      *                             +-- name = uid
  271.      *                     +-- field
  272.      *                             +-- name = firstName
  273.      *                     ...
  274.      *     +-- table
  275.      *             +-- name = documents
  276.      *             +-- fields
  277.      *                    ...
  278.      * </pre>
  279.      * <p>
  280.      * In this example a database structure is defined, for example all fields of the first table could be accessed using the key
  281.      * {@code tables.table(0).fields.field.name}. If now properties are to be added, it must be exactly specified at which
  282.      * 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
  283.      * to say just
  284.      * </p>
  285.      *
  286.      * <pre>
  287.      * config.addProperty(&quot;tables.table.fields.field.name&quot;, &quot;newField&quot;);
  288.      * </pre>
  289.      * <p>
  290.      * The statement given above contains some ambiguity. For instance it is not clear, to which table the new field should
  291.      * be added. If this method finds such an ambiguity, it is resolved by following the last valid path. Here this would be
  292.      * the last table. The same is true for the {@code field}; because there are multiple fields and no explicit index is
  293.      * provided, a new {@code name} property would be added to the last field - which is probably not what was desired.
  294.      * </p>
  295.      * <p>
  296.      * To make things clear explicit indices should be provided whenever possible. In the example above the exact table
  297.      * could be specified by providing an index for the {@code table} element as in {@code tables.table(1).fields}. By
  298.      * specifying an index it can also be expressed that at a given position in the configuration tree a new branch should
  299.      * be added. In the example above we did not want to add an additional {@code name} element to the last field of the
  300.      * table, but we want a complete new {@code field} element. This can be achieved by specifying an invalid index (like
  301.      * -1) after the element where a new branch should be created. Given this our example would run:
  302.      * </p>
  303.      *
  304.      * <pre>
  305.      * config.addProperty(&quot;tables.table(1).fields.field(-1).name&quot;, &quot;newField&quot;);
  306.      * </pre>
  307.      * <p>
  308.      * With this notation it is possible to add new branches everywhere. We could for instance create a new {@code table}
  309.      * element by specifying
  310.      * </p>
  311.      *
  312.      * <pre>
  313.      * config.addProperty(&quot;tables.table(-1).fields.field.name&quot;, &quot;newField2&quot;);
  314.      * </pre>
  315.      * <p>
  316.      * (Note that because after the {@code table} element a new branch is created indices in following elements are not
  317.      * relevant; the branch is new so there cannot be any ambiguities.)
  318.      * </p>
  319.      *
  320.      * @param <T> the type of the nodes to be dealt with
  321.      * @param root the root node of the nodes hierarchy
  322.      * @param key the key of the new property
  323.      * @param handler the node handler
  324.      * @return a data object with information needed for the add operation
  325.      */
  326.     @Override
  327.     public <T> NodeAddData<T> prepareAdd(final T root, final String key, final NodeHandler<T> handler) {
  328.         final DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(this, key).iterator();
  329.         if (!it.hasNext()) {
  330.             throw new IllegalArgumentException("Key for add operation must be defined!");
  331.         }

  332.         final T parent = findLastPathNode(it, root, handler);
  333.         final List<String> pathNodes = new LinkedList<>();

  334.         while (it.hasNext()) {
  335.             if (!it.isPropertyKey()) {
  336.                 throw new IllegalArgumentException("Invalid key for add operation: " + key + " (Attribute key in the middle.)");
  337.             }
  338.             pathNodes.add(it.currentKey());
  339.             it.next();
  340.         }

  341.         return new NodeAddData<>(parent, it.currentKey(), !it.isPropertyKey(), pathNodes);
  342.     }

  343.     /**
  344.      * Called by {@code findNodesForKey()} to process the sub nodes of the current node depending on the type of the current
  345.      * key part (children, attributes, or both).
  346.      *
  347.      * @param <T> the type of the nodes to be dealt with
  348.      * @param keyPart the key part
  349.      * @param subNodes a list with the sub nodes to process
  350.      * @param nodes the target collection
  351.      * @param handler the node handler
  352.      */
  353.     private <T> void processSubNodes(final DefaultConfigurationKey.KeyIterator keyPart, final List<T> subNodes, final Collection<QueryResult<T>> nodes,
  354.         final NodeHandler<T> handler) {
  355.         if (keyPart.hasIndex()) {
  356.             if (keyPart.getIndex() >= 0 && keyPart.getIndex() < subNodes.size()) {
  357.                 findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart.clone(), subNodes.get(keyPart.getIndex()), nodes, handler);
  358.             }
  359.         } else {
  360.             subNodes.forEach(node -> findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart.clone(), node, nodes, handler));
  361.         }
  362.     }

  363.     /**
  364.      * {@inheritDoc} This method supports the syntax as described in the class comment.
  365.      */
  366.     @Override
  367.     public <T> List<QueryResult<T>> query(final T root, final String key, final NodeHandler<T> handler) {
  368.         final List<QueryResult<T>> results = new LinkedList<>();
  369.         findNodesForKey(new DefaultConfigurationKey(this, key).iterator(), root, results, handler);
  370.         return results;
  371.     }
  372. }