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