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 mFilename; 052 053 /** the text */ 054 private final FileText mText; 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> mJavadocComments = Maps.newHashMap(); 060 /** map of the C++ comments indexed on the first line of the comment. */ 061 private final Map<Integer, TextBlock> mCPlusPlusComments = 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>> mCComments = Maps.newHashMap(); 069 070 /** 071 * Creates a new <code>FileContents</code> instance. 072 * 073 * @param aFilename name of the file 074 * @param aLines 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 aFilename, String[] aLines) 079 { 080 mFilename = aFilename; 081 mText = FileText.fromLines(new File(aFilename), Arrays.asList(aLines)); 082 } 083 084 /** 085 * Creates a new <code>FileContents</code> instance. 086 * 087 * @param aText the contents of the file 088 */ 089 public FileContents(FileText aText) 090 { 091 mFilename = aText.getFile().toString(); 092 mText = aText; 093 } 094 095 /** {@inheritDoc} */ 096 public void reportSingleLineComment(String aType, int aStartLineNo, 097 int aStartColNo) 098 { 099 reportCppComment(aStartLineNo, aStartColNo); 100 } 101 102 /** {@inheritDoc} */ 103 public void reportBlockComment(String aType, int aStartLineNo, 104 int aStartColNo, int aEndLineNo, int aEndColNo) 105 { 106 reportCComment(aStartLineNo, aStartColNo, aEndLineNo, aEndColNo); 107 } 108 109 /** 110 * Report the location of a C++ style comment. 111 * @param aStartLineNo the starting line number 112 * @param aStartColNo the starting column number 113 **/ 114 public void reportCppComment(int aStartLineNo, int aStartColNo) 115 { 116 final String line = line(aStartLineNo - 1); 117 final String[] txt = new String[] {line.substring(aStartColNo)}; 118 final Comment comment = new Comment(txt, aStartColNo, aStartLineNo, 119 line.length() - 1); 120 mCPlusPlusComments.put(aStartLineNo, comment); 121 } 122 123 /** 124 * Returns a map of all the C++ style comments. The key is a line number, 125 * the value is the comment {@link TextBlock} at the line. 126 * @return the Map of comments 127 */ 128 public ImmutableMap<Integer, TextBlock> getCppComments() 129 { 130 return ImmutableMap.copyOf(mCPlusPlusComments); 131 } 132 133 /** 134 * Report the location of a C-style comment. 135 * @param aStartLineNo the starting line number 136 * @param aStartColNo the starting column number 137 * @param aEndLineNo the ending line number 138 * @param aEndColNo the ending column number 139 **/ 140 public void reportCComment(int aStartLineNo, int aStartColNo, 141 int aEndLineNo, int aEndColNo) 142 { 143 final String[] cc = extractCComment(aStartLineNo, aStartColNo, 144 aEndLineNo, aEndColNo); 145 final Comment comment = new Comment(cc, aStartColNo, aEndLineNo, 146 aEndColNo); 147 148 // save the comment 149 if (mCComments.containsKey(aStartLineNo)) { 150 final List<TextBlock> entries = mCComments.get(aStartLineNo); 151 entries.add(comment); 152 } 153 else { 154 final List<TextBlock> entries = Lists.newArrayList(); 155 entries.add(comment); 156 mCComments.put(aStartLineNo, entries); 157 } 158 159 // Remember if possible Javadoc comment 160 if (line(aStartLineNo - 1).indexOf("/**", aStartColNo) != -1) { 161 mJavadocComments.put(aEndLineNo - 1, comment); 162 } 163 } 164 165 /** 166 * Returns a map of all C style comments. The key is the line number, the 167 * value is a {@link List} of C style comment {@link TextBlock}s 168 * that start at that line. 169 * @return the map of comments 170 */ 171 public ImmutableMap<Integer, List<TextBlock>> getCComments() 172 { 173 return ImmutableMap.copyOf(mCComments); 174 } 175 176 /** 177 * Returns the specified C comment as a String array. 178 * @return C comment as a array 179 * @param aStartLineNo the starting line number 180 * @param aStartColNo the starting column number 181 * @param aEndLineNo the ending line number 182 * @param aEndColNo the ending column number 183 **/ 184 private String[] extractCComment(int aStartLineNo, int aStartColNo, 185 int aEndLineNo, int aEndColNo) 186 { 187 String[] retVal; 188 if (aStartLineNo == aEndLineNo) { 189 retVal = new String[1]; 190 retVal[0] = line(aStartLineNo - 1).substring(aStartColNo, 191 aEndColNo + 1); 192 } 193 else { 194 retVal = new String[aEndLineNo - aStartLineNo + 1]; 195 retVal[0] = line(aStartLineNo - 1).substring(aStartColNo); 196 for (int i = aStartLineNo; i < aEndLineNo; i++) { 197 retVal[i - aStartLineNo + 1] = line(i); 198 } 199 retVal[retVal.length - 1] = line(aEndLineNo - 1).substring(0, 200 aEndColNo + 1); 201 } 202 return retVal; 203 } 204 205 /** 206 * Returns the Javadoc comment before the specified line. 207 * A return value of <code>null</code> means there is no such comment. 208 * @return the Javadoc comment, or <code>null</code> if none 209 * @param aLineNo the line number to check before 210 **/ 211 public TextBlock getJavadocBefore(int aLineNo) 212 { 213 // Lines start at 1 to the callers perspective, so need to take off 2 214 int lineNo = aLineNo - 2; 215 216 // skip blank lines 217 while ((lineNo > 0) && (lineIsBlank(lineNo) || lineIsComment(lineNo))) { 218 lineNo--; 219 } 220 221 return mJavadocComments.get(lineNo); 222 } 223 224 /** 225 * Get a single line. 226 * For internal use only, as getText().get(lineNo) is just as 227 * suitable for external use and avoids method duplication. 228 * @param aLineNo the number of the line to get 229 * @return the corresponding line, without terminator 230 * @throws IndexOutOfBoundsException if lineNo is invalid 231 */ 232 private String line(int aLineNo) 233 { 234 return mText.get(aLineNo); 235 } 236 237 /** 238 * Get the full text of the file. 239 * @return an object containing the full text of the file 240 */ 241 public FileText getText() 242 { 243 return mText; 244 } 245 246 /** @return the lines in the file */ 247 public String[] getLines() 248 { 249 return mText.toLinesArray(); 250 } 251 252 /** @return the name of the file */ 253 public String getFilename() 254 { 255 return mFilename; 256 } 257 258 /** 259 * Checks if the specified line is blank. 260 * @param aLineNo the line number to check 261 * @return if the specified line consists only of tabs and spaces. 262 **/ 263 public boolean lineIsBlank(int aLineNo) 264 { 265 // possible improvement: avoid garbage creation in trim() 266 return "".equals(line(aLineNo).trim()); 267 } 268 269 /** 270 * Checks if the specified line is a single-line comment without code. 271 * @param aLineNo the line number to check 272 * @return if the specified line consists of only a single line comment 273 * without code. 274 **/ 275 public boolean lineIsComment(int aLineNo) 276 { 277 return MATCH_SINGLELINE_COMMENT.matcher(line(aLineNo)).matches(); 278 } 279 280 /** 281 * Checks if the specified position intersects with a comment. 282 * @param aStartLineNo the starting line number 283 * @param aStartColNo the starting column number 284 * @param aEndLineNo the ending line number 285 * @param aEndColNo the ending column number 286 * @return true if the positions intersects with a comment. 287 **/ 288 public boolean hasIntersectionWithComment(int aStartLineNo, 289 int aStartColNo, int aEndLineNo, int aEndColNo) 290 { 291 // Check C comments (all comments should be checked) 292 final Collection<List<TextBlock>> values = mCComments.values(); 293 for (final List<TextBlock> row : values) { 294 for (final TextBlock comment : row) { 295 if (comment.intersects(aStartLineNo, aStartColNo, aEndLineNo, 296 aEndColNo)) 297 { 298 return true; 299 } 300 } 301 } 302 303 // Check CPP comments (line searching is possible) 304 for (int lineNumber = aStartLineNo; lineNumber <= aEndLineNo; 305 lineNumber++) 306 { 307 final TextBlock comment = mCPlusPlusComments.get(lineNumber); 308 if ((comment != null) 309 && comment.intersects(aStartLineNo, aStartColNo, 310 aEndLineNo, aEndColNo)) 311 { 312 return true; 313 } 314 } 315 return false; 316 } 317 318 /** 319 * Checks if the current file is a package-info.java file. 320 * @return true if the package file. 321 */ 322 public boolean inPackageInfo() 323 { 324 return this.getFilename().endsWith("package-info.java"); 325 } 326}