View Javadoc
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    *     https://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  
18  package org.apache.commons.configuration2;
19  
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.LinkedHashSet;
25  import java.util.LinkedList;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Objects;
29  import java.util.Set;
30  import java.util.Stack;
31  import java.util.stream.Collectors;
32  
33  import org.apache.commons.configuration2.event.ConfigurationEvent;
34  import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
35  import org.apache.commons.configuration2.sync.NoOpSynchronizer;
36  import org.apache.commons.configuration2.tree.ConfigurationNodeVisitorAdapter;
37  import org.apache.commons.configuration2.tree.DefaultExpressionEngine;
38  import org.apache.commons.configuration2.tree.ExpressionEngine;
39  import org.apache.commons.configuration2.tree.NodeAddData;
40  import org.apache.commons.configuration2.tree.NodeHandler;
41  import org.apache.commons.configuration2.tree.NodeKeyResolver;
42  import org.apache.commons.configuration2.tree.NodeModel;
43  import org.apache.commons.configuration2.tree.NodeTreeWalker;
44  import org.apache.commons.configuration2.tree.NodeUpdateData;
45  import org.apache.commons.configuration2.tree.QueryResult;
46  
47  /**
48   * <p>
49   * A specialized configuration class that extends its base class by the ability of keeping more structure in the stored
50   * properties.
51   * </p>
52   * <p>
53   * There are some sources of configuration data that cannot be stored very well in a {@code BaseConfiguration} object
54   * because then their structure is lost. This is for instance true for XML documents. This class can deal with such
55   * structured configuration sources by storing the properties in a tree-like organization. The exact storage structure
56   * of the underlying data does not matter for the configuration instance; it uses a {@link NodeModel} object for
57   * accessing it.
58   * </p>
59   * <p>
60   * The hierarchical organization allows for a more sophisticated access to single properties. As an example consider the
61   * following XML document:
62   * </p>
63   *
64   * <pre>
65   * &lt;database&gt;
66   *   &lt;tables&gt;
67   *     &lt;table&gt;
68   *       &lt;name&gt;users&lt;/name&gt;
69   *       &lt;fields&gt;
70   *         &lt;field&gt;
71   *           &lt;name&gt;lid&lt;/name&gt;
72   *           &lt;type&gt;long&lt;/name&gt;
73   *         &lt;/field&gt;
74   *         &lt;field&gt;
75   *           &lt;name&gt;usrName&lt;/name&gt;
76   *           &lt;type&gt;java.lang.String&lt;/type&gt;
77   *         &lt;/field&gt;
78   *        ...
79   *       &lt;/fields&gt;
80   *     &lt;/table&gt;
81   *     &lt;table&gt;
82   *       &lt;name&gt;documents&lt;/name&gt;
83   *       &lt;fields&gt;
84   *         &lt;field&gt;
85   *           &lt;name&gt;docid&lt;/name&gt;
86   *           &lt;type&gt;long&lt;/type&gt;
87   *         &lt;/field&gt;
88   *         ...
89   *       &lt;/fields&gt;
90   *     &lt;/table&gt;
91   *     ...
92   *   &lt;/tables&gt;
93   * &lt;/database&gt;
94   * </pre>
95   *
96   * <p>
97   * If this document is parsed and stored in a hierarchical configuration object (which can be done by one of the sub
98   * classes), there are enhanced possibilities of accessing properties. Per default, the keys for querying information
99   * 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  */
154 public 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      * @since 2.11.0
453      */
454     @Override
455     protected boolean containsValueInternal(final Object value) {
456         return contains(getKeys(), value);
457     }
458 
459     /**
460      * Helper method for resolving the specified key.
461      *
462      * @param key the key
463      * @return a list with all results selected by this key
464      */
465     protected List<QueryResult<T>> fetchNodeList(final String key) {
466         final NodeHandler<T> nodeHandler = getModel().getNodeHandler();
467         return resolveKey(nodeHandler.getRootNode(), key, nodeHandler);
468     }
469 
470     /**
471      * Gets the expression engine used by this configuration. This method will never return <strong>null</strong>; if no specific
472      * expression engine was set, the default expression engine will be returned.
473      *
474      * @return the current expression engine
475      * @since 1.3
476      */
477     @Override
478     public ExpressionEngine getExpressionEngine() {
479         return expressionEngine != null ? expressionEngine : DefaultExpressionEngine.INSTANCE;
480     }
481 
482     /**
483      * Gets an iterator with all keys defined in this configuration. Note that the keys returned by this method will not
484      * contain any indices. This means that some structure will be lost.
485      *
486      * @return an iterator with the defined keys in this configuration
487      */
488     @Override
489     protected Iterator<String> getKeysInternal() {
490         return visitDefinedKeys().getKeyList().iterator();
491     }
492 
493     /**
494      * Gets an iterator with all keys defined in this configuration that start with the given prefix. The returned keys
495      * will not contain any indices. This implementation tries to locate a node whose key is the same as the passed in
496      * prefix. Then the subtree of this node is traversed, and the keys of all nodes encountered (including attributes) are
497      * added to the result set.
498      *
499      * @param prefix the prefix of the keys to start with
500      * @return an iterator with the found keys
501      */
502     @Override
503     protected Iterator<String> getKeysInternal(final String prefix) {
504         return getKeysInternal(prefix, DELIMITER);
505     }
506 
507     /**
508      * Gets an iterator with all keys defined in this configuration that start with the given prefix. The returned keys
509      * will not contain any indices. This implementation tries to locate a node whose key is the same as the passed in
510      * prefix. Then the subtree of this node is traversed, and the keys of all nodes encountered (including attributes) are
511      * added to the result set.
512      *
513      * @param prefix the prefix of the keys to start with
514      * @param delimiter TODO
515      * @return an iterator with the found keys
516      * @since 2.12.0
517      */
518     @Override
519     protected Iterator<String> getKeysInternal(final String prefix, final String delimiter) {
520         final DefinedKeysVisitor visitor = new DefinedKeysVisitor(prefix);
521         if (containsKey(prefix)) {
522             // explicitly add the prefix
523             visitor.getKeyList().add(prefix);
524         }
525 
526         final List<QueryResult<T>> results = fetchNodeList(prefix);
527         final NodeHandler<T> handler = getModel().getNodeHandler();
528 
529         results.forEach(result -> {
530             if (!result.isAttributeResult()) {
531                 handler.getChildren(result.getNode()).forEach(c -> NodeTreeWalker.INSTANCE.walkDFS(c, visitor, handler));
532                 visitor.handleAttributeKeys(prefix, result.getNode(), handler);
533             }
534         });
535 
536         return visitor.getKeyList().iterator();
537     }
538     /**
539      * Gets the maximum defined index for the given key. This is useful if there are multiple values for this key. They
540      * can then be addressed separately by specifying indices from 0 to the return value of this method. If the passed in
541      * key is not contained in this configuration, result is -1.
542      *
543      * @param key the key to be checked
544      * @return the maximum defined index for this key
545      */
546     @Override
547     public final int getMaxIndex(final String key) {
548         return syncRead(() -> getMaxIndexInternal(key), false);
549     }
550 
551     /**
552      * Actually retrieves the maximum defined index for the given key. This method is called by {@code getMaxIndex()}.
553      * Subclasses that need to adapt this operation have to override this method.
554      *
555      * @param key the key to be checked
556      * @return the maximum defined index for this key
557      * @since 2.0
558      */
559     protected int getMaxIndexInternal(final String key) {
560         return fetchNodeList(key).size() - 1;
561     }
562 
563     /**
564      * Gets the {@code NodeModel} used by this configuration. This method is intended for internal use only. Access to
565      * the model is granted without any synchronization. This is in contrast to the &quot;official&quot;
566      * {@code getNodeModel()} method which is guarded by the configuration's {@code Synchronizer}.
567      *
568      * @return the node model
569      */
570     protected NodeModel<T> getModel() {
571         return nodeModel;
572     }
573 
574     /**
575      * {@inheritDoc} This implementation returns the configuration's {@code NodeModel}. It is guarded by the current
576      * {@code Synchronizer}.
577      */
578     @Override
579     public NodeModel<T> getNodeModel() {
580         return syncRead(this::getModel, false);
581     }
582 
583     /**
584      * Fetches the specified property. This task is delegated to the associated expression engine.
585      *
586      * @param key the key to be looked up
587      * @return the found value
588      */
589     @Override
590     protected Object getPropertyInternal(final String key) {
591         final List<QueryResult<T>> results = fetchNodeList(key);
592 
593         if (results.isEmpty()) {
594             return null;
595         }
596         final NodeHandler<T> handler = getModel().getNodeHandler();
597         final List<Object> list = results.stream().map(r -> valueFromResult(r, handler)).filter(Objects::nonNull).collect(Collectors.toList());
598 
599         if (list.size() < 1) {
600             return null;
601         }
602         return list.size() == 1 ? list.get(0) : list;
603     }
604 
605     /**
606      * {@inheritDoc} This implementation handles synchronization and delegates to {@code getRootElementNameInternal()}.
607      */
608     @Override
609     public final String getRootElementName() {
610         return syncRead(this::getRootElementNameInternal, false);
611     }
612 
613     /**
614      * Actually obtains the name of the root element. This method is called by {@code getRootElementName()}. It just returns
615      * the name of the root node. Subclasses that treat the root element name differently can override this method.
616      *
617      * @return the name of this configuration's root element
618      */
619     protected String getRootElementNameInternal() {
620         final NodeHandler<T> nodeHandler = getModel().getNodeHandler();
621         return nodeHandler.nodeName(nodeHandler.getRootNode());
622     }
623 
624     /**
625      * Checks if this configuration is empty. Empty means that there are no keys with any values, though there can be some
626      * (empty) nodes.
627      *
628      * @return a flag if this configuration is empty
629      */
630     @Override
631     protected boolean isEmptyInternal() {
632         return !nodeDefined(getModel().getNodeHandler().getRootNode());
633     }
634 
635     /**
636      * Checks if the specified node is defined.
637      *
638      * @param node the node to be checked
639      * @return a flag if this node is defined
640      */
641     protected boolean nodeDefined(final T node) {
642         final DefinedVisitor<T> visitor = new DefinedVisitor<>();
643         NodeTreeWalker.INSTANCE.walkBFS(node, visitor, getModel().getNodeHandler());
644         return visitor.isDefined();
645     }
646 
647     /**
648      * {@inheritDoc} This implementation uses the expression engine to generate a canonical key for the passed in node. For
649      * this purpose, the path to the root node has to be traversed. The cache is used to store and access keys for nodes
650      * encountered on the path.
651      */
652     @Override
653     public String nodeKey(final T node, final Map<T, String> cache, final NodeHandler<T> handler) {
654         final List<T> paths = new LinkedList<>();
655         T currentNode = node;
656         String key = cache.get(node);
657         while (key == null && currentNode != null) {
658             paths.add(0, currentNode);
659             currentNode = handler.getParent(currentNode);
660             key = cache.get(currentNode);
661         }
662 
663         for (final T n : paths) {
664             final String currentKey = getExpressionEngine().canonicalKey(n, key, handler);
665             cache.put(n, currentKey);
666             key = currentKey;
667         }
668 
669         return key;
670     }
671 
672     /**
673      * {@inheritDoc} This implementation delegates to the expression engine.
674      */
675     @Override
676     public NodeAddData<T> resolveAddKey(final T root, final String key, final NodeHandler<T> handler) {
677         return getExpressionEngine().prepareAdd(root, key, handler);
678     }
679 
680     /**
681      * {@inheritDoc} This implementation delegates to the expression engine.
682      */
683     @Override
684     public List<QueryResult<T>> resolveKey(final T root, final String key, final NodeHandler<T> handler) {
685         return getExpressionEngine().query(root, key, handler);
686     }
687 
688     /**
689      * {@inheritDoc} This implementation delegates to {@code resolveKey()} and then filters out attribute results.
690      */
691     @Override
692     public List<T> resolveNodeKey(final T root, final String key, final NodeHandler<T> handler) {
693         return resolveKey(root, key, handler).stream().filter(r -> !r.isAttributeResult()).map(QueryResult::getNode)
694             .collect(Collectors.toCollection(LinkedList::new));
695     }
696 
697     /**
698      * {@inheritDoc} This implementation executes a query for the given key and constructs a {@code NodeUpdateData} object
699      * based on the results. It determines which nodes need to be changed and whether new ones need to be added or existing
700      * ones need to be removed.
701      */
702     @Override
703     public NodeUpdateData<T> resolveUpdateKey(final T root, final String key, final Object newValue, final NodeHandler<T> handler) {
704         final Iterator<QueryResult<T>> itNodes = fetchNodeList(key).iterator();
705         final Iterator<?> itValues = getListDelimiterHandler().parse(newValue).iterator();
706         final Map<QueryResult<T>, Object> changedValues = new HashMap<>();
707         Collection<Object> additionalValues = null;
708         Collection<QueryResult<T>> removedItems = null;
709 
710         while (itNodes.hasNext() && itValues.hasNext()) {
711             changedValues.put(itNodes.next(), itValues.next());
712         }
713 
714         // Add additional nodes if necessary
715         if (itValues.hasNext()) {
716             additionalValues = new LinkedList<>();
717             itValues.forEachRemaining(additionalValues::add);
718         }
719 
720         // Remove remaining nodes
721         if (itNodes.hasNext()) {
722             removedItems = new LinkedList<>();
723             itNodes.forEachRemaining(removedItems::add);
724         }
725 
726         return new NodeUpdateData<>(changedValues, additionalValues, removedItems, key);
727     }
728 
729     /**
730      * Sets the expression engine to be used by this configuration. All property keys this configuration has to deal with
731      * will be interpreted by this engine.
732      *
733      * @param expressionEngine the new expression engine; can be <strong>null</strong>, then the default expression engine will be
734      *        used
735      * @since 1.3
736      */
737     @Override
738     public void setExpressionEngine(final ExpressionEngine expressionEngine) {
739         this.expressionEngine = expressionEngine;
740     }
741 
742     /**
743      * Sets the value of the specified property.
744      *
745      * @param key the key of the property to set
746      * @param value the new value of this property
747      */
748     @Override
749     protected void setPropertyInternal(final String key, final Object value) {
750         getModel().setProperty(key, value, this);
751     }
752 
753     /**
754      * {@inheritDoc} This implementation is slightly more efficient than the default implementation. It does not iterate
755      * over the key set, but directly queries its size after it has been constructed. Note that constructing the key set is
756      * still an O(n) operation.
757      */
758     @Override
759     protected int sizeInternal() {
760         return visitDefinedKeys().getKeyList().size();
761     }
762 
763     @Override
764     public String toString() {
765         return super.toString() + "(" + getRootElementNameInternal() + ")";
766     }
767 
768     /**
769      * Extracts the value from a query result.
770      *
771      * @param result the {@code QueryResult}
772      * @param handler the {@code NodeHandler}
773      * @return the value of this result (may be <strong>null</strong>)
774      */
775     private Object valueFromResult(final QueryResult<T> result, final NodeHandler<T> handler) {
776         return result.isAttributeResult() ? result.getAttributeValue(handler) : handler.getValue(result.getNode());
777     }
778 
779     /**
780      * Creates a {@code DefinedKeysVisitor} and visits all defined keys with it.
781      *
782      * @return the visitor after all keys have been visited
783      */
784     private DefinedKeysVisitor visitDefinedKeys() {
785         final DefinedKeysVisitor visitor = new DefinedKeysVisitor();
786         final NodeHandler<T> nodeHandler = getModel().getNodeHandler();
787         NodeTreeWalker.INSTANCE.walkDFS(nodeHandler.getRootNode(), visitor, nodeHandler);
788         return visitor;
789     }
790 }