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