View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2018 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.grammars.GeneratedJavaLexer;
40  import com.puppycrawl.tools.checkstyle.grammars.GeneratedJavaRecognizer;
41  import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
42  
43  /**
44   * Helper methods to parse java source files.
45   *
46   * @author Oliver Burn
47   * @author Pavel Bludov
48   */
49  public final class JavaParser {
50  
51      /**
52       * Enum to be used for test if comments should be used.
53       */
54      public enum Options {
55  
56          /**
57           * Comments nodes should be processed.
58           */
59          WITH_COMMENTS,
60  
61          /**
62           * Comments nodes should be ignored.
63           */
64          WITHOUT_COMMENTS
65  
66      }
67  
68      /** Stop instances being created. **/
69      private JavaParser() {
70      }
71  
72      /**
73       * Static helper method to parses a Java source file.
74       * @param contents contains the contents of the file
75       * @return the root of the AST
76       * @throws CheckstyleException if the contents is not a valid Java source
77       */
78      public static DetailAST parse(FileContents contents)
79              throws CheckstyleException {
80          final String fullText = contents.getText().getFullText().toString();
81          final Reader reader = new StringReader(fullText);
82          final GeneratedJavaLexer lexer = new GeneratedJavaLexer(reader);
83          lexer.setCommentListener(contents);
84          lexer.setTokenObjectClass("antlr.CommonHiddenStreamToken");
85  
86          final TokenStreamHiddenTokenFilter filter = new TokenStreamHiddenTokenFilter(lexer);
87          filter.hide(TokenTypes.SINGLE_LINE_COMMENT);
88          filter.hide(TokenTypes.BLOCK_COMMENT_BEGIN);
89  
90          final GeneratedJavaRecognizer parser = new GeneratedJavaRecognizer(filter);
91          parser.setFilename(contents.getFileName());
92          parser.setASTNodeClass(DetailAST.class.getName());
93          try {
94              parser.compilationUnit();
95          }
96          catch (RecognitionException | TokenStreamException ex) {
97              final String exceptionMsg = String.format(Locale.ROOT,
98                  "%s occurred while parsing file %s.",
99                  ex.getClass().getSimpleName(), contents.getFileName());
100             throw new CheckstyleException(exceptionMsg, ex);
101         }
102 
103         return (DetailAST) parser.getAST();
104     }
105 
106     /**
107      * Parse a text and return the parse tree.
108      * @param text the text to parse
109      * @param options {@link Options} to control inclusion of comment nodes
110      * @return the root node of the parse tree
111      * @throws CheckstyleException if the text is not a valid Java source
112      */
113     public static DetailAST parseFileText(FileText text, Options options)
114             throws CheckstyleException {
115         final FileContents contents = new FileContents(text);
116         DetailAST ast = parse(contents);
117         if (options == Options.WITH_COMMENTS) {
118             ast = appendHiddenCommentNodes(ast);
119         }
120         return ast;
121     }
122 
123     /**
124      * Parses Java source file.
125      * @param file the file to parse
126      * @param options {@link Options} to control inclusion of comment nodes
127      * @return DetailAST tree
128      * @throws IOException if the file could not be read
129      * @throws CheckstyleException if the file is not a valid Java source file
130      */
131     public static DetailAST parseFile(File file, Options options)
132             throws IOException, CheckstyleException {
133         final FileText text = new FileText(file.getAbsoluteFile(),
134             System.getProperty("file.encoding", StandardCharsets.UTF_8.name()));
135         return parseFileText(text, options);
136     }
137 
138     /**
139      * Appends comment nodes to existing AST.
140      * It traverses each node in AST, looks for hidden comment tokens
141      * and appends found comment tokens as nodes in AST.
142      * @param root of AST
143      * @return root of AST with comment nodes
144      */
145     public static DetailAST appendHiddenCommentNodes(DetailAST root) {
146         DetailAST result = root;
147         DetailAST curNode = root;
148         DetailAST lastNode = root;
149 
150         while (curNode != null) {
151             if (isPositionGreater(curNode, lastNode)) {
152                 lastNode = curNode;
153             }
154 
155             CommonHiddenStreamToken tokenBefore = curNode.getHiddenBefore();
156             DetailAST currentSibling = curNode;
157             while (tokenBefore != null) {
158                 final DetailAST newCommentNode =
159                          createCommentAstFromToken(tokenBefore);
160 
161                 currentSibling.addPreviousSibling(newCommentNode);
162 
163                 if (currentSibling == result) {
164                     result = newCommentNode;
165                 }
166 
167                 currentSibling = newCommentNode;
168                 tokenBefore = tokenBefore.getHiddenBefore();
169             }
170 
171             DetailAST toVisit = curNode.getFirstChild();
172             while (curNode != null && toVisit == null) {
173                 toVisit = curNode.getNextSibling();
174                 if (toVisit == null) {
175                     curNode = curNode.getParent();
176                 }
177             }
178             curNode = toVisit;
179         }
180         if (lastNode != null) {
181             CommonHiddenStreamToken tokenAfter = lastNode.getHiddenAfter();
182             DetailAST currentSibling = lastNode;
183             while (tokenAfter != null) {
184                 final DetailAST newCommentNode =
185                         createCommentAstFromToken(tokenAfter);
186 
187                 currentSibling.addNextSibling(newCommentNode);
188 
189                 currentSibling = newCommentNode;
190                 tokenAfter = tokenAfter.getHiddenAfter();
191             }
192         }
193         return result;
194     }
195 
196     /**
197      * Checks if position of first DetailAST is greater than position of
198      * second DetailAST. Position is line number and column number in source file.
199      * @param ast1 first DetailAST node
200      * @param ast2 second DetailAST node
201      * @return true if position of ast1 is greater than position of ast2
202      */
203     private static boolean isPositionGreater(DetailAST ast1, DetailAST ast2) {
204         boolean isGreater = ast1.getLineNo() > ast2.getLineNo();
205         if (!isGreater && ast1.getLineNo() == ast2.getLineNo()) {
206             isGreater = ast1.getColumnNo() > ast2.getColumnNo();
207         }
208         return isGreater;
209     }
210 
211     /**
212      * Create comment AST from token. Depending on token type
213      * SINGLE_LINE_COMMENT or BLOCK_COMMENT_BEGIN is created.
214      * @param token to create the AST
215      * @return DetailAST of comment node
216      */
217     private static DetailAST createCommentAstFromToken(Token token) {
218         final DetailAST commentAst;
219         if (token.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
220             commentAst = createSlCommentNode(token);
221         }
222         else {
223             commentAst = CommonUtils.createBlockCommentNode(token);
224         }
225         return commentAst;
226     }
227 
228     /**
229      * Create single-line comment from token.
230      * @param token to create the AST
231      * @return DetailAST with SINGLE_LINE_COMMENT type
232      */
233     private static DetailAST createSlCommentNode(Token token) {
234         final DetailAST slComment = new DetailAST();
235         slComment.setType(TokenTypes.SINGLE_LINE_COMMENT);
236         slComment.setText("//");
237 
238         // column counting begins from 0
239         slComment.setColumnNo(token.getColumn() - 1);
240         slComment.setLineNo(token.getLine());
241 
242         final DetailAST slCommentContent = new DetailAST();
243         slCommentContent.setType(TokenTypes.COMMENT_CONTENT);
244 
245         // column counting begins from 0
246         // plus length of '//'
247         slCommentContent.setColumnNo(token.getColumn() - 1 + 2);
248         slCommentContent.setLineNo(token.getLine());
249         slCommentContent.setText(token.getText());
250 
251         slComment.addChild(slCommentContent);
252         return slComment;
253     }
254 
255 }