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.imports;
5   
6   import java.util.ArrayList;
7   import java.util.List;
8   
9   import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
10  import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
11  import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration;
12  import net.sourceforge.pmd.lang.java.ast.ASTName;
13  import net.sourceforge.pmd.lang.java.ast.ASTPackageDeclaration;
14  import net.sourceforge.pmd.lang.java.ast.JavaNode;
15  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
16  
17  public class UnnecessaryFullyQualifiedNameRule extends AbstractJavaRule {
18  
19      private List<ASTImportDeclaration> imports = new ArrayList<>();
20      private List<ASTImportDeclaration> matches = new ArrayList<>();
21      private List<PotentialViolation> violations = new ArrayList<>();
22      private List<PotentialViolation> enumViolations = new ArrayList<>();
23  
24      public UnnecessaryFullyQualifiedNameRule() {
25  	super.addRuleChainVisit(ASTCompilationUnit.class);
26  	super.addRuleChainVisit(ASTImportDeclaration.class);
27  	super.addRuleChainVisit(ASTClassOrInterfaceType.class);
28  	super.addRuleChainVisit(ASTName.class);
29      }
30  
31      @Override
32      public Object visit(ASTCompilationUnit node, Object data) {
33  	imports.clear();
34  	violations.clear();
35  	enumViolations.clear();
36  
37  	super.visit(node, data);
38  
39  	filterPotentialViolations();
40  	reportViolations(data);
41  	return data;
42      }
43  
44      @Override
45      public Object visit(ASTImportDeclaration node, Object data) {
46  	imports.add(node);
47  	return data;
48      }
49  
50      @Override
51      public Object visit(ASTClassOrInterfaceType node, Object data) {
52  	checkImports(node);
53  	return data;
54      }
55  
56      @Override
57      public Object visit(ASTName node, Object data) {
58  	if (!(node.jjtGetParent() instanceof ASTImportDeclaration)
59  	        && !(node.jjtGetParent() instanceof ASTPackageDeclaration)) {
60  	    checkImports(node);
61  	}
62  	return data;
63      }
64  
65      private void checkImports(JavaNode node) {
66  	String name = node.getImage();
67  	matches.clear();
68  
69  	//  Find all "matching" import declarations
70  	for (ASTImportDeclaration importDeclaration : imports) {
71  	    if (importDeclaration.isImportOnDemand()) {
72  		// On demand import exactly matches the package of the type
73  		if (name.startsWith(importDeclaration.getImportedName())) {
74  		    if (name.lastIndexOf('.') == importDeclaration.getImportedName().length()) {
75  			matches.add(importDeclaration);
76  			continue;
77  		    }
78  		}
79  	    } else {
80  		// Exact match of imported class
81  		if (name.equals(importDeclaration.getImportedName())) {
82  		    matches.add(importDeclaration);
83  		    continue;
84  		}
85  		// Match of static method call on imported class
86  		if (name.startsWith(importDeclaration.getImportedName())) {
87  		    if (name.lastIndexOf('.') == importDeclaration.getImportedName().length()) {
88  			matches.add(importDeclaration);
89  			continue;
90  		    }
91  		}
92  	    }
93  	}
94  
95  	// If there is no direct match, consider if we match the tail end of a
96  	// direct static import, but also a static method on a class import?
97  	// For example:
98  	//
99  	//    import java.util.Arrays;
100 	//    import static java.util.Arrays.asList;
101 	//    static {
102 	//       List list1 = Arrays.asList("foo");  // Array class name not needed!
103 	//       List list2 = asList("foo"); // Preferred, used static import
104 	//    }
105 	if (matches.isEmpty() && name.indexOf('.') >= 0) {
106 	    for (ASTImportDeclaration importDeclaration : imports) {
107 		if (importDeclaration.isStatic()) {
108 		    String[] importParts = importDeclaration.getImportedName().split("\\.");
109 		    String[] nameParts = name.split("\\.");
110 		    if (importDeclaration.isImportOnDemand()) {
111 			//  Name class part matches class part of static import?
112 			if (nameParts[nameParts.length - 2].equals(importParts[importParts.length - 1])) {
113 			    matches.add(importDeclaration);
114 			}
115 		    } else {
116 			// Last 2 parts match?
117 			if (nameParts[nameParts.length - 1].equals(importParts[importParts.length - 1])
118 				&& nameParts[nameParts.length - 2].equals(importParts[importParts.length - 2])) {
119 			    matches.add(importDeclaration);
120 			}
121 		    }
122 		}
123 	    }
124 	}
125 
126 	if (!matches.isEmpty()) {
127 	    ASTImportDeclaration firstMatch = matches.get(0);
128         String importStr = firstMatch.getImportedName() + (matches.get(0).isImportOnDemand() ? ".*" : "");
129 	    String type = firstMatch.isStatic() ? "static " : "";
130 
131 	    PotentialViolation v = new PotentialViolation(node, importStr, type);
132 	    violations.add(v);
133 	    if (isEnum(firstMatch.getType())) {
134 	        enumViolations.add(v);
135 	    }
136 	}
137 
138 	matches.clear();
139     }
140 
141     private static class PotentialViolation {
142         private JavaNode node;
143         private String  importStr;
144         private String  importType;
145 
146         public PotentialViolation(JavaNode node, String importStr, String importType) {
147             this.node = node;
148             this.importStr = importStr;
149             this.importType = importType;
150         }
151 
152         public void addViolation(UnnecessaryFullyQualifiedNameRule rule, Object data) {
153             rule.addViolation(data, node, new Object[] { node.getImage(), importStr, importType });
154         }
155     }
156 
157     private void filterPotentialViolations() {
158         if (enumViolations.size() > 1) {
159             violations.removeAll(enumViolations);
160         }
161     }
162 
163     private void reportViolations(Object data) {
164         for (PotentialViolation v : violations) {
165             v.addViolation(this, data);
166         }
167     }
168 
169     private boolean isEnum(Class<?> type) {
170         if (type != null && Enum.class.isAssignableFrom(type)) {
171             return true;
172         }
173         return false;
174     }
175 }