1
2
3
4 package net.sourceforge.pmd.lang.java.rule.coupling;
5
6 import java.util.ArrayList;
7 import java.util.Collections;
8 import java.util.Iterator;
9 import java.util.List;
10 import java.util.Set;
11
12 import net.sourceforge.pmd.RuleContext;
13 import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression;
14 import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator;
15 import net.sourceforge.pmd.lang.java.ast.ASTBlock;
16 import net.sourceforge.pmd.lang.java.ast.ASTForStatement;
17 import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
18 import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
19 import net.sourceforge.pmd.lang.java.ast.ASTName;
20 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
21 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
22 import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
23 import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
24 import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
25 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
26 import net.sourceforge.pmd.lang.java.symboltable.ClassScope;
27 import net.sourceforge.pmd.lang.java.symboltable.LocalScope;
28 import net.sourceforge.pmd.lang.java.symboltable.MethodScope;
29 import net.sourceforge.pmd.lang.java.symboltable.TypedNameDeclaration;
30 import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
31 import net.sourceforge.pmd.lang.symboltable.NameDeclaration;
32 import net.sourceforge.pmd.lang.symboltable.Scope;
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 public class LawOfDemeterRule extends AbstractJavaRule {
49 private static final String REASON_METHOD_CHAIN_CALLS = "method chain calls";
50 private static final String REASON_OBJECT_NOT_CREATED_LOCALLY = "object not created locally";
51 private static final String REASON_STATIC_ACCESS = "static property access";
52
53
54
55
56
57 @Override
58 public Object visit(ASTMethodDeclaration node, Object data) {
59 List<ASTPrimaryExpression> primaryExpressions = node.findDescendantsOfType(ASTPrimaryExpression.class);
60 for (ASTPrimaryExpression expression : primaryExpressions) {
61 List<MethodCall> calls = MethodCall.createMethodCalls(expression);
62 addViolations(calls, (RuleContext)data);
63 }
64 return null;
65 }
66
67 private void addViolations(List<MethodCall> calls, RuleContext ctx) {
68 for (MethodCall method : calls) {
69 if (method.isViolation()) {
70 addViolationWithMessage(ctx, method.getExpression(), getMessage() + " (" + method.getViolationReason() + ")");
71 }
72 }
73 }
74
75
76
77
78
79
80 private static class MethodCall {
81 private static final String METHOD_CALL_CHAIN = "result from previous method call";
82 private static final String SIMPLE_ASSIGNMENT_OPERATOR = "=";
83 private static final String SCOPE_METHOD_CHAINING = "method-chaining";
84 private static final String SCOPE_CLASS = "class";
85 private static final String SCOPE_METHOD = "method";
86 private static final String SCOPE_LOCAL = "local";
87 private static final String SCOPE_STATIC_CHAIN = "static-chain";
88 private static final String SUPER = "super";
89 private static final String THIS = "this";
90
91 private ASTPrimaryExpression expression;
92 private String baseName;
93 private String methodName;
94 private String baseScope;
95 private String baseTypeName;
96 private Class<?> baseType;
97 private boolean violation;
98 private String violationReason;
99
100
101
102
103 private MethodCall(ASTPrimaryExpression expression, ASTPrimaryPrefix prefix) {
104 this.expression = expression;
105 analyze(prefix);
106 determineType();
107 checkViolation();
108 }
109
110
111
112
113
114 private MethodCall(ASTPrimaryExpression expression, ASTPrimarySuffix suffix) {
115 this.expression = expression;
116 analyze(suffix);
117 determineType();
118 checkViolation();
119 }
120
121
122
123
124
125
126
127
128 public static List<MethodCall> createMethodCalls(ASTPrimaryExpression expression) {
129 List<MethodCall> result = new ArrayList<MethodCall>();
130
131 if (isNotAConstructorCall(expression) && isNotLiteral(expression) && hasSuffixesWithArguments(expression)) {
132 ASTPrimaryPrefix prefixNode = expression.getFirstDescendantOfType(ASTPrimaryPrefix.class);
133 MethodCall firstMethodCallInChain = new MethodCall(expression, prefixNode);
134 result.add(firstMethodCallInChain);
135
136 if (firstMethodCallInChain.isNotBuilder()) {
137 List<ASTPrimarySuffix> suffixes = findSuffixesWithoutArguments(expression);
138 for (ASTPrimarySuffix suffix : suffixes) {
139 result.add(new MethodCall(expression, suffix));
140 }
141 }
142 }
143
144 return result;
145 }
146
147 private static boolean isNotAConstructorCall(ASTPrimaryExpression expression) {
148 return !expression.hasDescendantOfType(ASTAllocationExpression.class);
149 }
150
151 private static boolean isNotLiteral(ASTPrimaryExpression expression) {
152 ASTPrimaryPrefix prefix = expression.getFirstDescendantOfType(ASTPrimaryPrefix.class);
153 if (prefix != null) {
154 return !prefix.hasDescendantOfType(ASTLiteral.class);
155 }
156 return true;
157 }
158
159 private boolean isNotBuilder() {
160 return baseType != StringBuffer.class
161 && baseType != StringBuilder.class
162 && !"StringBuilder".equals(baseTypeName)
163 && !"StringBuffer".equals(baseTypeName);
164 }
165
166 private static List<ASTPrimarySuffix> findSuffixesWithoutArguments(ASTPrimaryExpression expr) {
167 List<ASTPrimarySuffix> result = new ArrayList<ASTPrimarySuffix>();
168 if (hasRealPrefix(expr)) {
169 List<ASTPrimarySuffix> suffixes = expr.findDescendantsOfType(ASTPrimarySuffix.class);
170 for (ASTPrimarySuffix suffix : suffixes) {
171 if (!suffix.isArguments()) {
172 result.add(suffix);
173 }
174 }
175 }
176 return result;
177 }
178
179 private static boolean hasRealPrefix(ASTPrimaryExpression expr) {
180 ASTPrimaryPrefix prefix = expr.getFirstDescendantOfType(ASTPrimaryPrefix.class);
181 return !prefix.usesThisModifier() && !prefix.usesSuperModifier();
182 }
183
184 private static boolean hasSuffixesWithArguments(ASTPrimaryExpression expr) {
185 boolean result = false;
186 if (hasRealPrefix(expr)) {
187 List<ASTPrimarySuffix> suffixes = expr.findDescendantsOfType(ASTPrimarySuffix.class);
188 for (ASTPrimarySuffix suffix : suffixes) {
189 if (suffix.isArguments()) {
190 result = true;
191 break;
192 }
193 }
194 }
195 return result;
196 }
197
198 private void analyze(ASTPrimaryPrefix prefixNode) {
199 List<ASTName> names = prefixNode.findDescendantsOfType(ASTName.class);
200
201 baseName = "unknown";
202 methodName = "unknown";
203
204 if (!names.isEmpty()) {
205 baseName = names.get(0).getImage();
206
207 int dot = baseName.lastIndexOf('.');
208 if (dot == -1) {
209 methodName = baseName;
210 baseName = THIS;
211 } else {
212 methodName = baseName.substring(dot + 1);
213 baseName = baseName.substring(0, dot);
214 }
215
216 } else {
217 if (prefixNode.usesThisModifier()) {
218 baseName = THIS;
219 } else if (prefixNode.usesSuperModifier()) {
220 baseName = SUPER;
221 }
222 }
223 }
224
225 private void analyze(ASTPrimarySuffix suffix) {
226 baseName = METHOD_CALL_CHAIN;
227 methodName = suffix.getImage();
228 }
229
230 private void checkViolation() {
231 violation = false;
232 violationReason = null;
233
234 if (SCOPE_LOCAL.equals(baseScope)) {
235 Assignment lastAssignment = determineLastAssignment();
236 if (lastAssignment != null
237 && !lastAssignment.allocation
238 && !lastAssignment.iterator
239 && !lastAssignment.forLoop) {
240 violation = true;
241 violationReason = REASON_OBJECT_NOT_CREATED_LOCALLY;
242 }
243 } else if (SCOPE_METHOD_CHAINING.equals(baseScope)) {
244 violation = true;
245 violationReason = REASON_METHOD_CHAIN_CALLS;
246 } else if (SCOPE_STATIC_CHAIN.equals(baseScope)) {
247 violation = true;
248 violationReason = REASON_STATIC_ACCESS;
249 }
250 }
251
252 private void determineType() {
253 NameDeclaration var = null;
254 Scope scope = expression.getScope();
255
256 baseScope = SCOPE_LOCAL;
257 var = findInLocalScope(baseName, scope);
258 if (var == null) {
259 baseScope = SCOPE_METHOD;
260 var = determineTypeOfVariable(baseName, scope.getEnclosingScope(MethodScope.class).getVariableDeclarations().keySet());
261 }
262 if (var == null) {
263 baseScope = SCOPE_CLASS;
264 var = determineTypeOfVariable(baseName, scope.getEnclosingScope(ClassScope.class).getVariableDeclarations().keySet());
265 }
266 if (var == null) {
267 baseScope = SCOPE_METHOD_CHAINING;
268 }
269 if (var == null && (THIS.equals(baseName) || SUPER.equals(baseName))) {
270 baseScope = SCOPE_CLASS;
271 }
272
273 if (var != null && var instanceof TypedNameDeclaration) {
274 baseTypeName = ((TypedNameDeclaration)var).getTypeImage();
275 baseType = ((TypedNameDeclaration)var).getType();
276 } else if (METHOD_CALL_CHAIN.equals(baseName)) {
277 baseScope = SCOPE_METHOD_CHAINING;
278 } else if (baseName.contains(".") && !baseName.startsWith("System.")) {
279 baseScope = SCOPE_STATIC_CHAIN;
280 } else {
281
282 baseScope = null;
283 }
284 }
285
286 private VariableNameDeclaration findInLocalScope(String name, Scope scope) {
287 VariableNameDeclaration result = null;
288
289 result = determineTypeOfVariable(name, scope.getDeclarations(VariableNameDeclaration.class).keySet());
290 if (result == null && scope.getParent() instanceof LocalScope) {
291 result = findInLocalScope(name, scope.getParent());
292 }
293
294 return result;
295 }
296
297 private VariableNameDeclaration determineTypeOfVariable(String variableName, Set<VariableNameDeclaration> declarations) {
298 VariableNameDeclaration result = null;
299 for (VariableNameDeclaration var : declarations) {
300 if (variableName.equals(var.getImage())) {
301 result = var;
302 break;
303 }
304 }
305 return result;
306 }
307
308 private Assignment determineLastAssignment() {
309 List<Assignment> assignments = new ArrayList<Assignment>();
310
311 ASTBlock block = expression.getFirstParentOfType(ASTMethodDeclaration.class).getFirstChildOfType(ASTBlock.class);
312
313 List<ASTVariableDeclarator> variableDeclarators = block.findDescendantsOfType(ASTVariableDeclarator.class);
314 for (ASTVariableDeclarator declarator : variableDeclarators) {
315 ASTVariableDeclaratorId variableDeclaratorId = declarator.getFirstChildOfType(ASTVariableDeclaratorId.class);
316 if (variableDeclaratorId.hasImageEqualTo(baseName)) {
317 boolean allocationFound = declarator.getFirstDescendantOfType(ASTAllocationExpression.class) != null;
318 boolean iterator = isIterator() || isFactory(declarator);
319 boolean forLoop = isForLoop(declarator);
320 assignments.add(new Assignment(declarator.getBeginLine(), allocationFound, iterator, forLoop));
321 }
322 }
323
324 List<ASTAssignmentOperator> assignmentStmts = block.findDescendantsOfType(ASTAssignmentOperator.class);
325 for (ASTAssignmentOperator stmt : assignmentStmts) {
326 if (stmt.hasImageEqualTo(SIMPLE_ASSIGNMENT_OPERATOR)) {
327 boolean allocationFound = stmt.jjtGetParent().getFirstDescendantOfType(ASTAllocationExpression.class) != null;
328 boolean iterator = isIterator();
329 assignments.add(new Assignment(stmt.getBeginLine(), allocationFound, iterator, false));
330 }
331 }
332
333 Assignment result = null;
334 if (!assignments.isEmpty()) {
335 Collections.sort(assignments);
336 result = assignments.get(0);
337 }
338 return result;
339 }
340
341 private boolean isIterator() {
342 boolean iterator = false;
343 if ((baseType != null && baseType == Iterator.class)
344 || (baseTypeName != null && baseTypeName.endsWith("Iterator"))) {
345 iterator = true;
346 }
347 return iterator;
348 }
349 private boolean isFactory(ASTVariableDeclarator declarator) {
350 boolean factory = false;
351 List<ASTName> names = declarator.findDescendantsOfType(ASTName.class);
352 for (ASTName name : names) {
353 if (name.getImage().toLowerCase().contains("factory")) {
354 factory = true;
355 break;
356 }
357 }
358 return factory;
359 }
360 private boolean isForLoop(ASTVariableDeclarator declarator) {
361 return declarator.jjtGetParent().jjtGetParent() instanceof ASTForStatement;
362 }
363
364 public ASTPrimaryExpression getExpression() {
365 return expression;
366 }
367
368 public boolean isViolation() {
369 return violation;
370 }
371
372 public String getViolationReason() {
373 return violationReason;
374 }
375
376 @Override
377 public String toString() {
378 return "MethodCall on line " + expression.getBeginLine() + ":\n"
379 + " " + baseName + " name: "+ methodName+ "\n"
380 + " type: " + baseTypeName + " (" + baseType + "), \n"
381 + " scope: " + baseScope + "\n"
382 + " violation: " + violation + " (" + violationReason + ")\n";
383 }
384
385 }
386
387
388
389
390
391
392 private static class Assignment implements Comparable<Assignment> {
393 private int line;
394 private boolean allocation;
395 private boolean iterator;
396 private boolean forLoop;
397
398 public Assignment(int line, boolean allocation, boolean iterator, boolean forLoop) {
399 this.line = line;
400 this.allocation = allocation;
401 this.iterator = iterator;
402 this.forLoop = forLoop;
403 }
404
405 @Override
406 public String toString() {
407 return "assignment: line=" + line + " allocation:" + allocation
408 + " iterator:" + iterator + " forLoop: " + forLoop;
409 }
410
411 public int compareTo(Assignment o) {
412 return o.line - line;
413 }
414 }
415 }