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.JavadocUtils; 032import com.puppycrawl.tools.checkstyle.utils.TokenUtils; 033 034/** 035 * Class for printing AST to String. 036 * @author Vladislav Lisetskii 037 */ 038public final class AstTreeStringPrinter { 039 040 /** Newline pattern. */ 041 private static final Pattern NEWLINE = Pattern.compile("\n"); 042 /** Return pattern. */ 043 private static final Pattern RETURN = Pattern.compile("\r"); 044 /** Tab pattern. */ 045 private static final Pattern TAB = Pattern.compile("\t"); 046 047 /** OS specific line separator. */ 048 private static final String LINE_SEPARATOR = System.getProperty("line.separator"); 049 050 /** Prevent instances. */ 051 private AstTreeStringPrinter() { 052 // no code 053 } 054 055 /** 056 * Parse a file and print the parse tree. 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 * @param file java file 071 * @return Full tree 072 * @throws IOException Failed to open a file 073 * @throws CheckstyleException error while parsing the file 074 */ 075 public static String printJavaAndJavadocTree(File file) 076 throws IOException, CheckstyleException { 077 final DetailAST tree = JavaParser.parseFile(file, JavaParser.Options.WITH_COMMENTS); 078 return printJavaAndJavadocTree(tree); 079 } 080 081 /** 082 * Prints full tree (java + comments + javadoc) of the DetailAST. 083 * @param ast root DetailAST 084 * @return Full tree 085 */ 086 private static String printJavaAndJavadocTree(DetailAST ast) { 087 final StringBuilder messageBuilder = new StringBuilder(1024); 088 DetailAST node = ast; 089 while (node != null) { 090 messageBuilder.append(getIndentation(node)) 091 .append(getNodeInfo(node)) 092 .append(LINE_SEPARATOR); 093 if (node.getType() == TokenTypes.COMMENT_CONTENT 094 && JavadocUtils.isJavadocComment(node.getParent())) { 095 final String javadocTree = parseAndPrintJavadocTree(node); 096 messageBuilder.append(javadocTree); 097 } 098 else { 099 messageBuilder.append(printJavaAndJavadocTree(node.getFirstChild())); 100 } 101 node = node.getNextSibling(); 102 } 103 return messageBuilder.toString(); 104 } 105 106 /** 107 * Parses block comment as javadoc and prints its tree. 108 * @param node block comment begin 109 * @return string javadoc tree 110 */ 111 private static String parseAndPrintJavadocTree(DetailAST node) { 112 final DetailAST javadocBlock = node.getParent(); 113 final DetailNode tree = DetailNodeTreeStringPrinter.parseJavadocAsDetailNode(javadocBlock); 114 115 String baseIndentation = getIndentation(node); 116 baseIndentation = baseIndentation.substring(0, baseIndentation.length() - 2); 117 final String rootPrefix = baseIndentation + " `--"; 118 final String prefix = baseIndentation + " "; 119 return DetailNodeTreeStringPrinter.printTree(tree, rootPrefix, prefix); 120 } 121 122 /** 123 * Parse a file and print the parse tree. 124 * @param text the text to parse. 125 * @param options {@link JavaParser.Options} to control the inclusion of comment nodes. 126 * @return the AST of the file in String form. 127 * @throws CheckstyleException if the file is not a Java source. 128 */ 129 public static String printAst(FileText text, JavaParser.Options options) 130 throws CheckstyleException { 131 final DetailAST ast = JavaParser.parseFileText(text, options); 132 return printTree(ast); 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(1024); 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 + " -> " + escapeAllControlChars(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(1024); 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 escaped symbols. 200 * @param text the String to process. 201 * @return the processed String with all control chars escaped. 202 */ 203 private static String escapeAllControlChars(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}