View Javadoc

1   package net.sourceforge.pmd.lang.java.rule.coupling;
2   
3   import java.util.ArrayList;
4   import java.util.Arrays;
5   import java.util.Collections;
6   import java.util.List;
7   
8   import net.sourceforge.pmd.PropertySource;
9   import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
10  import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration;
11  import net.sourceforge.pmd.lang.java.ast.ASTPackageDeclaration;
12  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
13  import net.sourceforge.pmd.lang.rule.properties.StringMultiProperty;
14  import net.sourceforge.pmd.util.CollectionUtil;
15  
16  /**
17   * The loose package coupling Rule can be used to ensure coupling outside of
18   * a package hierarchy is minimized to all but an allowed set of classes from
19   * within the package hierarchy.
20   * <p>
21   * For example, supposed you have the following package hierarchy:
22   * <ul>
23   * 	<li><code>org.sample</code></li>
24   * 	<li><code>org.sample.impl</code></li>
25   * 	<li><code>org.sample.util</code></li>
26   * </ul>
27   * And the allowed class <code>org.sample.SampleInterface</code>.
28   * <p>
29   * This rule can be used to ensure that all classes within the
30   * <code>org.sample</code> package and its sub-packages are not used outside of
31   * the <code>org.sample</code> package hierarchy.  Further, the only allowed
32   * usage outside of a class in the <code>org.sample</code> hierarchy would be
33   * via <code>org.sample.SampleInterface</code>.
34   */
35  public class LoosePackageCouplingRule extends AbstractJavaRule {
36  
37      public static final StringMultiProperty PACKAGES_DESCRIPTOR = new StringMultiProperty("packages", "Restricted packages",
38  	    new String[] {}, 1.0f, ',');
39  
40      public static final StringMultiProperty CLASSES_DESCRIPTOR = new StringMultiProperty("classes", "Allowed classes",
41  	    new String[] {}, 2.0f, ',');
42  
43      // The package of this source file
44      private String thisPackage;
45  
46      // The restricted packages
47      private List<String> restrictedPackages;
48  
49      public LoosePackageCouplingRule() {
50  	definePropertyDescriptor(PACKAGES_DESCRIPTOR);
51  	definePropertyDescriptor(CLASSES_DESCRIPTOR);
52  
53  	addRuleChainVisit(ASTCompilationUnit.class);
54  	addRuleChainVisit(ASTPackageDeclaration.class);
55  	addRuleChainVisit(ASTImportDeclaration.class);
56      }
57  
58      @Override
59      public Object visit(ASTCompilationUnit node, Object data) {
60  	this.thisPackage = "";
61  
62  	// Sort the restricted packages in reverse order.  This will ensure the
63  	// child packages are in the list before their parent packages.
64  	this.restrictedPackages = new ArrayList<String>(Arrays.asList(super.getProperty(PACKAGES_DESCRIPTOR)));
65  	Collections.sort(restrictedPackages, Collections.reverseOrder());
66  
67  	return data;
68      }
69  
70      @Override
71      public Object visit(ASTPackageDeclaration node, Object data) {
72  	this.thisPackage = node.getPackageNameImage();
73  	return data;
74      }
75  
76      @Override
77      public Object visit(ASTImportDeclaration node, Object data) {
78  
79  	String importPackage = node.getPackageName();
80  
81  	// Check each restricted package
82  	for (String pkg : getRestrictedPackages()) {
83  	    // Is this import restricted?  Use the deepest sub-package which restricts this import.
84  	    if (isContainingPackage(pkg, importPackage)) {
85  		// Is this source in a sub-package of restricted package?
86  		if (pkg.equals(thisPackage) || isContainingPackage(pkg, thisPackage)) {
87  		    // Valid usage
88  		    break;
89  		} else {
90  		    // On demand imports automatically fail because they include everything
91  		    if (node.isImportOnDemand()) {
92  			addViolation(data, node, new Object[] { node.getImportedName(), pkg });
93  			break;
94  		    } else {
95  			if (!isAllowedClass(node)) {
96  			    addViolation(data, node, new Object[] { node.getImportedName(), pkg });
97  			    break;
98  			}
99  		    }
100 		}
101 	    }
102 	}
103 	return data;
104     }
105 
106     protected List<String> getRestrictedPackages() {
107 	return restrictedPackages;
108     }
109 
110     // Is 1st package a containing package of the 2nd package?
111     protected boolean isContainingPackage(String pkg1, String pkg2) {
112 	return pkg1.equals(pkg2)
113 		|| (pkg1.length() < pkg2.length() && pkg2.startsWith(pkg1) && pkg2.charAt(pkg1.length()) == '.');
114     }
115 
116     protected boolean isAllowedClass(ASTImportDeclaration node) {
117 	String importedName = node.getImportedName();
118 	for (String clazz : getProperty(CLASSES_DESCRIPTOR)) {
119 	    if (importedName.equals(clazz)) {
120 		return true;
121 	    }
122 
123 	}
124 	return false;
125     }
126 
127 	public boolean checksNothing() {
128 
129 		return
130 			CollectionUtil.isEmpty(getProperty(PACKAGES_DESCRIPTOR)) &&
131 			CollectionUtil.isEmpty(getProperty(CLASSES_DESCRIPTOR)) ;
132 	}
133 
134 	/**
135 	 * @see PropertySource#dysfunctionReason()
136 	 */
137 	@Override
138 	public String dysfunctionReason() {
139 		return checksNothing() ?
140 				"No packages or classes specified" :
141 				null;
142 	}
143 }