View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2020 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.api;
21  
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.regex.Pattern;
29  
30  import com.puppycrawl.tools.checkstyle.grammar.CommentListener;
31  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
32  
33  /**
34   * Represents the contents of a file.
35   *
36   */
37  public final class FileContents implements CommentListener {
38  
39      /**
40       * The pattern to match a single line comment containing only the comment
41       * itself -- no code.
42       */
43      private static final String MATCH_SINGLELINE_COMMENT_PAT = "^\\s*//.*$";
44      /** Compiled regexp to match a single-line comment line. */
45      private static final Pattern MATCH_SINGLELINE_COMMENT = Pattern
46              .compile(MATCH_SINGLELINE_COMMENT_PAT);
47  
48      /** The file name. */
49      private final String fileName;
50  
51      /** The text. */
52      private final FileText text;
53  
54      /**
55       * Map of the Javadoc comments indexed on the last line of the comment.
56       * The hack is it assumes that there is only one Javadoc comment per line.
57       */
58      private final Map<Integer, TextBlock> javadocComments = new HashMap<>();
59      /** Map of the C++ comments indexed on the first line of the comment. */
60      private final Map<Integer, TextBlock> cppComments = new HashMap<>();
61  
62      /**
63       * Map of the C comments indexed on the first line of the comment to a list
64       * of comments on that line.
65       */
66      private final Map<Integer, List<TextBlock>> clangComments = new HashMap<>();
67  
68      /**
69       * Creates a new {@code FileContents} instance.
70       *
71       * @param text the contents of the file
72       */
73      public FileContents(FileText text) {
74          fileName = text.getFile().toString();
75          this.text = new FileText(text);
76      }
77  
78      /**
79       * Get the full text of the file.
80       * @return an object containing the full text of the file
81       */
82      public FileText getText() {
83          return new FileText(text);
84      }
85  
86      /**
87       * Gets the lines in the file.
88       * @return the lines in the file
89       */
90      public String[] getLines() {
91          return text.toLinesArray();
92      }
93  
94      /**
95       * Get the line from text of the file.
96       * @param index index of the line
97       * @return line from text of the file
98       */
99      public String getLine(int index) {
100         return text.get(index);
101     }
102 
103     /**
104      * Gets the name of the file.
105      * @return the name of the file
106      */
107     public String getFileName() {
108         return fileName;
109     }
110 
111     @Override
112     public void reportSingleLineComment(String type, int startLineNo,
113             int startColNo) {
114         reportSingleLineComment(startLineNo, startColNo);
115     }
116 
117     /**
118      * Report the location of a single line comment.
119      *
120      * @param startLineNo the starting line number
121      * @param startColNo the starting column number
122      **/
123     public void reportSingleLineComment(int startLineNo, int startColNo) {
124         final String line = line(startLineNo - 1);
125         final String[] txt = {line.substring(startColNo)};
126         final Commenttyle/api/Comment.html#Comment">Comment comment = new Comment(txt, startColNo, startLineNo,
127                 line.length() - 1);
128         cppComments.put(startLineNo, comment);
129     }
130 
131     @Override
132     public void reportBlockComment(String type, int startLineNo,
133             int startColNo, int endLineNo, int endColNo) {
134         reportBlockComment(startLineNo, startColNo, endLineNo, endColNo);
135     }
136 
137     /**
138      * Report the location of a block comment.
139      *
140      * @param startLineNo the starting line number
141      * @param startColNo the starting column number
142      * @param endLineNo the ending line number
143      * @param endColNo the ending column number
144      **/
145     public void reportBlockComment(int startLineNo, int startColNo,
146             int endLineNo, int endColNo) {
147         final String[] cComment = extractBlockComment(startLineNo, startColNo,
148                 endLineNo, endColNo);
149         final Commenttyle/api/Comment.html#Comment">Comment comment = new Comment(cComment, startColNo, endLineNo,
150                 endColNo);
151 
152         // save the comment
153         if (clangComments.containsKey(startLineNo)) {
154             final List<TextBlock> entries = clangComments.get(startLineNo);
155             entries.add(comment);
156         }
157         else {
158             final List<TextBlock> entries = new ArrayList<>();
159             entries.add(comment);
160             clangComments.put(startLineNo, entries);
161         }
162 
163         // Remember if possible Javadoc comment
164         final String firstLine = line(startLineNo - 1);
165         if (firstLine.contains("/**") && !firstLine.contains("/**/")) {
166             javadocComments.put(endLineNo - 1, comment);
167         }
168     }
169 
170     /**
171      * Returns the specified block comment as a String array.
172      *
173      * @param startLineNo the starting line number
174      * @param startColNo the starting column number
175      * @param endLineNo the ending line number
176      * @param endColNo the ending column number
177      * @return block comment as an array
178      **/
179     private String[] extractBlockComment(int startLineNo, int startColNo,
180             int endLineNo, int endColNo) {
181         final String[] returnValue;
182         if (startLineNo == endLineNo) {
183             returnValue = new String[1];
184             returnValue[0] = line(startLineNo - 1).substring(startColNo,
185                     endColNo + 1);
186         }
187         else {
188             returnValue = new String[endLineNo - startLineNo + 1];
189             returnValue[0] = line(startLineNo - 1).substring(startColNo);
190             for (int i = startLineNo; i < endLineNo; i++) {
191                 returnValue[i - startLineNo + 1] = line(i);
192             }
193             returnValue[returnValue.length - 1] = line(endLineNo - 1).substring(0,
194                     endColNo + 1);
195         }
196         return returnValue;
197     }
198 
199     /**
200      * Get a single line.
201      * For internal use only, as getText().get(lineNo) is just as
202      * suitable for external use and avoids method duplication.
203      * @param lineNo the number of the line to get
204      * @return the corresponding line, without terminator
205      * @throws IndexOutOfBoundsException if lineNo is invalid
206      */
207     private String line(int lineNo) {
208         return text.get(lineNo);
209     }
210 
211     /**
212      * Returns the Javadoc comment before the specified line.
213      * A return value of {@code null} means there is no such comment.
214      *
215      * @param lineNoBefore the line number to check before
216      * @return the Javadoc comment, or {@code null} if none
217      **/
218     public TextBlock getJavadocBefore(int lineNoBefore) {
219         // Lines start at 1 to the callers perspective, so need to take off 2
220         int lineNo = lineNoBefore - 2;
221 
222         // skip blank lines
223         while (lineNo > 0 && (lineIsBlank(lineNo) || lineIsComment(lineNo))) {
224             lineNo--;
225         }
226 
227         return javadocComments.get(lineNo);
228     }
229 
230     /**
231      * Checks if the specified line is blank.
232      *
233      * @param lineNo the line number to check
234      * @return if the specified line consists only of tabs and spaces.
235      **/
236     public boolean lineIsBlank(int lineNo) {
237         return CommonUtil.isBlank(line(lineNo));
238     }
239 
240     /**
241      * Checks if the specified line is a single-line comment without code.
242      *
243      * @param lineNo  the line number to check
244      * @return if the specified line consists of only a single line comment
245      *         without code.
246      **/
247     public boolean lineIsComment(int lineNo) {
248         return MATCH_SINGLELINE_COMMENT.matcher(line(lineNo)).matches();
249     }
250 
251     /**
252      * Checks if the specified position intersects with a comment.
253      *
254      * @param startLineNo the starting line number
255      * @param startColNo the starting column number
256      * @param endLineNo the ending line number
257      * @param endColNo the ending column number
258      * @return true if the positions intersects with a comment.
259      **/
260     public boolean hasIntersectionWithComment(int startLineNo,
261             int startColNo, int endLineNo, int endColNo) {
262         return hasIntersectionWithBlockComment(startLineNo, startColNo, endLineNo, endColNo)
263                 || hasIntersectionWithSingleLineComment(startLineNo, startColNo, endLineNo,
264                         endColNo);
265     }
266 
267     /**
268      * Checks if the specified position intersects with a block comment.
269      *
270      * @param startLineNo the starting line number
271      * @param startColNo the starting column number
272      * @param endLineNo the ending line number
273      * @param endColNo the ending column number
274      * @return true if the positions intersects with a block comment.
275      */
276     private boolean hasIntersectionWithBlockComment(int startLineNo, int startColNo,
277             int endLineNo, int endColNo) {
278         boolean hasIntersection = false;
279         // Check C comments (all comments should be checked)
280         final Collection<List<TextBlock>> values = clangComments.values();
281         for (final List<TextBlock> row : values) {
282             for (final TextBlock comment : row) {
283                 if (comment.intersects(startLineNo, startColNo, endLineNo, endColNo)) {
284                     hasIntersection = true;
285                     break;
286                 }
287             }
288             if (hasIntersection) {
289                 break;
290             }
291         }
292         return hasIntersection;
293     }
294 
295     /**
296      * Checks if the specified position intersects with a single line comment.
297      *
298      * @param startLineNo the starting line number
299      * @param startColNo the starting column number
300      * @param endLineNo the ending line number
301      * @param endColNo the ending column number
302      * @return true if the positions intersects with a single line comment.
303      */
304     private boolean hasIntersectionWithSingleLineComment(int startLineNo, int startColNo,
305             int endLineNo, int endColNo) {
306         boolean hasIntersection = false;
307         // Check CPP comments (line searching is possible)
308         for (int lineNumber = startLineNo; lineNumber <= endLineNo;
309              lineNumber++) {
310             final TextBlock comment = cppComments.get(lineNumber);
311             if (comment != null && comment.intersects(startLineNo, startColNo,
312                     endLineNo, endColNo)) {
313                 hasIntersection = true;
314                 break;
315             }
316         }
317         return hasIntersection;
318     }
319 
320     /**
321      * Returns a map of all the single line comments. The key is a line number,
322      * the value is the comment {@link TextBlock} at the line.
323      * @return the Map of comments
324      */
325     public Map<Integer, TextBlock> getSingleLineComments() {
326         return Collections.unmodifiableMap(cppComments);
327     }
328 
329     /**
330      * Returns a map of all block comments. The key is the line number, the
331      * value is a {@link List} of block comment {@link TextBlock}s
332      * that start at that line.
333      * @return the map of comments
334      */
335     public Map<Integer, List<TextBlock>> getBlockComments() {
336         return Collections.unmodifiableMap(clangComments);
337     }
338 
339     /**
340      * Checks if the current file is a package-info.java file.
341      * @return true if the package file.
342      */
343     public boolean inPackageInfo() {
344         return fileName.endsWith("package-info.java");
345     }
346 
347 }