1
2
3
4 package net.sourceforge.pmd.lang.java.rule.design;
5
6 import java.util.ArrayList;
7 import java.util.HashMap;
8 import java.util.HashSet;
9 import java.util.List;
10 import java.util.Map;
11 import java.util.Set;
12
13 import net.sourceforge.pmd.RuleContext;
14 import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression;
15 import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement;
16 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
17 import net.sourceforge.pmd.lang.java.ast.ASTConditionalAndExpression;
18 import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression;
19 import net.sourceforge.pmd.lang.java.ast.ASTConditionalOrExpression;
20 import net.sourceforge.pmd.lang.java.ast.ASTForStatement;
21 import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
22 import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
23 import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
24 import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator;
25 import net.sourceforge.pmd.lang.java.ast.ASTName;
26 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
27 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
28 import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
29 import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
30 import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement;
31 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
32 import net.sourceforge.pmd.lang.java.rule.JavaRuleViolation;
33 import net.sourceforge.pmd.lang.java.symboltable.Scope;
34 import net.sourceforge.pmd.lang.java.symboltable.SourceFileScope;
35 import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
36 import net.sourceforge.pmd.util.StringUtil;
37
38
39
40
41
42
43
44
45
46
47
48 public class GodClassRule extends AbstractJavaRule {
49
50
51
52
53
54 private static final int WMC_VERY_HIGH = 47;
55
56
57
58
59
60 private static final int FEW_THRESHOLD = 5;
61
62
63
64
65
66 private static final double ONE_THIRD_THRESHOLD = 1.0/3.0;
67
68
69 private int wmcCounter;
70
71 private int atfdCounter;
72
73
74 private Map<String, Set<String>> methodAttributeAccess;
75
76 private String currentMethodName;
77
78
79
80
81
82
83
84 @Override
85 public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
86 wmcCounter = 0;
87 atfdCounter = 0;
88 methodAttributeAccess = new HashMap<String, Set<String>>();
89
90 Object result = super.visit(node, data);
91
92 double tcc = calculateTcc();
93
94
95
96
97
98
99
100
101
102 if (wmcCounter >= WMC_VERY_HIGH
103 && atfdCounter > FEW_THRESHOLD
104 && tcc < ONE_THIRD_THRESHOLD) {
105
106 StringBuilder sb = new StringBuilder();
107 sb.append(getMessage());
108 sb.append(" (")
109 .append("WMC=").append(wmcCounter).append(", ")
110 .append("ATFD=").append(atfdCounter).append(", ")
111 .append("TCC=").append(tcc).append(')');
112
113 RuleContext ctx = (RuleContext)data;
114 ctx.getReport().addRuleViolation(new JavaRuleViolation(this, ctx, node, sb.toString()));
115 }
116 return result;
117 }
118
119
120
121
122
123 private double calculateTcc() {
124 double tcc = 0.0;
125 int methodPairs = determineMethodPairs();
126 double totalMethodPairs = calculateTotalMethodPairs();
127 if (totalMethodPairs > 0) {
128 tcc = methodPairs / totalMethodPairs;
129 }
130 return tcc;
131 }
132
133
134
135
136
137
138
139 private double calculateTotalMethodPairs() {
140 int methodCount = methodAttributeAccess.size();
141 int n = methodCount - 1;
142 double totalMethodPairs = n * (n + 1) / 2.0;
143 return totalMethodPairs;
144 }
145
146
147
148
149
150
151 private int determineMethodPairs() {
152 List<String> methods = new ArrayList<String>(methodAttributeAccess.keySet());
153 int methodCount = methods.size();
154 int pairs = 0;
155
156 if (methodCount > 1) {
157 for (int i = 0; i < methodCount - 1; i++) {
158 String firstMethodName = methods.get(i);
159 String secondMethodName = methods.get(i + 1);
160 Set<String> accessesOfFirstMethod = methodAttributeAccess.get(firstMethodName);
161 Set<String> accessesOfSecondMethod = methodAttributeAccess.get(secondMethodName);
162 Set<String> combinedAccesses = new HashSet<String>();
163
164 combinedAccesses.addAll(accessesOfFirstMethod);
165 combinedAccesses.addAll(accessesOfSecondMethod);
166
167 if (combinedAccesses.size() < (accessesOfFirstMethod.size() + accessesOfSecondMethod.size())) {
168 pairs++;
169 }
170 }
171 }
172 return pairs;
173 }
174
175
176
177
178
179
180
181 @Override
182 public Object visit(ASTPrimaryExpression node, Object data) {
183 if (isForeignAttributeOrMethod(node)) {
184 if (isAttributeAccess(node)
185 || (isMethodCall(node) && isForeignGetterSetterCall(node))) {
186 atfdCounter++;
187 }
188 } else {
189 if (currentMethodName != null) {
190 Set<String> methodAccess = methodAttributeAccess.get(currentMethodName);
191 String variableName = getVariableName(node);
192 VariableNameDeclaration variableDeclaration = findVariableDeclaration(variableName, node.getScope().getEnclosingClassScope());
193 if (variableDeclaration != null) {
194 methodAccess.add(variableName);
195 }
196 }
197 }
198
199 return super.visit(node, data);
200 }
201
202
203 private boolean isForeignGetterSetterCall(ASTPrimaryExpression node) {
204
205 String methodOrAttributeName = getMethodOrAttributeName(node);
206
207 return methodOrAttributeName != null && StringUtil.startsWithAny(methodOrAttributeName, "get","is","set");
208 }
209
210
211 private boolean isMethodCall(ASTPrimaryExpression node) {
212 boolean result = false;
213 List<ASTPrimarySuffix> suffixes = node.findDescendantsOfType(ASTPrimarySuffix.class);
214 if (suffixes.size() == 1) {
215 result = suffixes.get(0).isArguments();
216 }
217 return result;
218 }
219
220
221 private boolean isForeignAttributeOrMethod(ASTPrimaryExpression node) {
222 boolean result = false;
223 String nameImage = getNameImage(node);
224
225 if (nameImage != null && (!nameImage.contains(".") || nameImage.startsWith("this."))) {
226 result = false;
227 } else if (nameImage == null && node.getFirstDescendantOfType(ASTPrimaryPrefix.class).usesThisModifier()) {
228 result = false;
229 } else if (nameImage == null && node.hasDecendantOfAnyType(ASTLiteral.class, ASTAllocationExpression.class)) {
230 result = false;
231 } else {
232 result = true;
233 }
234
235 return result;
236 }
237
238 private String getNameImage(ASTPrimaryExpression node) {
239 ASTPrimaryPrefix prefix = node.getFirstDescendantOfType(ASTPrimaryPrefix.class);
240 ASTName name = prefix.getFirstDescendantOfType(ASTName.class);
241
242 String image = null;
243 if (name != null) {
244 image = name.getImage();
245 }
246 return image;
247 }
248
249 private String getVariableName(ASTPrimaryExpression node) {
250 ASTPrimaryPrefix prefix = node.getFirstDescendantOfType(ASTPrimaryPrefix.class);
251 ASTName name = prefix.getFirstDescendantOfType(ASTName.class);
252
253 String variableName = null;
254
255 if (name != null) {
256 int dotIndex = name.getImage().indexOf(".");
257 if (dotIndex == -1) {
258 variableName = name.getImage();
259 } else {
260 variableName = name.getImage().substring(0, dotIndex);
261 }
262 }
263
264 return variableName;
265 }
266
267 private String getMethodOrAttributeName(ASTPrimaryExpression node) {
268 ASTPrimaryPrefix prefix = node.getFirstDescendantOfType(ASTPrimaryPrefix.class);
269 ASTName name = prefix.getFirstDescendantOfType(ASTName.class);
270
271 String methodOrAttributeName = null;
272
273 if (name != null) {
274 int dotIndex = name.getImage().indexOf(".");
275 if (dotIndex > -1) {
276 methodOrAttributeName = name.getImage().substring(dotIndex + 1);
277 }
278 }
279
280 return methodOrAttributeName;
281 }
282
283 private VariableNameDeclaration findVariableDeclaration(String variableName, Scope scope) {
284 VariableNameDeclaration result = null;
285
286 for (VariableNameDeclaration declaration : scope.getVariableDeclarations().keySet()) {
287 if (declaration.getImage().equals(variableName)) {
288 result = declaration;
289 break;
290 }
291 }
292
293 if (result == null && scope.getParent() != null && !(scope.getParent() instanceof SourceFileScope)) {
294 result = findVariableDeclaration(variableName, scope.getParent());
295 }
296
297 return result;
298 }
299
300 private boolean isAttributeAccess(ASTPrimaryExpression node) {
301 return node.findDescendantsOfType(ASTPrimarySuffix.class).isEmpty();
302 }
303
304
305
306 @Override
307 public Object visit(ASTMethodDeclaration node, Object data) {
308 wmcCounter++;
309
310 currentMethodName = node.getFirstChildOfType(ASTMethodDeclarator.class).getImage();
311 methodAttributeAccess.put(currentMethodName, new HashSet<String>());
312
313 Object result = super.visit(node, data);
314
315 currentMethodName = null;
316
317 return result;
318 }
319
320 @Override
321 public Object visit(ASTConditionalOrExpression node, Object data) {
322 wmcCounter++;
323 return super.visit(node, data);
324 }
325
326 @Override
327 public Object visit(ASTConditionalAndExpression node, Object data) {
328 wmcCounter++;
329 return super.visit(node, data);
330 }
331
332 @Override
333 public Object visit(ASTIfStatement node, Object data) {
334 wmcCounter++;
335 return super.visit(node, data);
336 }
337
338 @Override
339 public Object visit(ASTWhileStatement node, Object data) {
340 wmcCounter++;
341 return super.visit(node, data);
342 }
343
344 @Override
345 public Object visit(ASTForStatement node, Object data) {
346 wmcCounter++;
347 return super.visit(node, data);
348 }
349
350 @Override
351 public Object visit(ASTSwitchLabel node, Object data) {
352 wmcCounter++;
353 return super.visit(node, data);
354 }
355
356 @Override
357 public Object visit(ASTCatchStatement node, Object data) {
358 wmcCounter++;
359 return super.visit(node, data);
360 }
361
362 @Override
363 public Object visit(ASTConditionalExpression node, Object data) {
364 if (node.isTernary()) {
365 wmcCounter++;
366 }
367 return super.visit(node, data);
368 }
369
370
371 }