1
2
3
4 package net.sourceforge.pmd.lang.java.rule.design;
5
6 import java.util.ArrayList;
7 import java.util.Collections;
8 import java.util.Comparator;
9 import java.util.Iterator;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.Set;
13 import java.util.TreeMap;
14
15 import net.sourceforge.pmd.lang.ast.Node;
16 import net.sourceforge.pmd.lang.java.ast.ASTArguments;
17 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
18 import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
19 import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
20 import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
21 import net.sourceforge.pmd.lang.java.ast.ASTExplicitConstructorInvocation;
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.AccessNode;
30 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
31
32
33
34
35
36
37
38
39
40
41
42
43
44 public final class ConstructorCallsOverridableMethodRule extends AbstractJavaRule {
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156 private static class MethodInvocation {
157 private String name;
158 private ASTPrimaryExpression ape;
159 private List<String> referenceNames;
160 private List<String> qualifierNames;
161 private int argumentSize;
162 private boolean superCall;
163
164 private MethodInvocation(ASTPrimaryExpression ape, List<String> qualifierNames, List<String> referenceNames, String name, int argumentSize, boolean superCall) {
165 this.ape = ape;
166 this.qualifierNames = qualifierNames;
167 this.referenceNames = referenceNames;
168 this.name = name;
169 this.argumentSize = argumentSize;
170 this.superCall = superCall;
171 }
172
173 public boolean isSuper() {
174 return superCall;
175 }
176
177 public String getName() {
178 return name;
179 }
180
181 public int getArgumentCount() {
182 return argumentSize;
183 }
184
185 public List<String> getReferenceNames() {
186 return referenceNames;
187 }
188
189 public List<String> getQualifierNames() {
190 return qualifierNames;
191 }
192
193 public ASTPrimaryExpression getASTPrimaryExpression() {
194 return ape;
195 }
196
197 public static MethodInvocation getMethod(ASTPrimaryExpression node) {
198 MethodInvocation meth = null;
199 int i = node.jjtGetNumChildren();
200 if (i > 1) {
201
202 Node lastNode = node.jjtGetChild(i - 1);
203 if (lastNode.jjtGetNumChildren() == 1 && lastNode.jjtGetChild(0) instanceof ASTArguments) {
204
205
206 List<String> varNames = new ArrayList<String>();
207 List<String> packagesAndClasses = new ArrayList<String>();
208 String methodName = null;
209 ASTArguments args = (ASTArguments) lastNode.jjtGetChild(0);
210 int numOfArguments = args.getArgumentCount();
211 boolean superFirst = false;
212 int thisIndex = -1;
213
214 FIND_SUPER_OR_THIS: {
215
216
217
218
219
220 for (int x = 0; x < i - 1; x++) {
221 Node child = node.jjtGetChild(x);
222 if (child instanceof ASTPrimarySuffix) {
223 ASTPrimarySuffix child2 = (ASTPrimarySuffix) child;
224
225
226 if (child2.getImage() == null && child2.jjtGetNumChildren() == 0) {
227 thisIndex = x;
228 break;
229 }
230
231
232
233 } else if (child instanceof ASTPrimaryPrefix) {
234 ASTPrimaryPrefix child2 = (ASTPrimaryPrefix) child;
235 if (getNameFromPrefix(child2) == null) {
236 if (child2.getImage() == null) {
237 thisIndex = x;
238 break;
239 } else {
240 superFirst = true;
241 thisIndex = x;
242
243
244 break;
245 }
246 }
247 }
248
249
250
251 }
252 }
253
254 if (thisIndex != -1) {
255
256
257 if (superFirst) {
258
259 FIRSTNODE:{
260 ASTPrimaryPrefix child = (ASTPrimaryPrefix) node.jjtGetChild(0);
261 String name = child.getImage();
262 if (i == 2) {
263 methodName = name;
264 } else {
265 varNames.add(name);
266 }
267 }
268 OTHERNODES:{
269 for (int x = 1; x < i - 1; x++) {
270 Node child = node.jjtGetChild(x);
271 ASTPrimarySuffix ps = (ASTPrimarySuffix) child;
272 if (!ps.isArguments()) {
273 String name = ((ASTPrimarySuffix) child).getImage();
274 if (x == i - 2) {
275 methodName = name;
276 } else {
277 varNames.add(name);
278 }
279 }
280 }
281 }
282 } else {
283 FIRSTNODE:{
284 if (thisIndex == 1) {
285 ASTPrimaryPrefix child = (ASTPrimaryPrefix) node.jjtGetChild(0);
286 String toParse = getNameFromPrefix(child);
287
288 java.util.StringTokenizer st = new java.util.StringTokenizer(toParse, ".");
289 while (st.hasMoreTokens()) {
290 packagesAndClasses.add(st.nextToken());
291 }
292 }
293 }
294 OTHERNODES:{
295
296
297 for (int x = thisIndex + 1; x < i - 1; x++) {
298 ASTPrimarySuffix child = (ASTPrimarySuffix) node.jjtGetChild(x);
299 if (!child.isArguments()) {
300 String name = child.getImage();
301
302 if (x == i - 2) {
303 methodName = name;
304 } else {
305 varNames.add(name);
306 }
307 }
308 }
309 }
310 }
311 } else {
312
313 FIRSTNODE:{
314 ASTPrimaryPrefix child = (ASTPrimaryPrefix) node.jjtGetChild(0);
315 String toParse = getNameFromPrefix(child);
316
317 java.util.StringTokenizer st = new java.util.StringTokenizer(toParse, ".");
318 while (st.hasMoreTokens()) {
319 String value = st.nextToken();
320 if (!st.hasMoreTokens()) {
321 if (i == 2) {
322 methodName = value;
323 } else {
324 varNames.add(value);
325 }
326 } else {
327 varNames.add(value);
328 }
329 }
330 }
331 OTHERNODES:{
332 for (int x = 1; x < i - 1; x++) {
333 ASTPrimarySuffix child = (ASTPrimarySuffix) node.jjtGetChild(x);
334 if (!child.isArguments()) {
335 String name = child.getImage();
336 if (x == i - 2) {
337 methodName = name;
338 } else {
339 varNames.add(name);
340 }
341 }
342 }
343 }
344 }
345 meth = new MethodInvocation(node, packagesAndClasses, varNames, methodName, numOfArguments, superFirst);
346 }
347 }
348 return meth;
349 }
350
351 public void show() {
352 System.out.println("<MethodInvocation>");
353 System.out.println(" <Qualifiers>");
354 for (String name: getQualifierNames()) {
355 System.out.println(" " + name);
356 }
357 System.out.println(" </Qualifiers>");
358 System.out.println(" <Super>" + isSuper() + "</Super>");
359 System.out.println(" <References>");
360 for (String name: getReferenceNames()) {
361 System.out.println(" " + name);
362 }
363 System.out.println(" </References>");
364 System.out.println(" <Name>" + getName() + "</Name>");
365 System.out.println("</MethodInvocation>");
366 }
367 }
368
369 private static final class ConstructorInvocation {
370 private ASTExplicitConstructorInvocation eci;
371 private String name;
372 private int count = 0;
373
374 public ConstructorInvocation(ASTExplicitConstructorInvocation eci) {
375 this.eci = eci;
376 List<ASTArguments> l = eci.findChildrenOfType(ASTArguments.class);
377 if (!l.isEmpty()) {
378 ASTArguments aa = l.get(0);
379 count = aa.getArgumentCount();
380 }
381 name = eci.getImage();
382 }
383
384 public ASTExplicitConstructorInvocation getASTExplicitConstructorInvocation() {
385 return eci;
386 }
387
388 public int getArgumentCount() {
389 return count;
390 }
391
392 public String getName() {
393 return name;
394 }
395 }
396
397 private static final class MethodHolder {
398 private ASTMethodDeclarator amd;
399 private boolean dangerous;
400 private String called;
401
402 public MethodHolder(ASTMethodDeclarator amd) {
403 this.amd = amd;
404 }
405
406 public void setCalledMethod(String name) {
407 this.called = name;
408 }
409
410 public String getCalled() {
411 return this.called;
412 }
413
414 public ASTMethodDeclarator getASTMethodDeclarator() {
415 return amd;
416 }
417
418 public boolean isDangerous() {
419 return dangerous;
420 }
421
422 public void setDangerous() {
423 dangerous = true;
424 }
425 }
426
427 private final class ConstructorHolder {
428 private ASTConstructorDeclaration cd;
429 private boolean dangerous;
430 private ConstructorInvocation ci;
431 private boolean ciInitialized;
432
433 public ConstructorHolder(ASTConstructorDeclaration cd) {
434 this.cd = cd;
435 }
436
437 public ASTConstructorDeclaration getASTConstructorDeclaration() {
438 return cd;
439 }
440
441 public ConstructorInvocation getCalledConstructor() {
442 if (!ciInitialized) {
443 initCI();
444 }
445 return ci;
446 }
447
448 public ASTExplicitConstructorInvocation getASTExplicitConstructorInvocation() {
449 ASTExplicitConstructorInvocation eci = null;
450 if (!ciInitialized) {
451 initCI();
452 }
453 if (ci != null) {
454 eci = ci.getASTExplicitConstructorInvocation();
455 }
456 return eci;
457 }
458
459 private void initCI() {
460 List<ASTExplicitConstructorInvocation> expressions = cd.findChildrenOfType(ASTExplicitConstructorInvocation.class);
461 if (!expressions.isEmpty()) {
462 ASTExplicitConstructorInvocation eci = expressions.get(0);
463 ci = new ConstructorInvocation(eci);
464
465 }
466 ciInitialized = true;
467 }
468
469 public boolean isDangerous() {
470 return dangerous;
471 }
472
473 public void setDangerous(boolean dangerous) {
474 this.dangerous = dangerous;
475 }
476 }
477
478 private static int compareNodes(Node n1, Node n2) {
479 int l1 = n1.getBeginLine();
480 int l2 = n2.getBeginLine();
481 if (l1 == l2) {
482 return n1.getBeginColumn() - n2.getBeginColumn();
483 }
484 return l1 - l2;
485 }
486
487 private static class MethodHolderComparator implements Comparator<MethodHolder> {
488 public int compare(MethodHolder o1, MethodHolder o2) {
489 return compareNodes(o1.getASTMethodDeclarator(), o2.getASTMethodDeclarator());
490 }
491 }
492
493 private static class ConstructorHolderComparator implements Comparator<ConstructorHolder> {
494 public int compare(ConstructorHolder o1, ConstructorHolder o2) {
495 return compareNodes(o1.getASTConstructorDeclaration(), o2.getASTConstructorDeclaration());
496 }
497 }
498
499
500
501
502 private static class EvalPackage {
503 public EvalPackage() {
504 }
505
506 public EvalPackage(String className) {
507 this.className = className;
508 this.calledMethods = new ArrayList<MethodInvocation>();
509 this.allMethodsOfClass = new TreeMap<MethodHolder, List<MethodInvocation>>(new MethodHolderComparator());
510 this.calledConstructors = new ArrayList<ConstructorInvocation>();
511 this.allPrivateConstructorsOfClass = new TreeMap<ConstructorHolder, List<MethodInvocation>>(new ConstructorHolderComparator());
512 }
513
514 public String className;
515 public List<MethodInvocation> calledMethods;
516 public Map<MethodHolder, List<MethodInvocation>> allMethodsOfClass;
517
518 public List<ConstructorInvocation> calledConstructors;
519 public Map<ConstructorHolder, List<MethodInvocation>> allPrivateConstructorsOfClass;
520 }
521
522 private static final class NullEvalPackage extends EvalPackage {
523 public NullEvalPackage() {
524 className = "";
525 calledMethods = Collections.emptyList();
526 allMethodsOfClass = Collections.emptyMap();
527 calledConstructors = Collections.emptyList();
528 allPrivateConstructorsOfClass = Collections.emptyMap();
529 }
530 }
531
532 private static final NullEvalPackage NULL_EVAL_PACKAGE = new NullEvalPackage();
533
534
535
536
537
538 private final List<EvalPackage> evalPackages = new ArrayList<EvalPackage>();
539
540 private EvalPackage getCurrentEvalPackage() {
541 return evalPackages.get(evalPackages.size() - 1);
542 }
543
544
545
546
547 private void putEvalPackage(EvalPackage ep) {
548 evalPackages.add(ep);
549 }
550
551 private void removeCurrentEvalPackage() {
552 evalPackages.remove(evalPackages.size() - 1);
553 }
554
555 private void clearEvalPackages() {
556 evalPackages.clear();
557 }
558
559
560
561
562
563 private Object visitClassDec(ASTClassOrInterfaceDeclaration node, Object data) {
564 String className = node.getImage();
565 if (!node.isFinal()) {
566 putEvalPackage(new EvalPackage(className));
567 } else {
568 putEvalPackage(NULL_EVAL_PACKAGE);
569 }
570
571 super.visit(node, data);
572
573
574 if (!(getCurrentEvalPackage() instanceof NullEvalPackage)) {
575
576 while (evaluateDangerOfMethods(getCurrentEvalPackage().allMethodsOfClass)) { }
577
578 evaluateDangerOfConstructors1(getCurrentEvalPackage().allPrivateConstructorsOfClass, getCurrentEvalPackage().allMethodsOfClass.keySet());
579 while (evaluateDangerOfConstructors2(getCurrentEvalPackage().allPrivateConstructorsOfClass)) { }
580
581
582 for (MethodInvocation meth: getCurrentEvalPackage().calledMethods) {
583
584 for (MethodHolder h: getCurrentEvalPackage().allMethodsOfClass.keySet()) {
585 if (h.isDangerous()) {
586 String methName = h.getASTMethodDeclarator().getImage();
587 int count = h.getASTMethodDeclarator().getParameterCount();
588 if (methName.equals(meth.getName()) && meth.getArgumentCount() == count) {
589 addViolation(data, meth.getASTPrimaryExpression(), "method '" + h.getCalled() + "'");
590 }
591 }
592 }
593 }
594
595 for (ConstructorHolder ch: getCurrentEvalPackage().allPrivateConstructorsOfClass.keySet()) {
596 if (ch.isDangerous()) {
597
598 int paramCount = ch.getASTConstructorDeclaration().getParameterCount();
599 for (ConstructorInvocation ci: getCurrentEvalPackage().calledConstructors) {
600 if (ci.getArgumentCount() == paramCount) {
601
602 addViolation(data, ci.getASTExplicitConstructorInvocation(), "constructor");
603 }
604 }
605 }
606 }
607 }
608
609 removeCurrentEvalPackage();
610 return data;
611 }
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627 private boolean evaluateDangerOfMethods(Map<MethodHolder, List<MethodInvocation>> classMethodMap) {
628
629 boolean found = false;
630 for (Map.Entry<MethodHolder, List<MethodInvocation>> entry: classMethodMap.entrySet()) {
631 MethodHolder h = entry.getKey();
632 List<MethodInvocation> calledMeths = entry.getValue();
633 for (Iterator<MethodInvocation> calledMethsIter = calledMeths.iterator(); calledMethsIter.hasNext() && !h.isDangerous();) {
634
635 MethodInvocation meth = calledMethsIter.next();
636
637 for (MethodHolder h3: classMethodMap.keySet()) {
638 if (h3.isDangerous()) {
639 String matchMethodName = h3.getASTMethodDeclarator().getImage();
640 int matchMethodParamCount = h3.getASTMethodDeclarator().getParameterCount();
641
642 if (matchMethodName.equals(meth.getName()) && matchMethodParamCount == meth.getArgumentCount()) {
643 h.setDangerous();
644 h.setCalledMethod(matchMethodName);
645 found = true;
646 break;
647 }
648 }
649 }
650 }
651 }
652 return found;
653 }
654
655
656
657
658
659
660
661 private void evaluateDangerOfConstructors1(Map<ConstructorHolder, List<MethodInvocation>> classConstructorMap, Set<MethodHolder> evaluatedMethods) {
662
663 for (Map.Entry<ConstructorHolder, List<MethodInvocation>> entry: classConstructorMap.entrySet()) {
664 ConstructorHolder ch = entry.getKey();
665 if (!ch.isDangerous()) {
666
667 List<MethodInvocation> calledMeths = entry.getValue();
668
669 for (Iterator<MethodInvocation> calledMethsIter = calledMeths.iterator(); calledMethsIter.hasNext() && !ch.isDangerous();) {
670 MethodInvocation meth = calledMethsIter.next();
671 String methName = meth.getName();
672 int methArgCount = meth.getArgumentCount();
673
674 for (MethodHolder h: evaluatedMethods) {
675 if (h.isDangerous()) {
676 String matchName = h.getASTMethodDeclarator().getImage();
677 int matchParamCount = h.getASTMethodDeclarator().getParameterCount();
678 if (methName.equals(matchName) && methArgCount == matchParamCount) {
679 ch.setDangerous(true);
680
681 break;
682 }
683 }
684
685 }
686 }
687 }
688 }
689 }
690
691
692
693
694
695
696
697
698
699
700 private boolean evaluateDangerOfConstructors2(Map<ConstructorHolder, List<MethodInvocation>> classConstructorMap) {
701 boolean found = false;
702
703 for (ConstructorHolder ch: classConstructorMap.keySet()) {
704 ConstructorInvocation calledC = ch.getCalledConstructor();
705 if (calledC == null || ch.isDangerous()) {
706 continue;
707 }
708
709
710 int cCount = calledC.getArgumentCount();
711 for (Iterator<ConstructorHolder> innerConstIter = classConstructorMap.keySet().iterator(); innerConstIter.hasNext() && !ch.isDangerous();) {
712 ConstructorHolder h2 = innerConstIter.next();
713 if (h2.isDangerous()) {
714 int matchConstArgCount = h2.getASTConstructorDeclaration().getParameterCount();
715 if (matchConstArgCount == cCount) {
716 ch.setDangerous(true);
717 found = true;
718
719 }
720 }
721 }
722 }
723 return found;
724 }
725
726 @Override
727 public Object visit(ASTCompilationUnit node, Object data) {
728 clearEvalPackages();
729 return super.visit(node, data);
730 }
731
732 @Override
733 public Object visit(ASTEnumDeclaration node, Object data) {
734
735 return data;
736 }
737
738
739
740
741
742 @Override
743 public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
744 if (!node.isInterface()) {
745 return visitClassDec(node, data);
746 } else {
747 putEvalPackage(NULL_EVAL_PACKAGE);
748 Object o = super.visit(node, data);
749 removeCurrentEvalPackage();
750 return o;
751 }
752 }
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769 @Override
770 public Object visit(ASTConstructorDeclaration node, Object data) {
771 if (!(getCurrentEvalPackage() instanceof NullEvalPackage)) {
772 List<MethodInvocation> calledMethodsOfConstructor = new ArrayList<MethodInvocation>();
773 ConstructorHolder ch = new ConstructorHolder(node);
774 addCalledMethodsOfNode(node, calledMethodsOfConstructor, getCurrentEvalPackage().className);
775 if (!node.isPrivate()) {
776
777 getCurrentEvalPackage().calledMethods.addAll(calledMethodsOfConstructor);
778
779
780
781 ASTExplicitConstructorInvocation eci = ch.getASTExplicitConstructorInvocation();
782 if (eci != null && eci.isThis()) {
783 getCurrentEvalPackage().calledConstructors.add(ch.getCalledConstructor());
784 }
785 } else {
786
787
788 getCurrentEvalPackage().allPrivateConstructorsOfClass.put(ch, calledMethodsOfConstructor);
789 }
790 }
791 return super.visit(node, data);
792 }
793
794
795
796
797
798
799 @Override
800 public Object visit(ASTMethodDeclarator node, Object data) {
801 if (!(getCurrentEvalPackage() instanceof NullEvalPackage)) {
802 AccessNode parent = (AccessNode) node.jjtGetParent();
803 MethodHolder h = new MethodHolder(node);
804 if (!parent.isAbstract() && !parent.isPrivate() && !parent.isStatic() && !parent.isFinal()) {
805 h.setDangerous();
806 ASTMethodDeclaration decl = node.getFirstParentOfType(ASTMethodDeclaration.class);
807 h.setCalledMethod(decl.getMethodName());
808 }
809 List<MethodInvocation> l = new ArrayList<MethodInvocation>();
810 addCalledMethodsOfNode((Node)parent, l, getCurrentEvalPackage().className);
811 getCurrentEvalPackage().allMethodsOfClass.put(h, l);
812 }
813 return super.visit(node, data);
814 }
815
816
817
818
819
820 private static void addCalledMethodsOfNode(Node node, List<MethodInvocation> calledMethods, String className) {
821 List<ASTPrimaryExpression> expressions = new ArrayList<ASTPrimaryExpression>();
822 node.findDescendantsOfType(ASTPrimaryExpression.class, expressions, !(node instanceof AccessNode));
823 addCalledMethodsOfNodeImpl(expressions, calledMethods, className);
824 }
825
826 private static void addCalledMethodsOfNodeImpl(List<ASTPrimaryExpression> expressions, List<MethodInvocation> calledMethods, String className) {
827 for (ASTPrimaryExpression ape: expressions) {
828 MethodInvocation meth = findMethod(ape, className);
829 if (meth != null) {
830
831 calledMethods.add(meth);
832 }
833 }
834 }
835
836
837
838
839
840
841
842 private static MethodInvocation findMethod(ASTPrimaryExpression node, String className) {
843 if (node.jjtGetNumChildren() > 0
844 && node.jjtGetChild(0).jjtGetNumChildren() > 0
845 && node.jjtGetChild(0).jjtGetChild(0) instanceof ASTLiteral) {
846 return null;
847 }
848 MethodInvocation meth = MethodInvocation.getMethod(node);
849 boolean found = false;
850
851
852
853 if (meth != null) {
854
855 if (meth.getReferenceNames().isEmpty() && !meth.isSuper()) {
856
857
858 List<String> packClass = meth.getQualifierNames();
859 if (!packClass.isEmpty()) {
860 for (String name: packClass) {
861 if (name.equals(className)) {
862 found = true;
863 break;
864 }
865 }
866 } else {
867 found = true;
868 }
869 }
870 }
871
872 return found ? meth : null;
873 }
874
875
876
877
878 private static String getNameFromPrefix(ASTPrimaryPrefix node) {
879 String name = null;
880
881 if (node.jjtGetNumChildren() == 1) {
882 Node nnode = node.jjtGetChild(0);
883 if (nnode instanceof ASTName) {
884 name = ((ASTName) nnode).getImage();
885 }
886 }
887 return name;
888 }
889
890 }