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 }