001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2023 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.StandardCharsets;
025import java.util.List;
026import java.util.ListIterator;
027import java.util.Locale;
028
029import org.antlr.v4.runtime.BaseErrorListener;
030import org.antlr.v4.runtime.CharStream;
031import org.antlr.v4.runtime.CharStreams;
032import org.antlr.v4.runtime.CommonToken;
033import org.antlr.v4.runtime.CommonTokenStream;
034import org.antlr.v4.runtime.RecognitionException;
035import org.antlr.v4.runtime.Recognizer;
036import org.antlr.v4.runtime.Token;
037
038import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
039import com.puppycrawl.tools.checkstyle.api.DetailAST;
040import com.puppycrawl.tools.checkstyle.api.FileContents;
041import com.puppycrawl.tools.checkstyle.api.FileText;
042import com.puppycrawl.tools.checkstyle.api.TokenTypes;
043import com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageLexer;
044import com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageParser;
045import com.puppycrawl.tools.checkstyle.utils.ParserUtil;
046
047/**
048 * Helper methods to parse java source files.
049 *
050 */
051// -@cs[ClassDataAbstractionCoupling] No way to split up class usage.
052public final class JavaParser {
053
054    /**
055     * Enum to be used for test if comments should be used.
056     */
057    public enum Options {
058
059        /**
060         * Comments nodes should be processed.
061         */
062        WITH_COMMENTS,
063
064        /**
065         * Comments nodes should be ignored.
066         */
067        WITHOUT_COMMENTS,
068
069    }
070
071    /** Stop instances being created. **/
072    private JavaParser() {
073    }
074
075    /**
076     * Static helper method to parses a Java source file.
077     *
078     * @param contents contains the contents of the file
079     * @return the root of the AST
080     * @throws CheckstyleException if the contents is not a valid Java source
081     */
082    public static DetailAST parse(FileContents contents)
083            throws CheckstyleException {
084        final String fullText = contents.getText().getFullText().toString();
085        final CharStream codePointCharStream = CharStreams.fromString(fullText);
086        final JavaLanguageLexer lexer = new JavaLanguageLexer(codePointCharStream, true);
087        lexer.setCommentListener(contents);
088        lexer.removeErrorListeners();
089
090        final CommonTokenStream tokenStream = new CommonTokenStream(lexer);
091        final JavaLanguageParser parser =
092                new JavaLanguageParser(tokenStream, JavaLanguageParser.CLEAR_DFA_LIMIT);
093        parser.setErrorHandler(new CheckstyleParserErrorStrategy());
094        parser.removeErrorListeners();
095        parser.addErrorListener(new CheckstyleErrorListener());
096
097        final JavaLanguageParser.CompilationUnitContext compilationUnit;
098        try {
099            compilationUnit = parser.compilationUnit();
100        }
101        catch (IllegalStateException ex) {
102            final String exceptionMsg = String.format(Locale.ROOT,
103                "%s occurred while parsing file %s.",
104                ex.getClass().getSimpleName(), contents.getFileName());
105            throw new CheckstyleException(exceptionMsg, ex);
106        }
107
108        return new JavaAstVisitor(tokenStream).visit(compilationUnit);
109    }
110
111    /**
112     * Parse a text and return the parse tree.
113     *
114     * @param text the text to parse
115     * @param options {@link Options} to control inclusion of comment nodes
116     * @return the root node of the parse tree
117     * @throws CheckstyleException if the text is not a valid Java source
118     */
119    public static DetailAST parseFileText(FileText text, Options options)
120            throws CheckstyleException {
121        final FileContents contents = new FileContents(text);
122        final DetailAST ast = parse(contents);
123        if (options == Options.WITH_COMMENTS) {
124            appendHiddenCommentNodes(ast);
125        }
126        return ast;
127    }
128
129    /**
130     * Parses Java source file.
131     *
132     * @param file the file to parse
133     * @param options {@link Options} to control inclusion of comment nodes
134     * @return DetailAST tree
135     * @throws IOException if the file could not be read
136     * @throws CheckstyleException if the file is not a valid Java source file
137     */
138    public static DetailAST parseFile(File file, Options options)
139            throws IOException, CheckstyleException {
140        final FileText text = new FileText(file.getAbsoluteFile(),
141            StandardCharsets.UTF_8.name());
142        return parseFileText(text, options);
143    }
144
145    /**
146     * Appends comment nodes to existing AST.
147     * It traverses each node in AST, looks for hidden comment tokens
148     * and appends found comment tokens as nodes in AST.
149     *
150     * @param root of AST
151     * @return root of AST with comment nodes
152     */
153    public static DetailAST appendHiddenCommentNodes(DetailAST root) {
154        DetailAST curNode = root;
155        DetailAST lastNode = root;
156
157        while (curNode != null) {
158            lastNode = curNode;
159
160            final List<Token> hiddenBefore = ((DetailAstImpl) curNode).getHiddenBefore();
161            if (hiddenBefore != null) {
162                DetailAST currentSibling = curNode;
163
164                final ListIterator<Token> reverseCommentsIterator =
165                        hiddenBefore.listIterator(hiddenBefore.size());
166
167                while (reverseCommentsIterator.hasPrevious()) {
168                    final DetailAST newCommentNode =
169                            createCommentAstFromToken((CommonToken)
170                                    reverseCommentsIterator.previous());
171                    ((DetailAstImpl) currentSibling).addPreviousSibling(newCommentNode);
172
173                    currentSibling = newCommentNode;
174                }
175            }
176
177            DetailAST toVisit = curNode.getFirstChild();
178            while (curNode != null && toVisit == null) {
179                toVisit = curNode.getNextSibling();
180                curNode = curNode.getParent();
181            }
182            curNode = toVisit;
183        }
184        if (lastNode != null) {
185            final List<Token> hiddenAfter = ((DetailAstImpl) lastNode).getHiddenAfter();
186            if (hiddenAfter != null) {
187                DetailAST currentSibling = lastNode;
188                for (Token token : hiddenAfter) {
189                    final DetailAST newCommentNode =
190                            createCommentAstFromToken((CommonToken) token);
191
192                    ((DetailAstImpl) currentSibling).addNextSibling(newCommentNode);
193
194                    currentSibling = newCommentNode;
195                }
196            }
197        }
198        return root;
199    }
200
201    /**
202     * Create comment AST from token. Depending on token type
203     * SINGLE_LINE_COMMENT or BLOCK_COMMENT_BEGIN is created.
204     *
205     * @param token to create the AST
206     * @return DetailAST of comment node
207     */
208    private static DetailAST createCommentAstFromToken(CommonToken token) {
209        final DetailAST commentAst;
210        if (token.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
211            commentAst = createSlCommentNode(token);
212        }
213        else {
214            commentAst = ParserUtil.createBlockCommentNode(token);
215        }
216        return commentAst;
217    }
218
219    /**
220     * Create single-line comment from token.
221     *
222     * @param token to create the AST
223     * @return DetailAST with SINGLE_LINE_COMMENT type
224     */
225    private static DetailAST createSlCommentNode(Token token) {
226        final DetailAstImpl slComment = new DetailAstImpl();
227        slComment.setType(TokenTypes.SINGLE_LINE_COMMENT);
228        slComment.setText("//");
229
230        slComment.setColumnNo(token.getCharPositionInLine());
231        slComment.setLineNo(token.getLine());
232
233        final DetailAstImpl slCommentContent = new DetailAstImpl();
234        slCommentContent.setType(TokenTypes.COMMENT_CONTENT);
235
236        // plus length of '//'
237        slCommentContent.setColumnNo(token.getCharPositionInLine() + 2);
238        slCommentContent.setLineNo(token.getLine());
239        slCommentContent.setText(token.getText());
240
241        slComment.addChild(slCommentContent);
242        return slComment;
243    }
244
245    /**
246     * Custom error listener to provide detailed exception message.
247     */
248    private static final class CheckstyleErrorListener extends BaseErrorListener {
249
250        @Override
251        public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol,
252                                int line, int charPositionInLine,
253                                String msg, RecognitionException ex) {
254            final String message = line + ":" + charPositionInLine + ": " + msg;
255            throw new IllegalStateException(message, ex);
256        }
257    }
258}