001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.configuration2; 019 020import java.util.Collection; 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.Iterator; 024import java.util.LinkedHashSet; 025import java.util.LinkedList; 026import java.util.List; 027import java.util.Map; 028import java.util.Objects; 029import java.util.Set; 030import java.util.Stack; 031import java.util.stream.Collectors; 032 033import org.apache.commons.configuration2.event.ConfigurationEvent; 034import org.apache.commons.configuration2.ex.ConfigurationRuntimeException; 035import org.apache.commons.configuration2.sync.NoOpSynchronizer; 036import org.apache.commons.configuration2.tree.ConfigurationNodeVisitorAdapter; 037import org.apache.commons.configuration2.tree.DefaultExpressionEngine; 038import org.apache.commons.configuration2.tree.ExpressionEngine; 039import org.apache.commons.configuration2.tree.NodeAddData; 040import org.apache.commons.configuration2.tree.NodeHandler; 041import org.apache.commons.configuration2.tree.NodeKeyResolver; 042import org.apache.commons.configuration2.tree.NodeModel; 043import org.apache.commons.configuration2.tree.NodeTreeWalker; 044import org.apache.commons.configuration2.tree.NodeUpdateData; 045import org.apache.commons.configuration2.tree.QueryResult; 046 047/** 048 * <p> 049 * A specialized configuration class that extends its base class by the ability of keeping more structure in the stored 050 * properties. 051 * </p> 052 * <p> 053 * There are some sources of configuration data that cannot be stored very well in a {@code BaseConfiguration} object 054 * because then their structure is lost. This is for instance true for XML documents. This class can deal with such 055 * structured configuration sources by storing the properties in a tree-like organization. The exact storage structure 056 * of the underlying data does not matter for the configuration instance; it uses a {@link NodeModel} object for 057 * accessing it. 058 * </p> 059 * <p> 060 * The hierarchical organization allows for a more sophisticated access to single properties. As an example consider the 061 * following XML document: 062 * </p> 063 * 064 * <pre> 065 * <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 * 453 * @since 2.11.0 454 */ 455 @Override 456 protected boolean containsValueInternal(final Object value) { 457 return contains(getKeys(), value); 458 } 459 460 /** 461 * Helper method for resolving the specified key. 462 * 463 * @param key the key 464 * @return a list with all results selected by this key 465 */ 466 protected List<QueryResult<T>> fetchNodeList(final String key) { 467 final NodeHandler<T> nodeHandler = getModel().getNodeHandler(); 468 return resolveKey(nodeHandler.getRootNode(), key, nodeHandler); 469 } 470 471 /** 472 * Gets the expression engine used by this configuration. This method will never return <strong>null</strong>; if no specific 473 * expression engine was set, the default expression engine will be returned. 474 * 475 * @return the current expression engine 476 * @since 1.3 477 */ 478 @Override 479 public ExpressionEngine getExpressionEngine() { 480 return expressionEngine != null ? expressionEngine : DefaultExpressionEngine.INSTANCE; 481 } 482 483 /** 484 * Gets an iterator with all keys defined in this configuration. Note that the keys returned by this method will not 485 * contain any indices. This means that some structure will be lost. 486 * 487 * @return an iterator with the defined keys in this configuration 488 */ 489 @Override 490 protected Iterator<String> getKeysInternal() { 491 return visitDefinedKeys().getKeyList().iterator(); 492 } 493 494 /** 495 * Gets an iterator with all keys defined in this configuration that start with the given prefix. The returned keys 496 * will not contain any indices. This implementation tries to locate a node whose key is the same as the passed in 497 * prefix. Then the subtree of this node is traversed, and the keys of all nodes encountered (including attributes) are 498 * added to the result set. 499 * 500 * @param prefix the prefix of the keys to start with 501 * @return an iterator with the found keys 502 */ 503 @Override 504 protected Iterator<String> getKeysInternal(final String prefix) { 505 return getKeysInternal(prefix, DELIMITER); 506 } 507 508 /** 509 * Gets an iterator with all keys defined in this configuration that start with the given prefix. The returned keys 510 * will not contain any indices. This implementation tries to locate a node whose key is the same as the passed in 511 * prefix. Then the subtree of this node is traversed, and the keys of all nodes encountered (including attributes) are 512 * added to the result set. 513 * 514 * @param prefix the prefix of the keys to start with 515 * @param delimiter the prefix delimiter (unused) 516 * @return an iterator with the found keys 517 * @since 2.12.0 518 */ 519 @Override 520 protected Iterator<String> getKeysInternal(final String prefix, final String delimiter) { 521 final DefinedKeysVisitor visitor = new DefinedKeysVisitor(prefix); 522 if (containsKey(prefix)) { 523 // explicitly add the prefix 524 visitor.getKeyList().add(prefix); 525 } 526 527 final List<QueryResult<T>> results = fetchNodeList(prefix); 528 final NodeHandler<T> handler = getModel().getNodeHandler(); 529 530 results.forEach(result -> { 531 if (!result.isAttributeResult()) { 532 handler.getChildren(result.getNode()).forEach(c -> NodeTreeWalker.INSTANCE.walkDFS(c, visitor, handler)); 533 visitor.handleAttributeKeys(prefix, result.getNode(), handler); 534 } 535 }); 536 537 return visitor.getKeyList().iterator(); 538 } 539 540 /** 541 * Gets the maximum defined index for the given key. This is useful if there are multiple values for this key. They 542 * can then be addressed separately by specifying indices from 0 to the return value of this method. If the passed in 543 * key is not contained in this configuration, result is -1. 544 * 545 * @param key the key to be checked 546 * @return the maximum defined index for this key 547 */ 548 @Override 549 public final int getMaxIndex(final String key) { 550 return syncRead(() -> getMaxIndexInternal(key), false); 551 } 552 553 /** 554 * Actually retrieves the maximum defined index for the given key. This method is called by {@code getMaxIndex()}. 555 * Subclasses that need to adapt this operation have to override this method. 556 * 557 * @param key the key to be checked 558 * @return the maximum defined index for this key 559 * @since 2.0 560 */ 561 protected int getMaxIndexInternal(final String key) { 562 return fetchNodeList(key).size() - 1; 563 } 564 565 /** 566 * Gets the {@code NodeModel} used by this configuration. This method is intended for internal use only. Access to 567 * the model is granted without any synchronization. This is in contrast to the "official" 568 * {@code getNodeModel()} method which is guarded by the configuration's {@code Synchronizer}. 569 * 570 * @return the node model 571 */ 572 protected NodeModel<T> getModel() { 573 return nodeModel; 574 } 575 576 /** 577 * {@inheritDoc} This implementation returns the configuration's {@code NodeModel}. It is guarded by the current 578 * {@code Synchronizer}. 579 */ 580 @Override 581 public NodeModel<T> getNodeModel() { 582 return syncRead(this::getModel, false); 583 } 584 585 /** 586 * Fetches the specified property. This task is delegated to the associated expression engine. 587 * 588 * @param key the key to be looked up 589 * @return the found value 590 */ 591 @Override 592 protected Object getPropertyInternal(final String key) { 593 final List<QueryResult<T>> results = fetchNodeList(key); 594 595 if (results.isEmpty()) { 596 return null; 597 } 598 final NodeHandler<T> handler = getModel().getNodeHandler(); 599 final List<Object> list = results.stream().map(r -> valueFromResult(r, handler)).filter(Objects::nonNull).collect(Collectors.toList()); 600 601 if (list.size() < 1) { 602 return null; 603 } 604 return list.size() == 1 ? list.get(0) : list; 605 } 606 607 /** 608 * {@inheritDoc} This implementation handles synchronization and delegates to {@code getRootElementNameInternal()}. 609 */ 610 @Override 611 public final String getRootElementName() { 612 return syncRead(this::getRootElementNameInternal, false); 613 } 614 615 /** 616 * Actually obtains the name of the root element. This method is called by {@code getRootElementName()}. It just returns 617 * the name of the root node. Subclasses that treat the root element name differently can override this method. 618 * 619 * @return the name of this configuration's root element 620 */ 621 protected String getRootElementNameInternal() { 622 final NodeHandler<T> nodeHandler = getModel().getNodeHandler(); 623 return nodeHandler.nodeName(nodeHandler.getRootNode()); 624 } 625 626 /** 627 * Checks if this configuration is empty. Empty means that there are no keys with any values, though there can be some 628 * (empty) nodes. 629 * 630 * @return a flag if this configuration is empty 631 */ 632 @Override 633 protected boolean isEmptyInternal() { 634 return !nodeDefined(getModel().getNodeHandler().getRootNode()); 635 } 636 637 /** 638 * Checks if the specified node is defined. 639 * 640 * @param node the node to be checked 641 * @return a flag if this node is defined 642 */ 643 protected boolean nodeDefined(final T node) { 644 final DefinedVisitor<T> visitor = new DefinedVisitor<>(); 645 NodeTreeWalker.INSTANCE.walkBFS(node, visitor, getModel().getNodeHandler()); 646 return visitor.isDefined(); 647 } 648 649 /** 650 * {@inheritDoc} This implementation uses the expression engine to generate a canonical key for the passed in node. For 651 * this purpose, the path to the root node has to be traversed. The cache is used to store and access keys for nodes 652 * encountered on the path. 653 */ 654 @Override 655 public String nodeKey(final T node, final Map<T, String> cache, final NodeHandler<T> handler) { 656 final List<T> paths = new LinkedList<>(); 657 T currentNode = node; 658 String key = cache.get(node); 659 while (key == null && currentNode != null) { 660 paths.add(0, currentNode); 661 currentNode = handler.getParent(currentNode); 662 key = cache.get(currentNode); 663 } 664 665 for (final T n : paths) { 666 final String currentKey = getExpressionEngine().canonicalKey(n, key, handler); 667 cache.put(n, currentKey); 668 key = currentKey; 669 } 670 671 return key; 672 } 673 674 /** 675 * {@inheritDoc} This implementation delegates to the expression engine. 676 */ 677 @Override 678 public NodeAddData<T> resolveAddKey(final T root, final String key, final NodeHandler<T> handler) { 679 return getExpressionEngine().prepareAdd(root, key, handler); 680 } 681 682 /** 683 * {@inheritDoc} This implementation delegates to the expression engine. 684 */ 685 @Override 686 public List<QueryResult<T>> resolveKey(final T root, final String key, final NodeHandler<T> handler) { 687 return getExpressionEngine().query(root, key, handler); 688 } 689 690 /** 691 * {@inheritDoc} This implementation delegates to {@code resolveKey()} and then filters out attribute results. 692 */ 693 @Override 694 public List<T> resolveNodeKey(final T root, final String key, final NodeHandler<T> handler) { 695 return resolveKey(root, key, handler).stream().filter(r -> !r.isAttributeResult()).map(QueryResult::getNode) 696 .collect(Collectors.toCollection(LinkedList::new)); 697 } 698 699 /** 700 * {@inheritDoc} This implementation executes a query for the given key and constructs a {@code NodeUpdateData} object 701 * based on the results. It determines which nodes need to be changed and whether new ones need to be added or existing 702 * ones need to be removed. 703 */ 704 @Override 705 public NodeUpdateData<T> resolveUpdateKey(final T root, final String key, final Object newValue, final NodeHandler<T> handler) { 706 final Iterator<QueryResult<T>> itNodes = fetchNodeList(key).iterator(); 707 final Iterator<?> itValues = getListDelimiterHandler().parse(newValue).iterator(); 708 final Map<QueryResult<T>, Object> changedValues = new HashMap<>(); 709 Collection<Object> additionalValues = null; 710 Collection<QueryResult<T>> removedItems = null; 711 712 while (itNodes.hasNext() && itValues.hasNext()) { 713 changedValues.put(itNodes.next(), itValues.next()); 714 } 715 716 // Add additional nodes if necessary 717 if (itValues.hasNext()) { 718 additionalValues = new LinkedList<>(); 719 itValues.forEachRemaining(additionalValues::add); 720 } 721 722 // Remove remaining nodes 723 if (itNodes.hasNext()) { 724 removedItems = new LinkedList<>(); 725 itNodes.forEachRemaining(removedItems::add); 726 } 727 728 return new NodeUpdateData<>(changedValues, additionalValues, removedItems, key); 729 } 730 731 /** 732 * Sets the expression engine to be used by this configuration. All property keys this configuration has to deal with 733 * will be interpreted by this engine. 734 * 735 * @param expressionEngine the new expression engine; can be <strong>null</strong>, then the default expression engine will be 736 * used 737 * @since 1.3 738 */ 739 @Override 740 public void setExpressionEngine(final ExpressionEngine expressionEngine) { 741 this.expressionEngine = expressionEngine; 742 } 743 744 /** 745 * Sets the value of the specified property. 746 * 747 * @param key the key of the property to set 748 * @param value the new value of this property 749 */ 750 @Override 751 protected void setPropertyInternal(final String key, final Object value) { 752 getModel().setProperty(key, value, this); 753 } 754 755 /** 756 * {@inheritDoc} This implementation is slightly more efficient than the default implementation. It does not iterate 757 * over the key set, but directly queries its size after it has been constructed. Note that constructing the key set is 758 * still an O(n) operation. 759 */ 760 @Override 761 protected int sizeInternal() { 762 return visitDefinedKeys().getKeyList().size(); 763 } 764 765 @Override 766 public String toString() { 767 return super.toString() + "(" + getRootElementNameInternal() + ")"; 768 } 769 770 /** 771 * Extracts the value from a query result. 772 * 773 * @param result the {@code QueryResult} 774 * @param handler the {@code NodeHandler} 775 * @return the value of this result (may be <strong>null</strong>) 776 */ 777 private Object valueFromResult(final QueryResult<T> result, final NodeHandler<T> handler) { 778 return result.isAttributeResult() ? result.getAttributeValue(handler) : handler.getValue(result.getNode()); 779 } 780 781 /** 782 * Creates a {@code DefinedKeysVisitor} and visits all defined keys with it. 783 * 784 * @return the visitor after all keys have been visited 785 */ 786 private DefinedKeysVisitor visitDefinedKeys() { 787 final DefinedKeysVisitor visitor = new DefinedKeysVisitor(); 788 final NodeHandler<T> nodeHandler = getModel().getNodeHandler(); 789 NodeTreeWalker.INSTANCE.walkDFS(nodeHandler.getRootNode(), visitor, nodeHandler); 790 return visitor; 791 } 792}