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.apache.maven.report.projectinfo.dependencies;
20
21 import java.util.List;
22
23 import org.apache.maven.doxia.sink.Sink;
24 import org.apache.maven.shared.dependency.graph.DependencyNode;
25 import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
26
27 /**
28 * A dependency node visitor that serializes visited nodes to a sink writer. It's used to serialize tree in project
29 * information report page.
30 *
31 * @author <a href="mailto:wangyf2010@gmail.com">Simon Wang</a>
32 */
33 public class SinkSerializingDependencyNodeVisitor implements DependencyNodeVisitor {
34 // classes ----------------------------------------------------------------
35
36 /**
37 * Provides tokens to use when serializing the dependency tree.
38 */
39 private class TreeTokens {
40 private final Sink sink;
41
42 TreeTokens(Sink sink) {
43 this.sink = sink;
44 }
45
46 void addNodeIndent(boolean last) {
47 if (last) {
48 sink.text("\\-");
49 sink.nonBreakingSpace();
50 } else {
51 sink.text("+-");
52 sink.nonBreakingSpace();
53 }
54 }
55
56 void fillIndent(boolean last) {
57 if (last) {
58 sink.nonBreakingSpace();
59 sink.nonBreakingSpace();
60 sink.nonBreakingSpace();
61 } else {
62 sink.text("|");
63 sink.nonBreakingSpace();
64 sink.nonBreakingSpace();
65 }
66 }
67 }
68
69 // fields -----------------------------------------------------------------
70
71 /**
72 * The writer to serialize to.
73 */
74 private final Sink sink;
75
76 /**
77 * The tokens to use when serializing the dependency tree.
78 */
79 private final TreeTokens tokens;
80
81 /**
82 * The depth of the currently visited dependency node.
83 */
84 private int depth;
85
86 // constructors -----------------------------------------------------------
87
88 /**
89 * Creates a dependency node visitor that serializes visited nodes to the specified writer using the specified
90 * tokens.
91 *
92 * @param sink the writer to serialize to
93 */
94 public SinkSerializingDependencyNodeVisitor(Sink sink) {
95 this.sink = sink;
96 this.tokens = new TreeTokens(sink);
97 depth = 0;
98 }
99
100 // DependencyNodeVisitor methods ------------------------------------------
101
102 /**
103 * {@inheritDoc}
104 */
105 public boolean visit(DependencyNode node) {
106 indent(node);
107
108 sink.text(node.toNodeString());
109 sink.lineBreak();
110
111 depth++;
112
113 return true;
114 }
115
116 /**
117 * {@inheritDoc}
118 */
119 public boolean endVisit(DependencyNode node) {
120 depth--;
121
122 return true;
123 }
124
125 // private methods --------------------------------------------------------
126
127 /**
128 * Writes the necessary tokens to indent the specified dependency node to this visitor's writer.
129 *
130 * @param node the dependency node to indent
131 */
132 private void indent(DependencyNode node) {
133 for (int i = 1; i < depth; i++) {
134 tokens.fillIndent(isLast(node, i));
135 }
136
137 if (depth > 0) {
138 tokens.addNodeIndent(isLast(node));
139 }
140 }
141
142 /**
143 * Gets whether the specified dependency node is the last of its siblings.
144 *
145 * @param node the dependency node to check
146 * @return <code>true</code> if the specified dependency node is the last of its last siblings
147 */
148 private boolean isLast(DependencyNode node) {
149 // TODO: remove node argument and calculate from visitor calls only
150
151 DependencyNode parent = node.getParent();
152
153 boolean last;
154
155 if (parent == null) {
156 last = true;
157 } else {
158 List<DependencyNode> siblings = parent.getChildren();
159
160 last = (siblings.indexOf(node) == siblings.size() - 1);
161 }
162
163 return last;
164 }
165
166 /**
167 * Gets whether the specified dependency node ancestor is the last of its siblings.
168 *
169 * @param node the dependency node whose ancestor to check
170 * @param ancestorDepth the depth of the ancestor of the specified dependency node to check
171 * @return <code>true</code> if the specified dependency node ancestor is the last of its siblings
172 */
173 private boolean isLast(DependencyNode node, int ancestorDepth) {
174 // TODO: remove node argument and calculate from visitor calls only
175
176 int distance = depth - ancestorDepth;
177
178 while (distance-- > 0) {
179 node = node.getParent();
180 }
181
182 return isLast(node);
183 }
184 }