//////////////////////////////////////////////////////////////////////////////// // checkstyle: Checks Java source code for adherence to a set of rules. // Copyright (C) 2001-2012 Oliver Burn // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //////////////////////////////////////////////////////////////////////////////// package com.puppycrawl.tools.checkstyle.api; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.puppycrawl.tools.checkstyle.grammars.CommentListener; import java.io.File; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.regex.Pattern; /** * Represents the contents of a file. * * @author Oliver Burn * @version 1.0 */ public final class FileContents implements CommentListener { /** * the pattern to match a single line comment containing only the comment * itself -- no code. */ private static final String MATCH_SINGLELINE_COMMENT_PAT = "^\\s*//.*$"; /** compiled regexp to match a single-line comment line */ private static final Pattern MATCH_SINGLELINE_COMMENT = Pattern .compile(MATCH_SINGLELINE_COMMENT_PAT); /** the file name */ private final String mFilename; /** the text */ private final FileText mText; /** map of the Javadoc comments indexed on the last line of the comment. * The hack is it assumes that there is only one Javadoc comment per line. */ private final Map mJavadocComments = Maps.newHashMap(); /** map of the C++ comments indexed on the first line of the comment. */ private final Map mCPlusPlusComments = Maps.newHashMap(); /** * map of the C comments indexed on the first line of the comment to a list * of comments on that line */ private final Map> mCComments = Maps.newHashMap(); /** * Creates a new FileContents instance. * * @param aFilename name of the file * @param aLines the contents of the file * @deprecated Use {@link #FileContents(FileText)} instead * in order to preserve the original line breaks where possible. */ @Deprecated public FileContents(String aFilename, String[] aLines) { mFilename = aFilename; mText = FileText.fromLines(new File(aFilename), Arrays.asList(aLines)); } /** * Creates a new FileContents instance. * * @param aText the contents of the file */ public FileContents(FileText aText) { mFilename = aText.getFile().toString(); mText = aText; } /** {@inheritDoc} */ public void reportSingleLineComment(String aType, int aStartLineNo, int aStartColNo) { reportCppComment(aStartLineNo, aStartColNo); } /** {@inheritDoc} */ public void reportBlockComment(String aType, int aStartLineNo, int aStartColNo, int aEndLineNo, int aEndColNo) { reportCComment(aStartLineNo, aStartColNo, aEndLineNo, aEndColNo); } /** * Report the location of a C++ style comment. * @param aStartLineNo the starting line number * @param aStartColNo the starting column number **/ public void reportCppComment(int aStartLineNo, int aStartColNo) { final String line = line(aStartLineNo - 1); final String[] txt = new String[] {line.substring(aStartColNo)}; final Comment comment = new Comment(txt, aStartColNo, aStartLineNo, line.length() - 1); mCPlusPlusComments.put(aStartLineNo, comment); } /** * Returns a map of all the C++ style comments. The key is a line number, * the value is the comment {@link TextBlock} at the line. * @return the Map of comments */ public ImmutableMap getCppComments() { return ImmutableMap.copyOf(mCPlusPlusComments); } /** * Report the location of a C-style comment. * @param aStartLineNo the starting line number * @param aStartColNo the starting column number * @param aEndLineNo the ending line number * @param aEndColNo the ending column number **/ public void reportCComment(int aStartLineNo, int aStartColNo, int aEndLineNo, int aEndColNo) { final String[] cc = extractCComment(aStartLineNo, aStartColNo, aEndLineNo, aEndColNo); final Comment comment = new Comment(cc, aStartColNo, aEndLineNo, aEndColNo); // save the comment if (mCComments.containsKey(aStartLineNo)) { final List entries = mCComments.get(aStartLineNo); entries.add(comment); } else { final List entries = Lists.newArrayList(); entries.add(comment); mCComments.put(aStartLineNo, entries); } // Remember if possible Javadoc comment if (line(aStartLineNo - 1).indexOf("/**", aStartColNo) != -1) { mJavadocComments.put(aEndLineNo - 1, comment); } } /** * Returns a map of all C style comments. The key is the line number, the * value is a {@link List} of C style comment {@link TextBlock}s * that start at that line. * @return the map of comments */ public ImmutableMap> getCComments() { return ImmutableMap.copyOf(mCComments); } /** * Returns the specified C comment as a String array. * @return C comment as a array * @param aStartLineNo the starting line number * @param aStartColNo the starting column number * @param aEndLineNo the ending line number * @param aEndColNo the ending column number **/ private String[] extractCComment(int aStartLineNo, int aStartColNo, int aEndLineNo, int aEndColNo) { String[] retVal; if (aStartLineNo == aEndLineNo) { retVal = new String[1]; retVal[0] = line(aStartLineNo - 1).substring(aStartColNo, aEndColNo + 1); } else { retVal = new String[aEndLineNo - aStartLineNo + 1]; retVal[0] = line(aStartLineNo - 1).substring(aStartColNo); for (int i = aStartLineNo; i < aEndLineNo; i++) { retVal[i - aStartLineNo + 1] = line(i); } retVal[retVal.length - 1] = line(aEndLineNo - 1).substring(0, aEndColNo + 1); } return retVal; } /** * Returns the Javadoc comment before the specified line. * A return value of null means there is no such comment. * @return the Javadoc comment, or null if none * @param aLineNo the line number to check before **/ public TextBlock getJavadocBefore(int aLineNo) { // Lines start at 1 to the callers perspective, so need to take off 2 int lineNo = aLineNo - 2; // skip blank lines while ((lineNo > 0) && (lineIsBlank(lineNo) || lineIsComment(lineNo))) { lineNo--; } return mJavadocComments.get(lineNo); } /** * Get a single line. * For internal use only, as getText().get(lineNo) is just as * suitable for external use and avoids method duplication. * @param aLineNo the number of the line to get * @return the corresponding line, without terminator * @throws IndexOutOfBoundsException if lineNo is invalid */ private String line(int aLineNo) { return mText.get(aLineNo); } /** * Get the full text of the file. * @return an object containing the full text of the file */ public FileText getText() { return mText; } /** @return the lines in the file */ public String[] getLines() { return mText.toLinesArray(); } /** @return the name of the file */ public String getFilename() { return mFilename; } /** * Checks if the specified line is blank. * @param aLineNo the line number to check * @return if the specified line consists only of tabs and spaces. **/ public boolean lineIsBlank(int aLineNo) { // possible improvement: avoid garbage creation in trim() return "".equals(line(aLineNo).trim()); } /** * Checks if the specified line is a single-line comment without code. * @param aLineNo the line number to check * @return if the specified line consists of only a single line comment * without code. **/ public boolean lineIsComment(int aLineNo) { return MATCH_SINGLELINE_COMMENT.matcher(line(aLineNo)).matches(); } /** * Checks if the specified position intersects with a comment. * @param aStartLineNo the starting line number * @param aStartColNo the starting column number * @param aEndLineNo the ending line number * @param aEndColNo the ending column number * @return true if the positions intersects with a comment. **/ public boolean hasIntersectionWithComment(int aStartLineNo, int aStartColNo, int aEndLineNo, int aEndColNo) { // Check C comments (all comments should be checked) final Collection> values = mCComments.values(); for (final List row : values) { for (final TextBlock comment : row) { if (comment.intersects(aStartLineNo, aStartColNo, aEndLineNo, aEndColNo)) { return true; } } } // Check CPP comments (line searching is possible) for (int lineNumber = aStartLineNo; lineNumber <= aEndLineNo; lineNumber++) { final TextBlock comment = mCPlusPlusComments.get(lineNumber); if ((comment != null) && comment.intersects(aStartLineNo, aStartColNo, aEndLineNo, aEndColNo)) { return true; } } return false; } /** * Checks if the current file is a package-info.java file. * @return true if the package file. */ public boolean inPackageInfo() { return this.getFilename().endsWith("package-info.java"); } }