001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 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.Locale; 025import java.util.regex.Pattern; 026 027import antlr.RecognitionException; 028import antlr.TokenStreamException; 029import com.puppycrawl.tools.checkstyle.api.CheckstyleException; 030import com.puppycrawl.tools.checkstyle.api.DetailAST; 031import com.puppycrawl.tools.checkstyle.api.DetailNode; 032import com.puppycrawl.tools.checkstyle.api.FileContents; 033import com.puppycrawl.tools.checkstyle.api.FileText; 034import com.puppycrawl.tools.checkstyle.api.TokenTypes; 035import com.puppycrawl.tools.checkstyle.utils.JavadocUtils; 036import com.puppycrawl.tools.checkstyle.utils.TokenUtils; 037 038/** 039 * Class for printing AST to String. 040 * @author Vladislav Lisetskii 041 */ 042public final class AstTreeStringPrinter { 043 044 /** Newline pattern. */ 045 private static final Pattern NEWLINE = Pattern.compile("\n"); 046 /** Return pattern. */ 047 private static final Pattern RETURN = Pattern.compile("\r"); 048 /** Tab pattern. */ 049 private static final Pattern TAB = Pattern.compile("\t"); 050 051 /** OS specific line separator. */ 052 private static final String LINE_SEPARATOR = System.getProperty("line.separator"); 053 054 /** Prevent instances. */ 055 private AstTreeStringPrinter() { 056 // no code 057 } 058 059 /** 060 * Parse a file and print the parse tree. 061 * @param file the file to print. 062 * @param withComments true to include comments to AST 063 * @return the AST of the file in String form. 064 * @throws IOException if the file could not be read. 065 * @throws CheckstyleException if the file is not a Java source. 066 */ 067 public static String printFileAst(File file, boolean withComments) 068 throws IOException, CheckstyleException { 069 return printTree(parseFile(file, withComments)); 070 } 071 072 /** 073 * Prints full AST (java + comments + javadoc) of the java file. 074 * @param file java file 075 * @return Full tree 076 * @throws IOException Failed to open a file 077 * @throws CheckstyleException error while parsing the file 078 */ 079 public static String printJavaAndJavadocTree(File file) 080 throws IOException, CheckstyleException { 081 final DetailAST tree = parseFile(file, true); 082 return printJavaAndJavadocTree(tree); 083 } 084 085 /** 086 * Prints full tree (java + comments + javadoc) of the DetailAST. 087 * @param ast root DetailAST 088 * @return Full tree 089 */ 090 private static String printJavaAndJavadocTree(DetailAST ast) { 091 final StringBuilder messageBuilder = new StringBuilder(); 092 DetailAST node = ast; 093 while (node != null) { 094 messageBuilder.append(getIndentation(node)) 095 .append(getNodeInfo(node)) 096 .append(LINE_SEPARATOR); 097 if (node.getType() == TokenTypes.COMMENT_CONTENT 098 && JavadocUtils.isJavadocComment(node.getParent())) { 099 final String javadocTree = parseAndPrintJavadocTree(node); 100 messageBuilder.append(javadocTree); 101 } 102 else { 103 messageBuilder.append(printJavaAndJavadocTree(node.getFirstChild())); 104 } 105 node = node.getNextSibling(); 106 } 107 return messageBuilder.toString(); 108 } 109 110 /** 111 * Parses block comment as javadoc and prints its tree. 112 * @param node block comment begin 113 * @return string javadoc tree 114 */ 115 private static String parseAndPrintJavadocTree(DetailAST node) { 116 final DetailAST javadocBlock = node.getParent(); 117 final DetailNode tree = DetailNodeTreeStringPrinter.parseJavadocAsDetailNode(javadocBlock); 118 119 String baseIndentation = getIndentation(node); 120 baseIndentation = baseIndentation.substring(0, baseIndentation.length() - 2); 121 final String rootPrefix = baseIndentation + " `--"; 122 final String prefix = baseIndentation + " "; 123 return DetailNodeTreeStringPrinter.printTree(tree, rootPrefix, prefix); 124 } 125 126 /** 127 * Parse a file and print the parse tree. 128 * @param text the text to parse. 129 * @param withComments true to include comments to AST 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, boolean withComments) throws CheckstyleException { 134 return printTree(parseFileText(text, withComments)); 135 } 136 137 /** 138 * Print AST. 139 * @param ast the root AST node. 140 * @return string AST. 141 */ 142 private static String printTree(DetailAST ast) { 143 final StringBuilder messageBuilder = new StringBuilder(); 144 DetailAST node = ast; 145 while (node != null) { 146 messageBuilder.append(getIndentation(node)) 147 .append(getNodeInfo(node)) 148 .append(LINE_SEPARATOR) 149 .append(printTree(node.getFirstChild())); 150 node = node.getNextSibling(); 151 } 152 return messageBuilder.toString(); 153 } 154 155 /** 156 * Get string representation of the node as token name, 157 * node text, line number and column number. 158 * @param node DetailAST 159 * @return node info 160 */ 161 private static String getNodeInfo(DetailAST node) { 162 return TokenUtils.getTokenName(node.getType()) 163 + " -> " + escapeAllControlChars(node.getText()) 164 + " [" + node.getLineNo() + ':' + node.getColumnNo() + ']'; 165 } 166 167 /** 168 * Get indentation for an AST node. 169 * @param ast the AST to get the indentation for. 170 * @return the indentation in String format. 171 */ 172 private static String getIndentation(DetailAST ast) { 173 final boolean isLastChild = ast.getNextSibling() == null; 174 DetailAST node = ast; 175 final StringBuilder indentation = new StringBuilder(); 176 while (node.getParent() != null) { 177 node = node.getParent(); 178 if (node.getParent() == null) { 179 if (isLastChild) { 180 // only ASCII symbols must be used due to 181 // problems with running tests on Windows 182 indentation.append("`--"); 183 } 184 else { 185 indentation.append("|--"); 186 } 187 } 188 else { 189 if (node.getNextSibling() == null) { 190 indentation.insert(0, " "); 191 } 192 else { 193 indentation.insert(0, "| "); 194 } 195 } 196 } 197 return indentation.toString(); 198 } 199 200 /** 201 * Replace all control chars with escaped symbols. 202 * @param text the String to process. 203 * @return the processed String with all control chars escaped. 204 */ 205 private static String escapeAllControlChars(String text) { 206 final String textWithoutNewlines = NEWLINE.matcher(text).replaceAll("\\\\n"); 207 final String textWithoutReturns = RETURN.matcher(textWithoutNewlines).replaceAll("\\\\r"); 208 return TAB.matcher(textWithoutReturns).replaceAll("\\\\t"); 209 } 210 211 /** 212 * Parse a file and return the parse tree. 213 * @param file the file to parse. 214 * @param withComments true to include comment nodes to the tree 215 * @return the root node of the parse tree. 216 * @throws IOException if the file could not be read. 217 * @throws CheckstyleException if the file is not a Java source. 218 */ 219 private static DetailAST parseFile(File file, boolean withComments) 220 throws IOException, CheckstyleException { 221 final FileText text = new FileText(file.getAbsoluteFile(), 222 System.getProperty("file.encoding", "UTF-8")); 223 return parseFileText(text, withComments); 224 } 225 226 /** 227 * Parse a text and return the parse tree. 228 * @param text the text to parse. 229 * @param withComments true to include comment nodes to the tree 230 * @return the root node of the parse tree. 231 * @throws CheckstyleException if the file is not a Java source. 232 */ 233 private static DetailAST parseFileText(FileText text, boolean withComments) 234 throws CheckstyleException { 235 final FileContents contents = new FileContents(text); 236 final DetailAST result; 237 try { 238 if (withComments) { 239 result = TreeWalker.parseWithComments(contents); 240 } 241 else { 242 result = TreeWalker.parse(contents); 243 } 244 } 245 catch (RecognitionException | TokenStreamException ex) { 246 final String exceptionMsg = String.format(Locale.ROOT, 247 "%s occurred during the analysis of file %s.", 248 ex.getClass().getSimpleName(), text.getFile().getPath()); 249 throw new CheckstyleException(exceptionMsg, ex); 250 } 251 252 return result; 253 } 254}