AstTreeStringPrinter.java
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2021 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
////////////////////////////////////////////////////////////////////////////////
package com.puppycrawl.tools.checkstyle;
import java.io.File;
import java.io.IOException;
import java.util.regex.Pattern;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.DetailNode;
import com.puppycrawl.tools.checkstyle.api.FileText;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
/**
* Class for printing AST to String.
*/
public final class AstTreeStringPrinter {
/** Newline pattern. */
private static final Pattern NEWLINE = Pattern.compile("\n");
/** Return pattern. */
private static final Pattern RETURN = Pattern.compile("\r");
/** Tab pattern. */
private static final Pattern TAB = Pattern.compile("\t");
/** OS specific line separator. */
private static final String LINE_SEPARATOR = System.lineSeparator();
/** Prevent instances. */
private AstTreeStringPrinter() {
// no code
}
/**
* Parse a file and print the parse tree.
*
* @param file the file to print.
* @param options {@link JavaParser.Options} to control the inclusion of comment nodes.
* @return the AST of the file in String form.
* @throws IOException if the file could not be read.
* @throws CheckstyleException if the file is not a Java source.
*/
public static String printFileAst(File file, JavaParser.Options options)
throws IOException, CheckstyleException {
return printTree(JavaParser.parseFile(file, options));
}
/**
* Prints full AST (java + comments + javadoc) of the java file.
*
* @param file java file
* @return Full tree
* @throws IOException Failed to open a file
* @throws CheckstyleException error while parsing the file
*/
public static String printJavaAndJavadocTree(File file)
throws IOException, CheckstyleException {
final DetailAST tree = JavaParser.parseFile(file, JavaParser.Options.WITH_COMMENTS);
return printJavaAndJavadocTree(tree);
}
/**
* Prints full tree (java + comments + javadoc) of the DetailAST.
*
* @param ast root DetailAST
* @return Full tree
*/
private static String printJavaAndJavadocTree(DetailAST ast) {
final StringBuilder messageBuilder = new StringBuilder(1024);
DetailAST node = ast;
while (node != null) {
messageBuilder.append(getIndentation(node))
.append(getNodeInfo(node))
.append(LINE_SEPARATOR);
if (node.getType() == TokenTypes.COMMENT_CONTENT
&& JavadocUtil.isJavadocComment(node.getParent())) {
final String javadocTree = parseAndPrintJavadocTree(node);
messageBuilder.append(javadocTree);
}
else {
messageBuilder.append(printJavaAndJavadocTree(node.getFirstChild()));
}
node = node.getNextSibling();
}
return messageBuilder.toString();
}
/**
* Parses block comment as javadoc and prints its tree.
*
* @param node block comment begin
* @return string javadoc tree
*/
private static String parseAndPrintJavadocTree(DetailAST node) {
final DetailAST javadocBlock = node.getParent();
final DetailNode tree = DetailNodeTreeStringPrinter.parseJavadocAsDetailNode(javadocBlock);
String baseIndentation = getIndentation(node);
baseIndentation = baseIndentation.substring(0, baseIndentation.length() - 2);
final String rootPrefix = baseIndentation + " `--";
final String prefix = baseIndentation + " ";
return DetailNodeTreeStringPrinter.printTree(tree, rootPrefix, prefix);
}
/**
* Parse a file and print the parse tree.
*
* @param text the text to parse.
* @param options {@link JavaParser.Options} to control the inclusion of comment nodes.
* @return the AST of the file in String form.
* @throws CheckstyleException if the file is not a Java source.
*/
public static String printAst(FileText text, JavaParser.Options options)
throws CheckstyleException {
final DetailAST ast = JavaParser.parseFileText(text, options);
return printTree(ast);
}
/**
* Print branch info from root down to given {@code node}.
*
* @param node last item of the branch
* @return branch as string
*/
public static String printBranch(DetailAST node) {
final String result;
if (node == null) {
result = "";
}
else {
result = printBranch(node.getParent())
+ getIndentation(node)
+ getNodeInfo(node)
+ LINE_SEPARATOR;
}
return result;
}
/**
* Print AST.
*
* @param ast the root AST node.
* @return string AST.
*/
private static String printTree(DetailAST ast) {
final StringBuilder messageBuilder = new StringBuilder(1024);
DetailAST node = ast;
while (node != null) {
messageBuilder.append(getIndentation(node))
.append(getNodeInfo(node))
.append(LINE_SEPARATOR)
.append(printTree(node.getFirstChild()));
node = node.getNextSibling();
}
return messageBuilder.toString();
}
/**
* Get string representation of the node as token name,
* node text, line number and column number.
*
* @param node DetailAST
* @return node info
*/
private static String getNodeInfo(DetailAST node) {
return TokenUtil.getTokenName(node.getType())
+ " -> " + escapeAllControlChars(node.getText())
+ " [" + node.getLineNo() + ':' + node.getColumnNo() + ']';
}
/**
* Get indentation for an AST node.
*
* @param ast the AST to get the indentation for.
* @return the indentation in String format.
*/
private static String getIndentation(DetailAST ast) {
final boolean isLastChild = ast.getNextSibling() == null;
DetailAST node = ast;
final StringBuilder indentation = new StringBuilder(1024);
while (node.getParent() != null) {
node = node.getParent();
if (node.getParent() == null) {
if (isLastChild) {
// only ASCII symbols must be used due to
// problems with running tests on Windows
indentation.append("`--");
}
else {
indentation.append("|--");
}
}
else {
if (node.getNextSibling() == null) {
indentation.insert(0, " ");
}
else {
indentation.insert(0, "| ");
}
}
}
return indentation.toString();
}
/**
* Replace all control chars with escaped symbols.
*
* @param text the String to process.
* @return the processed String with all control chars escaped.
*/
private static String escapeAllControlChars(String text) {
final String textWithoutNewlines = NEWLINE.matcher(text).replaceAll("\\\\n");
final String textWithoutReturns = RETURN.matcher(textWithoutNewlines).replaceAll("\\\\r");
return TAB.matcher(textWithoutReturns).replaceAll("\\\\t");
}
}