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