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;
024
025import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser.ParseErrorMessage;
026import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser.ParseStatus;
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.JavadocTokenTypes;
031import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
032import com.puppycrawl.tools.checkstyle.api.TokenTypes;
033import com.puppycrawl.tools.checkstyle.utils.JavadocUtils;
034
035/**
036 * Parses file as javadoc DetailNode tree and prints to system output stream.
037 * @author bizmailov
038 */
039public final class DetailNodeTreeStringPrinter {
040
041    /** OS specific line separator. */
042    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
043
044    /** Symbols with which javadoc starts. */
045    private static final String JAVADOC_START = "/**";
046
047    /** Prevent instances. */
048    private DetailNodeTreeStringPrinter() {
049        // no code
050    }
051
052    /**
053     * Parse a file and print the parse tree.
054     * @param file the file to print.
055     * @return parse tree as a string
056     * @throws IOException if the file could not be read.
057     */
058    public static String printFileAst(File file) throws IOException {
059        return printTree(parseFile(file), "", "");
060    }
061
062    /**
063     * Parse block comment DetailAST as Javadoc DetailNode tree.
064     * @param blockComment DetailAST
065     * @return DetailNode tree
066     */
067    public static DetailNode parseJavadocAsDetailNode(DetailAST blockComment) {
068        final JavadocDetailNodeParser parser = new JavadocDetailNodeParser();
069        final ParseStatus status = parser.parseJavadocAsDetailNode(blockComment);
070        if (status.getParseErrorMessage() != null) {
071            throw new IllegalArgumentException(getParseErrorMessage(status.getParseErrorMessage()));
072        }
073        return status.getTree();
074    }
075
076    /**
077     * Parse javadoc comment to DetailNode tree.
078     * @param javadocComment javadoc comment content
079     * @return tree
080     */
081    private static DetailNode parseJavadocAsDetailNode(String javadocComment) {
082        final DetailAST blockComment = createFakeBlockComment(javadocComment);
083        return parseJavadocAsDetailNode(blockComment);
084    }
085
086    /**
087     * Builds error message base on ParseErrorMessage's message key, its arguments, etc.
088     * @param parseErrorMessage ParseErrorMessage
089     * @return error message
090     */
091    private static String getParseErrorMessage(ParseErrorMessage parseErrorMessage) {
092        final LocalizedMessage lmessage = new LocalizedMessage(
093                parseErrorMessage.getLineNumber(),
094                "com.puppycrawl.tools.checkstyle.checks.javadoc.messages",
095                parseErrorMessage.getMessageKey(),
096                parseErrorMessage.getMessageArguments(),
097                "",
098                DetailNodeTreeStringPrinter.class,
099                null);
100        return "[ERROR:" + parseErrorMessage.getLineNumber() + "] " + lmessage.getMessage();
101    }
102
103    /**
104     * Print AST.
105     * @param ast the root AST node.
106     * @param rootPrefix prefix for the root node
107     * @param prefix prefix for other nodes
108     * @return string AST.
109     */
110    public static String printTree(DetailNode ast, String rootPrefix, String prefix) {
111        final StringBuilder messageBuilder = new StringBuilder();
112        DetailNode node = ast;
113        while (node != null) {
114            if (node.getType() == JavadocTokenTypes.JAVADOC) {
115                messageBuilder.append(rootPrefix);
116            }
117            else {
118                messageBuilder.append(prefix);
119            }
120            messageBuilder.append(getIndentation(node))
121                    .append(JavadocUtils.getTokenName(node.getType())).append(" -> ")
122                    .append(JavadocUtils.escapeAllControlChars(node.getText())).append(" [")
123                    .append(node.getLineNumber()).append(':').append(node.getColumnNumber())
124                    .append(']').append(LINE_SEPARATOR)
125                    .append(printTree(JavadocUtils.getFirstChild(node), rootPrefix, prefix));
126            node = JavadocUtils.getNextSibling(node);
127        }
128        return messageBuilder.toString();
129    }
130
131    /**
132     * Get indentation for a node.
133     * @param node the DetailNode to get the indentation for.
134     * @return the indentation in String format.
135     */
136    private static String getIndentation(DetailNode node) {
137        final boolean isLastChild = JavadocUtils.getNextSibling(node) == null;
138        DetailNode currentNode = node;
139        final StringBuilder indentation = new StringBuilder();
140        while (currentNode.getParent() != null) {
141            currentNode = currentNode.getParent();
142            if (currentNode.getParent() == null) {
143                if (isLastChild) {
144                    // only ASCII symbols must be used due to
145                    // problems with running tests on Windows
146                    indentation.append("`--");
147                }
148                else {
149                    indentation.append("|--");
150                }
151            }
152            else {
153                if (JavadocUtils.getNextSibling(currentNode) == null) {
154                    indentation.insert(0, "    ");
155                }
156                else {
157                    indentation.insert(0, "|   ");
158                }
159            }
160        }
161        return indentation.toString();
162    }
163
164    /**
165     * Parse a file and return the parse tree.
166     * @param file the file to parse.
167     * @return the root node of the parse tree.
168     * @throws IOException if the file could not be read.
169     */
170    private static DetailNode parseFile(File file) throws IOException {
171        // Details: https://github.com/checkstyle/checkstyle/issues/3034
172        //noinspection MismatchedQueryAndUpdateOfCollection
173        final FileText text = new FileText(file.getAbsoluteFile(),
174            System.getProperty("file.encoding", "UTF-8"));
175        return parseJavadocAsDetailNode(text.getFullText().toString());
176    }
177
178    /**
179     * Creates DetailAST block comment to pass it to the Javadoc parser.
180     * @param content comment content.
181     * @return DetailAST block comment
182     */
183    private static DetailAST createFakeBlockComment(String content) {
184        final DetailAST blockCommentBegin = new DetailAST();
185        blockCommentBegin.setType(TokenTypes.BLOCK_COMMENT_BEGIN);
186        blockCommentBegin.setText("/*");
187        blockCommentBegin.setLineNo(0);
188        blockCommentBegin.setColumnNo(-JAVADOC_START.length());
189
190        final DetailAST commentContent = new DetailAST();
191        commentContent.setType(TokenTypes.COMMENT_CONTENT);
192        commentContent.setText("*" + content);
193        commentContent.setLineNo(0);
194        // javadoc should starts at 0 column, so COMMENT_CONTENT node
195        // that contains javadoc identificator has -1 column
196        commentContent.setColumnNo(-1);
197
198        final DetailAST blockCommentEnd = new DetailAST();
199        blockCommentEnd.setType(TokenTypes.BLOCK_COMMENT_END);
200        blockCommentEnd.setText("*/");
201
202        blockCommentBegin.setFirstChild(commentContent);
203        commentContent.setNextSibling(blockCommentEnd);
204        return blockCommentBegin;
205    }
206
207}