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 }