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 reportCppComment(startLineNo, startColNo); 096 } 097 098 @Override 099 public void reportBlockComment(String type, int startLineNo, 100 int startColNo, int endLineNo, int endColNo) { 101 reportCComment(startLineNo, startColNo, endLineNo, endColNo); 102 } 103 104 /** 105 * Report the location of a C++ style comment. 106 * @param startLineNo the starting line number 107 * @param startColNo the starting column number 108 **/ 109 public void reportCppComment(int startLineNo, int startColNo) { 110 final String line = line(startLineNo - 1); 111 final String[] txt = {line.substring(startColNo)}; 112 final Comment comment = new Comment(txt, startColNo, startLineNo, 113 line.length() - 1); 114 cppComments.put(startLineNo, comment); 115 } 116 117 /** 118 * Returns a map of all the C++ style comments. The key is a line number, 119 * the value is the comment {@link TextBlock} at the line. 120 * @return the Map of comments 121 */ 122 public ImmutableMap<Integer, TextBlock> getCppComments() { 123 return ImmutableMap.copyOf(cppComments); 124 } 125 126 /** 127 * Report the location of a C-style comment. 128 * @param startLineNo the starting line number 129 * @param startColNo the starting column number 130 * @param endLineNo the ending line number 131 * @param endColNo the ending column number 132 **/ 133 public void reportCComment(int startLineNo, int startColNo, 134 int endLineNo, int endColNo) { 135 final String[] cComment = extractCComment(startLineNo, startColNo, 136 endLineNo, endColNo); 137 final Comment comment = new Comment(cComment, startColNo, endLineNo, 138 endColNo); 139 140 // save the comment 141 if (clangComments.containsKey(startLineNo)) { 142 final List<TextBlock> entries = clangComments.get(startLineNo); 143 entries.add(comment); 144 } 145 else { 146 final List<TextBlock> entries = new ArrayList<>(); 147 entries.add(comment); 148 clangComments.put(startLineNo, entries); 149 } 150 151 // Remember if possible Javadoc comment 152 final String firstLine = line(startLineNo - 1); 153 if (firstLine.contains("/**") && !firstLine.contains("/**/")) { 154 javadocComments.put(endLineNo - 1, comment); 155 } 156 } 157 158 /** 159 * Returns a map of all C style comments. The key is the line number, the 160 * value is a {@link List} of C style comment {@link TextBlock}s 161 * that start at that line. 162 * @return the map of comments 163 */ 164 public ImmutableMap<Integer, List<TextBlock>> getCComments() { 165 return ImmutableMap.copyOf(clangComments); 166 } 167 168 /** 169 * Returns the specified C comment as a String array. 170 * @param startLineNo the starting line number 171 * @param startColNo the starting column number 172 * @param endLineNo the ending line number 173 * @param endColNo the ending column number 174 * @return C comment as a array 175 **/ 176 private String[] extractCComment(int startLineNo, int startColNo, 177 int endLineNo, int endColNo) { 178 final String[] returnValue; 179 if (startLineNo == endLineNo) { 180 returnValue = new String[1]; 181 returnValue[0] = line(startLineNo - 1).substring(startColNo, 182 endColNo + 1); 183 } 184 else { 185 returnValue = new String[endLineNo - startLineNo + 1]; 186 returnValue[0] = line(startLineNo - 1).substring(startColNo); 187 for (int i = startLineNo; i < endLineNo; i++) { 188 returnValue[i - startLineNo + 1] = line(i); 189 } 190 returnValue[returnValue.length - 1] = line(endLineNo - 1).substring(0, 191 endColNo + 1); 192 } 193 return returnValue; 194 } 195 196 /** 197 * Returns the Javadoc comment before the specified line. 198 * A return value of {@code null} means there is no such comment. 199 * @param lineNoBefore the line number to check before 200 * @return the Javadoc comment, or {@code null} if none 201 **/ 202 public TextBlock getJavadocBefore(int lineNoBefore) { 203 // Lines start at 1 to the callers perspective, so need to take off 2 204 int lineNo = lineNoBefore - 2; 205 206 // skip blank lines 207 while (lineNo > 0 && (lineIsBlank(lineNo) || lineIsComment(lineNo))) { 208 lineNo--; 209 } 210 211 return javadocComments.get(lineNo); 212 } 213 214 /** 215 * Get a single line. 216 * For internal use only, as getText().get(lineNo) is just as 217 * suitable for external use and avoids method duplication. 218 * @param lineNo the number of the line to get 219 * @return the corresponding line, without terminator 220 * @throws IndexOutOfBoundsException if lineNo is invalid 221 */ 222 private String line(int lineNo) { 223 return text.get(lineNo); 224 } 225 226 /** 227 * Get the full text of the file. 228 * @return an object containing the full text of the file 229 */ 230 public FileText getText() { 231 return new FileText(text); 232 } 233 234 /** 235 * Gets the lines in the file. 236 * @return the lines in the file 237 */ 238 public String[] getLines() { 239 return text.toLinesArray(); 240 } 241 242 /** 243 * Get the line from text of the file. 244 * @param index index of the line 245 * @return line from text of the file 246 */ 247 public String getLine(int index) { 248 return text.get(index); 249 } 250 251 /** 252 * Gets the name of the file. 253 * @return the name of the file 254 */ 255 public String getFileName() { 256 return fileName; 257 } 258 259 /** 260 * Getter. 261 * @return the name of the file 262 * @deprecated use {@link #getFileName} instead 263 */ 264 @Deprecated 265 public String getFilename() { 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 // possible improvement: avoid garbage creation in trim() 276 return line(lineNo).trim().isEmpty(); 277 } 278 279 /** 280 * Checks if the specified line is a single-line comment without code. 281 * @param lineNo the line number to check 282 * @return if the specified line consists of only a single line comment 283 * without code. 284 **/ 285 public boolean lineIsComment(int lineNo) { 286 return MATCH_SINGLELINE_COMMENT.matcher(line(lineNo)).matches(); 287 } 288 289 /** 290 * Checks if the specified position intersects with a comment. 291 * @param startLineNo the starting line number 292 * @param startColNo the starting column number 293 * @param endLineNo the ending line number 294 * @param endColNo the ending column number 295 * @return true if the positions intersects with a comment. 296 **/ 297 public boolean hasIntersectionWithComment(int startLineNo, 298 int startColNo, int endLineNo, int endColNo) { 299 return hasIntersectionWithCComment(startLineNo, startColNo, endLineNo, endColNo) 300 || hasIntersectionWithCppComment(startLineNo, startColNo, endLineNo, endColNo); 301 } 302 303 /** 304 * Checks if the current file is a package-info.java file. 305 * @return true if the package file. 306 */ 307 public boolean inPackageInfo() { 308 return fileName.endsWith("package-info.java"); 309 } 310 311 /** 312 * Checks if the specified position intersects with a C comment. 313 * @param startLineNo the starting line number 314 * @param startColNo the starting column number 315 * @param endLineNo the ending line number 316 * @param endColNo the ending column number 317 * @return true if the positions intersects with a C comment. 318 */ 319 private boolean hasIntersectionWithCComment(int startLineNo, int startColNo, 320 int endLineNo, int endColNo) { 321 // Check C comments (all comments should be checked) 322 final Collection<List<TextBlock>> values = clangComments.values(); 323 for (final List<TextBlock> row : values) { 324 for (final TextBlock comment : row) { 325 if (comment.intersects(startLineNo, startColNo, endLineNo, endColNo)) { 326 return true; 327 } 328 } 329 } 330 return false; 331 } 332 333 /** 334 * Checks if the specified position intersects with a CPP comment. 335 * @param startLineNo the starting line number 336 * @param startColNo the starting column number 337 * @param endLineNo the ending line number 338 * @param endColNo the ending column number 339 * @return true if the positions intersects with a CPP comment. 340 */ 341 private boolean hasIntersectionWithCppComment(int startLineNo, int startColNo, 342 int endLineNo, int endColNo) { 343 // Check CPP comments (line searching is possible) 344 for (int lineNumber = startLineNo; lineNumber <= endLineNo; 345 lineNumber++) { 346 final TextBlock comment = cppComments.get(lineNumber); 347 if (comment != null && comment.intersects(startLineNo, startColNo, 348 endLineNo, endColNo)) { 349 return true; 350 } 351 } 352 return false; 353 } 354}