3 * Copyright (C) 2009-2022 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.ce.task.projectanalysis.component;
22 import com.google.common.base.Function;
23 import com.google.common.base.Joiner;
24 import javax.annotation.Nonnull;
25 import javax.annotation.concurrent.Immutable;
27 import static com.google.common.collect.FluentIterable.from;
28 import static java.lang.String.format;
29 import static java.util.Objects.requireNonNull;
30 import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER;
31 import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order.PRE_ORDER;
34 * A {@link ComponentCrawler} which provide access to a representation of the path from the root to the currently visited
35 * Component. It also provides a way to have an object associated to each Component and access it and all of its
37 * As for {@link DepthTraversalTypeAwareCrawler}, this crawler supports max depth visit and ordering.
39 public final class PathAwareCrawler<T> implements ComponentCrawler {
40 private final PathAwareVisitor<T> visitor;
41 private final DequeBasedPath<T> stack = new DequeBasedPath<>();
43 public PathAwareCrawler(PathAwareVisitor<T> visitor) {
44 this.visitor = requireNonNull(visitor);
48 public void visit(Component component) {
51 } catch (RuntimeException e) {
52 VisitException.rethrowOrWrap(
54 "Visit failed for Component {key=%s,type=%s} %s",
55 component.getDbKey(), component.getType(), new ComponentPathPrinter<>(stack));
59 private void visitImpl(Component component) {
60 if (!verifyDepth(component)) {
64 stack.add(new PathElementImpl<>(component, createForComponent(component)));
66 if (this.visitor.getOrder() == PRE_ORDER) {
70 visitChildren(component);
72 if (this.visitor.getOrder() == POST_ORDER) {
79 private boolean verifyDepth(Component component) {
80 CrawlerDepthLimit maxDepth = this.visitor.getMaxDepth();
81 return maxDepth.isSameAs(component.getType()) || maxDepth.isDeeperThan(component.getType());
84 private void visitChildren(Component component) {
85 for (Component child : component.getChildren()) {
86 if (verifyDepth(component)) {
92 private void visitNode(Component component) {
93 this.visitor.visitAny(component, stack);
94 switch (component.getType()) {
96 this.visitor.visitProject(component, stack);
99 this.visitor.visitDirectory(component, stack);
102 this.visitor.visitFile(component, stack);
105 this.visitor.visitView(component, stack);
108 this.visitor.visitSubView(component, stack);
111 this.visitor.visitProjectView(component, stack);
114 throw new IllegalArgumentException(format("Unsupported component type %s, no visitor method to call", component.getType()));
118 private T createForComponent(Component component) {
119 switch (component.getType()) {
121 return this.visitor.getFactory().createForProject(component);
123 return this.visitor.getFactory().createForDirectory(component);
125 return this.visitor.getFactory().createForFile(component);
127 return this.visitor.getFactory().createForView(component);
129 return this.visitor.getFactory().createForSubView(component);
131 return this.visitor.getFactory().createForProjectView(component);
133 throw new IllegalArgumentException(format("Unsupported component type %s, can not create stack object", component.getType()));
138 * A simple object wrapping the currentPath allowing to compute the string representing the path only if
139 * the VisitException is actually built (ie. method {@link ComponentPathPrinter#toString()} is called
140 * by the internal {@link String#format(String, Object...)} of
141 * {@link VisitException#rethrowOrWrap(RuntimeException, String, Object...)}.
144 private static final class ComponentPathPrinter<T> {
146 private static final Joiner PATH_ELEMENTS_JOINER = Joiner.on("->");
148 private final DequeBasedPath<T> currentPath;
150 private ComponentPathPrinter(DequeBasedPath<T> currentPath) {
151 this.currentPath = currentPath;
155 public String toString() {
156 if (currentPath.isRoot()) {
159 return " located " + toKeyPath(currentPath);
162 private static <T> String toKeyPath(Iterable<PathAwareVisitor.PathElement<T>> currentPath) {
163 return PATH_ELEMENTS_JOINER.join(from(currentPath).transform(PathElementToComponentAsString.INSTANCE).skip(1));
166 private enum PathElementToComponentAsString implements Function<PathAwareVisitor.PathElement<?>, String> {
171 public String apply(@Nonnull PathAwareVisitor.PathElement<?> input) {
172 return format("%s(type=%s)", input.getComponent().getDbKey(), input.getComponent().getType());