001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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.nio.charset.Charset;
025
026import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser.ParseErrorMessage;
027import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser.ParseStatus;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029import com.puppycrawl.tools.checkstyle.api.DetailNode;
030import com.puppycrawl.tools.checkstyle.api.FileText;
031import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
032import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
033import com.puppycrawl.tools.checkstyle.utils.ParserUtil;
034
035/**
036 * Parses file as javadoc DetailNode tree and prints to system output stream.
037 */
038public final class DetailNodeTreeStringPrinter {
039
040    /** OS specific line separator. */
041    private static final String LINE_SEPARATOR = System.lineSeparator();
042
043    /** Prevent instances. */
044    private DetailNodeTreeStringPrinter() {
045        // no code
046    }
047
048    /**
049     * Parse a file and print the parse tree.
050     *
051     * @param file the file to print.
052     * @return parse tree as a string
053     * @throws IOException if the file could not be read.
054     */
055    public static String printFileAst(File file) throws IOException {
056        return printTree(parseFile(file), "", "");
057    }
058
059    /**
060     * Parse block comment DetailAST as Javadoc DetailNode tree.
061     *
062     * @param blockComment DetailAST
063     * @return DetailNode tree
064     * @throws IllegalArgumentException if there is an error parsing the Javadoc.
065     */
066    public static DetailNode parseJavadocAsDetailNode(DetailAST blockComment) {
067        final JavadocDetailNodeParser parser = new JavadocDetailNodeParser();
068        final ParseStatus status = parser.parseJavadocComment(blockComment);
069        final ParseErrorMessage parseErrorMessage = status.getParseErrorMessage();
070        if (parseErrorMessage != null) {
071            throw new IllegalArgumentException(getParseErrorMessage(parseErrorMessage));
072        }
073        return status.getTree();
074    }
075
076    /**
077     * Builds violation base on ParseErrorMessage's violation key, its arguments, etc.
078     *
079     * @param parseErrorMessage ParseErrorMessage
080     * @return error violation
081     */
082    private static String getParseErrorMessage(ParseErrorMessage parseErrorMessage) {
083        final LocalizedMessage message = new LocalizedMessage(
084                "com.puppycrawl.tools.checkstyle.checks.javadoc.messages",
085                DetailNodeTreeStringPrinter.class,
086                parseErrorMessage.getMessageKey(),
087                parseErrorMessage.getMessageArguments());
088        return "[ERROR:" + parseErrorMessage.getLineNumber() + "] " + message.getMessage();
089    }
090
091    /**
092     * Print AST.
093     *
094     * @param ast the root AST node.
095     * @param rootPrefix prefix for the root node
096     * @param prefix prefix for other nodes
097     * @return string AST.
098     */
099    public static String printTree(DetailNode ast, String rootPrefix, String prefix) {
100        final StringBuilder messageBuilder = new StringBuilder(1024);
101        DetailNode node = ast;
102        while (node != null) {
103            if (node.getType() == JavadocCommentsTokenTypes.JAVADOC_CONTENT) {
104                messageBuilder.append(rootPrefix);
105            }
106            else {
107                messageBuilder.append(prefix);
108            }
109            messageBuilder.append(getIndentation(node))
110                    .append(JavadocUtil.getTokenName(node.getType())).append(" -> ")
111                    .append(JavadocUtil.escapeAllControlChars(node.getText())).append(" [")
112                    .append(node.getLineNumber()).append(':').append(node.getColumnNumber() + 1)
113                    .append(']').append(LINE_SEPARATOR)
114                    .append(printTree(node.getFirstChild(), rootPrefix, prefix));
115            node = node.getNextSibling();
116        }
117        return messageBuilder.toString();
118    }
119
120    /**
121     * Get indentation for a node.
122     *
123     * @param node the DetailNode to get the indentation for.
124     * @return the indentation in String format.
125     */
126    private static String getIndentation(DetailNode node) {
127        final boolean isLastChild = node.getNextSibling() == null;
128        DetailNode currentNode = node;
129        final StringBuilder indentation = new StringBuilder(1024);
130        DetailNode parent = currentNode.getParent();
131        while (parent != null) {
132            currentNode = parent;
133            parent = currentNode.getParent();
134            if (parent == null) {
135                if (isLastChild) {
136                    // only ASCII symbols must be used due to
137                    // problems with running tests on Windows
138                    indentation.append("`--");
139                }
140                else {
141                    indentation.append("|--");
142                }
143            }
144            else {
145                if (currentNode.getNextSibling() == null) {
146                    indentation.insert(0, "    ");
147                }
148                else {
149                    indentation.insert(0, "|   ");
150                }
151            }
152        }
153        return indentation.toString();
154    }
155
156    /**
157     * Parse a file and return the parse tree.
158     *
159     * @param file the file to parse.
160     * @return the root node of the parse tree.
161     * @throws IOException if the file could not be read.
162     */
163    private static DetailNode parseFile(File file) throws IOException {
164        final FileText text = new FileText(file, Charset.defaultCharset().name());
165        final DetailAST comment = ParserUtil.createBlockCommentNode(text.getFullText().toString());
166        return parseJavadocAsDetailNode(comment);
167    }
168
169}