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 * <database>
66 * <tables>
67 * <table>
68 * <name>users</name>
69 * <fields>
70 * <field>
71 * <name>lid</name>
72 * <type>long</name>
73 * </field>
74 * <field>
75 * <name>usrName</name>
76 * <type>java.lang.String</type>
77 * </field>
78 * ...
79 * </fields>
80 * </table>
81 * <table>
82 * <name>documents</name>
83 * <fields>
84 * <field>
85 * <name>docid</name>
86 * <type>long</type>
87 * </field>
88 * ...
89 * </fields>
90 * </table>
91 * ...
92 * </tables>
93 * </database>
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. "Defined" 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 "foo" and a property with the key "foo.bar", 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 * "contained" 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 "official"
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 }