1 /**
2 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3 */
4 package net.sourceforge.pmd.rules.design;
5
6 import java.util.ArrayList;
7 import java.util.List;
8 import java.util.concurrent.atomic.AtomicLong;
9 import java.util.regex.Pattern;
10
11 import net.sourceforge.pmd.AbstractJavaRule;
12 import net.sourceforge.pmd.PropertyDescriptor;
13 import net.sourceforge.pmd.RuleContext;
14 import net.sourceforge.pmd.ast.ASTClassOrInterfaceType;
15 import net.sourceforge.pmd.ast.ASTCompilationUnit;
16 import net.sourceforge.pmd.ast.ASTImportDeclaration;
17 import net.sourceforge.pmd.ast.SimpleNode;
18 import net.sourceforge.pmd.properties.StringProperty;
19 import net.sourceforge.pmd.rules.regex.RegexHelper;
20
21 import org.jaxen.JaxenException;
22
23 /**
24 * <p>A generic rule that can be configured to "count" classes of certain
25 * type based on either their name (full name, prefix, suffixes anything can
26 * be matched with a regex), and/or
27 * their type.</p>
28 *
29 * <p>Example of configurations:
30 * <!-- Property order is MANDATORY !!! -->
31 * <!-- Several regexes may be provided to ensure a match... -->
32 * <property name="nameMatch" description="a regex on which to match"
33 * value="^Abstract.*Bean*$,^*EJB*$"/>
34 * <!-- An operand to refine match strategy TODO: Not implemented yet !!! -->
35 * <property name"operand" description=""
36 * value="and"/> <!-- possible values are and/or -->
37 * <!-- Must be a full name to ensure type control !!! -->
38 * <property name="typeMatch" description="a regex to match on implements/extends classname"
39 * value="javax.servlet.Filter"/>
40 * <!-- Define after how many occurences one should log a violation -->
41 * <property name="threshold" description="Defines how many occurences are legal"
42 * value="2"/>
43 * <!-- TODO: Add a parameter to allow "ignore" pattern based on name -->
44 * </p>
45 *
46 * @author Ryan Gutafson, rgustav@users.sourceforge.net
47 * @author Romain PELISSE, belaran@gmail.com
48 *
49 */
50 public class GenericClassCounterRule extends AbstractJavaRule {
51
52
53 private static final PropertyDescriptor nameMatchDescriptor = new StringProperty("nameMatch",
54 "A series of regex, separeted by ',' to match on the classname", new String[] {""},1.0f,',');
55
56 private static final PropertyDescriptor operandDescriptor = new StringProperty("operand",
57 "or/and value to refined match criteria",new String(),2.0f);
58
59 private static final PropertyDescriptor typeMatchDescriptor = new StringProperty("typeMatch",
60 "A series of regex, separeted by ',' to match on implements/extends classname",new String[]{""},3.0f,',');
61
62 private static final PropertyDescriptor thresholdDescriptor = new StringProperty("threshold",
63 "Defines how many occurences are legal",new String(),4.0f);
64
65
66 private List<Pattern> namesMatch = new ArrayList<Pattern>(0);
67 private List<Pattern> typesMatch = new ArrayList<Pattern>(0);
68 private List<SimpleNode> matches = new ArrayList<SimpleNode>(0);
69 private List<String> simpleClassname = new ArrayList<String>(0);
70
71
72 @SuppressWarnings("PMD")
73 private String operand;
74 private int threshold;
75
76 private static String COUNTER_LABEL;
77
78 /**
79 * Default empty constructor
80 */
81 public GenericClassCounterRule() {
82 super();
83 }
84
85 private List<String> arrayAsList(String[] array) {
86 List<String> list = new ArrayList<String>(array.length);
87 int nbItem = 0;
88 while (nbItem < array.length )
89 list.add(array[nbItem++]);
90 return list;
91 }
92
93 protected void init(){
94
95 COUNTER_LABEL = this.getClass().getSimpleName() + ".number of match";
96
97 this.namesMatch = RegexHelper.compilePatternsFromList(arrayAsList(getStringProperties(nameMatchDescriptor)));
98 this.operand = getStringProperty(operandDescriptor);
99 this.typesMatch = RegexHelper.compilePatternsFromList(arrayAsList(getStringProperties(typeMatchDescriptor)));
100 String thresholdAsString = getStringProperty(thresholdDescriptor);
101 this.threshold = Integer.valueOf(thresholdAsString);
102
103 this.matches = new ArrayList<SimpleNode>();
104
105 }
106
107 @Override
108 public void start(RuleContext ctx) {
109
110 ctx.setAttribute(COUNTER_LABEL, new AtomicLong());
111 super.start(ctx);
112 }
113
114 @Override
115 public Object visit(ASTCompilationUnit node, Object data) {
116 init();
117 return super.visit(node,data);
118 }
119
120 @Override
121 public Object visit(ASTImportDeclaration node, Object data) {
122
123 for (Pattern pattern : this.typesMatch) {
124 if ( RegexHelper.isMatch(pattern,node.getImportedName())) {
125 if ( simpleClassname == null )
126 simpleClassname = new ArrayList<String>(1);
127 simpleClassname.add(node.getImportedName());
128 }
129
130 }
131 return super.visit(node, data);
132 }
133
134 @Override
135 public Object visit(ASTClassOrInterfaceType classType,Object data) {
136
137
138 for (String matchType : simpleClassname) {
139 if ( searchForAMatch(matchType,classType)) {
140 addAMatch(classType, data);
141 }
142 }
143
144
145 for (Pattern pattern : this.namesMatch)
146 if ( RegexHelper.isMatch(pattern, classType.getImage()))
147 addAMatch(classType, data);
148 return super.visit(classType, data);
149 }
150
151 private void addAMatch(SimpleNode node,Object data) {
152
153 RuleContext ctx = (RuleContext)data;
154 AtomicLong total = (AtomicLong)ctx.getAttribute(COUNTER_LABEL);
155 total.incrementAndGet();
156
157 this.matches.add(node);
158 }
159
160 @SuppressWarnings("unchecked")
161 private boolean searchForAMatch(String matchType,SimpleNode node) {
162 boolean status = false;
163 String xpathQuery = "//ClassOrInterfaceDeclaration[" +
164 "(./ExtendsList/ClassOrInterfaceType[@Image = '" + matchType + "'])" +
165 "or" +
166 "(./ImplementsList/ClassOrInterfaceType[@Image = '" + matchType + "'])" +
167 "]";
168 try
169 {
170 List list = node.findChildNodesWithXPath(xpathQuery);
171 if ( list != null && list.size() > 0 ) {
172
173 status = true;
174 }
175 }
176 catch (JaxenException e) {
177
178 e.printStackTrace();
179 }
180 return status;
181 }
182
183 @Override
184 public void end(RuleContext ctx) {
185 AtomicLong total = (AtomicLong)ctx.getAttribute(COUNTER_LABEL);
186
187 if ( total.get() > this.threshold )
188 for (SimpleNode node : this.matches)
189 addViolation(ctx,node , new Object[] { total });
190
191 ctx.removeAttribute(COUNTER_LABEL);
192 super.start(ctx);
193 }
194 }