001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 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.getProperty("line.separator"); 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 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}