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