001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle; 021 022import java.io.File; 023import java.io.IOException; 024import java.util.regex.Pattern; 025 026import com.puppycrawl.tools.checkstyle.api.CheckstyleException; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.DetailNode; 029import com.puppycrawl.tools.checkstyle.api.FileText; 030import com.puppycrawl.tools.checkstyle.api.TokenTypes; 031import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 032import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 033 034/** 035 * Class for printing AST to String. 036 */ 037public final class AstTreeStringPrinter { 038 039 /** Newline pattern. */ 040 private static final Pattern NEWLINE = Pattern.compile("\n"); 041 /** Return pattern. */ 042 private static final Pattern RETURN = Pattern.compile("\r"); 043 /** Tab pattern. */ 044 private static final Pattern TAB = Pattern.compile("\t"); 045 046 /** OS specific line separator. */ 047 private static final String LINE_SEPARATOR = System.lineSeparator(); 048 049 /** Prevent instances. */ 050 private AstTreeStringPrinter() { 051 // no code 052 } 053 054 /** 055 * Parse a file and print the parse tree. 056 * @param file the file to print. 057 * @param options {@link JavaParser.Options} to control the inclusion of comment nodes. 058 * @return the AST of the file in String form. 059 * @throws IOException if the file could not be read. 060 * @throws CheckstyleException if the file is not a Java source. 061 */ 062 public static String printFileAst(File file, JavaParser.Options options) 063 throws IOException, CheckstyleException { 064 return printTree(JavaParser.parseFile(file, options)); 065 } 066 067 /** 068 * Prints full AST (java + comments + javadoc) of the java file. 069 * @param file java file 070 * @return Full tree 071 * @throws IOException Failed to open a file 072 * @throws CheckstyleException error while parsing the file 073 */ 074 public static String printJavaAndJavadocTree(File file) 075 throws IOException, CheckstyleException { 076 final DetailAST tree = JavaParser.parseFile(file, JavaParser.Options.WITH_COMMENTS); 077 return printJavaAndJavadocTree(tree); 078 } 079 080 /** 081 * Prints full tree (java + comments + javadoc) of the DetailAST. 082 * @param ast root DetailAST 083 * @return Full tree 084 */ 085 private static String printJavaAndJavadocTree(DetailAST ast) { 086 final StringBuilder messageBuilder = new StringBuilder(1024); 087 DetailAST node = ast; 088 while (node != null) { 089 messageBuilder.append(getIndentation(node)) 090 .append(getNodeInfo(node)) 091 .append(LINE_SEPARATOR); 092 if (node.getType() == TokenTypes.COMMENT_CONTENT 093 && JavadocUtil.isJavadocComment(node.getParent())) { 094 final String javadocTree = parseAndPrintJavadocTree(node); 095 messageBuilder.append(javadocTree); 096 } 097 else { 098 messageBuilder.append(printJavaAndJavadocTree(node.getFirstChild())); 099 } 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 branch info from root down to given {@code node}. 136 * @param node last item of the branch 137 * @return branch as string 138 */ 139 public static String printBranch(DetailAST node) { 140 final String result; 141 if (node == null) { 142 result = ""; 143 } 144 else { 145 result = printBranch(node.getParent()) 146 + getIndentation(node) 147 + getNodeInfo(node) 148 + LINE_SEPARATOR; 149 } 150 return result; 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 TokenUtil.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}