blob: cb33d34334a5be76158e905b77ea5464a4b3aa64 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2017 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.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.regex.Pattern;
import antlr.RecognitionException;
import antlr.TokenStreamException;
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.FileContents;
import com.puppycrawl.tools.checkstyle.api.FileText;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.utils.JavadocUtils;
import com.puppycrawl.tools.checkstyle.utils.TokenUtils;
/**
* Class for printing AST to String.
* @author Vladislav Lisetskii
*/
public final class AstTreeStringPrinter {
/**
* Enum to be used for test if comments should be printed.
*/
public enum PrintOptions {
/**
* Comments has to be printed.
*/
WITH_COMMENTS,
/**
* Comments has NOT to be printed.
*/
WITHOUT_COMMENTS
}
/** 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.getProperty("line.separator");
/** Prevent instances. */
private AstTreeStringPrinter() {
// no code
}
/**
* Parse a file and print the parse tree.
* @param file the file to print.
* @param withComments true to include comments to AST
* @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, PrintOptions withComments)
throws IOException, CheckstyleException {
return printTree(parseFile(file, withComments));
}
/**
* 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 = parseFile(file, PrintOptions.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
&& JavadocUtils.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 withComments true to include comments to AST
* @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,
PrintOptions withComments) throws CheckstyleException {
return printTree(parseFileText(text, withComments));
}
/**
* 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 TokenUtils.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");
}
/**
* Parse a file and return the parse tree.
* @param file the file to parse.
* @param withComments true to include comment nodes to the tree
* @return the root node of the parse tree.
* @throws IOException if the file could not be read.
* @throws CheckstyleException if the file is not a Java source.
*/
private static DetailAST parseFile(File file, PrintOptions withComments)
throws IOException, CheckstyleException {
final FileText text = new FileText(file.getAbsoluteFile(),
System.getProperty("file.encoding", StandardCharsets.UTF_8.name()));
return parseFileText(text, withComments);
}
/**
* Parse a text and return the parse tree.
* @param text the text to parse.
* @param withComments true to include comment nodes to the tree
* @return the root node of the parse tree.
* @throws CheckstyleException if the file is not a Java source.
*/
private static DetailAST parseFileText(FileText text, PrintOptions withComments)
throws CheckstyleException {
final FileContents contents = new FileContents(text);
final DetailAST result;
try {
if (withComments == PrintOptions.WITH_COMMENTS) {
result = TreeWalker.parseWithComments(contents);
}
else {
result = TreeWalker.parse(contents);
}
}
catch (RecognitionException | TokenStreamException ex) {
final String exceptionMsg = String.format(Locale.ROOT,
"%s occurred during the analysis of file %s.",
ex.getClass().getSimpleName(), text.getFile().getPath());
throw new CheckstyleException(exceptionMsg, ex);
}
return result;
}
}