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