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