View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration2.tree;
18  
19  import java.util.LinkedList;
20  import java.util.List;
21  
22  /**
23   * <p>
24   * A class providing different algorithms for traversing a hierarchy of configuration nodes.
25   * </p>
26   * <p>
27   * The methods provided by this class accept a {@link ConfigurationNodeVisitor} and visit all nodes in a hierarchy
28   * starting from a given root node. Because a {@link NodeHandler} has to be passed in, too, arbitrary types of nodes can
29   * be processed. The {@code walk()} methods differ in the order in which nodes are visited. Details can be found in the
30   * method documentation.
31   * </p>
32   * <p>
33   * An instance of this class does not define any state; therefore, it can be shared and used concurrently. The
34   * {@code INSTANCE} member field can be used for accessing a default instance. If desired (for example for testing purposes),
35   * new instances can be created.
36   * </p>
37   *
38   * @since 2.0
39   */
40  public class NodeTreeWalker {
41  
42      /** The default instance of this class. */
43      public static final NodeTreeWalker INSTANCE = new NodeTreeWalker();
44  
45      /**
46       * Helper method for performing a BFS traversal. Implementation node: This method organizes the nodes to be visited in
47       * structures on the heap. Therefore, it can deal with larger structures than would be the case in a recursive approach
48       * (where the stack size limits the size of the structures which can be traversed).
49       *
50       * @param root the root node to be navigated
51       * @param visitor the visitor
52       * @param handler the handler
53       * @param <T> the type of the nodes involved
54       */
55      private static <T> void bfs(final T root, final ConfigurationNodeVisitor<T> visitor, final NodeHandler<T> handler) {
56          final List<T> pendingNodes = new LinkedList<>();
57          pendingNodes.add(root);
58          boolean cancel = false;
59  
60          while (!pendingNodes.isEmpty() && !cancel) {
61              final T node = pendingNodes.remove(0);
62              visitor.visitBeforeChildren(node, handler);
63              cancel = visitor.terminate();
64              pendingNodes.addAll(handler.getChildren(node));
65          }
66      }
67  
68      /**
69       * Helper method for checking the parameters for the walk() methods. If mandatory parameters are missing, an exception
70       * is thrown. The return value indicates whether an operation can be performed.
71       *
72       * @param root the root node
73       * @param visitor the visitor
74       * @param handler the handler
75       * @param <T> the type of the nodes involved
76       * @return <strong>true</strong> if a walk operation can be performed, <strong>false</strong> otherwise
77       * @throws IllegalArgumentException if a required parameter is missing
78       */
79      private static <T> boolean checkParameters(final T root, final ConfigurationNodeVisitor<T> visitor, final NodeHandler<T> handler) {
80          if (visitor == null) {
81              throw new IllegalArgumentException("Visitor must not be null.");
82          }
83          if (handler == null) {
84              throw new IllegalArgumentException("NodeHandler must not be null.");
85          }
86          return root != null;
87      }
88  
89      /**
90       * Recursive helper method for performing a DFS traversal.
91       *
92       * @param node the current node
93       * @param visitor the visitor
94       * @param handler the handler
95       * @param <T> the type of the nodes involved
96       */
97      private static <T> void dfs(final T node, final ConfigurationNodeVisitor<T> visitor, final NodeHandler<T> handler) {
98          if (!visitor.terminate()) {
99              visitor.visitBeforeChildren(node, handler);
100             handler.getChildren(node).forEach(c -> dfs(c, visitor, handler));
101             if (!visitor.terminate()) {
102                 visitor.visitAfterChildren(node, handler);
103             }
104         }
105     }
106 
107     /**
108      * Constructs a new instance.
109      */
110     public NodeTreeWalker() {
111         // empty
112     }
113 
114     /**
115      * Visits all nodes in the hierarchy represented by the given root node in <em>breadth first search</em> manner. This
116      * means that the nodes are visited in an order corresponding to the distance from the root node: first the root node is
117      * visited, then all direct children of the root node, then all direct children of the first child of the root node,
118      * etc. In this mode of traversal, there is no direct connection between the encounter of a node and its children.
119      * <strong>Therefore, on the visitor object only the {@code visitBeforeChildren()} method gets called!</strong>.
120      *
121      * @param root the root node of the hierarchy to be processed (may be <strong>null</strong>, then this call has no effect)
122      * @param visitor the {@code ConfigurationNodeVisitor} (must not be <strong>null</strong>)
123      * @param handler the {@code NodeHandler} (must not be <strong>null</strong>)
124      * @param <T> the type of the nodes involved
125      * @throws IllegalArgumentException if a required parameter is <strong>null</strong>
126      */
127     public <T> void walkBFS(final T root, final ConfigurationNodeVisitor<T> visitor, final NodeHandler<T> handler) {
128         if (checkParameters(root, visitor, handler)) {
129             bfs(root, visitor, handler);
130         }
131     }
132 
133     /**
134      * Visits all nodes in the hierarchy represented by the given root node in <em>depth first search</em> manner. This
135      * means that first {@link ConfigurationNodeVisitor#visitBeforeChildren(Object, NodeHandler)} is called on a node, then
136      * recursively all of its children are processed, and eventually
137      * {@link ConfigurationNodeVisitor#visitAfterChildren(Object, NodeHandler)} gets invoked.
138      *
139      * @param root the root node of the hierarchy to be processed (may be <strong>null</strong>, then this call has no effect)
140      * @param visitor the {@code ConfigurationNodeVisitor} (must not be <strong>null</strong>)
141      * @param handler the {@code NodeHandler} (must not be <strong>null</strong>)
142      * @param <T> the type of the nodes involved
143      * @throws IllegalArgumentException if a required parameter is <strong>null</strong>
144      */
145     public <T> void walkDFS(final T root, final ConfigurationNodeVisitor<T> visitor, final NodeHandler<T> handler) {
146         if (checkParameters(root, visitor, handler)) {
147             dfs(root, visitor, handler);
148         }
149     }
150 }