001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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.io.Reader;
025import java.io.StringReader;
026import java.nio.charset.StandardCharsets;
027import java.util.Locale;
028
029import antlr.CommonASTWithHiddenTokens;
030import antlr.CommonHiddenStreamToken;
031import antlr.RecognitionException;
032import antlr.Token;
033import antlr.TokenStreamException;
034import antlr.TokenStreamHiddenTokenFilter;
035import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
036import com.puppycrawl.tools.checkstyle.api.DetailAST;
037import com.puppycrawl.tools.checkstyle.api.FileContents;
038import com.puppycrawl.tools.checkstyle.api.FileText;
039import com.puppycrawl.tools.checkstyle.api.TokenTypes;
040import com.puppycrawl.tools.checkstyle.grammar.GeneratedJavaLexer;
041import com.puppycrawl.tools.checkstyle.grammar.GeneratedJavaRecognizer;
042import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
043
044/**
045 * Helper methods to parse java source files.
046 *
047 */
048// -@cs[ClassDataAbstractionCoupling] No way to split up class usage.
049public final class JavaParser {
050
051    /**
052     * Enum to be used for test if comments should be used.
053     */
054    public enum Options {
055
056        /**
057         * Comments nodes should be processed.
058         */
059        WITH_COMMENTS,
060
061        /**
062         * Comments nodes should be ignored.
063         */
064        WITHOUT_COMMENTS,
065
066    }
067
068    /** Stop instances being created. **/
069    private JavaParser() {
070    }
071
072    /**
073     * Static helper method to parses a Java source file.
074     * @param contents contains the contents of the file
075     * @return the root of the AST
076     * @throws CheckstyleException if the contents is not a valid Java source
077     */
078    public static DetailAST parse(FileContents contents)
079            throws CheckstyleException {
080        final String fullText = contents.getText().getFullText().toString();
081        final Reader reader = new StringReader(fullText);
082        final GeneratedJavaLexer lexer = new GeneratedJavaLexer(reader);
083        lexer.setCommentListener(contents);
084        lexer.setTokenObjectClass("antlr.CommonHiddenStreamToken");
085
086        final TokenStreamHiddenTokenFilter filter = new TokenStreamHiddenTokenFilter(lexer);
087        filter.hide(TokenTypes.SINGLE_LINE_COMMENT);
088        filter.hide(TokenTypes.BLOCK_COMMENT_BEGIN);
089
090        final GeneratedJavaRecognizer parser = new GeneratedJavaRecognizer(filter) {
091            @Override
092            public void reportError(RecognitionException ex) {
093                throw new IllegalStateException(ex);
094            }
095        };
096        parser.setFilename(contents.getFileName());
097        parser.setASTNodeClass(DetailAstImpl.class.getName());
098        try {
099            parser.compilationUnit();
100        }
101        catch (RecognitionException | TokenStreamException | 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 (DetailAST) parser.getAST();
109    }
110
111    /**
112     * Parse a text and return the parse tree.
113     * @param text the text to parse
114     * @param options {@link Options} to control inclusion of comment nodes
115     * @return the root node of the parse tree
116     * @throws CheckstyleException if the text is not a valid Java source
117     */
118    public static DetailAST parseFileText(FileText text, Options options)
119            throws CheckstyleException {
120        final FileContents contents = new FileContents(text);
121        DetailAST ast = parse(contents);
122        if (options == Options.WITH_COMMENTS) {
123            ast = appendHiddenCommentNodes(ast);
124        }
125        return ast;
126    }
127
128    /**
129     * Parses Java source file.
130     * @param file the file to parse
131     * @param options {@link Options} to control inclusion of comment nodes
132     * @return DetailAST tree
133     * @throws IOException if the file could not be read
134     * @throws CheckstyleException if the file is not a valid Java source file
135     */
136    public static DetailAST parseFile(File file, Options options)
137            throws IOException, CheckstyleException {
138        final FileText text = new FileText(file.getAbsoluteFile(),
139            System.getProperty("file.encoding", StandardCharsets.UTF_8.name()));
140        return parseFileText(text, options);
141    }
142
143    /**
144     * Appends comment nodes to existing AST.
145     * It traverses each node in AST, looks for hidden comment tokens
146     * and appends found comment tokens as nodes in AST.
147     * @param root of AST
148     * @return root of AST with comment nodes
149     */
150    public static DetailAST appendHiddenCommentNodes(DetailAST root) {
151        DetailAST result = root;
152        DetailAST curNode = root;
153        DetailAST lastNode = root;
154
155        while (curNode != null) {
156            lastNode = curNode;
157
158            CommonHiddenStreamToken tokenBefore = ((CommonASTWithHiddenTokens) curNode)
159                    .getHiddenBefore();
160            DetailAST currentSibling = curNode;
161            while (tokenBefore != null) {
162                final DetailAST newCommentNode =
163                         createCommentAstFromToken(tokenBefore);
164
165                ((DetailAstImpl) currentSibling).addPreviousSibling(newCommentNode);
166
167                if (currentSibling == result) {
168                    result = newCommentNode;
169                }
170
171                currentSibling = newCommentNode;
172                tokenBefore = tokenBefore.getHiddenBefore();
173            }
174
175            DetailAST toVisit = curNode.getFirstChild();
176            while (curNode != null && toVisit == null) {
177                toVisit = curNode.getNextSibling();
178                curNode = curNode.getParent();
179            }
180            curNode = toVisit;
181        }
182        if (lastNode != null) {
183            CommonHiddenStreamToken tokenAfter = ((CommonASTWithHiddenTokens) lastNode)
184                    .getHiddenAfter();
185            DetailAST currentSibling = lastNode;
186            while (tokenAfter != null) {
187                final DetailAST newCommentNode =
188                        createCommentAstFromToken(tokenAfter);
189
190                ((DetailAstImpl) currentSibling).addNextSibling(newCommentNode);
191
192                currentSibling = newCommentNode;
193                tokenAfter = tokenAfter.getHiddenAfter();
194            }
195        }
196        return result;
197    }
198
199    /**
200     * Create comment AST from token. Depending on token type
201     * SINGLE_LINE_COMMENT or BLOCK_COMMENT_BEGIN is created.
202     * @param token to create the AST
203     * @return DetailAST of comment node
204     */
205    private static DetailAST createCommentAstFromToken(Token token) {
206        final DetailAST commentAst;
207        if (token.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
208            commentAst = createSlCommentNode(token);
209        }
210        else {
211            commentAst = CommonUtil.createBlockCommentNode(token);
212        }
213        return commentAst;
214    }
215
216    /**
217     * Create single-line comment from token.
218     * @param token to create the AST
219     * @return DetailAST with SINGLE_LINE_COMMENT type
220     */
221    private static DetailAST createSlCommentNode(Token token) {
222        final DetailAstImpl slComment = new DetailAstImpl();
223        slComment.setType(TokenTypes.SINGLE_LINE_COMMENT);
224        slComment.setText("//");
225
226        // column counting begins from 0
227        slComment.setColumnNo(token.getColumn() - 1);
228        slComment.setLineNo(token.getLine());
229
230        final DetailAstImpl slCommentContent = new DetailAstImpl();
231        slCommentContent.setType(TokenTypes.COMMENT_CONTENT);
232
233        // column counting begins from 0
234        // plus length of '//'
235        slCommentContent.setColumnNo(token.getColumn() - 1 + 2);
236        slCommentContent.setLineNo(token.getLine());
237        slCommentContent.setText(token.getText());
238
239        slComment.addChild(slCommentContent);
240        return slComment;
241    }
242
243}