/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.rule.comments;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.sourceforge.pmd.lang.java.ast.ASTAnnotation;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBodyDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator;
import net.sourceforge.pmd.lang.java.ast.ASTName;
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
import net.sourceforge.pmd.lang.java.ast.AbstractJavaAccessNode;
import net.sourceforge.pmd.lang.java.ast.Comment;
import net.sourceforge.pmd.lang.rule.properties.StringProperty;
/**
* Check for Methods, Fields and Nested Classes that have a default access
* modifier
*
* @author Damián Techeira
*/
public class CommentDefaultAccessModifierRule extends AbstractCommentRule {
private static final StringProperty REGEX_DESCRIPTOR = new StringProperty("regex", "Regular expression", "", 1.0f);
private static final String MESSAGE = "To avoid mistakes add a comment "
+ "at the beginning of the %s %s if you want a default access modifier";
private final Set<Integer> interestingLineNumberComments = new HashSet<Integer>();
public CommentDefaultAccessModifierRule() {
definePropertyDescriptor(REGEX_DESCRIPTOR);
}
public CommentDefaultAccessModifierRule(final String regex) {
this();
setRegex(regex);
}
public void setRegex(final String regex) {
setProperty(CommentDefaultAccessModifierRule.REGEX_DESCRIPTOR, regex);
}
@Override
public Object visit(final ASTCompilationUnit node, final Object data) {
interestingLineNumberComments.clear();
final List<Comment> comments = node.getComments();
for (final Comment comment : comments) {
if (comment.getImage().matches(getProperty(REGEX_DESCRIPTOR).trim())) {
interestingLineNumberComments.add(comment.getBeginLine());
}
}
return super.visit(node, data);
}
@Override
public Object visit(final ASTMethodDeclaration decl, final Object data) {
if (shouldReport(decl)) {
addViolationWithMessage(data, decl,
String.format(MESSAGE, decl.getFirstChildOfType(ASTMethodDeclarator.class).getImage(), "method"));
}
return super.visit(decl, data);
}
@Override
public Object visit(final ASTFieldDeclaration decl, final Object data) {
if (shouldReport(decl)) {
addViolationWithMessage(data, decl, String.format(MESSAGE,
decl.getFirstDescendantOfType(ASTVariableDeclaratorId.class).getImage(), "field"));
}
return super.visit(decl, data);
}
@Override
public Object visit(final ASTClassOrInterfaceDeclaration decl, final Object data) {
// check for nested classes
if (decl.isNested() && shouldReport(decl)) {
addViolationWithMessage(data, decl, String.format(MESSAGE, decl.getImage(), "nested class"));
}
return super.visit(decl, data);
}
private boolean shouldReport(final AbstractJavaAccessNode decl) {
List<ASTClassOrInterfaceDeclaration> parentClassOrInterface = decl
.getParentsOfType(ASTClassOrInterfaceDeclaration.class);
// ignore if is a Interface
return (!parentClassOrInterface.isEmpty() && !parentClassOrInterface.get(0).isInterface())
// check if the field/method/nested class has a default access
// modifier
&& decl.isPackagePrivate()
// if is a default access modifier check if there is a comment
// in this line
&& !interestingLineNumberComments.contains(decl.getBeginLine())
// that it is not annotated with @VisibleForTesting
&& hasNoVisibleForTestingAnnotation(decl);
}
private boolean hasNoVisibleForTestingAnnotation(AbstractJavaAccessNode decl) {
boolean result = true;
ASTClassOrInterfaceBodyDeclaration parent = decl.getFirstParentOfType(ASTClassOrInterfaceBodyDeclaration.class);
if (parent != null) {
List<ASTAnnotation> annotations = parent.findChildrenOfType(ASTAnnotation.class);
for (ASTAnnotation annotation : annotations) {
List<ASTName> names = annotation.findDescendantsOfType(ASTName.class);
for (ASTName name : names) {
if (name.hasImageEqualTo("VisibleForTesting")) {
result = false;
break;
}
}
if (result == false) {
break;
}
}
}
return result;
}
}