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.utils;
21
22 import java.nio.file.Path;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Optional;
29 import java.util.Set;
30 import java.util.function.Predicate;
31 import java.util.regex.Pattern;
32 import java.util.stream.Collectors;
33 import java.util.stream.Stream;
34
35 import com.puppycrawl.tools.checkstyle.api.DetailAST;
36 import com.puppycrawl.tools.checkstyle.api.FullIdent;
37 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
38 import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption;
39
40
41
42
43
44 public final class CheckUtil {
45
46
47
48 private static final int BASE_2 = 2;
49
50
51 private static final int BASE_8 = 8;
52
53
54 private static final int BASE_10 = 10;
55
56
57 private static final int BASE_16 = 16;
58
59
60 private static final Pattern UNDERSCORE_PATTERN = Pattern.compile("_");
61
62
63 private static final Pattern ALL_NEW_LINES = Pattern.compile("\\R");
64
65
66 private static final char PACKAGE_SEPARATOR = '.';
67
68
69 private CheckUtil() {
70 }
71
72
73
74
75
76
77
78
79 public static boolean isEqualsMethod(DetailAST ast) {
80 boolean equalsMethod = false;
81
82 if (ast.getType() == TokenTypes.METHOD_DEF) {
83 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
84 final boolean staticOrAbstract =
85 modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null
86 || modifiers.findFirstToken(TokenTypes.ABSTRACT) != null;
87
88 if (!staticOrAbstract) {
89 final DetailAST nameNode = ast.findFirstToken(TokenTypes.IDENT);
90 final String name = nameNode.getText();
91
92 if ("equals".equals(name)) {
93
94 final DetailAST paramsNode = ast.findFirstToken(TokenTypes.PARAMETERS);
95 equalsMethod = paramsNode.getChildCount() == 1;
96 }
97 }
98 }
99 return equalsMethod;
100 }
101
102
103
104
105
106
107
108
109
110
111 public static double parseDouble(String text, int type) {
112 String txt = UNDERSCORE_PATTERN.matcher(text).replaceAll("");
113
114 return switch (type) {
115 case TokenTypes.NUM_FLOAT, TokenTypes.NUM_DOUBLE -> Double.parseDouble(txt);
116
117 case TokenTypes.NUM_INT, TokenTypes.NUM_LONG -> {
118 int radix = BASE_10;
119 if (txt.startsWith("0x") || txt.startsWith("0X")) {
120 radix = BASE_16;
121 txt = txt.substring(2);
122 }
123 else if (txt.startsWith("0b") || txt.startsWith("0B")) {
124 radix = BASE_2;
125 txt = txt.substring(2);
126 }
127 else if (txt.startsWith("0")) {
128 radix = BASE_8;
129 }
130 yield parseNumber(txt, radix, type);
131 }
132
133 default -> Double.NaN;
134 };
135 }
136
137
138
139
140
141
142
143
144
145
146
147
148
149 private static double parseNumber(final String text, final int radix, final int type) {
150 String txt = text;
151 if (txt.endsWith("L") || txt.endsWith("l")) {
152 txt = txt.substring(0, txt.length() - 1);
153 }
154 final double result;
155
156 final boolean negative = txt.charAt(0) == '-';
157 if (type == TokenTypes.NUM_INT) {
158 if (negative) {
159 result = Integer.parseInt(txt, radix);
160 }
161 else {
162 result = Integer.parseUnsignedInt(txt, radix);
163 }
164 }
165 else {
166 if (negative) {
167 result = Long.parseLong(txt, radix);
168 }
169 else {
170 result = Long.parseUnsignedLong(txt, radix);
171 }
172 }
173
174 return result;
175 }
176
177
178
179
180
181
182
183 public static DetailAST getFirstNode(final DetailAST node) {
184 DetailAST currentNode = node;
185 DetailAST child = node.getFirstChild();
186 while (child != null) {
187 final DetailAST newNode = getFirstNode(child);
188 if (isBeforeInSource(newNode, currentNode)) {
189 currentNode = newNode;
190 }
191 child = child.getNextSibling();
192 }
193
194 return currentNode;
195 }
196
197
198
199
200
201
202
203
204 public static boolean isBeforeInSource(DetailAST ast1, DetailAST ast2) {
205 return ast1.getLineNo() < ast2.getLineNo()
206 || TokenUtil.areOnSameLine(ast1, ast2)
207 && ast1.getColumnNo() < ast2.getColumnNo();
208 }
209
210
211
212
213
214
215
216 public static List<String> getTypeParameterNames(final DetailAST node) {
217 final DetailAST typeParameters =
218 node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
219
220 final List<String> typeParameterNames = new ArrayList<>();
221 if (typeParameters != null) {
222 final DetailAST typeParam =
223 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
224 typeParameterNames.add(
225 typeParam.findFirstToken(TokenTypes.IDENT).getText());
226
227 DetailAST sibling = typeParam.getNextSibling();
228 while (sibling != null) {
229 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
230 typeParameterNames.add(
231 sibling.findFirstToken(TokenTypes.IDENT).getText());
232 }
233 sibling = sibling.getNextSibling();
234 }
235 }
236
237 return typeParameterNames;
238 }
239
240
241
242
243
244
245
246 public static List<DetailAST> getTypeParameters(final DetailAST node) {
247 final DetailAST typeParameters =
248 node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
249
250 final List<DetailAST> typeParams = new ArrayList<>();
251 if (typeParameters != null) {
252 final DetailAST typeParam =
253 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
254 typeParams.add(typeParam);
255
256 DetailAST sibling = typeParam.getNextSibling();
257 while (sibling != null) {
258 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
259 typeParams.add(sibling);
260 }
261 sibling = sibling.getNextSibling();
262 }
263 }
264
265 return typeParams;
266 }
267
268
269
270
271
272
273
274 public static boolean isNonVoidMethod(DetailAST methodDefAst) {
275 boolean returnValue = false;
276 if (methodDefAst.getType() == TokenTypes.METHOD_DEF) {
277 final DetailAST typeAST = methodDefAst.findFirstToken(TokenTypes.TYPE);
278 if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) == null) {
279 returnValue = true;
280 }
281 }
282 return returnValue;
283 }
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301 public static boolean isReceiverParameter(DetailAST parameterDefAst) {
302 return parameterDefAst.findFirstToken(TokenTypes.IDENT) == null;
303 }
304
305
306
307
308
309
310
311
312
313 public static AccessModifierOption getAccessModifierFromModifiersToken(DetailAST ast) {
314 AccessModifierOption accessModifier;
315 if (ast.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
316 accessModifier = AccessModifierOption.PUBLIC;
317 }
318 else {
319 final DetailAST modsToken = ast.findFirstToken(TokenTypes.MODIFIERS);
320 accessModifier = getAccessModifierFromModifiersTokenDirectly(modsToken);
321 }
322
323 if (accessModifier == AccessModifierOption.PACKAGE) {
324 if (ScopeUtil.isInEnumBlock(ast) && ast.getType() == TokenTypes.CTOR_DEF) {
325 accessModifier = AccessModifierOption.PRIVATE;
326 }
327 else if (ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) {
328 accessModifier = AccessModifierOption.PUBLIC;
329 }
330 }
331
332 return accessModifier;
333 }
334
335
336
337
338
339
340
341
342
343 private static AccessModifierOption getAccessModifierFromModifiersTokenDirectly(
344 DetailAST modifiersToken) {
345 if (modifiersToken == null) {
346 throw new IllegalArgumentException("expected non-null AST-token with type 'MODIFIERS'");
347 }
348
349 AccessModifierOption accessModifier = AccessModifierOption.PACKAGE;
350 for (DetailAST token = modifiersToken.getFirstChild(); token != null;
351 token = token.getNextSibling()) {
352 final int tokenType = token.getType();
353 if (tokenType == TokenTypes.LITERAL_PUBLIC) {
354 accessModifier = AccessModifierOption.PUBLIC;
355 }
356 else if (tokenType == TokenTypes.LITERAL_PROTECTED) {
357 accessModifier = AccessModifierOption.PROTECTED;
358 }
359 else if (tokenType == TokenTypes.LITERAL_PRIVATE) {
360 accessModifier = AccessModifierOption.PRIVATE;
361 }
362 }
363 return accessModifier;
364 }
365
366
367
368
369
370
371
372 public static Optional<AccessModifierOption> getSurroundingAccessModifier(DetailAST node) {
373 Optional<AccessModifierOption> returnValue = Optional.empty();
374 for (DetailAST token = node;
375 returnValue.isEmpty() && !TokenUtil.isRootNode(token);
376 token = token.getParent()) {
377 final int type = token.getType();
378 if (type == TokenTypes.CLASS_DEF
379 || type == TokenTypes.INTERFACE_DEF
380 || type == TokenTypes.ANNOTATION_DEF
381 || type == TokenTypes.ENUM_DEF) {
382 returnValue = Optional.ofNullable(getAccessModifierFromModifiersToken(token));
383 }
384 else if (type == TokenTypes.LITERAL_NEW) {
385 break;
386 }
387 }
388
389 return returnValue;
390 }
391
392
393
394
395
396
397
398 public static Set<String> parseClassNames(String... classNames) {
399 return Arrays.stream(classNames)
400 .flatMap(className -> Stream.of(className, CommonUtil.baseClassName(className)))
401 .filter(Predicate.not(String::isEmpty))
402 .collect(Collectors.toUnmodifiableSet());
403 }
404
405
406
407
408
409
410
411
412
413
414 public static String stripIndentAndInitialNewLineFromTextBlock(String textBlockContent) {
415 final String contentWithInitialNewLineRemoved =
416 ALL_NEW_LINES.matcher(textBlockContent).replaceFirst("");
417 final List<String> lines =
418 Arrays.asList(ALL_NEW_LINES.split(contentWithInitialNewLineRemoved));
419 final int indent = getSmallestIndent(lines);
420 final String suffix = "";
421
422 return lines.stream()
423 .map(line -> stripIndentAndTrailingWhitespaceFromLine(line, indent))
424 .collect(Collectors.joining(System.lineSeparator(), suffix, suffix));
425 }
426
427
428
429
430
431
432
433
434
435 private static String stripIndentAndTrailingWhitespaceFromLine(String line, int indent) {
436 final int lastNonWhitespace = lastIndexOfNonWhitespace(line);
437 String returnString = "";
438 if (lastNonWhitespace > 0) {
439 returnString = line.substring(indent, lastNonWhitespace);
440 }
441 return returnString;
442 }
443
444
445
446
447
448
449
450
451 private static int getSmallestIndent(Collection<String> lines) {
452 return lines.stream()
453 .mapToInt(CommonUtil::indexOfNonWhitespace)
454 .min()
455 .orElse(0);
456 }
457
458
459
460
461
462
463
464 private static int lastIndexOfNonWhitespace(String line) {
465 int length;
466 for (length = line.length(); length > 0; length--) {
467 if (!Character.isWhitespace(line.charAt(length - 1))) {
468 break;
469 }
470 }
471 return length;
472 }
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489 public static int typeDeclarationNameMatchingCount(String patternClass,
490 String classToBeMatched) {
491 final int length = Math.min(classToBeMatched.length(), patternClass.length());
492 int result = 0;
493 for (int index = 0;
494 index < length
495 && patternClass.charAt(index) == classToBeMatched.charAt(index);
496 index++) {
497 if (patternClass.charAt(index) == PACKAGE_SEPARATOR) {
498 result = index;
499 }
500 }
501 return result;
502 }
503
504
505
506
507
508
509
510
511
512
513
514 public static String getQualifiedTypeDeclarationName(String packageName,
515 String outerClassQualifiedName,
516 String className) {
517 final String qualifiedClassName;
518
519 if (outerClassQualifiedName == null) {
520 if (packageName == null) {
521 qualifiedClassName = className;
522 }
523 else {
524 qualifiedClassName = packageName + PACKAGE_SEPARATOR + className;
525 }
526 }
527 else {
528 qualifiedClassName = outerClassQualifiedName + PACKAGE_SEPARATOR + className;
529 }
530 return qualifiedClassName;
531 }
532
533
534
535
536
537
538
539
540 public static String extractQualifiedName(DetailAST ast) {
541 return FullIdent.createFullIdent(ast).getText();
542 }
543
544
545
546
547
548
549
550
551
552
553
554
555 public static String getShortNameOfAnonInnerClass(DetailAST literalNewAst) {
556 DetailAST parentAst = literalNewAst;
557 while (TokenUtil.isOfType(parentAst, TokenTypes.LITERAL_NEW, TokenTypes.DOT)) {
558 parentAst = parentAst.getParent();
559 }
560 final DetailAST firstChild = parentAst.getFirstChild();
561 return extractQualifiedName(firstChild);
562 }
563
564
565
566
567
568
569
570 public static boolean isPackageInfo(String filePath) {
571 final Path filename = Path.of(filePath).getFileName();
572 return filename != null && "package-info.java".equals(filename.toString());
573 }
574
575
576
577
578
579
580
581 public static boolean isTerminated(final DetailAST ast) {
582 return isTerminated(ast, true, true, new HashSet<>());
583 }
584
585
586
587
588
589
590
591
592
593
594
595
596
597 private static boolean isTerminated(final DetailAST ast, boolean useBreak, boolean useContinue,
598 Set<String> labelsForCurrentSwitchScope) {
599
600 return switch (ast.getType()) {
601 case TokenTypes.LITERAL_RETURN, TokenTypes.LITERAL_YIELD,
602 TokenTypes.LITERAL_THROW -> true;
603 case TokenTypes.LITERAL_BREAK -> useBreak
604 || hasLabel(ast, labelsForCurrentSwitchScope);
605 case TokenTypes.LITERAL_CONTINUE -> useContinue
606 || hasLabel(ast, labelsForCurrentSwitchScope);
607 case TokenTypes.SLIST -> checkSlist(ast, useBreak, useContinue,
608 labelsForCurrentSwitchScope);
609 case TokenTypes.LITERAL_IF -> checkIf(ast, useBreak, useContinue,
610 labelsForCurrentSwitchScope);
611 case TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO ->
612 checkLoop(ast, labelsForCurrentSwitchScope);
613 case TokenTypes.LITERAL_TRY -> checkTry(ast, useBreak, useContinue,
614 labelsForCurrentSwitchScope);
615 case TokenTypes.LITERAL_SWITCH -> checkSwitch(ast, useContinue,
616 labelsForCurrentSwitchScope);
617 case TokenTypes.LITERAL_SYNCHRONIZED ->
618 checkSynchronized(ast, useBreak, useContinue,
619 labelsForCurrentSwitchScope);
620 case TokenTypes.LABELED_STAT -> {
621 labelsForCurrentSwitchScope.add(ast.getFirstChild().getText());
622 yield isTerminated(ast.getLastChild(), useBreak, useContinue,
623 labelsForCurrentSwitchScope);
624 }
625 default -> false;
626 };
627 }
628
629
630
631
632
633
634
635
636 private static boolean hasLabel(DetailAST statement, Set<String> labelsForCurrentSwitchScope) {
637 return Optional.ofNullable(statement)
638 .map(DetailAST::getFirstChild)
639 .filter(child -> child.getType() == TokenTypes.IDENT)
640 .map(DetailAST::getText)
641 .filter(label -> !labelsForCurrentSwitchScope.contains(label))
642 .isPresent();
643 }
644
645
646
647
648
649
650
651
652
653
654
655 private static boolean checkSlist(final DetailAST slistAst, boolean useBreak,
656 boolean useContinue, Set<String> labels) {
657 DetailAST lastStmt = slistAst.getLastChild();
658
659 if (lastStmt.getType() == TokenTypes.RCURLY) {
660 lastStmt = lastStmt.getPreviousSibling();
661 }
662
663 while (TokenUtil.isOfType(lastStmt, TokenTypes.SINGLE_LINE_COMMENT,
664 TokenTypes.BLOCK_COMMENT_BEGIN)) {
665 lastStmt = lastStmt.getPreviousSibling();
666 }
667
668 return lastStmt != null
669 && isTerminated(lastStmt, useBreak, useContinue, labels);
670 }
671
672
673
674
675
676
677
678
679
680
681
682 private static boolean checkIf(final DetailAST ast, boolean useBreak,
683 boolean useContinue, Set<String> labels) {
684 final DetailAST thenStmt = getNextNonCommentAst(ast.findFirstToken(TokenTypes.RPAREN));
685
686 final DetailAST elseStmt = ast.findFirstToken(TokenTypes.LITERAL_ELSE);
687
688 final boolean isTerminated;
689
690 if (checkInfiniteLoop(ast.getParent().getParent())) {
691 isTerminated = isTerminated(thenStmt, useBreak, useContinue, labels)
692 || elseStmt != null
693 && isTerminated(elseStmt.getLastChild(), useBreak, useContinue, labels);
694 }
695 else {
696 isTerminated = elseStmt != null
697 && isTerminated(thenStmt, useBreak, useContinue, labels)
698 && isTerminated(elseStmt.getLastChild(), useBreak, useContinue, labels);
699 }
700
701 return isTerminated;
702 }
703
704
705
706
707
708
709
710 public static DetailAST getNextNonCommentAst(DetailAST ast) {
711 DetailAST nextSibling = ast.getNextSibling();
712 while (TokenUtil.isOfType(nextSibling, TokenTypes.SINGLE_LINE_COMMENT,
713 TokenTypes.BLOCK_COMMENT_BEGIN)) {
714 nextSibling = nextSibling.getNextSibling();
715 }
716 return nextSibling;
717 }
718
719
720
721
722
723
724
725
726
727 private static boolean checkLoop(final DetailAST ast, Set<String> labels) {
728 final DetailAST loopBody;
729 if (ast.getType() == TokenTypes.LITERAL_DO) {
730 final DetailAST lparen = ast.findFirstToken(TokenTypes.DO_WHILE);
731 loopBody = lparen.getPreviousSibling();
732 }
733 else {
734 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
735 loopBody = rparen.getNextSibling();
736 }
737
738 return checkInfiniteLoop(ast) && !isTerminated(loopBody, true, false, labels)
739 || isTerminated(loopBody, false, false, labels);
740 }
741
742
743
744
745
746
747
748
749
750
751
752 private static boolean checkTry(final DetailAST ast, boolean useBreak,
753 boolean useContinue, Set<String> labels) {
754 final DetailAST finalStmt = ast.getLastChild();
755 boolean isTerminated = finalStmt.getType() == TokenTypes.LITERAL_FINALLY
756 && isTerminated(finalStmt.findFirstToken(TokenTypes.SLIST),
757 useBreak, useContinue, labels);
758
759 if (!isTerminated) {
760 DetailAST firstChild = ast.getFirstChild();
761
762 if (firstChild.getType() == TokenTypes.RESOURCE_SPECIFICATION) {
763 firstChild = firstChild.getNextSibling();
764 }
765
766 isTerminated = isTerminated(firstChild,
767 useBreak, useContinue, labels);
768
769 DetailAST catchStmt = ast.findFirstToken(TokenTypes.LITERAL_CATCH);
770 while (catchStmt != null
771 && isTerminated
772 && catchStmt.getType() == TokenTypes.LITERAL_CATCH) {
773 final DetailAST catchBody =
774 catchStmt.findFirstToken(TokenTypes.SLIST);
775 isTerminated = isTerminated(catchBody, useBreak, useContinue, labels);
776 catchStmt = catchStmt.getNextSibling();
777 }
778 }
779 return isTerminated;
780 }
781
782
783
784
785
786
787
788
789
790
791 private static boolean checkSwitch(DetailAST literalSwitchAst,
792 boolean useContinue, Set<String> labels) {
793 DetailAST caseGroup = literalSwitchAst.findFirstToken(TokenTypes.CASE_GROUP);
794 boolean isTerminated = caseGroup != null;
795 while (isTerminated && caseGroup.getType() != TokenTypes.RCURLY) {
796 final DetailAST caseBody =
797 caseGroup.findFirstToken(TokenTypes.SLIST);
798 isTerminated = caseBody != null
799 && isTerminated(caseBody, false, useContinue, labels);
800 caseGroup = caseGroup.getNextSibling();
801 }
802 return isTerminated;
803 }
804
805
806
807
808
809
810
811
812
813
814
815 private static boolean checkSynchronized(final DetailAST synchronizedAst, boolean useBreak,
816 boolean useContinue, Set<String> labels) {
817 return isTerminated(
818 synchronizedAst.findFirstToken(TokenTypes.SLIST), useBreak, useContinue, labels);
819 }
820
821
822
823
824
825
826
827 private static boolean checkInfiniteLoop(DetailAST ast) {
828 boolean isInfiniteLoop = false;
829 if (ast.getType() == TokenTypes.LITERAL_FOR) {
830 isInfiniteLoop = checkForLoop(ast);
831 }
832 else if (ast.getType() == TokenTypes.LITERAL_WHILE
833 || ast.getType() == TokenTypes.LITERAL_DO) {
834 final DetailAST lparen = NullUtil.notNull(ast.findFirstToken(TokenTypes.LPAREN));
835 final DetailAST expression = NullUtil.notNull(lparen.getNextSibling());
836 final DetailAST firstChild = NullUtil.notNull(expression.getFirstChild());
837 isInfiniteLoop = firstChild.getType() == TokenTypes.LITERAL_TRUE;
838 }
839 return isInfiniteLoop;
840 }
841
842
843
844
845
846
847
848 private static boolean checkForLoop(DetailAST forLoop) {
849 boolean isInfiniteLoop = false;
850 if (forLoop.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) {
851 final DetailAST condition =
852 NullUtil.notNull(forLoop.findFirstToken(TokenTypes.FOR_CONDITION));
853 DetailAST child = condition.getFirstChild();
854 if (child != null) {
855
856 child = NullUtil.notNull(child.getFirstChild());
857 }
858 isInfiniteLoop = child == null || child.getType() == TokenTypes.LITERAL_TRUE;
859 }
860 return isInfiniteLoop;
861 }
862
863 }