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.shared.dependency.graph.traversal;
20
21 import java.io.PrintWriter;
22 import java.io.Writer;
23 import java.util.List;
24
25 import org.apache.maven.shared.dependency.graph.DependencyNode;
26
27 /**
28 * A dependency node visitor that serializes visited nodes to a writer.
29 *
30 * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
31 */
32 public class SerializingDependencyNodeVisitor implements DependencyNodeVisitor {
33 // classes ----------------------------------------------------------------
34
35 /**
36 * Provides tokens to use when serializing the dependency graph.
37 */
38 public static class GraphTokens {
39 private final String nodeIndent;
40
41 private final String lastNodeIndent;
42
43 private final String fillIndent;
44
45 private final String lastFillIndent;
46
47 public GraphTokens(String nodeIndent, String lastNodeIndent, String fillIndent, String lastFillIndent) {
48 this.nodeIndent = nodeIndent;
49 this.lastNodeIndent = lastNodeIndent;
50 this.fillIndent = fillIndent;
51 this.lastFillIndent = lastFillIndent;
52 }
53
54 public String getNodeIndent(boolean last) {
55 return last ? lastNodeIndent : nodeIndent;
56 }
57
58 public String getFillIndent(boolean last) {
59 return last ? lastFillIndent : fillIndent;
60 }
61 }
62
63 // constants --------------------------------------------------------------
64
65 /**
66 * Whitespace tokens to use when outputing the dependency graph.
67 */
68 public static final GraphTokens WHITESPACE_TOKENS = new GraphTokens(" ", " ", " ", " ");
69
70 /**
71 * The standard ASCII tokens to use when outputing the dependency graph.
72 */
73 public static final GraphTokens STANDARD_TOKENS = new GraphTokens("+- ", "\\- ", "| ", " ");
74
75 /**
76 * The extended ASCII tokens to use when outputing the dependency graph.
77 */
78 public static final GraphTokens EXTENDED_TOKENS =
79 new GraphTokens("\u251C\u2500 ", "\u2514\u2500 ", "\u2502 ", " ");
80
81 // fields -----------------------------------------------------------------
82
83 /**
84 * The writer to serialize to.
85 */
86 private final PrintWriter writer;
87
88 /**
89 * The tokens to use when serializing the dependency graph.
90 */
91 private final GraphTokens tokens;
92
93 /**
94 * The depth of the currently visited dependency node.
95 */
96 private int depth;
97
98 // constructors -----------------------------------------------------------
99
100 /**
101 * Creates a dependency node visitor that serializes visited nodes to the specified writer using whitespace tokens.
102 *
103 * @param writer the writer to serialize to
104 */
105 public SerializingDependencyNodeVisitor(Writer writer) {
106 this(writer, WHITESPACE_TOKENS);
107 }
108
109 /**
110 * Creates a dependency node visitor that serializes visited nodes to the specified writer using the specified
111 * tokens.
112 *
113 * @param writer the writer to serialize to
114 * @param tokens the tokens to use when serializing the dependency graph
115 */
116 public SerializingDependencyNodeVisitor(Writer writer, GraphTokens tokens) {
117 if (writer instanceof PrintWriter) {
118 this.writer = (PrintWriter) writer;
119 } else {
120 this.writer = new PrintWriter(writer, true);
121 }
122
123 this.tokens = tokens;
124
125 depth = 0;
126 }
127
128 // DependencyNodeVisitor methods ------------------------------------------
129
130 /**
131 * {@inheritDoc}
132 */
133 @Override
134 public boolean visit(DependencyNode node) {
135 indent(node);
136
137 writer.println(node.toNodeString());
138
139 depth++;
140
141 return true;
142 }
143
144 /**
145 * {@inheritDoc}
146 */
147 @Override
148 public boolean endVisit(DependencyNode node) {
149 depth--;
150
151 return true;
152 }
153
154 // private methods --------------------------------------------------------
155
156 /**
157 * Writes the necessary tokens to indent the specified dependency node to this visitor's writer.
158 *
159 * @param node the dependency node to indent
160 */
161 private void indent(DependencyNode node) {
162 for (int i = 1; i < depth; i++) {
163 writer.write(tokens.getFillIndent(isLast(node, i)));
164 }
165
166 if (depth > 0) {
167 writer.write(tokens.getNodeIndent(isLast(node)));
168 }
169 }
170
171 /**
172 * Gets whether the specified dependency node is the last of its siblings.
173 *
174 * @param node the dependency node to check
175 * @return <code>true</code> if the specified dependency node is the last of its last siblings
176 */
177 private boolean isLast(DependencyNode node) {
178 // TODO: remove node argument and calculate from visitor calls only
179
180 DependencyNode parent = node.getParent();
181
182 boolean last;
183
184 if (parent == null) {
185 last = true;
186 } else {
187 List<DependencyNode> siblings = parent.getChildren();
188
189 last = (siblings.indexOf(node) == siblings.size() - 1);
190 }
191
192 return last;
193 }
194
195 /**
196 * Gets whether the specified dependency node ancestor is the last of its siblings.
197 *
198 * @param node the dependency node whose ancestor to check
199 * @param ancestorDepth the depth of the ancestor of the specified dependency node to check
200 * @return <code>true</code> if the specified dependency node ancestor is the last of its siblings
201 */
202 private boolean isLast(DependencyNode node, int ancestorDepth) {
203 // TODO: remove node argument and calculate from visitor calls only
204
205 int distance = depth - ancestorDepth;
206
207 while (distance-- > 0) {
208 node = node.getParent();
209 }
210
211 return isLast(node);
212 }
213 }