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<ASTImportDeclaration>();
20      private List<ASTImportDeclaration> matches = new ArrayList<ASTImportDeclaration>();
21  
22      public UnnecessaryFullyQualifiedNameRule() {
23  	super.addRuleChainVisit(ASTCompilationUnit.class);
24  	super.addRuleChainVisit(ASTImportDeclaration.class);
25  	super.addRuleChainVisit(ASTClassOrInterfaceType.class);
26  	super.addRuleChainVisit(ASTName.class);
27      }
28  
29      @Override
30      public Object visit(ASTCompilationUnit node, Object data) {
31  	imports.clear();
32  	return data;
33      }
34  
35      @Override
36      public Object visit(ASTImportDeclaration node, Object data) {
37  	imports.add(node);
38  	return data;
39      }
40  
41      @Override
42      public Object visit(ASTClassOrInterfaceType node, Object data) {
43  	checkImports(node, data, false);
44  	return data;
45      }
46  
47      @Override
48      public Object visit(ASTName node, Object data) {
49  	if (!(node.jjtGetParent() instanceof ASTImportDeclaration)
50  	        && !(node.jjtGetParent() instanceof ASTPackageDeclaration)) {
51  	    checkImports(node, data, true);
52  	}
53  	return data;
54      }
55  
56      private void checkImports(JavaNode node, Object data, boolean checkStatic) {
57  	String name = node.getImage();
58  	matches.clear();
59  
60  	//  Find all "matching" import declarations
61  	for (ASTImportDeclaration importDeclaration : imports) {
62  	    if (importDeclaration.isImportOnDemand()) {
63  		// On demand import exactly matches the package of the type
64  		if (name.startsWith(importDeclaration.getImportedName())) {
65  		    if (name.lastIndexOf('.') == importDeclaration.getImportedName().length()) {
66  			matches.add(importDeclaration);
67  			continue;
68  		    }
69  		}
70  	    } else {
71  		// Exact match of imported class
72  		if (name.equals(importDeclaration.getImportedName())) {
73  		    matches.add(importDeclaration);
74  		    continue;
75  		}
76  		// Match of static method call on imported class
77  		if (name.startsWith(importDeclaration.getImportedName())) {
78  		    if (name.lastIndexOf('.') == importDeclaration.getImportedName().length()) {
79  			matches.add(importDeclaration);
80  			continue;
81  		    }
82  		}
83  	    }
84  	}
85  
86  	// If there is no direct match, consider if we match the tail end of a
87  	// direct static import, but also a static method on a class import?
88  	// For example:
89  	//
90  	//    import java.util.Arrays;
91  	//    import static java.util.Arrays.asList;
92  	//    static {
93  	//       List list1 = Arrays.asList("foo");  // Array class name not needed!
94  	//       List list2 = asList("foo"); // Preferred, used static import
95  	//    }
96  	if (matches.isEmpty() && name.indexOf('.') >= 0) {
97  	    for (ASTImportDeclaration importDeclaration : imports) {
98  		if (importDeclaration.isStatic()) {
99  		    String[] importParts = importDeclaration.getImportedName().split("\\.");
100 		    String[] nameParts = name.split("\\.");
101 		    boolean checkClassImport = false;
102 		    if (importDeclaration.isImportOnDemand()) {
103 			//  Name class part matches class part of static import?
104 			if (nameParts[nameParts.length - 2].equals(importParts[importParts.length - 1])) {
105 			    checkClassImport = true;
106 			}
107 		    } else {
108 			// Last 2 parts match?
109 			if (nameParts[nameParts.length - 1].equals(importParts[importParts.length - 1])
110 				&& nameParts[nameParts.length - 2].equals(importParts[importParts.length - 2])) {
111 			    checkClassImport = true;
112 			}
113 		    }
114 		    if (checkClassImport) {
115 			// Name class part matches a direct class import?
116 			String nameEnd = "." + nameParts[nameParts.length - 2];
117 			for (ASTImportDeclaration importDeclaration2 : imports) {
118 			    if (!importDeclaration2.isStatic() && !importDeclaration2.isImportOnDemand()
119 				    && importDeclaration2.getImportedName().endsWith(nameEnd)) {
120 				matches.add(importDeclaration2);
121 			    }
122 			}
123 		    }
124 		}
125 	    }
126 	}
127 
128 	if (!matches.isEmpty()) {
129 	    String importStr = matches.get(0).getImportedName() + (matches.get(0).isImportOnDemand() ? ".*" : "");
130 	    addViolation(data, node, new Object[] { node.getImage(), importStr });
131 	}
132 
133 	matches.clear();
134     }
135 }