View Javadoc
1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.java.rule.comments;
5   
6   import java.util.HashSet;
7   import java.util.List;
8   import java.util.Set;
9   
10  import net.sourceforge.pmd.lang.java.ast.ASTAnnotation;
11  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBodyDeclaration;
12  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
13  import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
14  import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
15  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
16  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator;
17  import net.sourceforge.pmd.lang.java.ast.ASTName;
18  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
19  import net.sourceforge.pmd.lang.java.ast.AbstractJavaAccessNode;
20  import net.sourceforge.pmd.lang.java.ast.Comment;
21  import net.sourceforge.pmd.lang.rule.properties.StringProperty;
22  
23  /**
24   * Check for Methods, Fields and Nested Classes that have a default access modifier
25   *
26   * @author Damián Techeira
27   */
28  public class CommentDefaultAccessModifierRule extends AbstractCommentRule {
29  
30  	private static final StringProperty REGEX_DESCRIPTOR = new StringProperty("regex", "Regular expression", "", 1.0f);
31  	private static final String MESSAGE = "To avoid mistakes add a comment " +
32  			"at the beginning of the %s %s if you want a default access modifier";
33  	private final Set<Integer> interestingLineNumberComments = new HashSet<Integer>();
34  
35  	public CommentDefaultAccessModifierRule() {
36  		definePropertyDescriptor(REGEX_DESCRIPTOR);
37  	}
38  
39  	public CommentDefaultAccessModifierRule(final String regex) {
40  		this();
41  		setRegex(regex);
42  	}
43  
44  	public void setRegex(final String regex) {
45  		setProperty(CommentDefaultAccessModifierRule.REGEX_DESCRIPTOR, regex);
46  	}
47  
48  	@Override
49  	public Object visit(final ASTCompilationUnit node, final Object data) {
50  		interestingLineNumberComments.clear();
51  		final List<Comment> comments = node.getComments();
52  		for (final Comment comment : comments) {
53  			if (comment.getImage().matches(getProperty(REGEX_DESCRIPTOR).trim())) {
54  				interestingLineNumberComments.add(comment.getBeginLine());
55  			}
56  		}
57  		return super.visit(node, data);
58  	}
59  
60  	@Override
61  	public Object visit(final ASTMethodDeclaration decl, final Object data) {
62  		if (shouldReport(decl)) {
63  			addViolationWithMessage(data, decl, String.format(MESSAGE,
64  					decl.getFirstChildOfType(ASTMethodDeclarator.class).getImage(), "method"));
65  		}
66  		return super.visit(decl, data);
67  	}
68  
69  	@Override
70  	public Object visit(final ASTFieldDeclaration decl, final Object data) {
71  		if (shouldReport(decl)) {
72  			addViolationWithMessage(data, decl, String.format(MESSAGE,
73  					decl.getFirstDescendantOfType(ASTVariableDeclaratorId.class).getImage(), "field"));
74  		}
75  		return super.visit(decl, data);
76  	}
77  
78  	@Override
79  	public Object visit(final ASTClassOrInterfaceDeclaration decl, final Object data) {
80  		// check for nested classes
81  		if (decl.isNested() && shouldReport(decl)) {
82  			addViolationWithMessage(data, decl, String.format(MESSAGE, decl.getImage(), "nested class"));
83  		}
84  		return super.visit(decl, data);
85  	}
86  
87  	private boolean shouldReport(final AbstractJavaAccessNode decl) {
88  		List<ASTClassOrInterfaceDeclaration> parentClassOrInterface =
89  		        decl.getParentsOfType(ASTClassOrInterfaceDeclaration.class);
90  		// ignore if is a Interface
91          return (!parentClassOrInterface.isEmpty() && !parentClassOrInterface.get(0).isInterface())
92  		// check if the field/method/nested class has a default access modifier
93  		&& decl.isPackagePrivate()
94  		// if is a default access modifier check if there is a comment in this line
95  		&& !interestingLineNumberComments.contains(decl.getBeginLine())
96  		// that it is not annotated with @VisibleForTesting
97  		&& hasNoVisibleForTestingAnnotation(decl);
98  	}
99  
100     private boolean hasNoVisibleForTestingAnnotation(AbstractJavaAccessNode decl) {
101         boolean result = true;
102         ASTClassOrInterfaceBodyDeclaration parent = decl.getFirstParentOfType(ASTClassOrInterfaceBodyDeclaration.class);
103         if (parent != null) {
104             List<ASTAnnotation> annotations = parent.findChildrenOfType(ASTAnnotation.class);
105             for (ASTAnnotation annotation : annotations) {
106                 List<ASTName> names = annotation.findDescendantsOfType(ASTName.class);
107                 for (ASTName name : names) {
108                     if (name.hasImageEqualTo("VisibleForTesting")) {
109                         result = false;
110                         break;
111                     }
112                 }
113                 if (result == false) {
114                     break;
115                 }
116             }
117         }
118         return result;
119     }
120 }