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 * @since 2.0 152 * @param <T> the type of the nodes managed by this hierarchical configuration 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 <b>null</b>, 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 303 beginWrite(false); 304 try { 305 fireEvent(ConfigurationEvent.ADD_NODES, key, nodes, true); 306 addNodesInternal(key, nodes); 307 fireEvent(ConfigurationEvent.ADD_NODES, key, nodes, false); 308 } finally { 309 endWrite(); 310 } 311 } 312 313 /** 314 * Actually adds a collection of new nodes to this configuration. This method is called by {@code addNodes()}. It can be 315 * overridden by subclasses that need to adapt this operation. 316 * 317 * @param key the key where the nodes are to be added; can be <b>null</b>, then they are added to the root node 318 * @param nodes a collection with the {@code Node} objects to be added 319 * @since 2.0 320 */ 321 protected void addNodesInternal(final String key, final Collection<? extends T> nodes) { 322 getModel().addNodes(key, nodes, this); 323 } 324 325 /** 326 * {@inheritDoc} This method is not called in the normal way (via {@code addProperty()} for hierarchical configurations 327 * because all values to be added for the property have to be passed to the model in a single step. However, to allow 328 * derived classes to add an arbitrary value as an object, a special implementation is provided here. The passed in 329 * object is not parsed as a list, but passed directly as only value to the model. 330 */ 331 @Override 332 protected void addPropertyDirect(final String key, final Object value) { 333 addPropertyToModel(key, Collections.singleton(value)); 334 } 335 336 /** 337 * Adds the property with the specified key. This task will be delegated to the associated {@code ExpressionEngine}, so 338 * the passed in key must match the requirements of this implementation. 339 * 340 * @param key the key of the new property 341 * @param obj the value of the new property 342 */ 343 @Override 344 protected void addPropertyInternal(final String key, final Object obj) { 345 addPropertyToModel(key, getListDelimiterHandler().parse(obj)); 346 } 347 348 /** 349 * Helper method for executing an add property operation on the model. 350 * 351 * @param key the key of the new property 352 * @param values the values to be added for this property 353 */ 354 private void addPropertyToModel(final String key, final Iterable<?> values) { 355 getModel().addProperty(key, values, this); 356 } 357 358 /** 359 * Clears this configuration. This is a more efficient implementation than the one inherited from the base class. It 360 * delegates to the node model. 361 */ 362 @Override 363 protected void clearInternal() { 364 getModel().clear(this); 365 } 366 367 /** 368 * Removes the property with the given key. Properties with names that start with the given key (i.e. properties below 369 * the specified key in the hierarchy) won't be affected. This implementation delegates to the node+ model. 370 * 371 * @param key the key of the property to be removed 372 */ 373 @Override 374 protected void clearPropertyDirect(final String key) { 375 getModel().clearProperty(key, this); 376 } 377 378 /** 379 * Removes all values of the property with the given name and of keys that start with this name. So if there is a 380 * property with the key "foo" and a property with the key "foo.bar", a call of 381 * {@code clearTree("foo")} would remove both properties. 382 * 383 * @param key the key of the property to be removed 384 */ 385 @Override 386 public final void clearTree(final String key) { 387 beginWrite(false); 388 try { 389 fireEvent(ConfigurationEvent.CLEAR_TREE, key, null, true); 390 fireEvent(ConfigurationEvent.CLEAR_TREE, key, clearTreeInternal(key), false); 391 } finally { 392 endWrite(); 393 } 394 } 395 396 /** 397 * Actually clears the tree of elements referenced by the given key. This method is called by {@code clearTree()}. 398 * Subclasses that need to adapt this operation can override this method. This base implementation delegates to the node 399 * model. 400 * 401 * @param key the key of the property to be removed 402 * @return an object with information about the nodes that have been removed (this is needed for firing a meaningful 403 * event of type CLEAR_TREE) 404 * @since 2.0 405 */ 406 protected Object clearTreeInternal(final String key) { 407 return getModel().clearTree(key, this); 408 } 409 410 /** 411 * Creates a copy of this object. This new configuration object will contain copies of all nodes in the same structure. 412 * Registered event listeners won't be cloned; so they are not registered at the returned copy. 413 * 414 * @return the copy 415 * @since 1.2 416 */ 417 @Override 418 public Object clone() { 419 beginRead(false); 420 try { 421 @SuppressWarnings("unchecked") // clone returns the same type 422 final AbstractHierarchicalConfiguration<T> copy = (AbstractHierarchicalConfiguration<T>) super.clone(); 423 copy.setSynchronizer(NoOpSynchronizer.INSTANCE); 424 copy.cloneInterpolator(this); 425 copy.setSynchronizer(ConfigurationUtils.cloneSynchronizer(getSynchronizer())); 426 copy.nodeModel = cloneNodeModel(); 427 428 return copy; 429 } catch (final CloneNotSupportedException cex) { 430 // should not happen 431 throw new ConfigurationRuntimeException(cex); 432 } finally { 433 endRead(); 434 } 435 } 436 437 /** 438 * Creates a clone of the node model. This method is called by {@code clone()}. 439 * 440 * @return the clone of the {@code NodeModel} 441 * @since 2.0 442 */ 443 protected abstract NodeModel<T> cloneNodeModel(); 444 445 /** 446 * Checks if the specified key is contained in this configuration. Note that for this configuration the term 447 * "contained" means that the key has an associated value. If there is a node for this key that has no value 448 * but children (either defined or undefined), this method will still return <b>false </b>. 449 * 450 * @param key the key to be checked 451 * @return a flag if this key is contained in this configuration 452 */ 453 @Override 454 protected boolean containsKeyInternal(final String key) { 455 return getPropertyInternal(key) != null; 456 } 457 458 /** 459 * Tests whether this configuration contains one or more matches to this value. This operation stops at first 460 * match but may be more expensive than the containsKey method. 461 * @since 2.11.0 462 */ 463 @Override 464 protected boolean containsValueInternal(final Object value) { 465 return contains(getKeys(), value); 466 } 467 468 /** 469 * Helper method for resolving the specified key. 470 * 471 * @param key the key 472 * @return a list with all results selected by this key 473 */ 474 protected List<QueryResult<T>> fetchNodeList(final String key) { 475 final NodeHandler<T> nodeHandler = getModel().getNodeHandler(); 476 return resolveKey(nodeHandler.getRootNode(), key, nodeHandler); 477 } 478 479 /** 480 * Gets the expression engine used by this configuration. This method will never return <b>null</b>; if no specific 481 * expression engine was set, the default expression engine will be returned. 482 * 483 * @return the current expression engine 484 * @since 1.3 485 */ 486 @Override 487 public ExpressionEngine getExpressionEngine() { 488 return expressionEngine != null ? expressionEngine : DefaultExpressionEngine.INSTANCE; 489 } 490 491 /** 492 * Gets an iterator with all keys defined in this configuration. Note that the keys returned by this method will not 493 * contain any indices. This means that some structure will be lost. 494 * 495 * @return an iterator with the defined keys in this configuration 496 */ 497 @Override 498 protected Iterator<String> getKeysInternal() { 499 return visitDefinedKeys().getKeyList().iterator(); 500 } 501 502 /** 503 * Gets an iterator with all keys defined in this configuration that start with the given prefix. The returned keys 504 * will not contain any indices. This implementation tries to locate a node whose key is the same as the passed in 505 * prefix. Then the subtree of this node is traversed, and the keys of all nodes encountered (including attributes) are 506 * added to the result set. 507 * 508 * @param prefix the prefix of the keys to start with 509 * @return an iterator with the found keys 510 */ 511 @Override 512 protected Iterator<String> getKeysInternal(final String prefix) { 513 final DefinedKeysVisitor visitor = new DefinedKeysVisitor(prefix); 514 if (containsKey(prefix)) { 515 // explicitly add the prefix 516 visitor.getKeyList().add(prefix); 517 } 518 519 final List<QueryResult<T>> results = fetchNodeList(prefix); 520 final NodeHandler<T> handler = getModel().getNodeHandler(); 521 522 results.forEach(result -> { 523 if (!result.isAttributeResult()) { 524 handler.getChildren(result.getNode()).forEach(c -> NodeTreeWalker.INSTANCE.walkDFS(c, visitor, handler)); 525 visitor.handleAttributeKeys(prefix, result.getNode(), handler); 526 } 527 }); 528 529 return visitor.getKeyList().iterator(); 530 } 531 532 /** 533 * Gets the maximum defined index for the given key. This is useful if there are multiple values for this key. They 534 * can then be addressed separately by specifying indices from 0 to the return value of this method. If the passed in 535 * key is not contained in this configuration, result is -1. 536 * 537 * @param key the key to be checked 538 * @return the maximum defined index for this key 539 */ 540 @Override 541 public final int getMaxIndex(final String key) { 542 beginRead(false); 543 try { 544 return getMaxIndexInternal(key); 545 } finally { 546 endRead(); 547 } 548 } 549 550 /** 551 * Actually retrieves the maximum defined index for the given key. This method is called by {@code getMaxIndex()}. 552 * Subclasses that need to adapt this operation have to override this method. 553 * 554 * @param key the key to be checked 555 * @return the maximum defined index for this key 556 * @since 2.0 557 */ 558 protected int getMaxIndexInternal(final String key) { 559 return fetchNodeList(key).size() - 1; 560 } 561 562 /** 563 * Gets the {@code NodeModel} used by this configuration. This method is intended for internal use only. Access to 564 * the model is granted without any synchronization. This is in contrast to the "official" 565 * {@code getNodeModel()} method which is guarded by the configuration's {@code Synchronizer}. 566 * 567 * @return the node model 568 */ 569 protected NodeModel<T> getModel() { 570 return nodeModel; 571 } 572 573 /** 574 * {@inheritDoc} This implementation returns the configuration's {@code NodeModel}. It is guarded by the current 575 * {@code Synchronizer}. 576 */ 577 @Override 578 public NodeModel<T> getNodeModel() { 579 beginRead(false); 580 try { 581 return getModel(); 582 } finally { 583 endRead(); 584 } 585 } 586 587 /** 588 * Fetches the specified property. This task is delegated to the associated expression engine. 589 * 590 * @param key the key to be looked up 591 * @return the found value 592 */ 593 @Override 594 protected Object getPropertyInternal(final String key) { 595 final List<QueryResult<T>> results = fetchNodeList(key); 596 597 if (results.isEmpty()) { 598 return null; 599 } 600 final NodeHandler<T> handler = getModel().getNodeHandler(); 601 final List<Object> list = results.stream().map(r -> valueFromResult(r, handler)).filter(Objects::nonNull).collect(Collectors.toList()); 602 603 if (list.size() < 1) { 604 return null; 605 } 606 return list.size() == 1 ? list.get(0) : list; 607 } 608 609 /** 610 * {@inheritDoc} This implementation handles synchronization and delegates to {@code getRootElementNameInternal()}. 611 */ 612 @Override 613 public final String getRootElementName() { 614 beginRead(false); 615 try { 616 return getRootElementNameInternal(); 617 } finally { 618 endRead(); 619 } 620 } 621 622 /** 623 * Actually obtains the name of the root element. This method is called by {@code getRootElementName()}. It just returns 624 * the name of the root node. Subclasses that treat the root element name differently can override this method. 625 * 626 * @return the name of this configuration's root element 627 */ 628 protected String getRootElementNameInternal() { 629 final NodeHandler<T> nodeHandler = getModel().getNodeHandler(); 630 return nodeHandler.nodeName(nodeHandler.getRootNode()); 631 } 632 633 /** 634 * Checks if this configuration is empty. Empty means that there are no keys with any values, though there can be some 635 * (empty) nodes. 636 * 637 * @return a flag if this configuration is empty 638 */ 639 @Override 640 protected boolean isEmptyInternal() { 641 return !nodeDefined(getModel().getNodeHandler().getRootNode()); 642 } 643 644 /** 645 * Checks if the specified node is defined. 646 * 647 * @param node the node to be checked 648 * @return a flag if this node is defined 649 */ 650 protected boolean nodeDefined(final T node) { 651 final DefinedVisitor<T> visitor = new DefinedVisitor<>(); 652 NodeTreeWalker.INSTANCE.walkBFS(node, visitor, getModel().getNodeHandler()); 653 return visitor.isDefined(); 654 } 655 656 /** 657 * {@inheritDoc} This implementation uses the expression engine to generate a canonical key for the passed in node. For 658 * this purpose, the path to the root node has to be traversed. The cache is used to store and access keys for nodes 659 * encountered on the path. 660 */ 661 @Override 662 public String nodeKey(final T node, final Map<T, String> cache, final NodeHandler<T> handler) { 663 final List<T> paths = new LinkedList<>(); 664 T currentNode = node; 665 String key = cache.get(node); 666 while (key == null && currentNode != null) { 667 paths.add(0, currentNode); 668 currentNode = handler.getParent(currentNode); 669 key = cache.get(currentNode); 670 } 671 672 for (final T n : paths) { 673 final String currentKey = getExpressionEngine().canonicalKey(n, key, handler); 674 cache.put(n, currentKey); 675 key = currentKey; 676 } 677 678 return key; 679 } 680 681 /** 682 * {@inheritDoc} This implementation delegates to the expression engine. 683 */ 684 @Override 685 public NodeAddData<T> resolveAddKey(final T root, final String key, final NodeHandler<T> handler) { 686 return getExpressionEngine().prepareAdd(root, key, handler); 687 } 688 689 /** 690 * {@inheritDoc} This implementation delegates to the expression engine. 691 */ 692 @Override 693 public List<QueryResult<T>> resolveKey(final T root, final String key, final NodeHandler<T> handler) { 694 return getExpressionEngine().query(root, key, handler); 695 } 696 697 /** 698 * {@inheritDoc} This implementation delegates to {@code resolveKey()} and then filters out attribute results. 699 */ 700 @Override 701 public List<T> resolveNodeKey(final T root, final String key, final NodeHandler<T> handler) { 702 return resolveKey(root, key, handler).stream().filter(r -> !r.isAttributeResult()).map(QueryResult::getNode) 703 .collect(Collectors.toCollection(LinkedList::new)); 704 } 705 706 /** 707 * {@inheritDoc} This implementation executes a query for the given key and constructs a {@code NodeUpdateData} object 708 * based on the results. It determines which nodes need to be changed and whether new ones need to be added or existing 709 * ones need to be removed. 710 */ 711 @Override 712 public NodeUpdateData<T> resolveUpdateKey(final T root, final String key, final Object newValue, final NodeHandler<T> handler) { 713 final Iterator<QueryResult<T>> itNodes = fetchNodeList(key).iterator(); 714 final Iterator<?> itValues = getListDelimiterHandler().parse(newValue).iterator(); 715 final Map<QueryResult<T>, Object> changedValues = new HashMap<>(); 716 Collection<Object> additionalValues = null; 717 Collection<QueryResult<T>> removedItems = null; 718 719 while (itNodes.hasNext() && itValues.hasNext()) { 720 changedValues.put(itNodes.next(), itValues.next()); 721 } 722 723 // Add additional nodes if necessary 724 if (itValues.hasNext()) { 725 additionalValues = new LinkedList<>(); 726 itValues.forEachRemaining(additionalValues::add); 727 } 728 729 // Remove remaining nodes 730 if (itNodes.hasNext()) { 731 removedItems = new LinkedList<>(); 732 itNodes.forEachRemaining(removedItems::add); 733 } 734 735 return new NodeUpdateData<>(changedValues, additionalValues, removedItems, key); 736 } 737 738 /** 739 * Sets the expression engine to be used by this configuration. All property keys this configuration has to deal with 740 * will be interpreted by this engine. 741 * 742 * @param expressionEngine the new expression engine; can be <b>null</b>, then the default expression engine will be 743 * used 744 * @since 1.3 745 */ 746 @Override 747 public void setExpressionEngine(final ExpressionEngine expressionEngine) { 748 this.expressionEngine = expressionEngine; 749 } 750 751 /** 752 * Sets the value of the specified property. 753 * 754 * @param key the key of the property to set 755 * @param value the new value of this property 756 */ 757 @Override 758 protected void setPropertyInternal(final String key, final Object value) { 759 getModel().setProperty(key, value, this); 760 } 761 762 /** 763 * {@inheritDoc} This implementation is slightly more efficient than the default implementation. It does not iterate 764 * over the key set, but directly queries its size after it has been constructed. Note that constructing the key set is 765 * still an O(n) operation. 766 */ 767 @Override 768 protected int sizeInternal() { 769 return visitDefinedKeys().getKeyList().size(); 770 } 771 772 @Override 773 public String toString() { 774 return super.toString() + "(" + getRootElementNameInternal() + ")"; 775 } 776 777 /** 778 * Extracts the value from a query result. 779 * 780 * @param result the {@code QueryResult} 781 * @param handler the {@code NodeHandler} 782 * @return the value of this result (may be <b>null</b>) 783 */ 784 private Object valueFromResult(final QueryResult<T> result, final NodeHandler<T> handler) { 785 return result.isAttributeResult() ? result.getAttributeValue(handler) : handler.getValue(result.getNode()); 786 } 787 788 /** 789 * Creates a {@code DefinedKeysVisitor} and visits all defined keys with it. 790 * 791 * @return the visitor after all keys have been visited 792 */ 793 private DefinedKeysVisitor visitDefinedKeys() { 794 final DefinedKeysVisitor visitor = new DefinedKeysVisitor(); 795 final NodeHandler<T> nodeHandler = getModel().getNodeHandler(); 796 NodeTreeWalker.INSTANCE.walkDFS(nodeHandler.getRootNode(), visitor, nodeHandler); 797 return visitor; 798 } 799}