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