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     *
075     * @param contents contains the contents of the file
076     * @return the root of the AST
077     * @throws CheckstyleException if the contents is not a valid Java source
078     */
079    public static DetailAST parse(FileContents contents)
080            throws CheckstyleException {
081        final String fullText = contents.getText().getFullText().toString();
082        final Reader reader = new StringReader(fullText);
083        final GeneratedJavaLexer lexer = new GeneratedJavaLexer(reader);
084        lexer.setCommentListener(contents);
085        lexer.setTokenObjectClass("antlr.CommonHiddenStreamToken");
086
087        final TokenStreamHiddenTokenFilter filter = new TokenStreamHiddenTokenFilter(lexer);
088        filter.hide(TokenTypes.SINGLE_LINE_COMMENT);
089        filter.hide(TokenTypes.BLOCK_COMMENT_BEGIN);
090
091        final GeneratedJavaRecognizer parser = new GeneratedJavaRecognizer(filter) {
092            @Override
093            public void reportError(RecognitionException ex) {
094                throw new IllegalStateException(ex);
095            }
096        };
097        parser.setFilename(contents.getFileName());
098        parser.setASTNodeClass(DetailAstImpl.class.getName());
099        try {
100            parser.compilationUnit();
101        }
102        catch (RecognitionException | TokenStreamException | IllegalStateException ex) {
103            final String exceptionMsg = String.format(Locale.ROOT,
104                "%s occurred while parsing file %s.",
105                ex.getClass().getSimpleName(), contents.getFileName());
106            throw new CheckstyleException(exceptionMsg, ex);
107        }
108
109        return (DetailAST) parser.getAST();
110    }
111
112    /**
113     * Parse a text and return the parse tree.
114     *
115     * @param text the text to parse
116     * @param options {@link Options} to control inclusion of comment nodes
117     * @return the root node of the parse tree
118     * @throws CheckstyleException if the text is not a valid Java source
119     */
120    public static DetailAST parseFileText(FileText text, Options options)
121            throws CheckstyleException {
122        final FileContents contents = new FileContents(text);
123        DetailAST ast = parse(contents);
124        if (options == Options.WITH_COMMENTS) {
125            ast = appendHiddenCommentNodes(ast);
126        }
127        return ast;
128    }
129
130    /**
131     * Parses Java source file.
132     *
133     * @param file the file to parse
134     * @param options {@link Options} to control inclusion of comment nodes
135     * @return DetailAST tree
136     * @throws IOException if the file could not be read
137     * @throws CheckstyleException if the file is not a valid Java source file
138     */
139    public static DetailAST parseFile(File file, Options options)
140            throws IOException, CheckstyleException {
141        final FileText text = new FileText(file.getAbsoluteFile(),
142            StandardCharsets.UTF_8.name());
143        return parseFileText(text, options);
144    }
145
146    /**
147     * Appends comment nodes to existing AST.
148     * It traverses each node in AST, looks for hidden comment tokens
149     * and appends found comment tokens as nodes in AST.
150     *
151     * @param root of AST
152     * @return root of AST with comment nodes
153     */
154    public static DetailAST appendHiddenCommentNodes(DetailAST root) {
155        DetailAST result = root;
156        DetailAST curNode = root;
157        DetailAST lastNode = root;
158
159        while (curNode != null) {
160            lastNode = curNode;
161
162            CommonHiddenStreamToken tokenBefore = ((CommonASTWithHiddenTokens) curNode)
163                    .getHiddenBefore();
164            DetailAST currentSibling = curNode;
165            while (tokenBefore != null) {
166                final DetailAST newCommentNode =
167                         createCommentAstFromToken(tokenBefore);
168
169                ((DetailAstImpl) currentSibling).addPreviousSibling(newCommentNode);
170
171                if (currentSibling == result) {
172                    result = newCommentNode;
173                }
174
175                currentSibling = newCommentNode;
176                tokenBefore = tokenBefore.getHiddenBefore();
177            }
178
179            DetailAST toVisit = curNode.getFirstChild();
180            while (curNode != null && toVisit == null) {
181                toVisit = curNode.getNextSibling();
182                curNode = curNode.getParent();
183            }
184            curNode = toVisit;
185        }
186        if (lastNode != null) {
187            CommonHiddenStreamToken tokenAfter = ((CommonASTWithHiddenTokens) lastNode)
188                    .getHiddenAfter();
189            DetailAST currentSibling = lastNode;
190            while (tokenAfter != null) {
191                final DetailAST newCommentNode =
192                        createCommentAstFromToken(tokenAfter);
193
194                ((DetailAstImpl) currentSibling).addNextSibling(newCommentNode);
195
196                currentSibling = newCommentNode;
197                tokenAfter = tokenAfter.getHiddenAfter();
198            }
199        }
200        return result;
201    }
202
203    /**
204     * Create comment AST from token. Depending on token type
205     * SINGLE_LINE_COMMENT or BLOCK_COMMENT_BEGIN is created.
206     *
207     * @param token to create the AST
208     * @return DetailAST of comment node
209     */
210    private static DetailAST createCommentAstFromToken(Token token) {
211        final DetailAST commentAst;
212        if (token.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
213            commentAst = createSlCommentNode(token);
214        }
215        else {
216            commentAst = CommonUtil.createBlockCommentNode(token);
217        }
218        return commentAst;
219    }
220
221    /**
222     * Create single-line comment from token.
223     *
224     * @param token to create the AST
225     * @return DetailAST with SINGLE_LINE_COMMENT type
226     */
227    private static DetailAST createSlCommentNode(Token token) {
228        final DetailAstImpl slComment = new DetailAstImpl();
229        slComment.setType(TokenTypes.SINGLE_LINE_COMMENT);
230        slComment.setText("//");
231
232        // column counting begins from 0
233        slComment.setColumnNo(token.getColumn() - 1);
234        slComment.setLineNo(token.getLine());
235
236        final DetailAstImpl slCommentContent = new DetailAstImpl();
237        slCommentContent.setType(TokenTypes.COMMENT_CONTENT);
238
239        // column counting begins from 0
240        // plus length of '//'
241        slCommentContent.setColumnNo(token.getColumn() - 1 + 2);
242        slCommentContent.setLineNo(token.getLine());
243        slCommentContent.setText(token.getText());
244
245        slComment.addChild(slCommentContent);
246        return slComment;
247    }
248
249}