Source code
Description: source: package: org.apache.commons.configuration2, class: AbstractHierarchicalConfiguration

    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *     https://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.configuration2;
    019
    020import java.util.Collection;
    021import java.util.Collections;
    022import java.util.HashMap;
    023import java.util.Iterator;
    024import java.util.LinkedHashSet;
    025import java.util.LinkedList;
    026import java.util.List;
    027import java.util.Map;
    028import java.util.Objects;
    029import java.util.Set;
    030import java.util.Stack;
    031import java.util.stream.Collectors;
    032
    033import org.apache.commons.configuration2.event.ConfigurationEvent;
    034import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
    035import org.apache.commons.configuration2.sync.NoOpSynchronizer;
    036import org.apache.commons.configuration2.tree.ConfigurationNodeVisitorAdapter;
    037import org.apache.commons.configuration2.tree.DefaultExpressionEngine;
    038import org.apache.commons.configuration2.tree.ExpressionEngine;
    039import org.apache.commons.configuration2.tree.NodeAddData;
    040import org.apache.commons.configuration2.tree.NodeHandler;
    041import org.apache.commons.configuration2.tree.NodeKeyResolver;
    042import org.apache.commons.configuration2.tree.NodeModel;
    043import org.apache.commons.configuration2.tree.NodeTreeWalker;
    044import org.apache.commons.configuration2.tree.NodeUpdateData;
    045import org.apache.commons.configuration2.tree.QueryResult;
    046
    047/**
    048 * <p>
    049 * A specialized configuration class that extends its base class by the ability of keeping more structure in the stored
    050 * properties.
    051 * </p>
    052 * <p>
    053 * There are some sources of configuration data that cannot be stored very well in a {@code BaseConfiguration} object
    054 * because then their structure is lost. This is for instance true for XML documents. This class can deal with such
    055 * structured configuration sources by storing the properties in a tree-like organization. The exact storage structure
    056 * of the underlying data does not matter for the configuration instance; it uses a {@link NodeModel} object for
    057 * accessing it.
    058 * </p>
    059 * <p>
    060 * The hierarchical organization allows for a more sophisticated access to single properties. As an example consider the
    061 * following XML document:
    062 * </p>
    063 *
    064 * <pre>
    065 * &lt;database&gt;
    066 *   &lt;tables&gt;
    067 *     &lt;table&gt;
    068 *       &lt;name&gt;users&lt;/name&gt;
    069 *       &lt;fields&gt;
    070 *         &lt;field&gt;
    071 *           &lt;name&gt;lid&lt;/name&gt;
    072 *           &lt;type&gt;long&lt;/name&gt;
    073 *         &lt;/field&gt;
    074 *         &lt;field&gt;
    075 *           &lt;name&gt;usrName&lt;/name&gt;
    076 *           &lt;type&gt;java.lang.String&lt;/type&gt;
    077 *         &lt;/field&gt;
    078 *        ...
    079 *       &lt;/fields&gt;
    080 *     &lt;/table&gt;
    081 *     &lt;table&gt;
    082 *       &lt;name&gt;documents&lt;/name&gt;
    083 *       &lt;fields&gt;
    084 *         &lt;field&gt;
    085 *           &lt;name&gt;docid&lt;/name&gt;
    086 *           &lt;type&gt;long&lt;/type&gt;
    087 *         &lt;/field&gt;
    088 *         ...
    089 *       &lt;/fields&gt;
    090 *     &lt;/table&gt;
    091 *     ...
    092 *   &lt;/tables&gt;
    093 * &lt;/database&gt;
    094 * </pre>
    095 *
    096 * <p>
    097 * If this document is parsed and stored in a hierarchical configuration object (which can be done by one of the sub
    098 * classes), there are enhanced possibilities of accessing properties. Per default, the keys for querying information
    099 * can contain indices that select a specific element if there are multiple hits.
    100 * </p>
    101 * <p>
    102 * For instance the key {@code tables.table(0).name} can be used to find out the name of the first table. In opposite
    103 * {@code tables.table.name} would return a collection with the names of all available tables. Similarly the key
    104 * {@code tables.table(1).fields.field.name} returns a collection with the names of all fields of the second table. If
    105 * another index is added after the {@code field} element, a single field can be accessed:
    106 * {@code tables.table(1).fields.field(0).name}.
    107 * </p>
    108 * <p>
    109 * There is a {@code getMaxIndex()} method that returns the maximum allowed index that can be added to a given property
    110 * key. This method can be used to iterate over all values defined for a certain property.
    111 * </p>
    112 * <p>
    113 * Since the 1.3 release of <em>Commons Configuration</em> hierarchical configurations support an <em>expression
    114 * engine</em>. This expression engine is responsible for evaluating the passed in configuration keys and map them to
    115 * the stored properties. The examples above are valid for the default expression engine, which is used when a new
    116 * {@code AbstractHierarchicalConfiguration} instance is created. With the {@code setExpressionEngine()} method a
    117 * different expression engine can be set. For instance with
    118 * {@link org.apache.commons.configuration2.tree.xpath.XPathExpressionEngine} there is an expression engine available
    119 * that supports configuration keys in XPATH syntax.
    120 * </p>
    121 * <p>
    122 * In addition to the events common for all configuration classes, hierarchical configurations support some more events
    123 * that correspond to some specific methods and features. For those events specific event type constants in
    124 * {@code ConfigurationEvent} exist:
    125 * </p>
    126 * <dl>
    127 * <dt><em>ADD_NODES</em></dt>
    128 * <dd>The {@code addNodes()} method was called; the event object contains the key, to which the nodes were added, and a
    129 * collection with the new nodes as value.</dd>
    130 * <dt><em>CLEAR_TREE</em></dt>
    131 * <dd>The {@code clearTree()} method was called; the event object stores the key of the removed sub tree.</dd>
    132 * <dt><em>SUBNODE_CHANGED</em></dt>
    133 * <dd>A {@code SubnodeConfiguration} that was created from this configuration has been changed. The value property of
    134 * the event object contains the original event object as it was sent by the subnode configuration.</dd>
    135 * </dl>
    136 * <p>
    137 * Whether an {@code AbstractHierarchicalConfiguration} object is thread-safe or not depends on the underlying
    138 * {@code NodeModel} and the {@link org.apache.commons.configuration2.sync.Synchronizer Synchronizer} it is associated
    139 * with. Some {@code NodeModel} implementations are inherently thread-safe; they do not require a special
    140 * {@code Synchronizer}. (Per default, a dummy {@code Synchronizer} is used which is not thread-safe!) The methods for
    141 * querying or updating configuration data invoke this {@code Synchronizer} accordingly. When accessing the
    142 * configuration's root node directly, the client application is responsible for proper synchronization. This is
    143 * achieved by calling the methods {@link #lock(org.apache.commons.configuration2.sync.LockMode) lock()}, and
    144 * {@link #unlock(org.apache.commons.configuration2.sync.LockMode) unlock()} with a proper
    145 * {@link org.apache.commons.configuration2.sync.LockMode LockMode} argument. In any case, it is recommended to not
    146 * access the root node directly, but to use corresponding methods for querying or updating configuration data instead.
    147 * Direct manipulations of a configuration's node structure circumvent many internal mechanisms and thus can cause
    148 * undesired effects. For concrete subclasses dealing with specific node structures, this situation may be different.
    149 * </p>
    150 *
    151 * @param <T> the type of the nodes managed by this hierarchical configuration
    152 * @since 2.0
    153 */
    154public abstract class AbstractHierarchicalConfiguration<T> extends AbstractConfiguration
    155    implements Cloneable, NodeKeyResolver<T>, HierarchicalConfiguration<T> {
    156
    157    /**
    158     * A specialized visitor that fills a list with keys that are defined in a node hierarchy.
    159     */
    160    private final class DefinedKeysVisitor extends ConfigurationNodeVisitorAdapter<T> {
    161
    162        /** Stores the list to be filled. */
    163        private final Set<String> keyList;
    164
    165        /** A stack with the keys of the already processed nodes. */
    166        private final Stack<String> parentKeys;
    167
    168        /**
    169         * Default constructor.
    170         */
    171        public DefinedKeysVisitor() {
    172            keyList = new LinkedHashSet<>();
    173            parentKeys = new Stack<>();
    174        }
    175
    176        /**
    177         * Creates a new {@code DefinedKeysVisitor} instance and sets the prefix for the keys to fetch.
    178         *
    179         * @param prefix the prefix
    180         */
    181        public DefinedKeysVisitor(final String prefix) {
    182            this();
    183            parentKeys.push(prefix);
    184        }
    185
    186        /**
    187         * Gets the list with all defined keys.
    188         *
    189         * @return the list with the defined keys
    190         */
    191        public Set<String> getKeyList() {
    192            return keyList;
    193        }
    194
    195        /**
    196         * Appends all attribute keys of the current node.
    197         *
    198         * @param parentKey the parent key
    199         * @param node the current node
    200         * @param handler the {@code NodeHandler}
    201         */
    202        public void handleAttributeKeys(final String parentKey, final T node, final NodeHandler<T> handler) {
    203            handler.getAttributes(node).forEach(attr -> keyList.add(getExpressionEngine().attributeKey(parentKey, attr)));
    204        }
    205
    206        /**
    207         * {@inheritDoc} This implementation removes this node's key from the stack.
    208         */
    209        @Override
    210        public void visitAfterChildren(final T node, final NodeHandler<T> handler) {
    211            parentKeys.pop();
    212        }
    213
    214        /**
    215         * {@inheritDoc} If this node has a value, its key is added to the internal list.
    216         */
    217        @Override
    218        public void visitBeforeChildren(final T node, final NodeHandler<T> handler) {
    219            final String parentKey = parentKeys.isEmpty() ? null : parentKeys.peek();
    220            final String key = getExpressionEngine().nodeKey(node, parentKey, handler);
    221            parentKeys.push(key);
    222            if (handler.getValue(node) != null) {
    223                keyList.add(key);
    224            }
    225            handleAttributeKeys(key, node, handler);
    226        }
    227    }
    228
    229    /**
    230     * A specialized visitor that checks if a node is defined. &quot;Defined&quot; in this terms means that the node or at
    231     * least one of its sub nodes is associated with a value.
    232     *
    233     * @param <T> the type of the nodes managed by this hierarchical configuration
    234     */
    235    private static final class DefinedVisitor<T> extends ConfigurationNodeVisitorAdapter<T> {
    236
    237        /** Stores the defined flag. */
    238        private boolean defined;
    239
    240        /**
    241         * Returns the defined flag.
    242         *
    243         * @return the defined flag
    244         */
    245        public boolean isDefined() {
    246            return defined;
    247        }
    248
    249        /**
    250         * Checks if iteration should be stopped. This can be done if the first defined node is found.
    251         *
    252         * @return a flag if iteration should be stopped
    253         */
    254        @Override
    255        public boolean terminate() {
    256            return isDefined();
    257        }
    258
    259        /**
    260         * Visits the node. Checks if a value is defined.
    261         *
    262         * @param node the actual node
    263         */
    264        @Override
    265        public void visitBeforeChildren(final T node, final NodeHandler<T> handler) {
    266            defined = handler.getValue(node) != null || !handler.getAttributes(node).isEmpty();
    267        }
    268    }
    269
    270    /** The model for managing the data stored in this configuration. */
    271    private NodeModel<T> nodeModel;
    272
    273    /** Stores the expression engine for this instance. */
    274    private ExpressionEngine expressionEngine;
    275
    276    /**
    277     * Creates a new instance of {@code AbstractHierarchicalConfiguration} and sets the {@code NodeModel} to be used.
    278     *
    279     * @param nodeModel the {@code NodeModel}
    280     */
    281    protected AbstractHierarchicalConfiguration(final NodeModel<T> nodeModel) {
    282        this.nodeModel = nodeModel;
    283    }
    284
    285    /**
    286     * Adds a collection of nodes at the specified position of the configuration tree. This method works similar to
    287     * {@code addProperty()}, but instead of a single property a whole collection of nodes can be added - and thus complete
    288     * configuration sub trees. E.g. with this method it is possible to add parts of another
    289     * {@code BaseHierarchicalConfiguration} object to this object. If the passed in key refers to an existing and unique
    290     * node, the new nodes are added to this node. Otherwise a new node will be created at the specified position in the
    291     * hierarchy. Implementation node: This method performs some book-keeping and then delegates to
    292     * {@code addNodesInternal()}.
    293     *
    294     * @param key the key where the nodes are to be added; can be <strong>null</strong>, then they are added to the root node
    295     * @param nodes a collection with the {@code Node} objects to be added
    296     */
    297    @Override
    298    public final void addNodes(final String key, final Collection<? extends T> nodes) {
    299        if (nodes == null || nodes.isEmpty()) {
    300            return;
    301        }
    302        syncWrite(() -> {
    303            fireEvent(ConfigurationEvent.ADD_NODES, key, nodes, true);
    304            addNodesInternal(key, nodes);
    305            fireEvent(ConfigurationEvent.ADD_NODES, key, nodes, false);
    306        }, false);
    307    }
    308
    309    /**
    310     * Actually adds a collection of new nodes to this configuration. This method is called by {@code addNodes()}. It can be
    311     * overridden by subclasses that need to adapt this operation.
    312     *
    313     * @param key the key where the nodes are to be added; can be <strong>null</strong>, then they are added to the root node
    314     * @param nodes a collection with the {@code Node} objects to be added
    315     * @since 2.0
    316     */
    317    protected void addNodesInternal(final String key, final Collection<? extends T> nodes) {
    318        getModel().addNodes(key, nodes, this);
    319    }
    320
    321    /**
    322     * {@inheritDoc} This method is not called in the normal way (via {@code addProperty()} for hierarchical configurations
    323     * because all values to be added for the property have to be passed to the model in a single step. However, to allow
    324     * derived classes to add an arbitrary value as an object, a special implementation is provided here. The passed in
    325     * object is not parsed as a list, but passed directly as only value to the model.
    326     */
    327    @Override
    328    protected void addPropertyDirect(final String key, final Object value) {
    329        addPropertyToModel(key, Collections.singleton(value));
    330    }
    331
    332    /**
    333     * Adds the property with the specified key. This task will be delegated to the associated {@code ExpressionEngine}, so
    334     * the passed in key must match the requirements of this implementation.
    335     *
    336     * @param key the key of the new property
    337     * @param obj the value of the new property
    338     */
    339    @Override
    340    protected void addPropertyInternal(final String key, final Object obj) {
    341        addPropertyToModel(key, getListDelimiterHandler().parse(obj));
    342    }
    343
    344    /**
    345     * Helper method for executing an add property operation on the model.
    346     *
    347     * @param key the key of the new property
    348     * @param values the values to be added for this property
    349     */
    350    private void addPropertyToModel(final String key, final Iterable<?> values) {
    351        getModel().addProperty(key, values, this);
    352    }
    353
    354    /**
    355     * Clears this configuration. This is a more efficient implementation than the one inherited from the base class. It
    356     * delegates to the node model.
    357     */
    358    @Override
    359    protected void clearInternal() {
    360        getModel().clear(this);
    361    }
    362
    363    /**
    364     * Removes the property with the given key. Properties with names that start with the given key (i.e. properties below
    365     * the specified key in the hierarchy) won't be affected. This implementation delegates to the node+ model.
    366     *
    367     * @param key the key of the property to be removed
    368     */
    369    @Override
    370    protected void clearPropertyDirect(final String key) {
    371        getModel().clearProperty(key, this);
    372    }
    373
    374    /**
    375     * Removes all values of the property with the given name and of keys that start with this name. So if there is a
    376     * property with the key &quot;foo&quot; and a property with the key &quot;foo.bar&quot;, a call of
    377     * {@code clearTree("foo")} would remove both properties.
    378     *
    379     * @param key the key of the property to be removed
    380     */
    381    @Override
    382    public final void clearTree(final String key) {
    383        syncWrite(() -> {
    384            fireEvent(ConfigurationEvent.CLEAR_TREE, key, null, true);
    385            fireEvent(ConfigurationEvent.CLEAR_TREE, key, clearTreeInternal(key), false);
    386        }, false);
    387    }
    388
    389    /**
    390     * Actually clears the tree of elements referenced by the given key. This method is called by {@code clearTree()}.
    391     * Subclasses that need to adapt this operation can override this method. This base implementation delegates to the node
    392     * model.
    393     *
    394     * @param key the key of the property to be removed
    395     * @return an object with information about the nodes that have been removed (this is needed for firing a meaningful
    396     *         event of type CLEAR_TREE)
    397     * @since 2.0
    398     */
    399    protected Object clearTreeInternal(final String key) {
    400        return getModel().clearTree(key, this);
    401    }
    402
    403    /**
    404     * Creates a copy of this object. This new configuration object will contain copies of all nodes in the same structure.
    405     * Registered event listeners won't be cloned; so they are not registered at the returned copy.
    406     *
    407     * @return the copy
    408     * @since 1.2
    409     */
    410    @SuppressWarnings("unchecked")
    411    @Override
    412    public Object clone() {
    413        return syncRead(() -> {
    414            try {
    415                final AbstractHierarchicalConfiguration<T> copy = (AbstractHierarchicalConfiguration<T>) AbstractHierarchicalConfiguration.super.clone();
    416                copy.setSynchronizer(NoOpSynchronizer.INSTANCE);
    417                copy.cloneInterpolator(this);
    418                copy.setSynchronizer(ConfigurationUtils.cloneSynchronizer(getSynchronizer()));
    419                copy.nodeModel = cloneNodeModel();
    420                return copy;
    421            } catch (final CloneNotSupportedException cex) {
    422                // should not happen
    423                throw new ConfigurationRuntimeException(cex);
    424            }
    425        }, false);
    426    }
    427
    428    /**
    429     * Creates a clone of the node model. This method is called by {@code clone()}.
    430     *
    431     * @return the clone of the {@code NodeModel}
    432     * @since 2.0
    433     */
    434    protected abstract NodeModel<T> cloneNodeModel();
    435
    436    /**
    437     * Checks if the specified key is contained in this configuration. Note that for this configuration the term
    438     * &quot;contained&quot; means that the key has an associated value. If there is a node for this key that has no value
    439     * but children (either defined or undefined), this method will still return <strong>false </strong>.
    440     *
    441     * @param key the key to be checked
    442     * @return a flag if this key is contained in this configuration
    443     */
    444    @Override
    445    protected boolean containsKeyInternal(final String key) {
    446        return getPropertyInternal(key) != null;
    447    }
    448
    449    /**
    450     * Tests whether this configuration contains one or more matches to this value. This operation stops at first
    451     * match but may be more expensive than the containsKey method.
    452     *
    453     * @since 2.11.0
    454     */
    455    @Override
    456    protected boolean containsValueInternal(final Object value) {
    457        return contains(getKeys(), value);
    458    }
    459
    460    /**
    461     * Helper method for resolving the specified key.
    462     *
    463     * @param key the key
    464     * @return a list with all results selected by this key
    465     */
    466    protected List<QueryResult<T>> fetchNodeList(final String key) {
    467        final NodeHandler<T> nodeHandler = getModel().getNodeHandler();
    468        return resolveKey(nodeHandler.getRootNode(), key, nodeHandler);
    469    }
    470
    471    /**
    472     * Gets the expression engine used by this configuration. This method will never return <strong>null</strong>; if no specific
    473     * expression engine was set, the default expression engine will be returned.
    474     *
    475     * @return the current expression engine
    476     * @since 1.3
    477     */
    478    @Override
    479    public ExpressionEngine getExpressionEngine() {
    480        return expressionEngine != null ? expressionEngine : DefaultExpressionEngine.INSTANCE;
    481    }
    482
    483    /**
    484     * Gets an iterator with all keys defined in this configuration. Note that the keys returned by this method will not
    485     * contain any indices. This means that some structure will be lost.
    486     *
    487     * @return an iterator with the defined keys in this configuration
    488     */
    489    @Override
    490    protected Iterator<String> getKeysInternal() {
    491        return visitDefinedKeys().getKeyList().iterator();
    492    }
    493
    494    /**
    495     * Gets an iterator with all keys defined in this configuration that start with the given prefix. The returned keys
    496     * will not contain any indices. This implementation tries to locate a node whose key is the same as the passed in
    497     * prefix. Then the subtree of this node is traversed, and the keys of all nodes encountered (including attributes) are
    498     * added to the result set.
    499     *
    500     * @param prefix the prefix of the keys to start with
    501     * @return an iterator with the found keys
    502     */
    503    @Override
    504    protected Iterator<String> getKeysInternal(final String prefix) {
    505        return getKeysInternal(prefix, DELIMITER);
    506    }
    507
    508    /**
    509     * Gets an iterator with all keys defined in this configuration that start with the given prefix. The returned keys
    510     * will not contain any indices. This implementation tries to locate a node whose key is the same as the passed in
    511     * prefix. Then the subtree of this node is traversed, and the keys of all nodes encountered (including attributes) are
    512     * added to the result set.
    513     *
    514     * @param prefix the prefix of the keys to start with
    515     * @param delimiter the prefix delimiter (unused)
    516     * @return an iterator with the found keys
    517     * @since 2.12.0
    518     */
    519    @Override
    520    protected Iterator<String> getKeysInternal(final String prefix, final String delimiter) {
    521        final DefinedKeysVisitor visitor = new DefinedKeysVisitor(prefix);
    522        if (containsKey(prefix)) {
    523            // explicitly add the prefix
    524            visitor.getKeyList().add(prefix);
    525        }
    526
    527        final List<QueryResult<T>> results = fetchNodeList(prefix);
    528        final NodeHandler<T> handler = getModel().getNodeHandler();
    529
    530        results.forEach(result -> {
    531            if (!result.isAttributeResult()) {
    532                handler.getChildren(result.getNode()).forEach(c -> NodeTreeWalker.INSTANCE.walkDFS(c, visitor, handler));
    533                visitor.handleAttributeKeys(prefix, result.getNode(), handler);
    534            }
    535        });
    536
    537        return visitor.getKeyList().iterator();
    538    }
    539
    540    /**
    541     * Gets the maximum defined index for the given key. This is useful if there are multiple values for this key. They
    542     * can then be addressed separately by specifying indices from 0 to the return value of this method. If the passed in
    543     * key is not contained in this configuration, result is -1.
    544     *
    545     * @param key the key to be checked
    546     * @return the maximum defined index for this key
    547     */
    548    @Override
    549    public final int getMaxIndex(final String key) {
    550        return syncRead(() -> getMaxIndexInternal(key), false);
    551    }
    552
    553    /**
    554     * Actually retrieves the maximum defined index for the given key. This method is called by {@code getMaxIndex()}.
    555     * Subclasses that need to adapt this operation have to override this method.
    556     *
    557     * @param key the key to be checked
    558     * @return the maximum defined index for this key
    559     * @since 2.0
    560     */
    561    protected int getMaxIndexInternal(final String key) {
    562        return fetchNodeList(key).size() - 1;
    563    }
    564
    565    /**
    566     * Gets the {@code NodeModel} used by this configuration. This method is intended for internal use only. Access to
    567     * the model is granted without any synchronization. This is in contrast to the &quot;official&quot;
    568     * {@code getNodeModel()} method which is guarded by the configuration's {@code Synchronizer}.
    569     *
    570     * @return the node model
    571     */
    572    protected NodeModel<T> getModel() {
    573        return nodeModel;
    574    }
    575
    576    /**
    577     * {@inheritDoc} This implementation returns the configuration's {@code NodeModel}. It is guarded by the current
    578     * {@code Synchronizer}.
    579     */
    580    @Override
    581    public NodeModel<T> getNodeModel() {
    582        return syncRead(this::getModel, false);
    583    }
    584
    585    /**
    586     * Fetches the specified property. This task is delegated to the associated expression engine.
    587     *
    588     * @param key the key to be looked up
    589     * @return the found value
    590     */
    591    @Override
    592    protected Object getPropertyInternal(final String key) {
    593        final List<QueryResult<T>> results = fetchNodeList(key);
    594
    595        if (results.isEmpty()) {
    596            return null;
    597        }
    598        final NodeHandler<T> handler = getModel().getNodeHandler();
    599        final List<Object> list = results.stream().map(r -> valueFromResult(r, handler)).filter(Objects::nonNull).collect(Collectors.toList());
    600
    601        if (list.size() < 1) {
    602            return null;
    603        }
    604        return list.size() == 1 ? list.get(0) : list;
    605    }
    606
    607    /**
    608     * {@inheritDoc} This implementation handles synchronization and delegates to {@code getRootElementNameInternal()}.
    609     */
    610    @Override
    611    public final String getRootElementName() {
    612        return syncRead(this::getRootElementNameInternal, false);
    613    }
    614
    615    /**
    616     * Actually obtains the name of the root element. This method is called by {@code getRootElementName()}. It just returns
    617     * the name of the root node. Subclasses that treat the root element name differently can override this method.
    618     *
    619     * @return the name of this configuration's root element
    620     */
    621    protected String getRootElementNameInternal() {
    622        final NodeHandler<T> nodeHandler = getModel().getNodeHandler();
    623        return nodeHandler.nodeName(nodeHandler.getRootNode());
    624    }
    625
    626    /**
    627     * Checks if this configuration is empty. Empty means that there are no keys with any values, though there can be some
    628     * (empty) nodes.
    629     *
    630     * @return a flag if this configuration is empty
    631     */
    632    @Override
    633    protected boolean isEmptyInternal() {
    634        return !nodeDefined(getModel().getNodeHandler().getRootNode());
    635    }
    636
    637    /**
    638     * Checks if the specified node is defined.
    639     *
    640     * @param node the node to be checked
    641     * @return a flag if this node is defined
    642     */
    643    protected boolean nodeDefined(final T node) {
    644        final DefinedVisitor<T> visitor = new DefinedVisitor<>();
    645        NodeTreeWalker.INSTANCE.walkBFS(node, visitor, getModel().getNodeHandler());
    646        return visitor.isDefined();
    647    }
    648
    649    /**
    650     * {@inheritDoc} This implementation uses the expression engine to generate a canonical key for the passed in node. For
    651     * this purpose, the path to the root node has to be traversed. The cache is used to store and access keys for nodes
    652     * encountered on the path.
    653     */
    654    @Override
    655    public String nodeKey(final T node, final Map<T, String> cache, final NodeHandler<T> handler) {
    656        final List<T> paths = new LinkedList<>();
    657        T currentNode = node;
    658        String key = cache.get(node);
    659        while (key == null && currentNode != null) {
    660            paths.add(0, currentNode);
    661            currentNode = handler.getParent(currentNode);
    662            key = cache.get(currentNode);
    663        }
    664
    665        for (final T n : paths) {
    666            final String currentKey = getExpressionEngine().canonicalKey(n, key, handler);
    667            cache.put(n, currentKey);
    668            key = currentKey;
    669        }
    670
    671        return key;
    672    }
    673
    674    /**
    675     * {@inheritDoc} This implementation delegates to the expression engine.
    676     */
    677    @Override
    678    public NodeAddData<T> resolveAddKey(final T root, final String key, final NodeHandler<T> handler) {
    679        return getExpressionEngine().prepareAdd(root, key, handler);
    680    }
    681
    682    /**
    683     * {@inheritDoc} This implementation delegates to the expression engine.
    684     */
    685    @Override
    686    public List<QueryResult<T>> resolveKey(final T root, final String key, final NodeHandler<T> handler) {
    687        return getExpressionEngine().query(root, key, handler);
    688    }
    689
    690    /**
    691     * {@inheritDoc} This implementation delegates to {@code resolveKey()} and then filters out attribute results.
    692     */
    693    @Override
    694    public List<T> resolveNodeKey(final T root, final String key, final NodeHandler<T> handler) {
    695        return resolveKey(root, key, handler).stream().filter(r -> !r.isAttributeResult()).map(QueryResult::getNode)
    696            .collect(Collectors.toCollection(LinkedList::new));
    697    }
    698
    699    /**
    700     * {@inheritDoc} This implementation executes a query for the given key and constructs a {@code NodeUpdateData} object
    701     * based on the results. It determines which nodes need to be changed and whether new ones need to be added or existing
    702     * ones need to be removed.
    703     */
    704    @Override
    705    public NodeUpdateData<T> resolveUpdateKey(final T root, final String key, final Object newValue, final NodeHandler<T> handler) {
    706        final Iterator<QueryResult<T>> itNodes = fetchNodeList(key).iterator();
    707        final Iterator<?> itValues = getListDelimiterHandler().parse(newValue).iterator();
    708        final Map<QueryResult<T>, Object> changedValues = new HashMap<>();
    709        Collection<Object> additionalValues = null;
    710        Collection<QueryResult<T>> removedItems = null;
    711
    712        while (itNodes.hasNext() && itValues.hasNext()) {
    713            changedValues.put(itNodes.next(), itValues.next());
    714        }
    715
    716        // Add additional nodes if necessary
    717        if (itValues.hasNext()) {
    718            additionalValues = new LinkedList<>();
    719            itValues.forEachRemaining(additionalValues::add);
    720        }
    721
    722        // Remove remaining nodes
    723        if (itNodes.hasNext()) {
    724            removedItems = new LinkedList<>();
    725            itNodes.forEachRemaining(removedItems::add);
    726        }
    727
    728        return new NodeUpdateData<>(changedValues, additionalValues, removedItems, key);
    729    }
    730
    731    /**
    732     * Sets the expression engine to be used by this configuration. All property keys this configuration has to deal with
    733     * will be interpreted by this engine.
    734     *
    735     * @param expressionEngine the new expression engine; can be <strong>null</strong>, then the default expression engine will be
    736     *        used
    737     * @since 1.3
    738     */
    739    @Override
    740    public void setExpressionEngine(final ExpressionEngine expressionEngine) {
    741        this.expressionEngine = expressionEngine;
    742    }
    743
    744    /**
    745     * Sets the value of the specified property.
    746     *
    747     * @param key the key of the property to set
    748     * @param value the new value of this property
    749     */
    750    @Override
    751    protected void setPropertyInternal(final String key, final Object value) {
    752        getModel().setProperty(key, value, this);
    753    }
    754
    755    /**
    756     * {@inheritDoc} This implementation is slightly more efficient than the default implementation. It does not iterate
    757     * over the key set, but directly queries its size after it has been constructed. Note that constructing the key set is
    758     * still an O(n) operation.
    759     */
    760    @Override
    761    protected int sizeInternal() {
    762        return visitDefinedKeys().getKeyList().size();
    763    }
    764
    765    @Override
    766    public String toString() {
    767        return super.toString() + "(" + getRootElementNameInternal() + ")";
    768    }
    769
    770    /**
    771     * Extracts the value from a query result.
    772     *
    773     * @param result the {@code QueryResult}
    774     * @param handler the {@code NodeHandler}
    775     * @return the value of this result (may be <strong>null</strong>)
    776     */
    777    private Object valueFromResult(final QueryResult<T> result, final NodeHandler<T> handler) {
    778        return result.isAttributeResult() ? result.getAttributeValue(handler) : handler.getValue(result.getNode());
    779    }
    780
    781    /**
    782     * Creates a {@code DefinedKeysVisitor} and visits all defined keys with it.
    783     *
    784     * @return the visitor after all keys have been visited
    785     */
    786    private DefinedKeysVisitor visitDefinedKeys() {
    787        final DefinedKeysVisitor visitor = new DefinedKeysVisitor();
    788        final NodeHandler<T> nodeHandler = getModel().getNodeHandler();
    789        NodeTreeWalker.INSTANCE.walkDFS(nodeHandler.getRootNode(), visitor, nodeHandler);
    790        return visitor;
    791    }
    792}