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
76
77
78 private Map<String, Set<String>> methodAttributeAccess;
79
80 private String currentMethodName;
81
82
83
84
85
86
87 @Override
88 public Object visit(ASTCompilationUnit node, Object data) {
89 wmcCounter = 0;
90 atfdCounter = 0;
91 methodAttributeAccess = new HashMap<String, Set<String>>();
92
93 Object result = super.visit(node, data);
94
95 double tcc = calculateTcc();
96
97
98
99
100
101
102
103
104
105 if (wmcCounter >= WMC_VERY_HIGH && atfdCounter > FEW_THRESHOLD && tcc < ONE_THIRD_THRESHOLD) {
106
107 StringBuilder sb = new StringBuilder();
108 sb.append(getMessage()).append(" (").append("WMC=").append(wmcCounter).append(", ").append("ATFD=")
109 .append(atfdCounter).append(", ").append("TCC=").append(tcc).append(')');
110
111 RuleContext ctx = (RuleContext) data;
112 ctx.getReport().addRuleViolation(new JavaRuleViolation(this, ctx, node, sb.toString()));
113 }
114 return result;
115 }
116
117
118
119
120
121
122 private double calculateTcc() {
123 double tcc = 0.0;
124 int methodPairs = determineMethodPairs();
125 double totalMethodPairs = calculateTotalMethodPairs();
126 if (totalMethodPairs > 0) {
127 tcc = methodPairs / totalMethodPairs;
128 }
129 return tcc;
130 }
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
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 @Override
184 public Object visit(ASTPrimaryExpression node, Object data) {
185 if (isForeignAttributeOrMethod(node)) {
186 if (isAttributeAccess(node) || isMethodCall(node) && isForeignGetterSetterCall(node)) {
187 atfdCounter++;
188 }
189 } else {
190 if (currentMethodName != null) {
191 Set<String> methodAccess = methodAttributeAccess.get(currentMethodName);
192 String variableName = getVariableName(node);
193 VariableNameDeclaration variableDeclaration = findVariableDeclaration(variableName, node.getScope()
194 .getEnclosingScope(ClassScope.class));
195 if (variableDeclaration != null) {
196 methodAccess.add(variableName);
197 }
198 }
199 }
200
201 return super.visit(node, data);
202 }
203
204 private boolean isForeignGetterSetterCall(ASTPrimaryExpression node) {
205
206 String methodOrAttributeName = getMethodOrAttributeName(node);
207
208 return methodOrAttributeName != null && StringUtil.startsWithAny(methodOrAttributeName, "get", "is", "set");
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 private boolean isForeignAttributeOrMethod(ASTPrimaryExpression node) {
221 boolean result = false;
222 String nameImage = getNameImage(node);
223
224 if (nameImage != null && (!nameImage.contains(".") || nameImage.startsWith("this."))) {
225 result = false;
226 } else if (nameImage == null && node.getFirstDescendantOfType(ASTPrimaryPrefix.class).usesThisModifier()) {
227 result = false;
228 } else if (nameImage == null && node.hasDecendantOfAnyType(ASTLiteral.class, ASTAllocationExpression.class)) {
229 result = false;
230 } else {
231 result = true;
232 }
233
234 return result;
235 }
236
237 private String getNameImage(ASTPrimaryExpression node) {
238 ASTPrimaryPrefix prefix = node.getFirstDescendantOfType(ASTPrimaryPrefix.class);
239 ASTName name = prefix.getFirstDescendantOfType(ASTName.class);
240
241 String image = null;
242 if (name != null) {
243 image = name.getImage();
244 }
245 return image;
246 }
247
248 private String getVariableName(ASTPrimaryExpression node) {
249 ASTPrimaryPrefix prefix = node.getFirstDescendantOfType(ASTPrimaryPrefix.class);
250 ASTName name = prefix.getFirstDescendantOfType(ASTName.class);
251
252 String variableName = null;
253
254 if (name != null) {
255 int dotIndex = name.getImage().indexOf(".");
256 if (dotIndex == -1) {
257 variableName = name.getImage();
258 } else {
259 variableName = name.getImage().substring(0, dotIndex);
260 }
261 }
262
263 return variableName;
264 }
265
266 private String getMethodOrAttributeName(ASTPrimaryExpression node) {
267 ASTPrimaryPrefix prefix = node.getFirstDescendantOfType(ASTPrimaryPrefix.class);
268 ASTName name = prefix.getFirstDescendantOfType(ASTName.class);
269
270 String methodOrAttributeName = null;
271
272 if (name != null) {
273 int dotIndex = name.getImage().indexOf(".");
274 if (dotIndex > -1) {
275 methodOrAttributeName = name.getImage().substring(dotIndex + 1);
276 }
277 }
278
279 return methodOrAttributeName;
280 }
281
282 private VariableNameDeclaration findVariableDeclaration(String variableName, Scope scope) {
283 VariableNameDeclaration result = null;
284
285 for (VariableNameDeclaration declaration : scope.getDeclarations(VariableNameDeclaration.class).keySet()) {
286 if (declaration.getImage().equals(variableName)) {
287 result = declaration;
288 break;
289 }
290 }
291
292 if (result == null && scope.getParent() != null && !(scope.getParent() instanceof SourceFileScope)) {
293 result = findVariableDeclaration(variableName, scope.getParent());
294 }
295
296 return result;
297 }
298
299 private boolean isAttributeAccess(ASTPrimaryExpression node) {
300 return node.findDescendantsOfType(ASTPrimarySuffix.class).isEmpty();
301 }
302
303 @Override
304 public Object visit(ASTMethodDeclaration node, Object data) {
305 wmcCounter++;
306
307 currentMethodName = node.getFirstChildOfType(ASTMethodDeclarator.class).getImage();
308 methodAttributeAccess.put(currentMethodName, new HashSet<String>());
309
310 Object result = super.visit(node, data);
311
312 currentMethodName = null;
313
314 return result;
315 }
316
317 @Override
318 public Object visit(ASTConditionalOrExpression node, Object data) {
319 wmcCounter++;
320 return super.visit(node, data);
321 }
322
323 @Override
324 public Object visit(ASTConditionalAndExpression node, Object data) {
325 wmcCounter++;
326 return super.visit(node, data);
327 }
328
329 @Override
330 public Object visit(ASTIfStatement node, Object data) {
331 wmcCounter++;
332 return super.visit(node, data);
333 }
334
335 @Override
336 public Object visit(ASTWhileStatement node, Object data) {
337 wmcCounter++;
338 return super.visit(node, data);
339 }
340
341 @Override
342 public Object visit(ASTForStatement node, Object data) {
343 wmcCounter++;
344 return super.visit(node, data);
345 }
346
347 @Override
348 public Object visit(ASTSwitchLabel node, Object data) {
349 wmcCounter++;
350 return super.visit(node, data);
351 }
352
353 @Override
354 public Object visit(ASTCatchStatement node, Object data) {
355 wmcCounter++;
356 return super.visit(node, data);
357 }
358
359 @Override
360 public Object visit(ASTConditionalExpression node, Object data) {
361 if (node.isTernary()) {
362 wmcCounter++;
363 }
364 return super.visit(node, data);
365 }
366
367 }