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