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