View Javadoc
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 }