1
2
3
4 package net.sourceforge.pmd.lang.java.rule.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.RuleContext;
12 import net.sourceforge.pmd.lang.ast.Node;
13 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
14 import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
15 import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration;
16 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
17 import net.sourceforge.pmd.lang.java.rule.regex.RegexHelper;
18 import net.sourceforge.pmd.lang.rule.properties.StringMultiProperty;
19 import net.sourceforge.pmd.lang.rule.properties.StringProperty;
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 public class GenericClassCounterRule extends AbstractJavaRule {
49
50
51 private static final StringMultiProperty NAME_MATCH_DESCRIPTOR = new StringMultiProperty("nameMatch",
52 "A series of regex, separated by ',' to match on the classname", new String[] {""},1.0f,',');
53
54 private static final StringProperty OPERAND_DESCRIPTOR = new StringProperty("operand",
55 "or/and value to refined match criteria",new String(),2.0f);
56
57 private static final StringMultiProperty TYPE_MATCH_DESCRIPTOR = new StringMultiProperty("typeMatch",
58 "A series of regex, separated by ',' to match on implements/extends classname",new String[]{""},3.0f,',');
59
60
61 private static final StringProperty THRESHOLD_DESCRIPTOR = new StringProperty("threshold",
62 "Defines how many occurences are legal",new String(),4.0f);
63
64
65 private List<Pattern> namesMatch = new ArrayList<Pattern>(0);
66 private List<Pattern> typesMatch = new ArrayList<Pattern>(0);
67 private List<Node> matches = new ArrayList<Node>(0);
68 private List<String> simpleClassname = new ArrayList<String>(0);
69
70
71 @SuppressWarnings("PMD")
72 private String operand;
73 private int threshold;
74
75 private static String counterLabel;
76
77 public GenericClassCounterRule() {
78 definePropertyDescriptor(NAME_MATCH_DESCRIPTOR);
79 definePropertyDescriptor(OPERAND_DESCRIPTOR);
80 definePropertyDescriptor(TYPE_MATCH_DESCRIPTOR);
81 definePropertyDescriptor(THRESHOLD_DESCRIPTOR);
82 }
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 }
91 return list;
92 }
93
94 protected void init(){
95
96 counterLabel = this.getClass().getSimpleName() + ".number of match";
97
98 this.namesMatch = RegexHelper.compilePatternsFromList(arrayAsList(getProperty(NAME_MATCH_DESCRIPTOR)));
99 this.operand = getProperty(OPERAND_DESCRIPTOR);
100 this.typesMatch = RegexHelper.compilePatternsFromList(arrayAsList(getProperty(TYPE_MATCH_DESCRIPTOR)));
101 String thresholdAsString = getProperty(THRESHOLD_DESCRIPTOR);
102 this.threshold = Integer.valueOf(thresholdAsString);
103
104 this.matches = new ArrayList<Node>();
105
106 }
107
108 @Override
109 public void start(RuleContext ctx) {
110
111 ctx.setAttribute(counterLabel, new AtomicLong());
112 super.start(ctx);
113 }
114
115 @Override
116 public Object visit(ASTCompilationUnit node, Object data) {
117 init();
118 return super.visit(node,data);
119 }
120
121 @Override
122 public Object visit(ASTImportDeclaration node, Object data) {
123
124 for (Pattern pattern : this.typesMatch) {
125 if ( RegexHelper.isMatch(pattern,node.getImportedName())) {
126 if ( simpleClassname == null ) {
127 simpleClassname = new ArrayList<String>(1);
128 }
129 simpleClassname.add(node.getImportedName());
130 }
131
132 }
133 return super.visit(node, data);
134 }
135
136 @Override
137 public Object visit(ASTClassOrInterfaceType classType,Object data) {
138
139
140 for (String matchType : simpleClassname) {
141 if ( searchForAMatch(matchType,classType)) {
142 addAMatch(classType, data);
143 }
144 }
145
146
147 for (Pattern pattern : this.namesMatch) {
148 if ( RegexHelper.isMatch(pattern, classType.getImage())) {
149 addAMatch(classType, data);
150 }
151 }
152 return super.visit(classType, data);
153 }
154
155 private void addAMatch(Node node,Object data) {
156
157 RuleContext ctx = (RuleContext)data;
158 AtomicLong total = (AtomicLong)ctx.getAttribute(counterLabel);
159 total.incrementAndGet();
160
161 this.matches.add(node);
162 }
163
164 private boolean searchForAMatch(String matchType, Node node) {
165 String xpathQuery = "//ClassOrInterfaceDeclaration[(./ExtendsList/ClassOrInterfaceType[@Image = '" + matchType
166 + "']) or (./ImplementsList/ClassOrInterfaceType[@Image = '" + matchType + "'])]";
167
168 return node.hasDescendantMatchingXPath(xpathQuery);
169 }
170
171 @Override
172 public void end(RuleContext ctx) {
173 AtomicLong total = (AtomicLong)ctx.getAttribute(counterLabel);
174
175 if ( total.get() > this.threshold ) {
176 for (Node node : this.matches) {
177 addViolation(ctx,node , new Object[] { total });
178 }
179
180 ctx.removeAttribute(counterLabel);
181 super.end(ctx);
182 }
183 }
184 }