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.unusedcode;
5   
6   import java.util.ArrayList;
7   import java.util.HashSet;
8   import java.util.List;
9   import java.util.Map;
10  import java.util.Set;
11  
12  import net.sourceforge.pmd.lang.ast.Node;
13  import net.sourceforge.pmd.lang.java.ast.ASTAnnotation;
14  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBody;
15  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBodyDeclaration;
16  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
17  import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
18  import net.sourceforge.pmd.lang.java.ast.ASTEnumBody;
19  import net.sourceforge.pmd.lang.java.ast.ASTEnumConstant;
20  import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
21  import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration;
22  import net.sourceforge.pmd.lang.java.ast.ASTName;
23  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
24  import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
25  import net.sourceforge.pmd.lang.java.ast.AccessNode;
26  import net.sourceforge.pmd.lang.java.ast.JavaNode;
27  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
28  import net.sourceforge.pmd.lang.java.symboltable.JavaNameOccurrence;
29  import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
30  import net.sourceforge.pmd.lang.symboltable.NameDeclaration;
31  import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
32  
33  public class UnusedPrivateFieldRule extends AbstractJavaRule {
34  
35      private boolean lombokImported = false;
36      private static final String LOMBOK_PACKAGE = "lombok";
37      private static final Set<String> LOMBOK_ANNOTATIONS = new HashSet<>();
38      static {
39          LOMBOK_ANNOTATIONS.add("Data");
40          LOMBOK_ANNOTATIONS.add("Getter");
41          LOMBOK_ANNOTATIONS.add("Setter");
42          LOMBOK_ANNOTATIONS.add("Value");
43          LOMBOK_ANNOTATIONS.add("RequiredArgsConstructor");
44          LOMBOK_ANNOTATIONS.add("AllArgsConstructor");
45          LOMBOK_ANNOTATIONS.add("Builder");
46      }
47  
48      @Override
49      public Object visit(ASTCompilationUnit node, Object data) {
50          lombokImported = false;
51          return super.visit(node, data);
52      }
53  
54      @Override
55      public Object visit(ASTImportDeclaration node, Object data) {
56          ASTName name = node.getFirstChildOfType(ASTName.class);
57          if (!lombokImported && name != null && name.getImage() != null & name.getImage().startsWith(LOMBOK_PACKAGE)) {
58              lombokImported = true;
59          }
60          return super.visit(node, data);
61      }
62  
63      @Override
64      public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
65          boolean classHasLombok = hasLombokAnnotation(node);
66  
67          Map<VariableNameDeclaration, List<NameOccurrence>> vars = node.getScope().getDeclarations(
68                  VariableNameDeclaration.class);
69          for (Map.Entry<VariableNameDeclaration, List<NameOccurrence>> entry : vars.entrySet()) {
70              VariableNameDeclaration decl = entry.getKey();
71              AccessNode accessNodeParent = decl.getAccessNodeParent();
72              if (!accessNodeParent.isPrivate() || isOK(decl.getImage()) || classHasLombok || hasLombokAnnotation(accessNodeParent)) {
73                  continue;
74              }
75              if (!actuallyUsed(entry.getValue())) {
76                  if (!usedInOuterClass(node, decl) && !usedInOuterEnum(node, decl)) {
77                      addViolation(data, decl.getNode(), decl.getImage());
78                  }
79              }
80          }
81          return super.visit(node, data);
82      }
83  
84      private boolean hasLombokAnnotation(Node node) {
85          boolean result = false;
86          Node parent = node.jjtGetParent();
87          List<ASTAnnotation> annotations = parent.findChildrenOfType(ASTAnnotation.class);
88          for (ASTAnnotation annotation : annotations) {
89              ASTName name = annotation.getFirstDescendantOfType(ASTName.class);
90              if (name != null) {
91                  String annotationName = name.getImage();
92                  if (lombokImported) {
93                      if (LOMBOK_ANNOTATIONS.contains(annotationName)) {
94                          result = true;
95                      }
96                  } else {
97                      if (annotationName.startsWith(LOMBOK_PACKAGE + ".")) {
98                          String shortName = annotationName.substring(LOMBOK_PACKAGE.length() + 1);
99                          if (LOMBOK_ANNOTATIONS.contains(shortName)) {
100                             result = true;
101                         }
102                     }
103                 }
104             }
105         }
106         return result;
107     }
108 
109     private boolean usedInOuterEnum(ASTClassOrInterfaceDeclaration node, NameDeclaration decl) {
110         List<ASTEnumDeclaration> outerEnums = node.getParentsOfType(ASTEnumDeclaration.class);
111         for (ASTEnumDeclaration outerEnum : outerEnums) {
112             ASTEnumBody enumBody = outerEnum.getFirstChildOfType(ASTEnumBody.class);
113             if (usedInOuter(decl, enumBody)) {
114                 return true;
115             }
116         }
117         return false;
118     }
119 
120     /**
121      * Find out whether the variable is used in an outer class
122      */
123     private boolean usedInOuterClass(ASTClassOrInterfaceDeclaration node, NameDeclaration decl) {
124         List<ASTClassOrInterfaceDeclaration> outerClasses = node.getParentsOfType(ASTClassOrInterfaceDeclaration.class);
125         for (ASTClassOrInterfaceDeclaration outerClass : outerClasses) {
126             ASTClassOrInterfaceBody classOrInterfaceBody = outerClass.getFirstChildOfType(ASTClassOrInterfaceBody.class);
127             if (usedInOuter(decl, classOrInterfaceBody)) {
128                 return true;
129             }
130         }
131         return false;
132     }
133 
134     private boolean usedInOuter(NameDeclaration decl, JavaNode body) {
135         List<ASTClassOrInterfaceBodyDeclaration> classOrInterfaceBodyDeclarations = body
136                 .findChildrenOfType(ASTClassOrInterfaceBodyDeclaration.class);
137         List<ASTEnumConstant> enumConstants = body
138                 .findChildrenOfType(ASTEnumConstant.class);
139         List<JavaNode> nodes = new ArrayList<>();
140         nodes.addAll(classOrInterfaceBodyDeclarations);
141         nodes.addAll(enumConstants);
142 
143         for (JavaNode node : nodes) {
144             List<ASTPrimarySuffix> primarySuffixes = node.findDescendantsOfType(ASTPrimarySuffix.class);
145             for (ASTPrimarySuffix primarySuffix : primarySuffixes) {
146                 if (decl.getImage().equals(primarySuffix.getImage())) {
147                     return true; // No violation
148                 }
149             }
150 
151             List<ASTPrimaryPrefix> primaryPrefixes = node.findDescendantsOfType(ASTPrimaryPrefix.class);
152             for (ASTPrimaryPrefix primaryPrefix : primaryPrefixes) {
153                 ASTName name = primaryPrefix.getFirstDescendantOfType(ASTName.class);
154 
155                 if (name != null) {
156                     for (String id : name.getImage().split("\\.")) {
157                         if (id.equals(decl.getImage())) {
158                             return true; // No violation
159                         }
160                     }
161                 }
162             }
163         }
164         return false;
165     }
166 
167     private boolean actuallyUsed(List<NameOccurrence> usages) {
168         for (NameOccurrence nameOccurrence : usages) {
169             JavaNameOccurrence jNameOccurrence = (JavaNameOccurrence) nameOccurrence;
170             if (!jNameOccurrence.isOnLeftHandSide()) {
171                 return true;
172             }
173         }
174 
175         return false;
176     }
177 
178     private boolean isOK(String image) {
179         return image.equals("serialVersionUID") || image.equals("serialPersistentFields") || image.equals("IDENT");
180     }
181 }