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