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 * http://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 * <database> 066 * <tables> 067 * <table> 068 * <name>users</name> 069 * <fields> 070 * <field> 071 * <name>lid</name> 072 * <type>long</name> 073 * </field> 074 * <field> 075 * <name>usrName</name> 076 * <type>java.lang.String</type> 077 * </field> 078 * ... 079 * </fields> 080 * </table> 081 * <table> 082 * <name>documents</name> 083 * <fields> 084 * <field> 085 * <name>docid</name> 086 * <type>long</type> 087 * </field> 088 * ... 089 * </fields> 090 * </table> 091 * ... 092 * </tables> 093 * </database> 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. "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}