001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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 * 057 * @param file the file to print. 058 * @param options {@link JavaParser.Options} to control the inclusion of comment nodes. 059 * @return the AST of the file in String form. 060 * @throws IOException if the file could not be read. 061 * @throws CheckstyleException if the file is not a Java source. 062 */ 063 public static String printFileAst(File file, JavaParser.Options options) 064 throws IOException, CheckstyleException { 065 return printTree(JavaParser.parseFile(file, options)); 066 } 067 068 /** 069 * Prints full AST (java + comments + javadoc) of the java file. 070 * 071 * @param file java file 072 * @return Full tree 073 * @throws IOException Failed to open a file 074 * @throws CheckstyleException error while parsing the file 075 */ 076 public static String printJavaAndJavadocTree(File file) 077 throws IOException, CheckstyleException { 078 final DetailAST tree = JavaParser.parseFile(file, JavaParser.Options.WITH_COMMENTS); 079 return printJavaAndJavadocTree(tree); 080 } 081 082 /** 083 * Prints full tree (java + comments + javadoc) of the DetailAST. 084 * 085 * @param ast root DetailAST 086 * @return Full tree 087 */ 088 private static String printJavaAndJavadocTree(DetailAST ast) { 089 final StringBuilder messageBuilder = new StringBuilder(1024); 090 DetailAST node = ast; 091 while (node != null) { 092 messageBuilder.append(getIndentation(node)) 093 .append(getNodeInfo(node)) 094 .append(LINE_SEPARATOR); 095 if (node.getType() == TokenTypes.COMMENT_CONTENT 096 && JavadocUtil.isJavadocComment(node.getParent())) { 097 final String javadocTree = parseAndPrintJavadocTree(node); 098 messageBuilder.append(javadocTree); 099 } 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}