////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2017 the original author or authors.
//
// 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 java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import com.google.common.collect.ImmutableMap;
import com.puppycrawl.tools.checkstyle.grammars.CommentListener;
import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
/**
* Represents the contents of a file.
*
* @author Oliver Burn
*/
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 fileName;
/** The text. */
private final FileText text;
/** 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<Integer, TextBlock> javadocComments = new HashMap<>();
/** Map of the C++ comments indexed on the first line of the comment. */
private final Map<Integer, TextBlock> cppComments = new HashMap<>();
/**
* Map of the C comments indexed on the first line of the comment to a list
* of comments on that line.
*/
private final Map<Integer, List<TextBlock>> clangComments = new HashMap<>();
/**
* Creates a new {@code FileContents} instance.
*
* @param filename name of the file
* @param lines 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 filename, String... lines) {
fileName = filename;
text = FileText.fromLines(new File(filename), Arrays.asList(lines));
}
/**
* Creates a new {@code FileContents} instance.
*
* @param text the contents of the file
*/
public FileContents(FileText text) {
fileName = text.getFile().toString();
this.text = new FileText(text);
}
@Override
public void reportSingleLineComment(String type, int startLineNo,
int startColNo) {
reportSingleLineComment(startLineNo, startColNo);
}
/**
* Report the location of a single line comment.
* @param startLineNo the starting line number
* @param startColNo the starting column number
**/
public void reportSingleLineComment(int startLineNo, int startColNo) {
final String line = line(startLineNo - 1);
final String[] txt = {line.substring(startColNo)};
final Comment comment = new Comment(txt, startColNo, startLineNo,
line.length() - 1);
cppComments.put(startLineNo, comment);
}
@Override
public void reportBlockComment(String type, int startLineNo,
int startColNo, int endLineNo, int endColNo) {
reportBlockComment(startLineNo, startColNo, endLineNo, endColNo);
}
/**
* Report the location of a block comment.
* @param startLineNo the starting line number
* @param startColNo the starting column number
* @param endLineNo the ending line number
* @param endColNo the ending column number
**/
public void reportBlockComment(int startLineNo, int startColNo,
int endLineNo, int endColNo) {
final String[] cComment = extractBlockComment(startLineNo, startColNo,
endLineNo, endColNo);
final Comment comment = new Comment(cComment, startColNo, endLineNo,
endColNo);
// save the comment
if (clangComments.containsKey(startLineNo)) {
final List<TextBlock> entries = clangComments.get(startLineNo);
entries.add(comment);
}
else {
final List<TextBlock> entries = new ArrayList<>();
entries.add(comment);
clangComments.put(startLineNo, entries);
}
// Remember if possible Javadoc comment
final String firstLine = line(startLineNo - 1);
if (firstLine.contains("/**") && !firstLine.contains("/**/")) {
javadocComments.put(endLineNo - 1, comment);
}
}
/**
* Report the location of a C++ style comment.
* @param startLineNo the starting line number
* @param startColNo the starting column number
* @deprecated Use {@link #reportSingleLineComment(int, int)} instead.
**/
@Deprecated
public void reportCppComment(int startLineNo, int startColNo) {
reportSingleLineComment(startLineNo, startColNo);
}
/**
* 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
* @deprecated Use {@link #getSingleLineComments()} instead.
*/
@Deprecated
public ImmutableMap<Integer, TextBlock> getCppComments() {
return getSingleLineComments();
}
/**
* Returns a map of all the single line comments. The key is a line number,
* the value is the comment {@link TextBlock} at the line.
* @return the Map of comments
*/
public ImmutableMap<Integer, TextBlock> getSingleLineComments() {
return ImmutableMap.copyOf(cppComments);
}
/**
* Report the location of a C-style comment.
* @param startLineNo the starting line number
* @param startColNo the starting column number
* @param endLineNo the ending line number
* @param endColNo the ending column number
* @deprecated Use {@link #reportBlockComment(int, int, int, int)} instead.
**/
// -@cs[AbbreviationAsWordInName] Can't change yet since class is API.
@Deprecated
public void reportCComment(int startLineNo, int startColNo,
int endLineNo, int endColNo) {
reportBlockComment(startLineNo, startColNo, endLineNo, endColNo);
}
/**
* 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
* @deprecated Use {@link #getBlockComments()} instead.
*/
// -@cs[AbbreviationAsWordInName] Can't change yet since class is API.
@Deprecated
public ImmutableMap<Integer, List<TextBlock>> getCComments() {
return getBlockComments();
}
/**
* Returns a map of all block comments. The key is the line number, the
* value is a {@link List} of block comment {@link TextBlock}s
* that start at that line.
* @return the map of comments
*/
public ImmutableMap<Integer, List<TextBlock>> getBlockComments() {
return ImmutableMap.copyOf(clangComments);
}
/**
* Returns the specified block comment as a String array.
* @param startLineNo the starting line number
* @param startColNo the starting column number
* @param endLineNo the ending line number
* @param endColNo the ending column number
* @return block comment as an array
**/
private String[] extractBlockComment(int startLineNo, int startColNo,
int endLineNo, int endColNo) {
final String[] returnValue;
if (startLineNo == endLineNo) {
returnValue = new String[1];
returnValue[0] = line(startLineNo - 1).substring(startColNo,
endColNo + 1);
}
else {
returnValue = new String[endLineNo - startLineNo + 1];
returnValue[0] = line(startLineNo - 1).substring(startColNo);
for (int i = startLineNo; i < endLineNo; i++) {
returnValue[i - startLineNo + 1] = line(i);
}
returnValue[returnValue.length - 1] = line(endLineNo - 1).substring(0,
endColNo + 1);
}
return returnValue;
}
/**
* Returns the Javadoc comment before the specified line.
* A return value of {@code null} means there is no such comment.
* @param lineNoBefore the line number to check before
* @return the Javadoc comment, or {@code null} if none
**/
public TextBlock getJavadocBefore(int lineNoBefore) {
// Lines start at 1 to the callers perspective, so need to take off 2
int lineNo = lineNoBefore - 2;
// skip blank lines
while (lineNo > 0 && (lineIsBlank(lineNo) || lineIsComment(lineNo))) {
lineNo--;
}
return javadocComments.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 lineNo the number of the line to get
* @return the corresponding line, without terminator
* @throws IndexOutOfBoundsException if lineNo is invalid
*/
private String line(int lineNo) {
return text.get(lineNo);
}
/**
* Get the full text of the file.
* @return an object containing the full text of the file
*/
public FileText getText() {
return new FileText(text);
}
/**
* Gets the lines in the file.
* @return the lines in the file
*/
public String[] getLines() {
return text.toLinesArray();
}
/**
* Get the line from text of the file.
* @param index index of the line
* @return line from text of the file
*/
public String getLine(int index) {
return text.get(index);
}
/**
* Gets the name of the file.
* @return the name of the file
*/
public String getFileName() {
return fileName;
}
/**
* Getter.
* @return the name of the file
* @deprecated use {@link #getFileName} instead
*/
@Deprecated
public String getFilename() {
return fileName;
}
/**
* Checks if the specified line is blank.
* @param lineNo the line number to check
* @return if the specified line consists only of tabs and spaces.
**/
public boolean lineIsBlank(int lineNo) {
return CommonUtils.isBlank(line(lineNo));
}
/**
* Checks if the specified line is a single-line comment without code.
* @param lineNo the line number to check
* @return if the specified line consists of only a single line comment
* without code.
**/
public boolean lineIsComment(int lineNo) {
return MATCH_SINGLELINE_COMMENT.matcher(line(lineNo)).matches();
}
/**
* Checks if the specified position intersects with a comment.
* @param startLineNo the starting line number
* @param startColNo the starting column number
* @param endLineNo the ending line number
* @param endColNo the ending column number
* @return true if the positions intersects with a comment.
**/
public boolean hasIntersectionWithComment(int startLineNo,
int startColNo, int endLineNo, int endColNo) {
return hasIntersectionWithBlockComment(startLineNo, startColNo, endLineNo, endColNo)
|| hasIntersectionWithSingleLineComment(startLineNo, startColNo, endLineNo,
endColNo);
}
/**
* Checks if the current file is a package-info.java file.
* @return true if the package file.
*/
public boolean inPackageInfo() {
return fileName.endsWith("package-info.java");
}
/**
* Checks if the specified position intersects with a block comment.
* @param startLineNo the starting line number
* @param startColNo the starting column number
* @param endLineNo the ending line number
* @param endColNo the ending column number
* @return true if the positions intersects with a block comment.
*/
private boolean hasIntersectionWithBlockComment(int startLineNo, int startColNo,
int endLineNo, int endColNo) {
boolean hasIntersection = false;
// Check C comments (all comments should be checked)
final Collection<List<TextBlock>> values = clangComments.values();
for (final List<TextBlock> row : values) {
for (final TextBlock comment : row) {
if (comment.intersects(startLineNo, startColNo, endLineNo, endColNo)) {
hasIntersection = true;
break;
}
}
if (hasIntersection) {
break;
}
}
return hasIntersection;
}
/**
* Checks if the specified position intersects with a single line comment.
* @param startLineNo the starting line number
* @param startColNo the starting column number
* @param endLineNo the ending line number
* @param endColNo the ending column number
* @return true if the positions intersects with a single line comment.
*/
private boolean hasIntersectionWithSingleLineComment(int startLineNo, int startColNo,
int endLineNo, int endColNo) {
boolean hasIntersection = false;
// Check CPP comments (line searching is possible)
for (int lineNumber = startLineNo; lineNumber <= endLineNo;
lineNumber++) {
final TextBlock comment = cppComments.get(lineNumber);
if (comment != null && comment.intersects(startLineNo, startColNo,
endLineNo, endColNo)) {
hasIntersection = true;
break;
}
}
return hasIntersection;
}
}