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;
029import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
030import com.puppycrawl.tools.checkstyle.api.DetailAST;
031import com.puppycrawl.tools.checkstyle.api.FileContents;
032import com.puppycrawl.tools.checkstyle.api.FileText;
033import com.puppycrawl.tools.checkstyle.utils.TokenUtils;
034
035/**
036 * Class for printing AST to String.
037 * @author Vladislav Lisetskii
038 */
039public final class AstTreeStringPrinter {
040
041    /** Newline pattern. */
042    private static final Pattern NEWLINE = Pattern.compile("\n");
043    /** Return pattern. */
044    private static final Pattern RETURN = Pattern.compile("\r");
045    /** Tab pattern. */
046    private static final Pattern TAB = Pattern.compile("\t");
047
048    /** OS specific line separator. */
049    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
050
051    /** Prevent instances. */
052    private AstTreeStringPrinter() {
053        // no code
054    }
055
056    /**
057     * Parse a file and print the parse tree.
058     * @param file the file to print.
059     * @param withComments true to include comments to AST
060     * @return the AST of the file in String form.
061     * @throws IOException if the file could not be read.
062     * @throws CheckstyleException if the file is not a Java source.
063     */
064    public static String printFileAst(File file, boolean withComments)
065            throws IOException, CheckstyleException {
066        return printTree(parseFile(file, withComments));
067    }
068
069    /**
070     * Print AST.
071     * @param ast the root AST node.
072     * @return string AST.
073     */
074    private static String printTree(DetailAST ast) {
075        final StringBuilder messageBuilder = new StringBuilder();
076        DetailAST node = ast;
077        while (node != null) {
078            messageBuilder.append(getIndentation(node))
079                    .append(TokenUtils.getTokenName(node.getType())).append(" -> ")
080                    .append(excapeAllControlChars(node.getText())).append(" [")
081                    .append(node.getLineNo()).append(':').append(node.getColumnNo()).append(']')
082                    .append(LINE_SEPARATOR)
083                    .append(printTree(node.getFirstChild()));
084            node = node.getNextSibling();
085        }
086        return messageBuilder.toString();
087    }
088
089    /**
090     * Get indentation for an AST node.
091     * @param ast the AST to get the indentation for.
092     * @return the indentation in String format.
093     */
094    private static String getIndentation(DetailAST ast) {
095        final boolean isLastChild = ast.getNextSibling() == null;
096        DetailAST node = ast;
097        final StringBuilder indentation = new StringBuilder();
098        while (node.getParent() != null) {
099            node = node.getParent();
100            if (node.getParent() == null) {
101                if (isLastChild) {
102                    // only ASCII symbols must be used due to
103                    // problems with running tests on Windows
104                    indentation.append("`--");
105                }
106                else {
107                    indentation.append("|--");
108                }
109            }
110            else {
111                if (node.getNextSibling() == null) {
112                    indentation.insert(0, "    ");
113                }
114                else {
115                    indentation.insert(0, "|   ");
116                }
117            }
118        }
119        return indentation.toString();
120    }
121
122    /**
123     * Replace all control chars with excaped symbols.
124     * @param text the String to process.
125     * @return the processed String with all control chars excaped.
126     */
127    private static String excapeAllControlChars(String text) {
128        final String textWithoutNewlines = NEWLINE.matcher(text).replaceAll("\\\\n");
129        final String textWithoutReturns = RETURN.matcher(textWithoutNewlines).replaceAll("\\\\r");
130        return TAB.matcher(textWithoutReturns).replaceAll("\\\\t");
131    }
132
133    /**
134     * Parse a file and return the parse tree.
135     * @param file the file to parse.
136     * @param withComments true to include comment nodes to the tree
137     * @return the root node of the parse tree.
138     * @throws IOException if the file could not be read.
139     * @throws CheckstyleException if the file is not a Java source.
140     */
141    private static DetailAST parseFile(File file, boolean withComments)
142            throws IOException, CheckstyleException {
143        final FileText text = new FileText(file.getAbsoluteFile(),
144            System.getProperty("file.encoding", "UTF-8"));
145        final FileContents contents = new FileContents(text);
146        final DetailAST result;
147        try {
148            if (withComments) {
149                result = TreeWalker.parseWithComments(contents);
150            }
151            else {
152                result = TreeWalker.parse(contents);
153            }
154        }
155        catch (RecognitionException | TokenStreamException ex) {
156            final String exceptionMsg = String.format(Locale.ROOT,
157                "%s occurred during the analysis of file %s.",
158                ex.getClass().getSimpleName(), file.getPath());
159            throw new CheckstyleException(exceptionMsg, ex);
160        }
161
162        return result;
163    }
164}