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.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(ASTCompilationUnit 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; i++) {
158 for (int j = i + 1; j < methodCount; j++) {
159 String firstMethodName = methods.get(i);
160 String secondMethodName = methods.get(j);
161 Set<String> accessesOfFirstMethod = methodAttributeAccess.get(firstMethodName);
162 Set<String> accessesOfSecondMethod = methodAttributeAccess.get(secondMethodName);
163 Set<String> combinedAccesses = new HashSet<String>();
164
165 combinedAccesses.addAll(accessesOfFirstMethod);
166 combinedAccesses.addAll(accessesOfSecondMethod);
167
168 if (combinedAccesses.size() < (accessesOfFirstMethod.size() + accessesOfSecondMethod.size())) {
169 pairs++;
170 }
171 }
172 }
173 }
174 return pairs;
175 }
176
177
178
179
180
181
182
183 @Override
184 public Object visit(ASTPrimaryExpression node, Object data) {
185 if (isForeignAttributeOrMethod(node)) {
186 if (isAttributeAccess(node)
187 || (isMethodCall(node) && isForeignGetterSetterCall(node))) {
188 atfdCounter++;
189 }
190 } else {
191 if (currentMethodName != null) {
192 Set<String> methodAccess = methodAttributeAccess.get(currentMethodName);
193 String variableName = getVariableName(node);
194 VariableNameDeclaration variableDeclaration = findVariableDeclaration(variableName, node.getScope().getEnclosingClassScope());
195 if (variableDeclaration != null) {
196 methodAccess.add(variableName);
197 }
198 }
199 }
200
201 return super.visit(node, data);
202 }
203
204
205 private boolean isForeignGetterSetterCall(ASTPrimaryExpression node) {
206
207 String methodOrAttributeName = getMethodOrAttributeName(node);
208
209 return methodOrAttributeName != null && StringUtil.startsWithAny(methodOrAttributeName, "get","is","set");
210 }
211
212
213 private boolean isMethodCall(ASTPrimaryExpression node) {
214 boolean result = false;
215 List<ASTPrimarySuffix> suffixes = node.findDescendantsOfType(ASTPrimarySuffix.class);
216 if (suffixes.size() == 1) {
217 result = suffixes.get(0).isArguments();
218 }
219 return result;
220 }
221
222
223 private boolean isForeignAttributeOrMethod(ASTPrimaryExpression node) {
224 boolean result = false;
225 String nameImage = getNameImage(node);
226
227 if (nameImage != null && (!nameImage.contains(".") || nameImage.startsWith("this."))) {
228 result = false;
229 } else if (nameImage == null && node.getFirstDescendantOfType(ASTPrimaryPrefix.class).usesThisModifier()) {
230 result = false;
231 } else if (nameImage == null && node.hasDecendantOfAnyType(ASTLiteral.class, ASTAllocationExpression.class)) {
232 result = false;
233 } else {
234 result = true;
235 }
236
237 return result;
238 }
239
240 private String getNameImage(ASTPrimaryExpression node) {
241 ASTPrimaryPrefix prefix = node.getFirstDescendantOfType(ASTPrimaryPrefix.class);
242 ASTName name = prefix.getFirstDescendantOfType(ASTName.class);
243
244 String image = null;
245 if (name != null) {
246 image = name.getImage();
247 }
248 return image;
249 }
250
251 private String getVariableName(ASTPrimaryExpression node) {
252 ASTPrimaryPrefix prefix = node.getFirstDescendantOfType(ASTPrimaryPrefix.class);
253 ASTName name = prefix.getFirstDescendantOfType(ASTName.class);
254
255 String variableName = null;
256
257 if (name != null) {
258 int dotIndex = name.getImage().indexOf(".");
259 if (dotIndex == -1) {
260 variableName = name.getImage();
261 } else {
262 variableName = name.getImage().substring(0, dotIndex);
263 }
264 }
265
266 return variableName;
267 }
268
269 private String getMethodOrAttributeName(ASTPrimaryExpression node) {
270 ASTPrimaryPrefix prefix = node.getFirstDescendantOfType(ASTPrimaryPrefix.class);
271 ASTName name = prefix.getFirstDescendantOfType(ASTName.class);
272
273 String methodOrAttributeName = null;
274
275 if (name != null) {
276 int dotIndex = name.getImage().indexOf(".");
277 if (dotIndex > -1) {
278 methodOrAttributeName = name.getImage().substring(dotIndex + 1);
279 }
280 }
281
282 return methodOrAttributeName;
283 }
284
285 private VariableNameDeclaration findVariableDeclaration(String variableName, Scope scope) {
286 VariableNameDeclaration result = null;
287
288 for (VariableNameDeclaration declaration : scope.getVariableDeclarations().keySet()) {
289 if (declaration.getImage().equals(variableName)) {
290 result = declaration;
291 break;
292 }
293 }
294
295 if (result == null && scope.getParent() != null && !(scope.getParent() instanceof SourceFileScope)) {
296 result = findVariableDeclaration(variableName, scope.getParent());
297 }
298
299 return result;
300 }
301
302 private boolean isAttributeAccess(ASTPrimaryExpression node) {
303 return node.findDescendantsOfType(ASTPrimarySuffix.class).isEmpty();
304 }
305
306
307
308 @Override
309 public Object visit(ASTMethodDeclaration node, Object data) {
310 wmcCounter++;
311
312 currentMethodName = node.getFirstChildOfType(ASTMethodDeclarator.class).getImage();
313 methodAttributeAccess.put(currentMethodName, new HashSet<String>());
314
315 Object result = super.visit(node, data);
316
317 currentMethodName = null;
318
319 return result;
320 }
321
322 @Override
323 public Object visit(ASTConditionalOrExpression node, Object data) {
324 wmcCounter++;
325 return super.visit(node, data);
326 }
327
328 @Override
329 public Object visit(ASTConditionalAndExpression node, Object data) {
330 wmcCounter++;
331 return super.visit(node, data);
332 }
333
334 @Override
335 public Object visit(ASTIfStatement node, Object data) {
336 wmcCounter++;
337 return super.visit(node, data);
338 }
339
340 @Override
341 public Object visit(ASTWhileStatement node, Object data) {
342 wmcCounter++;
343 return super.visit(node, data);
344 }
345
346 @Override
347 public Object visit(ASTForStatement node, Object data) {
348 wmcCounter++;
349 return super.visit(node, data);
350 }
351
352 @Override
353 public Object visit(ASTSwitchLabel node, Object data) {
354 wmcCounter++;
355 return super.visit(node, data);
356 }
357
358 @Override
359 public Object visit(ASTCatchStatement node, Object data) {
360 wmcCounter++;
361 return super.visit(node, data);
362 }
363
364 @Override
365 public Object visit(ASTConditionalExpression node, Object data) {
366 if (node.isTernary()) {
367 wmcCounter++;
368 }
369 return super.visit(node, data);
370 }
371
372
373 }