1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.eclipse.aether.util.graph.visitor;
20
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.List;
24
25 import org.eclipse.aether.graph.DependencyFilter;
26 import org.eclipse.aether.graph.DependencyNode;
27 import org.eclipse.aether.graph.DependencyVisitor;
28
29 /**
30 * A dependency visitor that records all paths leading to nodes matching a certain filter criteria.
31 */
32 public final class PathRecordingDependencyVisitor implements DependencyVisitor {
33
34 private final DependencyFilter filter;
35
36 private final List<List<DependencyNode>> paths;
37
38 private final Stack<DependencyNode> parents;
39
40 private final boolean excludeChildrenOfMatches;
41
42 /**
43 * Creates a new visitor that uses the specified filter to identify terminal nodes of interesting paths. The visitor
44 * will not search for paths going beyond an already matched node.
45 *
46 * @param filter the filter used to select terminal nodes of paths to record, may be {@code null} to match any node
47 */
48 public PathRecordingDependencyVisitor(DependencyFilter filter) {
49 this(filter, true);
50 }
51
52 /**
53 * Creates a new visitor that uses the specified filter to identify terminal nodes of interesting paths.
54 *
55 * @param filter the filter used to select terminal nodes of paths to record, may be {@code null} to match any node
56 * @param excludeChildrenOfMatches flag controlling whether children of matched nodes should be excluded from the
57 * traversal, thereby ignoring any potential paths to other matching nodes beneath a matching ancestor
58 * node. If {@code true}, all recorded paths will have only one matching node (namely the terminal node),
59 * if {@code false} a recorded path can consist of multiple matching nodes.
60 */
61 public PathRecordingDependencyVisitor(DependencyFilter filter, boolean excludeChildrenOfMatches) {
62 this.filter = filter;
63 this.excludeChildrenOfMatches = excludeChildrenOfMatches;
64 paths = new ArrayList<>();
65 parents = new Stack<>();
66 }
67
68 /**
69 * Gets the filter being used to select terminal nodes.
70 *
71 * @return the filter being used or {@code null} if none
72 */
73 public DependencyFilter getFilter() {
74 return filter;
75 }
76
77 /**
78 * Gets the paths leading to nodes matching the filter that have been recorded during the graph visit. A path is
79 * given as a sequence of nodes, starting with the root node of the graph and ending with a node that matched the
80 * filter.
81 *
82 * @return the recorded paths, never {@code null}
83 */
84 public List<List<DependencyNode>> getPaths() {
85 return paths;
86 }
87
88 @Override
89 public boolean visitEnter(DependencyNode node) {
90 boolean accept = filter == null || filter.accept(node, parents);
91
92 boolean hasDuplicateNodeInParent = parents.contains(node);
93 parents.push(node);
94
95 if (accept) {
96 DependencyNode[] path = new DependencyNode[parents.size()];
97 for (int i = 0, n = parents.size(); i < n; i++) {
98 path[n - i - 1] = parents.get(i);
99 }
100 paths.add(Arrays.asList(path));
101
102 if (excludeChildrenOfMatches) {
103 return false;
104 }
105 }
106
107 return !hasDuplicateNodeInParent;
108 }
109
110 @Override
111 public boolean visitLeave(DependencyNode node) {
112 parents.pop();
113
114 return true;
115 }
116 }