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       *
81       * @return an object containing the full text of the file
82       */
83      public FileText getText() {
84          return new FileText(text);
85      }
86  
87      /**
88       * Gets the lines in the file.
89       *
90       * @return the lines in the file
91       */
92      public String[] getLines() {
93          return text.toLinesArray();
94      }
95  
96      /**
97       * Get the line from text of the file.
98       *
99       * @param index index of the line
100      * @return line from text of the file
101      */
102     public String getLine(int index) {
103         return text.get(index);
104     }
105 
106     /**
107      * Gets the name of the file.
108      *
109      * @return the name of the file
110      */
111     public String getFileName() {
112         return fileName;
113     }
114 
115     @Override
116     public void reportSingleLineComment(String type, int startLineNo,
117             int startColNo) {
118         reportSingleLineComment(startLineNo, startColNo);
119     }
120 
121     /**
122      * Report the location of a single line comment.
123      *
124      * @param startLineNo the starting line number
125      * @param startColNo the starting column number
126      **/
127     public void reportSingleLineComment(int startLineNo, int startColNo) {
128         final String line = line(startLineNo - 1);
129         final String[] txt = {line.substring(startColNo)};
130         final Commenttyle/api/Comment.html#Comment">Comment comment = new Comment(txt, startColNo, startLineNo,
131                 line.length() - 1);
132         cppComments.put(startLineNo, comment);
133     }
134 
135     @Override
136     public void reportBlockComment(String type, int startLineNo,
137             int startColNo, int endLineNo, int endColNo) {
138         reportBlockComment(startLineNo, startColNo, endLineNo, endColNo);
139     }
140 
141     /**
142      * Report the location of a block comment.
143      *
144      * @param startLineNo the starting line number
145      * @param startColNo the starting column number
146      * @param endLineNo the ending line number
147      * @param endColNo the ending column number
148      **/
149     public void reportBlockComment(int startLineNo, int startColNo,
150             int endLineNo, int endColNo) {
151         final String[] cComment = extractBlockComment(startLineNo, startColNo,
152                 endLineNo, endColNo);
153         final Commenttyle/api/Comment.html#Comment">Comment comment = new Comment(cComment, startColNo, endLineNo,
154                 endColNo);
155 
156         // save the comment
157         if (clangComments.containsKey(startLineNo)) {
158             final List<TextBlock> entries = clangComments.get(startLineNo);
159             entries.add(comment);
160         }
161         else {
162             final List<TextBlock> entries = new ArrayList<>();
163             entries.add(comment);
164             clangComments.put(startLineNo, entries);
165         }
166 
167         // Remember if possible Javadoc comment
168         final String firstLine = line(startLineNo - 1);
169         if (firstLine.contains("/**") && !firstLine.contains("/**/")) {
170             javadocComments.put(endLineNo - 1, comment);
171         }
172     }
173 
174     /**
175      * Returns the specified block comment as a String array.
176      *
177      * @param startLineNo the starting line number
178      * @param startColNo the starting column number
179      * @param endLineNo the ending line number
180      * @param endColNo the ending column number
181      * @return block comment as an array
182      **/
183     private String[] extractBlockComment(int startLineNo, int startColNo,
184             int endLineNo, int endColNo) {
185         final String[] returnValue;
186         if (startLineNo == endLineNo) {
187             returnValue = new String[1];
188             returnValue[0] = line(startLineNo - 1).substring(startColNo,
189                     endColNo + 1);
190         }
191         else {
192             returnValue = new String[endLineNo - startLineNo + 1];
193             returnValue[0] = line(startLineNo - 1).substring(startColNo);
194             for (int i = startLineNo; i < endLineNo; i++) {
195                 returnValue[i - startLineNo + 1] = line(i);
196             }
197             returnValue[returnValue.length - 1] = line(endLineNo - 1).substring(0,
198                     endColNo + 1);
199         }
200         return returnValue;
201     }
202 
203     /**
204      * Get a single line.
205      * For internal use only, as getText().get(lineNo) is just as
206      * suitable for external use and avoids method duplication.
207      *
208      * @param lineNo the number of the line to get
209      * @return the corresponding line, without terminator
210      * @throws IndexOutOfBoundsException if lineNo is invalid
211      */
212     private String line(int lineNo) {
213         return text.get(lineNo);
214     }
215 
216     /**
217      * Returns the Javadoc comment before the specified line.
218      * A return value of {@code null} means there is no such comment.
219      *
220      * @param lineNoBefore the line number to check before
221      * @return the Javadoc comment, or {@code null} if none
222      **/
223     public TextBlock getJavadocBefore(int lineNoBefore) {
224         // Lines start at 1 to the callers perspective, so need to take off 2
225         int lineNo = lineNoBefore - 2;
226 
227         // skip blank lines
228         while (lineNo > 0 && (lineIsBlank(lineNo) || lineIsComment(lineNo))) {
229             lineNo--;
230         }
231 
232         return javadocComments.get(lineNo);
233     }
234 
235     /**
236      * Checks if the specified line is blank.
237      *
238      * @param lineNo the line number to check
239      * @return if the specified line consists only of tabs and spaces.
240      **/
241     public boolean lineIsBlank(int lineNo) {
242         return CommonUtil.isBlank(line(lineNo));
243     }
244 
245     /**
246      * Checks if the specified line is a single-line comment without code.
247      *
248      * @param lineNo  the line number to check
249      * @return if the specified line consists of only a single line comment
250      *         without code.
251      **/
252     public boolean lineIsComment(int lineNo) {
253         return MATCH_SINGLELINE_COMMENT.matcher(line(lineNo)).matches();
254     }
255 
256     /**
257      * Checks if the specified position intersects with a comment.
258      *
259      * @param startLineNo the starting line number
260      * @param startColNo the starting column number
261      * @param endLineNo the ending line number
262      * @param endColNo the ending column number
263      * @return true if the positions intersects with a comment.
264      **/
265     public boolean hasIntersectionWithComment(int startLineNo,
266             int startColNo, int endLineNo, int endColNo) {
267         return hasIntersectionWithBlockComment(startLineNo, startColNo, endLineNo, endColNo)
268                 || hasIntersectionWithSingleLineComment(startLineNo, startColNo, endLineNo,
269                         endColNo);
270     }
271 
272     /**
273      * Checks if the specified position intersects with a block comment.
274      *
275      * @param startLineNo the starting line number
276      * @param startColNo the starting column number
277      * @param endLineNo the ending line number
278      * @param endColNo the ending column number
279      * @return true if the positions intersects with a block comment.
280      */
281     private boolean hasIntersectionWithBlockComment(int startLineNo, int startColNo,
282             int endLineNo, int endColNo) {
283         boolean hasIntersection = false;
284         // Check C comments (all comments should be checked)
285         final Collection<List<TextBlock>> values = clangComments.values();
286         for (final List<TextBlock> row : values) {
287             for (final TextBlock comment : row) {
288                 if (comment.intersects(startLineNo, startColNo, endLineNo, endColNo)) {
289                     hasIntersection = true;
290                     break;
291                 }
292             }
293             if (hasIntersection) {
294                 break;
295             }
296         }
297         return hasIntersection;
298     }
299 
300     /**
301      * Checks if the specified position intersects with a single line comment.
302      *
303      * @param startLineNo the starting line number
304      * @param startColNo the starting column number
305      * @param endLineNo the ending line number
306      * @param endColNo the ending column number
307      * @return true if the positions intersects with a single line comment.
308      */
309     private boolean hasIntersectionWithSingleLineComment(int startLineNo, int startColNo,
310             int endLineNo, int endColNo) {
311         boolean hasIntersection = false;
312         // Check CPP comments (line searching is possible)
313         for (int lineNumber = startLineNo; lineNumber <= endLineNo;
314              lineNumber++) {
315             final TextBlock comment = cppComments.get(lineNumber);
316             if (comment != null && comment.intersects(startLineNo, startColNo,
317                     endLineNo, endColNo)) {
318                 hasIntersection = true;
319                 break;
320             }
321         }
322         return hasIntersection;
323     }
324 
325     /**
326      * Returns a map of all the single line comments. The key is a line number,
327      * the value is the comment {@link TextBlock} at the line.
328      *
329      * @return the Map of comments
330      */
331     public Map<Integer, TextBlock> getSingleLineComments() {
332         return Collections.unmodifiableMap(cppComments);
333     }
334 
335     /**
336      * Returns a map of all block comments. The key is the line number, the
337      * value is a {@link List} of block comment {@link TextBlock}s
338      * that start at that line.
339      *
340      * @return the map of comments
341      */
342     public Map<Integer, List<TextBlock>> getBlockComments() {
343         return Collections.unmodifiableMap(clangComments);
344     }
345 
346     /**
347      * Checks if the current file is a package-info.java file.
348      *
349      * @return true if the package file.
350      */
351     public boolean inPackageInfo() {
352         return fileName.endsWith("package-info.java");
353     }
354 
355 }