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      /** The default instance of this class. */
42      public static final NodeTreeWalker INSTANCE = new NodeTreeWalker();
43  
44      /**
45       * Helper method for performing a BFS traversal. Implementation node: This method organizes the nodes to be visited in
46       * structures on the heap. Therefore, it can deal with larger structures than would be the case in a recursive approach
47       * (where the stack size limits the size of the structures which can be traversed).
48       *
49       * @param root the root node to be navigated
50       * @param visitor the visitor
51       * @param handler the handler
52       * @param <T> the type of the nodes involved
53       */
54      private static <T> void bfs(final T root, final ConfigurationNodeVisitor<T> visitor, final NodeHandler<T> handler) {
55          final List<T> pendingNodes = new LinkedList<>();
56          pendingNodes.add(root);
57          boolean cancel = false;
58  
59          while (!pendingNodes.isEmpty() && !cancel) {
60              final T node = pendingNodes.remove(0);
61              visitor.visitBeforeChildren(node, handler);
62              cancel = visitor.terminate();
63              pendingNodes.addAll(handler.getChildren(node));
64          }
65      }
66  
67      /**
68       * Helper method for checking the parameters for the walk() methods. If mandatory parameters are missing, an exception
69       * is thrown. The return value indicates whether an operation can be performed.
70       *
71       * @param root the root node
72       * @param visitor the visitor
73       * @param handler the handler
74       * @param <T> the type of the nodes involved
75       * @return <strong>true</strong> if a walk operation can be performed, <strong>false</strong> otherwise
76       * @throws IllegalArgumentException if a required parameter is missing
77       */
78      private static <T> boolean checkParameters(final T root, final ConfigurationNodeVisitor<T> visitor, final NodeHandler<T> handler) {
79          if (visitor == null) {
80              throw new IllegalArgumentException("Visitor must not be null!");
81          }
82          if (handler == null) {
83              throw new IllegalArgumentException("NodeHandler must not be null!");
84          }
85          return root != null;
86      }
87  
88      /**
89       * Recursive helper method for performing a DFS traversal.
90       *
91       * @param node the current node
92       * @param visitor the visitor
93       * @param handler the handler
94       * @param <T> the type of the nodes involved
95       */
96      private static <T> void dfs(final T node, final ConfigurationNodeVisitor<T> visitor, final NodeHandler<T> handler) {
97          if (!visitor.terminate()) {
98              visitor.visitBeforeChildren(node, handler);
99              handler.getChildren(node).forEach(c -> dfs(c, visitor, handler));
100             if (!visitor.terminate()) {
101                 visitor.visitAfterChildren(node, handler);
102             }
103         }
104     }
105 
106     /**
107      * Constructs a new instance.
108      */
109     public NodeTreeWalker() {
110         // empty
111     }
112 
113     /**
114      * Visits all nodes in the hierarchy represented by the given root node in <em>breadth first search</em> manner. This
115      * means that the nodes are visited in an order corresponding to the distance from the root node: first the root node is
116      * visited, then all direct children of the root node, then all direct children of the first child of the root node,
117      * etc. In this mode of traversal, there is no direct connection between the encounter of a node and its children.
118      * <strong>Therefore, on the visitor object only the {@code visitBeforeChildren()} method gets called!</strong>.
119      *
120      * @param root the root node of the hierarchy to be processed (may be <strong>null</strong>, then this call has no effect)
121      * @param visitor the {@code ConfigurationNodeVisitor} (must not be <strong>null</strong>)
122      * @param handler the {@code NodeHandler} (must not be <strong>null</strong>)
123      * @param <T> the type of the nodes involved
124      * @throws IllegalArgumentException if a required parameter is <strong>null</strong>
125      */
126     public <T> void walkBFS(final T root, final ConfigurationNodeVisitor<T> visitor, final NodeHandler<T> handler) {
127         if (checkParameters(root, visitor, handler)) {
128             bfs(root, visitor, handler);
129         }
130     }
131 
132     /**
133      * Visits all nodes in the hierarchy represented by the given root node in <em>depth first search</em> manner. This
134      * means that first {@link ConfigurationNodeVisitor#visitBeforeChildren(Object, NodeHandler)} is called on a node, then
135      * recursively all of its children are processed, and eventually
136      * {@link ConfigurationNodeVisitor#visitAfterChildren(Object, NodeHandler)} gets invoked.
137      *
138      * @param root the root node of the hierarchy to be processed (may be <strong>null</strong>, then this call has no effect)
139      * @param visitor the {@code ConfigurationNodeVisitor} (must not be <strong>null</strong>)
140      * @param handler the {@code NodeHandler} (must not be <strong>null</strong>)
141      * @param <T> the type of the nodes involved
142      * @throws IllegalArgumentException if a required parameter is <strong>null</strong>
143      */
144     public <T> void walkDFS(final T root, final ConfigurationNodeVisitor<T> visitor, final NodeHandler<T> handler) {
145         if (checkParameters(root, visitor, handler)) {
146             dfs(root, visitor, handler);
147         }
148     }
149 }