1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.Reader;
25  import java.io.StringReader;
26  import java.nio.charset.StandardCharsets;
27  import java.util.Locale;
28  
29  import antlr.CommonHiddenStreamToken;
30  import antlr.RecognitionException;
31  import antlr.Token;
32  import antlr.TokenStreamException;
33  import antlr.TokenStreamHiddenTokenFilter;
34  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
35  import com.puppycrawl.tools.checkstyle.api.DetailAST;
36  import com.puppycrawl.tools.checkstyle.api.FileContents;
37  import com.puppycrawl.tools.checkstyle.api.FileText;
38  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
39  import com.puppycrawl.tools.checkstyle.grammar.GeneratedJavaLexer;
40  import com.puppycrawl.tools.checkstyle.grammar.GeneratedJavaRecognizer;
41  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
42  
43  /**
44   * Helper methods to parse java source files.
45   *
46   */
47  public final class JavaParser {
48  
49      /**
50       * Enum to be used for test if comments should be used.
51       */
52      public enum Options {
53  
54          /**
55           * Comments nodes should be processed.
56           */
57          WITH_COMMENTS,
58  
59          /**
60           * Comments nodes should be ignored.
61           */
62          WITHOUT_COMMENTS,
63  
64      }
65  
66      /** Stop instances being created. **/
67      private JavaParser() {
68      }
69  
70      /**
71       * Static helper method to parses a Java source file.
72       * @param contents contains the contents of the file
73       * @return the root of the AST
74       * @throws CheckstyleException if the contents is not a valid Java source
75       */
76      public static DetailAST parse(FileContents contents)
77              throws CheckstyleException {
78          final String fullText = contents.getText().getFullText().toString();
79          final Reader reader = new StringReader(fullText);
80          final GeneratedJavaLexer lexer = new GeneratedJavaLexer(reader);
81          lexer.setCommentListener(contents);
82          lexer.setTokenObjectClass("antlr.CommonHiddenStreamToken");
83  
84          final TokenStreamHiddenTokenFilter filter = new TokenStreamHiddenTokenFilter(lexer);
85          filter.hide(TokenTypes.SINGLE_LINE_COMMENT);
86          filter.hide(TokenTypes.BLOCK_COMMENT_BEGIN);
87  
88          final GeneratedJavaRecognizer parser = new GeneratedJavaRecognizer(filter) {
89              @Override
90              public void reportError(RecognitionException ex) {
91                  throw new IllegalStateException(ex);
92              }
93          };
94          parser.setFilename(contents.getFileName());
95          parser.setASTNodeClass(DetailAST.class.getName());
96          try {
97              parser.compilationUnit();
98          }
99          catch (RecognitionException | TokenStreamException | IllegalStateException ex) {
100             final String exceptionMsg = String.format(Locale.ROOT,
101                 "%s occurred while parsing file %s.",
102                 ex.getClass().getSimpleName(), contents.getFileName());
103             throw new CheckstyleException(exceptionMsg, ex);
104         }
105 
106         return (DetailAST) parser.getAST();
107     }
108 
109     /**
110      * Parse a text and return the parse tree.
111      * @param text the text to parse
112      * @param options {@link Options} to control inclusion of comment nodes
113      * @return the root node of the parse tree
114      * @throws CheckstyleException if the text is not a valid Java source
115      */
116     public static DetailAST parseFileText(FileText text, Options options)
117             throws CheckstyleException {
118         final FileContents contents = new FileContents(text);
119         DetailAST ast = parse(contents);
120         if (options == Options.WITH_COMMENTS) {
121             ast = appendHiddenCommentNodes(ast);
122         }
123         return ast;
124     }
125 
126     /**
127      * Parses Java source file.
128      * @param file the file to parse
129      * @param options {@link Options} to control inclusion of comment nodes
130      * @return DetailAST tree
131      * @throws IOException if the file could not be read
132      * @throws CheckstyleException if the file is not a valid Java source file
133      */
134     public static DetailAST parseFile(File file, Options options)
135             throws IOException, CheckstyleException {
136         final FileText text = new FileText(file.getAbsoluteFile(),
137             System.getProperty("file.encoding", StandardCharsets.UTF_8.name()));
138         return parseFileText(text, options);
139     }
140 
141     /**
142      * Appends comment nodes to existing AST.
143      * It traverses each node in AST, looks for hidden comment tokens
144      * and appends found comment tokens as nodes in AST.
145      * @param root of AST
146      * @return root of AST with comment nodes
147      */
148     public static DetailAST appendHiddenCommentNodes(DetailAST root) {
149         DetailAST result = root;
150         DetailAST curNode = root;
151         DetailAST lastNode = root;
152 
153         while (curNode != null) {
154             lastNode = curNode;
155 
156             CommonHiddenStreamToken tokenBefore = curNode.getHiddenBefore();
157             DetailAST currentSibling = curNode;
158             while (tokenBefore != null) {
159                 final DetailAST newCommentNode =
160                          createCommentAstFromToken(tokenBefore);
161 
162                 currentSibling.addPreviousSibling(newCommentNode);
163 
164                 if (currentSibling == result) {
165                     result = newCommentNode;
166                 }
167 
168                 currentSibling = newCommentNode;
169                 tokenBefore = tokenBefore.getHiddenBefore();
170             }
171 
172             DetailAST toVisit = curNode.getFirstChild();
173             while (curNode != null && toVisit == null) {
174                 toVisit = curNode.getNextSibling();
175                 curNode = curNode.getParent();
176             }
177             curNode = toVisit;
178         }
179         if (lastNode != null) {
180             CommonHiddenStreamToken tokenAfter = lastNode.getHiddenAfter();
181             DetailAST currentSibling = lastNode;
182             while (tokenAfter != null) {
183                 final DetailAST newCommentNode =
184                         createCommentAstFromToken(tokenAfter);
185 
186                 currentSibling.addNextSibling(newCommentNode);
187 
188                 currentSibling = newCommentNode;
189                 tokenAfter = tokenAfter.getHiddenAfter();
190             }
191         }
192         return result;
193     }
194 
195     /**
196      * Create comment AST from token. Depending on token type
197      * SINGLE_LINE_COMMENT or BLOCK_COMMENT_BEGIN is created.
198      * @param token to create the AST
199      * @return DetailAST of comment node
200      */
201     private static DetailAST createCommentAstFromToken(Token token) {
202         final DetailAST commentAst;
203         if (token.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
204             commentAst = createSlCommentNode(token);
205         }
206         else {
207             commentAst = CommonUtil.createBlockCommentNode(token);
208         }
209         return commentAst;
210     }
211 
212     /**
213      * Create single-line comment from token.
214      * @param token to create the AST
215      * @return DetailAST with SINGLE_LINE_COMMENT type
216      */
217     private static DetailAST createSlCommentNode(Token token) {
218         final DetailAST slComment = new DetailAST();
219         slComment.setType(TokenTypes.SINGLE_LINE_COMMENT);
220         slComment.setText("//");
221 
222         // column counting begins from 0
223         slComment.setColumnNo(token.getColumn() - 1);
224         slComment.setLineNo(token.getLine());
225 
226         final DetailAST slCommentContent = new DetailAST();
227         slCommentContent.setType(TokenTypes.COMMENT_CONTENT);
228 
229         // column counting begins from 0
230         // plus length of '//'
231         slCommentContent.setColumnNo(token.getColumn() - 1 + 2);
232         slCommentContent.setLineNo(token.getLine());
233         slCommentContent.setText(token.getText());
234 
235         slComment.addChild(slCommentContent);
236         return slComment;
237     }
238 
239 }