1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.checks.coding;
21
22 import java.util.AbstractMap.SimpleEntry;
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.Map.Entry;
26 import java.util.regex.Matcher;
27 import java.util.regex.Pattern;
28
29 import com.puppycrawl.tools.checkstyle.StatelessCheck;
30 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
31 import com.puppycrawl.tools.checkstyle.api.DetailAST;
32 import com.puppycrawl.tools.checkstyle.api.FullIdent;
33 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
34
35
36
37
38
39
40
41
42
43
44
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186 @StatelessCheck
187 public class VariableDeclarationUsageDistanceCheck extends AbstractCheck {
188
189
190
191
192 public static final String MSG_KEY = "variable.declaration.usage.distance";
193
194
195
196
197 public static final String MSG_KEY_EXT = "variable.declaration.usage.distance.extend";
198
199
200
201
202
203 private static final int DEFAULT_DISTANCE = 3;
204
205
206
207
208
209 private int allowedDistance = DEFAULT_DISTANCE;
210
211
212
213
214
215 private Pattern ignoreVariablePattern = Pattern.compile("");
216
217
218
219
220
221 private boolean validateBetweenScopes;
222
223
224 private boolean ignoreFinal = true;
225
226
227
228
229
230
231
232
233 public void setAllowedDistance(int allowedDistance) {
234 this.allowedDistance = allowedDistance;
235 }
236
237
238
239
240
241 public void setIgnoreVariablePattern(Pattern pattern) {
242 ignoreVariablePattern = pattern;
243 }
244
245
246
247
248
249
250
251
252 public void setValidateBetweenScopes(boolean validateBetweenScopes) {
253 this.validateBetweenScopes = validateBetweenScopes;
254 }
255
256
257
258
259
260
261 public void setIgnoreFinal(boolean ignoreFinal) {
262 this.ignoreFinal = ignoreFinal;
263 }
264
265 @Override
266 public int[] getDefaultTokens() {
267 return getRequiredTokens();
268 }
269
270 @Override
271 public int[] getAcceptableTokens() {
272 return getRequiredTokens();
273 }
274
275 @Override
276 public int[] getRequiredTokens() {
277 return new int[] {TokenTypes.VARIABLE_DEF};
278 }
279
280 @Override
281 public void visitToken(DetailAST ast) {
282 final int parentType = ast.getParent().getType();
283 final DetailAST modifiers = ast.getFirstChild();
284
285 if (parentType != TokenTypes.OBJBLOCK
286 && (!ignoreFinal || modifiers.findFirstToken(TokenTypes.FINAL) == null)) {
287 final DetailAST variable = ast.findFirstToken(TokenTypes.IDENT);
288
289 if (!isVariableMatchesIgnorePattern(variable.getText())) {
290 final DetailAST semicolonAst = ast.getNextSibling();
291 final Entry<DetailAST, Integer> entry;
292 if (validateBetweenScopes) {
293 entry = calculateDistanceBetweenScopes(semicolonAst, variable);
294 }
295 else {
296 entry = calculateDistanceInSingleScope(semicolonAst, variable);
297 }
298 final DetailAST variableUsageAst = entry.getKey();
299 final int dist = entry.getValue();
300 if (dist > allowedDistance
301 && !isInitializationSequence(variableUsageAst, variable.getText())) {
302 if (ignoreFinal) {
303 log(variable.getLineNo(),
304 MSG_KEY_EXT, variable.getText(), dist, allowedDistance);
305 }
306 else {
307 log(variable.getLineNo(),
308 MSG_KEY, variable.getText(), dist, allowedDistance);
309 }
310 }
311 }
312 }
313 }
314
315
316
317
318
319
320
321 private static String getInstanceName(DetailAST methodCallAst) {
322 final String methodCallName =
323 FullIdent.createFullIdentBelow(methodCallAst).getText();
324 final int lastDotIndex = methodCallName.lastIndexOf('.');
325 String instanceName = "";
326 if (lastDotIndex != -1) {
327 instanceName = methodCallName.substring(0, lastDotIndex);
328 }
329 return instanceName;
330 }
331
332
333
334
335
336
337
338
339
340
341
342 private static boolean isInitializationSequence(
343 DetailAST variableUsageAst, String variableName) {
344 boolean result = true;
345 boolean isUsedVariableDeclarationFound = false;
346 DetailAST currentSiblingAst = variableUsageAst;
347 String initInstanceName = "";
348
349 while (result
350 && !isUsedVariableDeclarationFound
351 && currentSiblingAst != null) {
352 switch (currentSiblingAst.getType()) {
353 case TokenTypes.EXPR:
354 final DetailAST methodCallAst = currentSiblingAst.getFirstChild();
355
356 if (methodCallAst.getType() == TokenTypes.METHOD_CALL) {
357 final String instanceName =
358 getInstanceName(methodCallAst);
359
360 if (instanceName.isEmpty()) {
361 result = false;
362 }
363
364 else if (!instanceName.equals(initInstanceName)) {
365 if (initInstanceName.isEmpty()) {
366 initInstanceName = instanceName;
367 }
368 else {
369 result = false;
370 }
371 }
372 }
373 else {
374
375 result = false;
376 }
377 break;
378
379 case TokenTypes.VARIABLE_DEF:
380 final String currentVariableName = currentSiblingAst
381 .findFirstToken(TokenTypes.IDENT).getText();
382 isUsedVariableDeclarationFound = variableName.equals(currentVariableName);
383 break;
384
385 case TokenTypes.SEMI:
386 break;
387
388 default:
389 result = false;
390 }
391
392 currentSiblingAst = currentSiblingAst.getPreviousSibling();
393 }
394
395 return result;
396 }
397
398
399
400
401
402
403
404
405
406
407
408 private static Entry<DetailAST, Integer> calculateDistanceInSingleScope(
409 DetailAST semicolonAst, DetailAST variableIdentAst) {
410 int dist = 0;
411 boolean firstUsageFound = false;
412 DetailAST currentAst = semicolonAst;
413 DetailAST variableUsageAst = null;
414
415 while (!firstUsageFound && currentAst != null
416 && currentAst.getType() != TokenTypes.RCURLY) {
417 if (currentAst.getFirstChild() != null) {
418 if (isChild(currentAst, variableIdentAst)) {
419 dist = getDistToVariableUsageInChildNode(currentAst, variableIdentAst, dist);
420 variableUsageAst = currentAst;
421 firstUsageFound = true;
422 }
423 else if (currentAst.getType() != TokenTypes.VARIABLE_DEF) {
424 dist++;
425 }
426 }
427 currentAst = currentAst.getNextSibling();
428 }
429
430
431 if (!firstUsageFound) {
432 dist = 0;
433 }
434
435 return new SimpleEntry<>(variableUsageAst, dist);
436 }
437
438
439
440
441
442
443
444
445 private static int getDistToVariableUsageInChildNode(DetailAST childNode, DetailAST varIdent,
446 int currentDistToVarUsage) {
447 DetailAST examineNode = childNode;
448 if (examineNode.getType() == TokenTypes.LABELED_STAT) {
449 examineNode = examineNode.getFirstChild().getNextSibling();
450 }
451
452 int resultDist = currentDistToVarUsage;
453 switch (examineNode.getType()) {
454 case TokenTypes.VARIABLE_DEF:
455 resultDist++;
456 break;
457 case TokenTypes.SLIST:
458 resultDist = 0;
459 break;
460 case TokenTypes.LITERAL_FOR:
461 case TokenTypes.LITERAL_WHILE:
462 case TokenTypes.LITERAL_DO:
463 case TokenTypes.LITERAL_IF:
464 case TokenTypes.LITERAL_SWITCH:
465 if (isVariableInOperatorExpr(examineNode, varIdent)) {
466 resultDist++;
467 }
468 else {
469
470
471 resultDist = 0;
472 }
473 break;
474 default:
475 if (examineNode.findFirstToken(TokenTypes.SLIST) == null) {
476 resultDist++;
477 }
478 else {
479 resultDist = 0;
480 }
481 }
482 return resultDist;
483 }
484
485
486
487
488
489
490
491
492
493
494
495 private static Entry<DetailAST, Integer> calculateDistanceBetweenScopes(
496 DetailAST ast, DetailAST variable) {
497 int dist = 0;
498 DetailAST currentScopeAst = ast;
499 DetailAST variableUsageAst = null;
500 while (currentScopeAst != null) {
501 final Entry<List<DetailAST>, Integer> searchResult =
502 searchVariableUsageExpressions(variable, currentScopeAst);
503
504 currentScopeAst = null;
505
506 final List<DetailAST> variableUsageExpressions = searchResult.getKey();
507 dist += searchResult.getValue();
508
509
510
511 if (variableUsageExpressions.size() == 1) {
512 final DetailAST blockWithVariableUsage = variableUsageExpressions
513 .get(0);
514 DetailAST exprWithVariableUsage = null;
515 switch (blockWithVariableUsage.getType()) {
516 case TokenTypes.VARIABLE_DEF:
517 case TokenTypes.EXPR:
518 dist++;
519 break;
520 case TokenTypes.LITERAL_FOR:
521 case TokenTypes.LITERAL_WHILE:
522 case TokenTypes.LITERAL_DO:
523 exprWithVariableUsage = getFirstNodeInsideForWhileDoWhileBlocks(
524 blockWithVariableUsage, variable);
525 break;
526 case TokenTypes.LITERAL_IF:
527 exprWithVariableUsage = getFirstNodeInsideIfBlock(
528 blockWithVariableUsage, variable);
529 break;
530 case TokenTypes.LITERAL_SWITCH:
531 exprWithVariableUsage = getFirstNodeInsideSwitchBlock(
532 blockWithVariableUsage, variable);
533 break;
534 case TokenTypes.LITERAL_TRY:
535 exprWithVariableUsage =
536 getFirstNodeInsideTryCatchFinallyBlocks(blockWithVariableUsage,
537 variable);
538 break;
539 default:
540 exprWithVariableUsage = blockWithVariableUsage.getFirstChild();
541 }
542 currentScopeAst = exprWithVariableUsage;
543 if (exprWithVariableUsage == null) {
544 variableUsageAst = blockWithVariableUsage;
545 }
546 else {
547 variableUsageAst = exprWithVariableUsage;
548 }
549 }
550
551
552 else if (variableUsageExpressions.isEmpty()) {
553 variableUsageAst = null;
554 }
555
556
557 else {
558 dist++;
559 variableUsageAst = variableUsageExpressions.get(0);
560 }
561 }
562 return new SimpleEntry<>(variableUsageAst, dist);
563 }
564
565
566
567
568
569
570
571
572 private static Entry<List<DetailAST>, Integer>
573 searchVariableUsageExpressions(final DetailAST variableAst, final DetailAST statementAst) {
574 final List<DetailAST> variableUsageExpressions = new ArrayList<>();
575 int distance = 0;
576 DetailAST currentStatementAst = statementAst;
577 while (currentStatementAst != null
578 && currentStatementAst.getType() != TokenTypes.RCURLY) {
579 if (currentStatementAst.getFirstChild() != null) {
580 if (isChild(currentStatementAst, variableAst)) {
581 variableUsageExpressions.add(currentStatementAst);
582 }
583
584
585 else if (variableUsageExpressions.isEmpty()
586 && currentStatementAst.getType() != TokenTypes.VARIABLE_DEF) {
587 distance++;
588 }
589 }
590 currentStatementAst = currentStatementAst.getNextSibling();
591 }
592 return new SimpleEntry<>(variableUsageExpressions, distance);
593 }
594
595
596
597
598
599
600
601
602
603
604
605
606 private static DetailAST getFirstNodeInsideForWhileDoWhileBlocks(
607 DetailAST block, DetailAST variable) {
608 DetailAST firstNodeInsideBlock = null;
609
610 if (!isVariableInOperatorExpr(block, variable)) {
611 final DetailAST currentNode;
612
613
614 if (block.getType() == TokenTypes.LITERAL_DO) {
615 currentNode = block.getFirstChild();
616 }
617
618 else {
619
620
621 currentNode = block.findFirstToken(TokenTypes.RPAREN).getNextSibling();
622 }
623
624 final int currentNodeType = currentNode.getType();
625
626 if (currentNodeType == TokenTypes.SLIST) {
627 firstNodeInsideBlock = currentNode.getFirstChild();
628 }
629 else if (currentNodeType != TokenTypes.EXPR) {
630 firstNodeInsideBlock = currentNode;
631 }
632 }
633
634 return firstNodeInsideBlock;
635 }
636
637
638
639
640
641
642
643
644
645
646
647
648 private static DetailAST getFirstNodeInsideIfBlock(
649 DetailAST block, DetailAST variable) {
650 DetailAST firstNodeInsideBlock = null;
651
652 if (!isVariableInOperatorExpr(block, variable)) {
653 DetailAST currentNode = block.getLastChild();
654 final List<DetailAST> variableUsageExpressions =
655 new ArrayList<>();
656
657 while (currentNode != null
658 && currentNode.getType() == TokenTypes.LITERAL_ELSE) {
659 final DetailAST previousNode =
660 currentNode.getPreviousSibling();
661
662
663 if (isChild(previousNode, variable)) {
664 variableUsageExpressions.add(previousNode);
665 }
666
667
668 currentNode = currentNode.getFirstChild();
669
670 if (currentNode.getType() == TokenTypes.LITERAL_IF) {
671 currentNode = currentNode.getLastChild();
672 }
673 else if (isChild(currentNode, variable)) {
674 variableUsageExpressions.add(currentNode);
675 currentNode = null;
676 }
677 }
678
679
680
681 if (currentNode != null
682 && isChild(currentNode, variable)) {
683 variableUsageExpressions.add(currentNode);
684 }
685
686
687
688
689
690 if (variableUsageExpressions.size() == 1) {
691 firstNodeInsideBlock = variableUsageExpressions.get(0);
692 }
693 }
694
695 return firstNodeInsideBlock;
696 }
697
698
699
700
701
702
703
704
705
706
707
708
709 private static DetailAST getFirstNodeInsideSwitchBlock(
710 DetailAST block, DetailAST variable) {
711 DetailAST currentNode = block
712 .findFirstToken(TokenTypes.CASE_GROUP);
713 final List<DetailAST> variableUsageExpressions =
714 new ArrayList<>();
715
716
717 while (currentNode.getType() == TokenTypes.CASE_GROUP) {
718 final DetailAST lastNodeInCaseGroup =
719 currentNode.getLastChild();
720
721 if (isChild(lastNodeInCaseGroup, variable)) {
722 variableUsageExpressions.add(lastNodeInCaseGroup);
723 }
724 currentNode = currentNode.getNextSibling();
725 }
726
727
728
729
730
731 DetailAST firstNodeInsideBlock = null;
732 if (variableUsageExpressions.size() == 1) {
733 firstNodeInsideBlock = variableUsageExpressions.get(0);
734 }
735
736 return firstNodeInsideBlock;
737 }
738
739
740
741
742
743
744
745
746
747
748
749
750 private static DetailAST getFirstNodeInsideTryCatchFinallyBlocks(
751 DetailAST block, DetailAST variable) {
752 DetailAST currentNode = block.getFirstChild();
753 final List<DetailAST> variableUsageExpressions =
754 new ArrayList<>();
755
756
757 if (isChild(currentNode, variable)) {
758 variableUsageExpressions.add(currentNode);
759 }
760
761
762 currentNode = currentNode.getNextSibling();
763
764
765 while (currentNode != null
766 && currentNode.getType() == TokenTypes.LITERAL_CATCH) {
767 final DetailAST catchBlock = currentNode.getLastChild();
768
769 if (isChild(catchBlock, variable)) {
770 variableUsageExpressions.add(catchBlock);
771 }
772 currentNode = currentNode.getNextSibling();
773 }
774
775
776 if (currentNode != null) {
777 final DetailAST finalBlock = currentNode.getLastChild();
778
779 if (isChild(finalBlock, variable)) {
780 variableUsageExpressions.add(finalBlock);
781 }
782 }
783
784 DetailAST variableUsageNode = null;
785
786
787
788
789
790 if (variableUsageExpressions.size() == 1) {
791 variableUsageNode = variableUsageExpressions.get(0).getFirstChild();
792 }
793
794 return variableUsageNode;
795 }
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811 private static boolean isVariableInOperatorExpr(
812 DetailAST operator, DetailAST variable) {
813 boolean isVarInOperatorDeclaration = false;
814 final DetailAST openingBracket =
815 operator.findFirstToken(TokenTypes.LPAREN);
816
817
818 DetailAST exprBetweenBrackets = openingBracket.getNextSibling();
819
820
821 while (exprBetweenBrackets.getType() != TokenTypes.RPAREN) {
822 if (isChild(exprBetweenBrackets, variable)) {
823 isVarInOperatorDeclaration = true;
824 break;
825 }
826 exprBetweenBrackets = exprBetweenBrackets.getNextSibling();
827 }
828
829
830
831 if (!isVarInOperatorDeclaration && operator.getType() == TokenTypes.LITERAL_IF) {
832 final DetailAST elseBlock = operator.getLastChild();
833
834 if (elseBlock.getType() == TokenTypes.LITERAL_ELSE) {
835
836 final DetailAST firstNodeInsideElseBlock = elseBlock.getFirstChild();
837
838 if (firstNodeInsideElseBlock.getType() == TokenTypes.LITERAL_IF) {
839 isVarInOperatorDeclaration =
840 isVariableInOperatorExpr(firstNodeInsideElseBlock, variable);
841 }
842 }
843 }
844
845 return isVarInOperatorDeclaration;
846 }
847
848
849
850
851
852
853
854
855
856 private static boolean isChild(DetailAST parent, DetailAST ast) {
857 boolean isChild = false;
858 DetailAST curNode = parent.getFirstChild();
859
860 while (curNode != null) {
861 if (curNode.equals(ast)) {
862 isChild = true;
863 break;
864 }
865
866 DetailAST toVisit = curNode.getFirstChild();
867 while (toVisit == null) {
868 toVisit = curNode.getNextSibling();
869 curNode = curNode.getParent();
870
871 if (curNode == parent) {
872 break;
873 }
874 }
875
876 curNode = toVisit;
877 }
878
879 return isChild;
880 }
881
882
883
884
885
886
887
888 private boolean isVariableMatchesIgnorePattern(String variable) {
889 final Matcher matcher = ignoreVariablePattern.matcher(variable);
890 return matcher.matches();
891 }
892
893 }