View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.nio.charset.StandardCharsets;
25  import java.util.Locale;
26  import java.util.regex.Pattern;
27  
28  import antlr.RecognitionException;
29  import antlr.TokenStreamException;
30  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
31  import com.puppycrawl.tools.checkstyle.api.DetailAST;
32  import com.puppycrawl.tools.checkstyle.api.DetailNode;
33  import com.puppycrawl.tools.checkstyle.api.FileContents;
34  import com.puppycrawl.tools.checkstyle.api.FileText;
35  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
36  import com.puppycrawl.tools.checkstyle.utils.JavadocUtils;
37  import com.puppycrawl.tools.checkstyle.utils.TokenUtils;
38  
39  /**
40   * Class for printing AST to String.
41   * @author Vladislav Lisetskii
42   */
43  public final class AstTreeStringPrinter {
44  
45      /**
46       * Enum to be used for test if comments should be printed.
47       */
48      public enum PrintOptions {
49          /**
50           * Comments has to be printed.
51           */
52          WITH_COMMENTS,
53          /**
54           * Comments has NOT to be printed.
55           */
56          WITHOUT_COMMENTS
57      }
58  
59      /** Newline pattern. */
60      private static final Pattern NEWLINE = Pattern.compile("\n");
61      /** Return pattern. */
62      private static final Pattern RETURN = Pattern.compile("\r");
63      /** Tab pattern. */
64      private static final Pattern TAB = Pattern.compile("\t");
65  
66      /** OS specific line separator. */
67      private static final String LINE_SEPARATOR = System.getProperty("line.separator");
68  
69      /** Prevent instances. */
70      private AstTreeStringPrinter() {
71          // no code
72      }
73  
74      /**
75       * Parse a file and print the parse tree.
76       * @param file the file to print.
77       * @param withComments true to include comments to AST
78       * @return the AST of the file in String form.
79       * @throws IOException if the file could not be read.
80       * @throws CheckstyleException if the file is not a Java source.
81       */
82      public static String printFileAst(File file, PrintOptions withComments)
83              throws IOException, CheckstyleException {
84          return printTree(parseFile(file, withComments));
85      }
86  
87      /**
88       * Prints full AST (java + comments + javadoc) of the java file.
89       * @param file java file
90       * @return Full tree
91       * @throws IOException Failed to open a file
92       * @throws CheckstyleException error while parsing the file
93       */
94      public static String printJavaAndJavadocTree(File file)
95              throws IOException, CheckstyleException {
96          final DetailAST tree = parseFile(file, PrintOptions.WITH_COMMENTS);
97          return printJavaAndJavadocTree(tree);
98      }
99  
100     /**
101      * Prints full tree (java + comments + javadoc) of the DetailAST.
102      * @param ast root DetailAST
103      * @return Full tree
104      */
105     private static String printJavaAndJavadocTree(DetailAST ast) {
106         final StringBuilder messageBuilder = new StringBuilder(1024);
107         DetailAST node = ast;
108         while (node != null) {
109             messageBuilder.append(getIndentation(node))
110                 .append(getNodeInfo(node))
111                 .append(LINE_SEPARATOR);
112             if (node.getType() == TokenTypes.COMMENT_CONTENT
113                     && JavadocUtils.isJavadocComment(node.getParent())) {
114                 final String javadocTree = parseAndPrintJavadocTree(node);
115                 messageBuilder.append(javadocTree);
116             }
117             else {
118                 messageBuilder.append(printJavaAndJavadocTree(node.getFirstChild()));
119             }
120             node = node.getNextSibling();
121         }
122         return messageBuilder.toString();
123     }
124 
125     /**
126      * Parses block comment as javadoc and prints its tree.
127      * @param node block comment begin
128      * @return string javadoc tree
129      */
130     private static String parseAndPrintJavadocTree(DetailAST node) {
131         final DetailAST javadocBlock = node.getParent();
132         final DetailNode tree = DetailNodeTreeStringPrinter.parseJavadocAsDetailNode(javadocBlock);
133 
134         String baseIndentation = getIndentation(node);
135         baseIndentation = baseIndentation.substring(0, baseIndentation.length() - 2);
136         final String rootPrefix = baseIndentation + "   `--";
137         final String prefix = baseIndentation + "       ";
138         return DetailNodeTreeStringPrinter.printTree(tree, rootPrefix, prefix);
139     }
140 
141     /**
142      * Parse a file and print the parse tree.
143      * @param text the text to parse.
144      * @param withComments true to include comments to AST
145      * @return the AST of the file in String form.
146      * @throws CheckstyleException if the file is not a Java source.
147      */
148     public static String printAst(FileText text,
149                                   PrintOptions withComments) throws CheckstyleException {
150         return printTree(parseFileText(text, withComments));
151     }
152 
153     /**
154      * Print AST.
155      * @param ast the root AST node.
156      * @return string AST.
157      */
158     private static String printTree(DetailAST ast) {
159         final StringBuilder messageBuilder = new StringBuilder(1024);
160         DetailAST node = ast;
161         while (node != null) {
162             messageBuilder.append(getIndentation(node))
163                     .append(getNodeInfo(node))
164                     .append(LINE_SEPARATOR)
165                     .append(printTree(node.getFirstChild()));
166             node = node.getNextSibling();
167         }
168         return messageBuilder.toString();
169     }
170 
171     /**
172      * Get string representation of the node as token name,
173      * node text, line number and column number.
174      * @param node DetailAST
175      * @return node info
176      */
177     private static String getNodeInfo(DetailAST node) {
178         return TokenUtils.getTokenName(node.getType())
179                 + " -> " + escapeAllControlChars(node.getText())
180                 + " [" + node.getLineNo() + ':' + node.getColumnNo() + ']';
181     }
182 
183     /**
184      * Get indentation for an AST node.
185      * @param ast the AST to get the indentation for.
186      * @return the indentation in String format.
187      */
188     private static String getIndentation(DetailAST ast) {
189         final boolean isLastChild = ast.getNextSibling() == null;
190         DetailAST node = ast;
191         final StringBuilder indentation = new StringBuilder(1024);
192         while (node.getParent() != null) {
193             node = node.getParent();
194             if (node.getParent() == null) {
195                 if (isLastChild) {
196                     // only ASCII symbols must be used due to
197                     // problems with running tests on Windows
198                     indentation.append("`--");
199                 }
200                 else {
201                     indentation.append("|--");
202                 }
203             }
204             else {
205                 if (node.getNextSibling() == null) {
206                     indentation.insert(0, "    ");
207                 }
208                 else {
209                     indentation.insert(0, "|   ");
210                 }
211             }
212         }
213         return indentation.toString();
214     }
215 
216     /**
217      * Replace all control chars with escaped symbols.
218      * @param text the String to process.
219      * @return the processed String with all control chars escaped.
220      */
221     private static String escapeAllControlChars(String text) {
222         final String textWithoutNewlines = NEWLINE.matcher(text).replaceAll("\\\\n");
223         final String textWithoutReturns = RETURN.matcher(textWithoutNewlines).replaceAll("\\\\r");
224         return TAB.matcher(textWithoutReturns).replaceAll("\\\\t");
225     }
226 
227     /**
228      * Parse a file and return the parse tree.
229      * @param file the file to parse.
230      * @param withComments true to include comment nodes to the tree
231      * @return the root node of the parse tree.
232      * @throws IOException if the file could not be read.
233      * @throws CheckstyleException if the file is not a Java source.
234      */
235     private static DetailAST parseFile(File file, PrintOptions withComments)
236             throws IOException, CheckstyleException {
237         final FileText text = new FileText(file.getAbsoluteFile(),
238             System.getProperty("file.encoding", StandardCharsets.UTF_8.name()));
239         return parseFileText(text, withComments);
240     }
241 
242     /**
243      * Parse a text and return the parse tree.
244      * @param text the text to parse.
245      * @param withComments true to include comment nodes to the tree
246      * @return the root node of the parse tree.
247      * @throws CheckstyleException if the file is not a Java source.
248      */
249     private static DetailAST parseFileText(FileText text, PrintOptions withComments)
250             throws CheckstyleException {
251         final FileContents contents = new FileContents(text);
252         final DetailAST result;
253         try {
254             if (withComments == PrintOptions.WITH_COMMENTS) {
255                 result = TreeWalker.parseWithComments(contents);
256             }
257             else {
258                 result = TreeWalker.parse(contents);
259             }
260         }
261         catch (RecognitionException | TokenStreamException ex) {
262             final String exceptionMsg = String.format(Locale.ROOT,
263                 "%s occurred during the analysis of file %s.",
264                 ex.getClass().getSimpleName(), text.getFile().getPath());
265             throw new CheckstyleException(exceptionMsg, ex);
266         }
267 
268         return result;
269     }
270 }