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