1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 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.util.regex.Pattern;
25  
26  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
27  import com.puppycrawl.tools.checkstyle.api.DetailAST;
28  import com.puppycrawl.tools.checkstyle.api.DetailNode;
29  import com.puppycrawl.tools.checkstyle.api.FileText;
30  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
31  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
32  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
33  
34  /**
35   * Class for printing AST to String.
36   */
37  public final class AstTreeStringPrinter {
38  
39      /** Newline pattern. */
40      private static final Pattern NEWLINE = Pattern.compile("\n");
41      /** Return pattern. */
42      private static final Pattern RETURN = Pattern.compile("\r");
43      /** Tab pattern. */
44      private static final Pattern TAB = Pattern.compile("\t");
45  
46      /** OS specific line separator. */
47      private static final String LINE_SEPARATOR = System.getProperty("line.separator");
48  
49      /** Prevent instances. */
50      private AstTreeStringPrinter() {
51          // no code
52      }
53  
54      /**
55       * Parse a file and print the parse tree.
56       * @param file the file to print.
57       * @param options {@link JavaParser.Options} to control the inclusion of comment nodes.
58       * @return the AST of the file in String form.
59       * @throws IOException if the file could not be read.
60       * @throws CheckstyleException if the file is not a Java source.
61       */
62      public static String printFileAst(File file, JavaParser.Options options)
63              throws IOException, CheckstyleException {
64          return printTree(JavaParser.parseFile(file, options));
65      }
66  
67      /**
68       * Prints full AST (java + comments + javadoc) of the java file.
69       * @param file java file
70       * @return Full tree
71       * @throws IOException Failed to open a file
72       * @throws CheckstyleException error while parsing the file
73       */
74      public static String printJavaAndJavadocTree(File file)
75              throws IOException, CheckstyleException {
76          final DetailAST tree = JavaParser.parseFile(file, JavaParser.Options.WITH_COMMENTS);
77          return printJavaAndJavadocTree(tree);
78      }
79  
80      /**
81       * Prints full tree (java + comments + javadoc) of the DetailAST.
82       * @param ast root DetailAST
83       * @return Full tree
84       */
85      private static String printJavaAndJavadocTree(DetailAST ast) {
86          final StringBuilder messageBuilder = new StringBuilder(1024);
87          DetailAST node = ast;
88          while (node != null) {
89              messageBuilder.append(getIndentation(node))
90                  .append(getNodeInfo(node))
91                  .append(LINE_SEPARATOR);
92              if (node.getType() == TokenTypes.COMMENT_CONTENT
93                      && JavadocUtil.isJavadocComment(node.getParent())) {
94                  final String javadocTree = parseAndPrintJavadocTree(node);
95                  messageBuilder.append(javadocTree);
96              }
97              else {
98                  messageBuilder.append(printJavaAndJavadocTree(node.getFirstChild()));
99              }
100             node = node.getNextSibling();
101         }
102         return messageBuilder.toString();
103     }
104 
105     /**
106      * Parses block comment as javadoc and prints its tree.
107      * @param node block comment begin
108      * @return string javadoc tree
109      */
110     private static String parseAndPrintJavadocTree(DetailAST node) {
111         final DetailAST javadocBlock = node.getParent();
112         final DetailNode tree = DetailNodeTreeStringPrinter.parseJavadocAsDetailNode(javadocBlock);
113 
114         String baseIndentation = getIndentation(node);
115         baseIndentation = baseIndentation.substring(0, baseIndentation.length() - 2);
116         final String rootPrefix = baseIndentation + "   `--";
117         final String prefix = baseIndentation + "       ";
118         return DetailNodeTreeStringPrinter.printTree(tree, rootPrefix, prefix);
119     }
120 
121     /**
122      * Parse a file and print the parse tree.
123      * @param text the text to parse.
124      * @param options {@link JavaParser.Options} to control the inclusion of comment nodes.
125      * @return the AST of the file in String form.
126      * @throws CheckstyleException if the file is not a Java source.
127      */
128     public static String printAst(FileText text, JavaParser.Options options)
129             throws CheckstyleException {
130         final DetailAST ast = JavaParser.parseFileText(text, options);
131         return printTree(ast);
132     }
133 
134     /**
135      * Print AST.
136      * @param ast the root AST node.
137      * @return string AST.
138      */
139     private static String printTree(DetailAST ast) {
140         final StringBuilder messageBuilder = new StringBuilder(1024);
141         DetailAST node = ast;
142         while (node != null) {
143             messageBuilder.append(getIndentation(node))
144                     .append(getNodeInfo(node))
145                     .append(LINE_SEPARATOR)
146                     .append(printTree(node.getFirstChild()));
147             node = node.getNextSibling();
148         }
149         return messageBuilder.toString();
150     }
151 
152     /**
153      * Get string representation of the node as token name,
154      * node text, line number and column number.
155      * @param node DetailAST
156      * @return node info
157      */
158     private static String getNodeInfo(DetailAST node) {
159         return TokenUtil.getTokenName(node.getType())
160                 + " -> " + escapeAllControlChars(node.getText())
161                 + " [" + node.getLineNo() + ':' + node.getColumnNo() + ']';
162     }
163 
164     /**
165      * Get indentation for an AST node.
166      * @param ast the AST to get the indentation for.
167      * @return the indentation in String format.
168      */
169     private static String getIndentation(DetailAST ast) {
170         final boolean isLastChild = ast.getNextSibling() == null;
171         DetailAST node = ast;
172         final StringBuilder indentation = new StringBuilder(1024);
173         while (node.getParent() != null) {
174             node = node.getParent();
175             if (node.getParent() == null) {
176                 if (isLastChild) {
177                     // only ASCII symbols must be used due to
178                     // problems with running tests on Windows
179                     indentation.append("`--");
180                 }
181                 else {
182                     indentation.append("|--");
183                 }
184             }
185             else {
186                 if (node.getNextSibling() == null) {
187                     indentation.insert(0, "    ");
188                 }
189                 else {
190                     indentation.insert(0, "|   ");
191                 }
192             }
193         }
194         return indentation.toString();
195     }
196 
197     /**
198      * Replace all control chars with escaped symbols.
199      * @param text the String to process.
200      * @return the processed String with all control chars escaped.
201      */
202     private static String escapeAllControlChars(String text) {
203         final String textWithoutNewlines = NEWLINE.matcher(text).replaceAll("\\\\n");
204         final String textWithoutReturns = RETURN.matcher(textWithoutNewlines).replaceAll("\\\\r");
205         return TAB.matcher(textWithoutReturns).replaceAll("\\\\t");
206     }
207 
208 }