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