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