001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.api; 021 022import java.io.File; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.HashMap; 027import java.util.List; 028import java.util.Map; 029import java.util.regex.Pattern; 030 031import com.google.common.collect.ImmutableMap; 032import com.puppycrawl.tools.checkstyle.grammars.CommentListener; 033 034/** 035 * Represents the contents of a file. 036 * 037 * @author Oliver Burn 038 */ 039public final class FileContents implements CommentListener { 040 /** 041 * The pattern to match a single line comment containing only the comment 042 * itself -- no code. 043 */ 044 private static final String MATCH_SINGLELINE_COMMENT_PAT = "^\\s*//.*$"; 045 /** Compiled regexp to match a single-line comment line. */ 046 private static final Pattern MATCH_SINGLELINE_COMMENT = Pattern 047 .compile(MATCH_SINGLELINE_COMMENT_PAT); 048 049 /** The file name. */ 050 private final String fileName; 051 052 /** The text. */ 053 private final FileText text; 054 055 /** Map of the Javadoc comments indexed on the last line of the comment. 056 * The hack is it assumes that there is only one Javadoc comment per line. 057 */ 058 private final Map<Integer, TextBlock> javadocComments = new HashMap<>(); 059 /** Map of the C++ comments indexed on the first line of the comment. */ 060 private final Map<Integer, TextBlock> cppComments = new HashMap<>(); 061 062 /** 063 * Map of the C comments indexed on the first line of the comment to a list 064 * of comments on that line. 065 */ 066 private final Map<Integer, List<TextBlock>> clangComments = new HashMap<>(); 067 068 /** 069 * Creates a new {@code FileContents} instance. 070 * 071 * @param filename name of the file 072 * @param lines the contents of the file 073 * @deprecated Use {@link #FileContents(FileText)} instead 074 * in order to preserve the original line breaks where possible. 075 */ 076 @Deprecated 077 public FileContents(String filename, String... lines) { 078 fileName = filename; 079 text = FileText.fromLines(new File(filename), Arrays.asList(lines)); 080 } 081 082 /** 083 * Creates a new {@code FileContents} instance. 084 * 085 * @param text the contents of the file 086 */ 087 public FileContents(FileText text) { 088 fileName = text.getFile().toString(); 089 this.text = new FileText(text); 090 } 091 092 @Override 093 public void reportSingleLineComment(String type, int startLineNo, 094 int startColNo) { 095 reportSingleLineComment(startLineNo, startColNo); 096 } 097 098 /** 099 * Report the location of a single line comment. 100 * @param startLineNo the starting line number 101 * @param startColNo the starting column number 102 **/ 103 public void reportSingleLineComment(int startLineNo, int startColNo) { 104 final String line = line(startLineNo - 1); 105 final String[] txt = {line.substring(startColNo)}; 106 final Comment comment = new Comment(txt, startColNo, startLineNo, 107 line.length() - 1); 108 cppComments.put(startLineNo, comment); 109 } 110 111 @Override 112 public void reportBlockComment(String type, int startLineNo, 113 int startColNo, int endLineNo, int endColNo) { 114 reportBlockComment(startLineNo, startColNo, endLineNo, endColNo); 115 } 116 117 /** 118 * Report the location of a block comment. 119 * @param startLineNo the starting line number 120 * @param startColNo the starting column number 121 * @param endLineNo the ending line number 122 * @param endColNo the ending column number 123 **/ 124 public void reportBlockComment(int startLineNo, int startColNo, 125 int endLineNo, int endColNo) { 126 final String[] cComment = extractBlockComment(startLineNo, startColNo, 127 endLineNo, endColNo); 128 final Comment comment = new Comment(cComment, startColNo, endLineNo, 129 endColNo); 130 131 // save the comment 132 if (clangComments.containsKey(startLineNo)) { 133 final List<TextBlock> entries = clangComments.get(startLineNo); 134 entries.add(comment); 135 } 136 else { 137 final List<TextBlock> entries = new ArrayList<>(); 138 entries.add(comment); 139 clangComments.put(startLineNo, entries); 140 } 141 142 // Remember if possible Javadoc comment 143 final String firstLine = line(startLineNo - 1); 144 if (firstLine.contains("/**") && !firstLine.contains("/**/")) { 145 javadocComments.put(endLineNo - 1, comment); 146 } 147 } 148 149 /** 150 * Report the location of a C++ style comment. 151 * @param startLineNo the starting line number 152 * @param startColNo the starting column number 153 * @deprecated Use {@link #reportSingleLineComment(int, int)} instead. 154 **/ 155 @Deprecated 156 public void reportCppComment(int startLineNo, int startColNo) { 157 reportSingleLineComment(startLineNo, startColNo); 158 } 159 160 /** 161 * Returns a map of all the C++ style comments. The key is a line number, 162 * the value is the comment {@link TextBlock} at the line. 163 * @return the Map of comments 164 * @deprecated Use {@link #getSingleLineComments()} instead. 165 */ 166 @Deprecated 167 public ImmutableMap<Integer, TextBlock> getCppComments() { 168 return getSingleLineComments(); 169 } 170 171 /** 172 * Returns a map of all the single line comments. The key is a line number, 173 * the value is the comment {@link TextBlock} at the line. 174 * @return the Map of comments 175 */ 176 public ImmutableMap<Integer, TextBlock> getSingleLineComments() { 177 return ImmutableMap.copyOf(cppComments); 178 } 179 180 /** 181 * Report the location of a C-style comment. 182 * @param startLineNo the starting line number 183 * @param startColNo the starting column number 184 * @param endLineNo the ending line number 185 * @param endColNo the ending column number 186 * @deprecated Use {@link #reportBlockComment(int, int, int, int)} instead. 187 **/ 188 // -@cs[AbbreviationAsWordInName] Can't change yet since class is API. 189 @Deprecated 190 public void reportCComment(int startLineNo, int startColNo, 191 int endLineNo, int endColNo) { 192 reportBlockComment(startLineNo, startColNo, endLineNo, endColNo); 193 } 194 195 /** 196 * Returns a map of all C style comments. The key is the line number, the 197 * value is a {@link List} of C style comment {@link TextBlock}s 198 * that start at that line. 199 * @return the map of comments 200 * @deprecated Use {@link #getBlockComments()} instead. 201 */ 202 // -@cs[AbbreviationAsWordInName] Can't change yet since class is API. 203 @Deprecated 204 public ImmutableMap<Integer, List<TextBlock>> getCComments() { 205 return getBlockComments(); 206 } 207 208 /** 209 * Returns a map of all block comments. The key is the line number, the 210 * value is a {@link List} of block comment {@link TextBlock}s 211 * that start at that line. 212 * @return the map of comments 213 */ 214 public ImmutableMap<Integer, List<TextBlock>> getBlockComments() { 215 return ImmutableMap.copyOf(clangComments); 216 } 217 218 /** 219 * Returns the specified block comment as a String array. 220 * @param startLineNo the starting line number 221 * @param startColNo the starting column number 222 * @param endLineNo the ending line number 223 * @param endColNo the ending column number 224 * @return block comment as an array 225 **/ 226 private String[] extractBlockComment(int startLineNo, int startColNo, 227 int endLineNo, int endColNo) { 228 final String[] returnValue; 229 if (startLineNo == endLineNo) { 230 returnValue = new String[1]; 231 returnValue[0] = line(startLineNo - 1).substring(startColNo, 232 endColNo + 1); 233 } 234 else { 235 returnValue = new String[endLineNo - startLineNo + 1]; 236 returnValue[0] = line(startLineNo - 1).substring(startColNo); 237 for (int i = startLineNo; i < endLineNo; i++) { 238 returnValue[i - startLineNo + 1] = line(i); 239 } 240 returnValue[returnValue.length - 1] = line(endLineNo - 1).substring(0, 241 endColNo + 1); 242 } 243 return returnValue; 244 } 245 246 /** 247 * Returns the Javadoc comment before the specified line. 248 * A return value of {@code null} means there is no such comment. 249 * @param lineNoBefore the line number to check before 250 * @return the Javadoc comment, or {@code null} if none 251 **/ 252 public TextBlock getJavadocBefore(int lineNoBefore) { 253 // Lines start at 1 to the callers perspective, so need to take off 2 254 int lineNo = lineNoBefore - 2; 255 256 // skip blank lines 257 while (lineNo > 0 && (lineIsBlank(lineNo) || lineIsComment(lineNo))) { 258 lineNo--; 259 } 260 261 return javadocComments.get(lineNo); 262 } 263 264 /** 265 * Get a single line. 266 * For internal use only, as getText().get(lineNo) is just as 267 * suitable for external use and avoids method duplication. 268 * @param lineNo the number of the line to get 269 * @return the corresponding line, without terminator 270 * @throws IndexOutOfBoundsException if lineNo is invalid 271 */ 272 private String line(int lineNo) { 273 return text.get(lineNo); 274 } 275 276 /** 277 * Get the full text of the file. 278 * @return an object containing the full text of the file 279 */ 280 public FileText getText() { 281 return new FileText(text); 282 } 283 284 /** 285 * Gets the lines in the file. 286 * @return the lines in the file 287 */ 288 public String[] getLines() { 289 return text.toLinesArray(); 290 } 291 292 /** 293 * Get the line from text of the file. 294 * @param index index of the line 295 * @return line from text of the file 296 */ 297 public String getLine(int index) { 298 return text.get(index); 299 } 300 301 /** 302 * Gets the name of the file. 303 * @return the name of the file 304 */ 305 public String getFileName() { 306 return fileName; 307 } 308 309 /** 310 * Getter. 311 * @return the name of the file 312 * @deprecated use {@link #getFileName} instead 313 */ 314 @Deprecated 315 public String getFilename() { 316 return fileName; 317 } 318 319 /** 320 * Checks if the specified line is blank. 321 * @param lineNo the line number to check 322 * @return if the specified line consists only of tabs and spaces. 323 **/ 324 public boolean lineIsBlank(int lineNo) { 325 // possible improvement: avoid garbage creation in trim() 326 return line(lineNo).trim().isEmpty(); 327 } 328 329 /** 330 * Checks if the specified line is a single-line comment without code. 331 * @param lineNo the line number to check 332 * @return if the specified line consists of only a single line comment 333 * without code. 334 **/ 335 public boolean lineIsComment(int lineNo) { 336 return MATCH_SINGLELINE_COMMENT.matcher(line(lineNo)).matches(); 337 } 338 339 /** 340 * Checks if the specified position intersects with a comment. 341 * @param startLineNo the starting line number 342 * @param startColNo the starting column number 343 * @param endLineNo the ending line number 344 * @param endColNo the ending column number 345 * @return true if the positions intersects with a comment. 346 **/ 347 public boolean hasIntersectionWithComment(int startLineNo, 348 int startColNo, int endLineNo, int endColNo) { 349 return hasIntersectionWithBlockComment(startLineNo, startColNo, endLineNo, endColNo) 350 || hasIntersectionWithSingleLineComment(startLineNo, startColNo, endLineNo, 351 endColNo); 352 } 353 354 /** 355 * Checks if the current file is a package-info.java file. 356 * @return true if the package file. 357 */ 358 public boolean inPackageInfo() { 359 return fileName.endsWith("package-info.java"); 360 } 361 362 /** 363 * Checks if the specified position intersects with a block comment. 364 * @param startLineNo the starting line number 365 * @param startColNo the starting column number 366 * @param endLineNo the ending line number 367 * @param endColNo the ending column number 368 * @return true if the positions intersects with a block comment. 369 */ 370 private boolean hasIntersectionWithBlockComment(int startLineNo, int startColNo, 371 int endLineNo, int endColNo) { 372 boolean hasIntersection = false; 373 // Check C comments (all comments should be checked) 374 final Collection<List<TextBlock>> values = clangComments.values(); 375 for (final List<TextBlock> row : values) { 376 for (final TextBlock comment : row) { 377 if (comment.intersects(startLineNo, startColNo, endLineNo, endColNo)) { 378 hasIntersection = true; 379 break; 380 } 381 } 382 if (hasIntersection) { 383 break; 384 } 385 } 386 return hasIntersection; 387 } 388 389 /** 390 * Checks if the specified position intersects with a single line comment. 391 * @param startLineNo the starting line number 392 * @param startColNo the starting column number 393 * @param endLineNo the ending line number 394 * @param endColNo the ending column number 395 * @return true if the positions intersects with a single line comment. 396 */ 397 private boolean hasIntersectionWithSingleLineComment(int startLineNo, int startColNo, 398 int endLineNo, int endColNo) { 399 boolean hasIntersection = false; 400 // Check CPP comments (line searching is possible) 401 for (int lineNumber = startLineNo; lineNumber <= endLineNo; 402 lineNumber++) { 403 final TextBlock comment = cppComments.get(lineNumber); 404 if (comment != null && comment.intersects(startLineNo, startColNo, 405 endLineNo, endColNo)) { 406 hasIntersection = true; 407 break; 408 } 409 } 410 return hasIntersection; 411 } 412}