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.ArrayList; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.HashMap; 024import java.util.Iterator; 025import java.util.LinkedHashSet; 026import java.util.LinkedList; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030import java.util.Stack; 031 032import org.apache.commons.configuration2.event.ConfigurationEvent; 033import org.apache.commons.configuration2.ex.ConfigurationRuntimeException; 034import org.apache.commons.configuration2.sync.NoOpSynchronizer; 035import org.apache.commons.configuration2.tree.ConfigurationNodeVisitorAdapter; 036import org.apache.commons.configuration2.tree.DefaultExpressionEngine; 037import org.apache.commons.configuration2.tree.ExpressionEngine; 038import org.apache.commons.configuration2.tree.NodeAddData; 039import org.apache.commons.configuration2.tree.NodeHandler; 040import org.apache.commons.configuration2.tree.NodeKeyResolver; 041import org.apache.commons.configuration2.tree.NodeModel; 042import org.apache.commons.configuration2.tree.NodeTreeWalker; 043import org.apache.commons.configuration2.tree.NodeUpdateData; 044import org.apache.commons.configuration2.tree.QueryResult; 045 046/** 047 * <p> 048 * A specialized configuration class that extends its base class by the ability of keeping more structure in the stored 049 * properties. 050 * </p> 051 * <p> 052 * There are some sources of configuration data that cannot be stored very well in a {@code BaseConfiguration} object 053 * because then their structure is lost. This is for instance true for XML documents. This class can deal with such 054 * structured configuration sources by storing the properties in a tree-like organization. The exact storage structure 055 * of the underlying data does not matter for the configuration instance; it uses a {@link NodeModel} object for 056 * accessing it. 057 * </p> 058 * <p> 059 * The hierarchical organization allows for a more sophisticated access to single properties. As an example consider the 060 * following XML document: 061 * </p> 062 * 063 * <pre> 064 * <database> 065 * <tables> 066 * <table> 067 * <name>users</name> 068 * <fields> 069 * <field> 070 * <name>lid</name> 071 * <type>long</name> 072 * </field> 073 * <field> 074 * <name>usrName</name> 075 * <type>java.lang.String</type> 076 * </field> 077 * ... 078 * </fields> 079 * </table> 080 * <table> 081 * <name>documents</name> 082 * <fields> 083 * <field> 084 * <name>docid</name> 085 * <type>long</type> 086 * </field> 087 * ... 088 * </fields> 089 * </table> 090 * ... 091 * </tables> 092 * </database> 093 * </pre> 094 * 095 * <p> 096 * If this document is parsed and stored in a hierarchical configuration object (which can be done by one of the sub 097 * classes), there are enhanced possibilities of accessing properties. Per default, the keys for querying information 098 * can contain indices that select a specific element if there are multiple hits. 099 * </p> 100 * <p> 101 * For instance the key {@code tables.table(0).name} can be used to find out the name of the first table. In opposite 102 * {@code tables.table.name} would return a collection with the names of all available tables. Similarly the key 103 * {@code tables.table(1).fields.field.name} returns a collection with the names of all fields of the second table. If 104 * another index is added after the {@code field} element, a single field can be accessed: 105 * {@code tables.table(1).fields.field(0).name}. 106 * </p> 107 * <p> 108 * There is a {@code getMaxIndex()} method that returns the maximum allowed index that can be added to a given property 109 * key. This method can be used to iterate over all values defined for a certain property. 110 * </p> 111 * <p> 112 * Since the 1.3 release of <em>Commons Configuration</em> hierarchical configurations support an <em>expression 113 * engine</em>. This expression engine is responsible for evaluating the passed in configuration keys and map them to 114 * the stored properties. The examples above are valid for the default expression engine, which is used when a new 115 * {@code AbstractHierarchicalConfiguration} instance is created. With the {@code setExpressionEngine()} method a 116 * different expression engine can be set. For instance with 117 * {@link org.apache.commons.configuration2.tree.xpath.XPathExpressionEngine} there is an expression engine available 118 * that supports configuration keys in XPATH syntax. 119 * </p> 120 * <p> 121 * In addition to the events common for all configuration classes, hierarchical configurations support some more events 122 * that correspond to some specific methods and features. For those events specific event type constants in 123 * {@code ConfigurationEvent} exist: 124 * </p> 125 * <dl> 126 * <dt><em>ADD_NODES</em></dt> 127 * <dd>The {@code addNodes()} method was called; the event object contains the key, to which the nodes were added, and a 128 * collection with the new nodes as value.</dd> 129 * <dt><em>CLEAR_TREE</em></dt> 130 * <dd>The {@code clearTree()} method was called; the event object stores the key of the removed sub tree.</dd> 131 * <dt><em>SUBNODE_CHANGED</em></dt> 132 * <dd>A {@code SubnodeConfiguration} that was created from this configuration has been changed. The value property of 133 * the event object contains the original event object as it was sent by the subnode configuration.</dd> 134 * </dl> 135 * <p> 136 * Whether an {@code AbstractHierarchicalConfiguration} object is thread-safe or not depends on the underlying 137 * {@code NodeModel} and the {@link org.apache.commons.configuration2.sync.Synchronizer Synchronizer} it is associated 138 * with. Some {@code NodeModel} implementations are inherently thread-safe; they do not require a special 139 * {@code Synchronizer}. (Per default, a dummy {@code Synchronizer} is used which is not thread-safe!) The methods for 140 * querying or updating configuration data invoke this {@code Synchronizer} accordingly. When accessing the 141 * configuration's root node directly, the client application is responsible for proper synchronization. This is 142 * achieved by calling the methods {@link #lock(org.apache.commons.configuration2.sync.LockMode) lock()}, and 143 * {@link #unlock(org.apache.commons.configuration2.sync.LockMode) unlock()} with a proper 144 * {@link org.apache.commons.configuration2.sync.LockMode LockMode} argument. In any case, it is recommended to not 145 * access the root node directly, but to use corresponding methods for querying or updating configuration data instead. 146 * Direct manipulations of a configuration's node structure circumvent many internal mechanisms and thus can cause 147 * undesired effects. For concrete subclasses dealing with specific node structures, this situation may be different. 148 * </p> 149 * 150 * @since 2.0 151 * @param <T> the type of the nodes managed by this hierarchical configuration 152 */ 153public abstract class AbstractHierarchicalConfiguration<T> extends AbstractConfiguration 154 implements Cloneable, NodeKeyResolver<T>, HierarchicalConfiguration<T> { 155 /** The model for managing the data stored in this configuration. */ 156 private NodeModel<T> model; 157 158 /** Stores the expression engine for this instance. */ 159 private ExpressionEngine expressionEngine; 160 161 /** 162 * Creates a new instance of {@code AbstractHierarchicalConfiguration} and sets the {@code NodeModel} to be used. 163 * 164 * @param nodeModel the {@code NodeModel} 165 */ 166 protected AbstractHierarchicalConfiguration(final NodeModel<T> nodeModel) { 167 model = nodeModel; 168 } 169 170 /** 171 * {@inheritDoc} This implementation handles synchronization and delegates to {@code getRootElementNameInternal()}. 172 */ 173 @Override 174 public final String getRootElementName() { 175 beginRead(false); 176 try { 177 return getRootElementNameInternal(); 178 } finally { 179 endRead(); 180 } 181 } 182 183 /** 184 * Actually obtains the name of the root element. This method is called by {@code getRootElementName()}. It just returns 185 * the name of the root node. Subclasses that treat the root element name differently can override this method. 186 * 187 * @return the name of this configuration's root element 188 */ 189 protected String getRootElementNameInternal() { 190 final NodeHandler<T> nodeHandler = getModel().getNodeHandler(); 191 return nodeHandler.nodeName(nodeHandler.getRootNode()); 192 } 193 194 /** 195 * {@inheritDoc} This implementation returns the configuration's {@code NodeModel}. It is guarded by the current 196 * {@code Synchronizer}. 197 */ 198 @Override 199 public NodeModel<T> getNodeModel() { 200 beginRead(false); 201 try { 202 return getModel(); 203 } finally { 204 endRead(); 205 } 206 } 207 208 /** 209 * Returns the expression engine used by this configuration. This method will never return <b>null</b>; if no specific 210 * expression engine was set, the default expression engine will be returned. 211 * 212 * @return the current expression engine 213 * @since 1.3 214 */ 215 @Override 216 public ExpressionEngine getExpressionEngine() { 217 return expressionEngine != null ? expressionEngine : DefaultExpressionEngine.INSTANCE; 218 } 219 220 /** 221 * Sets the expression engine to be used by this configuration. All property keys this configuration has to deal with 222 * will be interpreted by this engine. 223 * 224 * @param expressionEngine the new expression engine; can be <b>null</b>, then the default expression engine will be 225 * used 226 * @since 1.3 227 */ 228 @Override 229 public void setExpressionEngine(final ExpressionEngine expressionEngine) { 230 this.expressionEngine = expressionEngine; 231 } 232 233 /** 234 * Fetches the specified property. This task is delegated to the associated expression engine. 235 * 236 * @param key the key to be looked up 237 * @return the found value 238 */ 239 @Override 240 protected Object getPropertyInternal(final String key) { 241 final List<QueryResult<T>> results = fetchNodeList(key); 242 243 if (results.isEmpty()) { 244 return null; 245 } 246 final NodeHandler<T> handler = getModel().getNodeHandler(); 247 final List<Object> list = new ArrayList<>(); 248 for (final QueryResult<T> result : results) { 249 final Object value = valueFromResult(result, handler); 250 if (value != null) { 251 list.add(value); 252 } 253 } 254 255 if (list.size() < 1) { 256 return null; 257 } 258 return list.size() == 1 ? list.get(0) : list; 259 } 260 261 /** 262 * Adds the property with the specified key. This task will be delegated to the associated {@code ExpressionEngine}, so 263 * the passed in key must match the requirements of this implementation. 264 * 265 * @param key the key of the new property 266 * @param obj the value of the new property 267 */ 268 @Override 269 protected void addPropertyInternal(final String key, final Object obj) { 270 addPropertyToModel(key, getListDelimiterHandler().parse(obj)); 271 } 272 273 /** 274 * {@inheritDoc} This method is not called in the normal way (via {@code addProperty()} for hierarchical configurations 275 * because all values to be added for the property have to be passed to the model in a single step. However, to allow 276 * derived classes to add an arbitrary value as an object, a special implementation is provided here. The passed in 277 * object is not parsed as a list, but passed directly as only value to the model. 278 */ 279 @Override 280 protected void addPropertyDirect(final String key, final Object value) { 281 addPropertyToModel(key, Collections.singleton(value)); 282 } 283 284 /** 285 * Helper method for executing an add property operation on the model. 286 * 287 * @param key the key of the new property 288 * @param values the values to be added for this property 289 */ 290 private void addPropertyToModel(final String key, final Iterable<?> values) { 291 getModel().addProperty(key, values, this); 292 } 293 294 /** 295 * Adds a collection of nodes at the specified position of the configuration tree. This method works similar to 296 * {@code addProperty()}, but instead of a single property a whole collection of nodes can be added - and thus complete 297 * configuration sub trees. E.g. with this method it is possible to add parts of another 298 * {@code BaseHierarchicalConfiguration} object to this object. If the passed in key refers to an existing and unique 299 * node, the new nodes are added to this node. Otherwise a new node will be created at the specified position in the 300 * hierarchy. Implementation node: This method performs some book-keeping and then delegates to 301 * {@code addNodesInternal()}. 302 * 303 * @param key the key where the nodes are to be added; can be <b>null</b>, then they are added to the root node 304 * @param nodes a collection with the {@code Node} objects to be added 305 */ 306 @Override 307 public final void addNodes(final String key, final Collection<? extends T> nodes) { 308 if (nodes == null || nodes.isEmpty()) { 309 return; 310 } 311 312 beginWrite(false); 313 try { 314 fireEvent(ConfigurationEvent.ADD_NODES, key, nodes, true); 315 addNodesInternal(key, nodes); 316 fireEvent(ConfigurationEvent.ADD_NODES, key, nodes, false); 317 } finally { 318 endWrite(); 319 } 320 } 321 322 /** 323 * Actually adds a collection of new nodes to this configuration. This method is called by {@code addNodes()}. It can be 324 * overridden by subclasses that need to adapt this operation. 325 * 326 * @param key the key where the nodes are to be added; can be <b>null</b>, then they are added to the root node 327 * @param nodes a collection with the {@code Node} objects to be added 328 * @since 2.0 329 */ 330 protected void addNodesInternal(final String key, final Collection<? extends T> nodes) { 331 getModel().addNodes(key, nodes, this); 332 } 333 334 /** 335 * Checks if this configuration is empty. Empty means that there are no keys with any values, though there can be some 336 * (empty) nodes. 337 * 338 * @return a flag if this configuration is empty 339 */ 340 @Override 341 protected boolean isEmptyInternal() { 342 return !nodeDefined(getModel().getNodeHandler().getRootNode()); 343 } 344 345 /** 346 * Checks if the specified key is contained in this configuration. Note that for this configuration the term 347 * "contained" means that the key has an associated value. If there is a node for this key that has no value 348 * but children (either defined or undefined), this method will still return <b>false </b>. 349 * 350 * @param key the key to be checked 351 * @return a flag if this key is contained in this configuration 352 */ 353 @Override 354 protected boolean containsKeyInternal(final String key) { 355 return getPropertyInternal(key) != null; 356 } 357 358 /** 359 * Sets the value of the specified property. 360 * 361 * @param key the key of the property to set 362 * @param value the new value of this property 363 */ 364 @Override 365 protected void setPropertyInternal(final String key, final Object value) { 366 getModel().setProperty(key, value, this); 367 } 368 369 /** 370 * {@inheritDoc} This implementation delegates to the expression engine. 371 */ 372 @Override 373 public List<QueryResult<T>> resolveKey(final T root, final String key, final NodeHandler<T> handler) { 374 return getExpressionEngine().query(root, key, handler); 375 } 376 377 /** 378 * {@inheritDoc} This implementation delegates to {@code resolveKey()} and then filters out attribute results. 379 */ 380 @Override 381 public List<T> resolveNodeKey(final T root, final String key, final NodeHandler<T> handler) { 382 final List<QueryResult<T>> results = resolveKey(root, key, handler); 383 final List<T> targetNodes = new LinkedList<>(); 384 for (final QueryResult<T> result : results) { 385 if (!result.isAttributeResult()) { 386 targetNodes.add(result.getNode()); 387 } 388 } 389 return targetNodes; 390 } 391 392 /** 393 * {@inheritDoc} This implementation delegates to the expression engine. 394 */ 395 @Override 396 public NodeAddData<T> resolveAddKey(final T root, final String key, final NodeHandler<T> handler) { 397 return getExpressionEngine().prepareAdd(root, key, handler); 398 } 399 400 /** 401 * {@inheritDoc} This implementation executes a query for the given key and constructs a {@code NodeUpdateData} object 402 * based on the results. It determines which nodes need to be changed and whether new ones need to be added or existing 403 * ones need to be removed. 404 */ 405 @Override 406 public NodeUpdateData<T> resolveUpdateKey(final T root, final String key, final Object newValue, final NodeHandler<T> handler) { 407 final Iterator<QueryResult<T>> itNodes = fetchNodeList(key).iterator(); 408 final Iterator<?> itValues = getListDelimiterHandler().parse(newValue).iterator(); 409 final Map<QueryResult<T>, Object> changedValues = new HashMap<>(); 410 Collection<Object> additionalValues = null; 411 Collection<QueryResult<T>> removedItems = null; 412 413 while (itNodes.hasNext() && itValues.hasNext()) { 414 changedValues.put(itNodes.next(), itValues.next()); 415 } 416 417 // Add additional nodes if necessary 418 if (itValues.hasNext()) { 419 additionalValues = new LinkedList<>(); 420 while (itValues.hasNext()) { 421 additionalValues.add(itValues.next()); 422 } 423 } 424 425 // Remove remaining nodes 426 if (itNodes.hasNext()) { 427 removedItems = new LinkedList<>(); 428 while (itNodes.hasNext()) { 429 removedItems.add(itNodes.next()); 430 } 431 } 432 433 return new NodeUpdateData<>(changedValues, additionalValues, removedItems, key); 434 } 435 436 /** 437 * {@inheritDoc} This implementation uses the expression engine to generate a canonical key for the passed in node. For 438 * this purpose, the path to the root node has to be traversed. The cache is used to store and access keys for nodes 439 * encountered on the path. 440 */ 441 @Override 442 public String nodeKey(final T node, final Map<T, String> cache, final NodeHandler<T> handler) { 443 final List<T> path = new LinkedList<>(); 444 T currentNode = node; 445 String key = cache.get(node); 446 while (key == null && currentNode != null) { 447 path.add(0, currentNode); 448 currentNode = handler.getParent(currentNode); 449 key = cache.get(currentNode); 450 } 451 452 for (final T n : path) { 453 final String currentKey = getExpressionEngine().canonicalKey(n, key, handler); 454 cache.put(n, currentKey); 455 key = currentKey; 456 } 457 458 return key; 459 } 460 461 /** 462 * Clears this configuration. This is a more efficient implementation than the one inherited from the base class. It 463 * delegates to the node model. 464 */ 465 @Override 466 protected void clearInternal() { 467 getModel().clear(this); 468 } 469 470 /** 471 * Removes all values of the property with the given name and of keys that start with this name. So if there is a 472 * property with the key "foo" and a property with the key "foo.bar", a call of 473 * {@code clearTree("foo")} would remove both properties. 474 * 475 * @param key the key of the property to be removed 476 */ 477 @Override 478 public final void clearTree(final String key) { 479 beginWrite(false); 480 try { 481 fireEvent(ConfigurationEvent.CLEAR_TREE, key, null, true); 482 final Object nodes = clearTreeInternal(key); 483 fireEvent(ConfigurationEvent.CLEAR_TREE, key, nodes, false); 484 } finally { 485 endWrite(); 486 } 487 } 488 489 /** 490 * Actually clears the tree of elements referenced by the given key. This method is called by {@code clearTree()}. 491 * Subclasses that need to adapt this operation can override this method. This base implementation delegates to the node 492 * model. 493 * 494 * @param key the key of the property to be removed 495 * @return an object with information about the nodes that have been removed (this is needed for firing a meaningful 496 * event of type CLEAR_TREE) 497 * @since 2.0 498 */ 499 protected Object clearTreeInternal(final String key) { 500 return getModel().clearTree(key, this); 501 } 502 503 /** 504 * Removes the property with the given key. Properties with names that start with the given key (i.e. properties below 505 * the specified key in the hierarchy) won't be affected. This implementation delegates to the node+ model. 506 * 507 * @param key the key of the property to be removed 508 */ 509 @Override 510 protected void clearPropertyDirect(final String key) { 511 getModel().clearProperty(key, this); 512 } 513 514 /** 515 * {@inheritDoc} This implementation is slightly more efficient than the default implementation. It does not iterate 516 * over the key set, but directly queries its size after it has been constructed. Note that constructing the key set is 517 * still an O(n) operation. 518 */ 519 @Override 520 protected int sizeInternal() { 521 return visitDefinedKeys().getKeyList().size(); 522 } 523 524 /** 525 * Returns an iterator with all keys defined in this configuration. Note that the keys returned by this method will not 526 * contain any indices. This means that some structure will be lost. 527 * 528 * @return an iterator with the defined keys in this configuration 529 */ 530 @Override 531 protected Iterator<String> getKeysInternal() { 532 return visitDefinedKeys().getKeyList().iterator(); 533 } 534 535 /** 536 * Creates a {@code DefinedKeysVisitor} and visits all defined keys with it. 537 * 538 * @return the visitor after all keys have been visited 539 */ 540 private DefinedKeysVisitor visitDefinedKeys() { 541 final DefinedKeysVisitor visitor = new DefinedKeysVisitor(); 542 final NodeHandler<T> nodeHandler = getModel().getNodeHandler(); 543 NodeTreeWalker.INSTANCE.walkDFS(nodeHandler.getRootNode(), visitor, nodeHandler); 544 return visitor; 545 } 546 547 /** 548 * Returns an iterator with all keys defined in this configuration that start with the given prefix. The returned keys 549 * will not contain any indices. This implementation tries to locate a node whose key is the same as the passed in 550 * prefix. Then the subtree of this node is traversed, and the keys of all nodes encountered (including attributes) are 551 * added to the result set. 552 * 553 * @param prefix the prefix of the keys to start with 554 * @return an iterator with the found keys 555 */ 556 @Override 557 protected Iterator<String> getKeysInternal(final String prefix) { 558 final DefinedKeysVisitor visitor = new DefinedKeysVisitor(prefix); 559 if (containsKey(prefix)) { 560 // explicitly add the prefix 561 visitor.getKeyList().add(prefix); 562 } 563 564 final List<QueryResult<T>> results = fetchNodeList(prefix); 565 final NodeHandler<T> handler = getModel().getNodeHandler(); 566 567 for (final QueryResult<T> result : results) { 568 if (!result.isAttributeResult()) { 569 for (final T c : handler.getChildren(result.getNode())) { 570 NodeTreeWalker.INSTANCE.walkDFS(c, visitor, handler); 571 } 572 visitor.handleAttributeKeys(prefix, result.getNode(), handler); 573 } 574 } 575 576 return visitor.getKeyList().iterator(); 577 } 578 579 /** 580 * Returns the maximum defined index for the given key. This is useful if there are multiple values for this key. They 581 * can then be addressed separately by specifying indices from 0 to the return value of this method. If the passed in 582 * key is not contained in this configuration, result is -1. 583 * 584 * @param key the key to be checked 585 * @return the maximum defined index for this key 586 */ 587 @Override 588 public final int getMaxIndex(final String key) { 589 beginRead(false); 590 try { 591 return getMaxIndexInternal(key); 592 } finally { 593 endRead(); 594 } 595 } 596 597 /** 598 * Actually retrieves the maximum defined index for the given key. This method is called by {@code getMaxIndex()}. 599 * Subclasses that need to adapt this operation have to override this method. 600 * 601 * @param key the key to be checked 602 * @return the maximum defined index for this key 603 * @since 2.0 604 */ 605 protected int getMaxIndexInternal(final String key) { 606 return fetchNodeList(key).size() - 1; 607 } 608 609 /** 610 * Creates a copy of this object. This new configuration object will contain copies of all nodes in the same structure. 611 * Registered event listeners won't be cloned; so they are not registered at the returned copy. 612 * 613 * @return the copy 614 * @since 1.2 615 */ 616 @Override 617 public Object clone() { 618 beginRead(false); 619 try { 620 @SuppressWarnings("unchecked") // clone returns the same type 621 final AbstractHierarchicalConfiguration<T> copy = (AbstractHierarchicalConfiguration<T>) super.clone(); 622 copy.setSynchronizer(NoOpSynchronizer.INSTANCE); 623 copy.cloneInterpolator(this); 624 copy.setSynchronizer(ConfigurationUtils.cloneSynchronizer(getSynchronizer())); 625 copy.model = cloneNodeModel(); 626 627 return copy; 628 } catch (final CloneNotSupportedException cex) { 629 // should not happen 630 throw new ConfigurationRuntimeException(cex); 631 } finally { 632 endRead(); 633 } 634 } 635 636 /** 637 * Creates a clone of the node model. This method is called by {@code clone()}. 638 * 639 * @return the clone of the {@code NodeModel} 640 * @since 2.0 641 */ 642 protected abstract NodeModel<T> cloneNodeModel(); 643 644 /** 645 * Helper method for resolving the specified key. 646 * 647 * @param key the key 648 * @return a list with all results selected by this key 649 */ 650 protected List<QueryResult<T>> fetchNodeList(final String key) { 651 final NodeHandler<T> nodeHandler = getModel().getNodeHandler(); 652 return resolveKey(nodeHandler.getRootNode(), key, nodeHandler); 653 } 654 655 /** 656 * Checks if the specified node is defined. 657 * 658 * @param node the node to be checked 659 * @return a flag if this node is defined 660 */ 661 protected boolean nodeDefined(final T node) { 662 final DefinedVisitor<T> visitor = new DefinedVisitor<>(); 663 NodeTreeWalker.INSTANCE.walkBFS(node, visitor, getModel().getNodeHandler()); 664 return visitor.isDefined(); 665 } 666 667 /** 668 * Returns the {@code NodeModel} used by this configuration. This method is intended for internal use only. Access to 669 * the model is granted without any synchronization. This is in contrast to the "official" 670 * {@code getNodeModel()} method which is guarded by the configuration's {@code Synchronizer}. 671 * 672 * @return the node model 673 */ 674 protected NodeModel<T> getModel() { 675 return model; 676 } 677 678 /** 679 * Extracts the value from a query result. 680 * 681 * @param result the {@code QueryResult} 682 * @param handler the {@code NodeHandler} 683 * @return the value of this result (may be <b>null</b>) 684 */ 685 private Object valueFromResult(final QueryResult<T> result, final NodeHandler<T> handler) { 686 return result.isAttributeResult() ? result.getAttributeValue(handler) : handler.getValue(result.getNode()); 687 } 688 689 /** 690 * A specialized visitor that checks if a node is defined. "Defined" in this terms means that the node or at 691 * least one of its sub nodes is associated with a value. 692 * 693 * @param <T> the type of the nodes managed by this hierarchical configuration 694 */ 695 private static class DefinedVisitor<T> extends ConfigurationNodeVisitorAdapter<T> { 696 /** Stores the defined flag. */ 697 private boolean defined; 698 699 /** 700 * Checks if iteration should be stopped. This can be done if the first defined node is found. 701 * 702 * @return a flag if iteration should be stopped 703 */ 704 @Override 705 public boolean terminate() { 706 return isDefined(); 707 } 708 709 /** 710 * Visits the node. Checks if a value is defined. 711 * 712 * @param node the actual node 713 */ 714 @Override 715 public void visitBeforeChildren(final T node, final NodeHandler<T> handler) { 716 defined = handler.getValue(node) != null || !handler.getAttributes(node).isEmpty(); 717 } 718 719 /** 720 * Returns the defined flag. 721 * 722 * @return the defined flag 723 */ 724 public boolean isDefined() { 725 return defined; 726 } 727 } 728 729 /** 730 * A specialized visitor that fills a list with keys that are defined in a node hierarchy. 731 */ 732 private class DefinedKeysVisitor extends ConfigurationNodeVisitorAdapter<T> { 733 /** Stores the list to be filled. */ 734 private final Set<String> keyList; 735 736 /** A stack with the keys of the already processed nodes. */ 737 private final Stack<String> parentKeys; 738 739 /** 740 * Default constructor. 741 */ 742 public DefinedKeysVisitor() { 743 keyList = new LinkedHashSet<>(); 744 parentKeys = new Stack<>(); 745 } 746 747 /** 748 * Creates a new {@code DefinedKeysVisitor} instance and sets the prefix for the keys to fetch. 749 * 750 * @param prefix the prefix 751 */ 752 public DefinedKeysVisitor(final String prefix) { 753 this(); 754 parentKeys.push(prefix); 755 } 756 757 /** 758 * Returns the list with all defined keys. 759 * 760 * @return the list with the defined keys 761 */ 762 public Set<String> getKeyList() { 763 return keyList; 764 } 765 766 /** 767 * {@inheritDoc} This implementation removes this node's key from the stack. 768 */ 769 @Override 770 public void visitAfterChildren(final T node, final NodeHandler<T> handler) { 771 parentKeys.pop(); 772 } 773 774 /** 775 * {@inheritDoc} If this node has a value, its key is added to the internal list. 776 */ 777 @Override 778 public void visitBeforeChildren(final T node, final NodeHandler<T> handler) { 779 final String parentKey = parentKeys.isEmpty() ? null : parentKeys.peek(); 780 final String key = getExpressionEngine().nodeKey(node, parentKey, handler); 781 parentKeys.push(key); 782 if (handler.getValue(node) != null) { 783 keyList.add(key); 784 } 785 handleAttributeKeys(key, node, handler); 786 } 787 788 /** 789 * Appends all attribute keys of the current node. 790 * 791 * @param parentKey the parent key 792 * @param node the current node 793 * @param handler the {@code NodeHandler} 794 */ 795 public void handleAttributeKeys(final String parentKey, final T node, final NodeHandler<T> handler) { 796 for (final String attr : handler.getAttributes(node)) { 797 keyList.add(getExpressionEngine().attributeKey(parentKey, attr)); 798 } 799 } 800 } 801 802 @Override 803 public String toString() { 804 return super.toString() + "(" + getRootElementNameInternal() + ")"; 805 } 806}