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