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 i = 0; i < length && patternClass.charAt(i) == classToBeMatched.charAt(i); ++i) {
494 if (patternClass.charAt(i) == PACKAGE_SEPARATOR) {
495 result = i;
496 }
497 }
498 return result;
499 }
500
501
502
503
504
505
506
507
508
509
510
511 public static String getQualifiedTypeDeclarationName(String packageName,
512 String outerClassQualifiedName,
513 String className) {
514 final String qualifiedClassName;
515
516 if (outerClassQualifiedName == null) {
517 if (packageName == null) {
518 qualifiedClassName = className;
519 }
520 else {
521 qualifiedClassName = packageName + PACKAGE_SEPARATOR + className;
522 }
523 }
524 else {
525 qualifiedClassName = outerClassQualifiedName + PACKAGE_SEPARATOR + className;
526 }
527 return qualifiedClassName;
528 }
529
530
531
532
533
534
535
536
537 public static String extractQualifiedName(DetailAST ast) {
538 return FullIdent.createFullIdent(ast).getText();
539 }
540
541
542
543
544
545
546
547
548
549
550
551
552 public static String getShortNameOfAnonInnerClass(DetailAST literalNewAst) {
553 DetailAST parentAst = literalNewAst;
554 while (TokenUtil.isOfType(parentAst, TokenTypes.LITERAL_NEW, TokenTypes.DOT)) {
555 parentAst = parentAst.getParent();
556 }
557 final DetailAST firstChild = parentAst.getFirstChild();
558 return extractQualifiedName(firstChild);
559 }
560
561
562
563
564
565
566
567 public static boolean isPackageInfo(String filePath) {
568 final Path filename = Path.of(filePath).getFileName();
569 return filename != null && "package-info.java".equals(filename.toString());
570 }
571
572
573
574
575
576
577
578 public static boolean isTerminated(final DetailAST ast) {
579 return isTerminated(ast, true, true, new HashSet<>());
580 }
581
582
583
584
585
586
587
588
589
590
591
592
593
594 private static boolean isTerminated(final DetailAST ast, boolean useBreak, boolean useContinue,
595 Set<String> labelsForCurrentSwitchScope) {
596
597 return switch (ast.getType()) {
598 case TokenTypes.LITERAL_RETURN, TokenTypes.LITERAL_YIELD,
599 TokenTypes.LITERAL_THROW -> true;
600 case TokenTypes.LITERAL_BREAK -> useBreak
601 || hasLabel(ast, labelsForCurrentSwitchScope);
602 case TokenTypes.LITERAL_CONTINUE -> useContinue
603 || hasLabel(ast, labelsForCurrentSwitchScope);
604 case TokenTypes.SLIST -> checkSlist(ast, useBreak, useContinue,
605 labelsForCurrentSwitchScope);
606 case TokenTypes.LITERAL_IF -> checkIf(ast, useBreak, useContinue,
607 labelsForCurrentSwitchScope);
608 case TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO ->
609 checkLoop(ast, labelsForCurrentSwitchScope);
610 case TokenTypes.LITERAL_TRY -> checkTry(ast, useBreak, useContinue,
611 labelsForCurrentSwitchScope);
612 case TokenTypes.LITERAL_SWITCH -> checkSwitch(ast, useContinue,
613 labelsForCurrentSwitchScope);
614 case TokenTypes.LITERAL_SYNCHRONIZED ->
615 checkSynchronized(ast, useBreak, useContinue,
616 labelsForCurrentSwitchScope);
617 case TokenTypes.LABELED_STAT -> {
618 labelsForCurrentSwitchScope.add(ast.getFirstChild().getText());
619 yield isTerminated(ast.getLastChild(), useBreak, useContinue,
620 labelsForCurrentSwitchScope);
621 }
622 default -> false;
623 };
624 }
625
626
627
628
629
630
631
632
633 private static boolean hasLabel(DetailAST statement, Set<String> labelsForCurrentSwitchScope) {
634 return Optional.ofNullable(statement)
635 .map(DetailAST::getFirstChild)
636 .filter(child -> child.getType() == TokenTypes.IDENT)
637 .map(DetailAST::getText)
638 .filter(label -> !labelsForCurrentSwitchScope.contains(label))
639 .isPresent();
640 }
641
642
643
644
645
646
647
648
649
650
651
652 private static boolean checkSlist(final DetailAST slistAst, boolean useBreak,
653 boolean useContinue, Set<String> labels) {
654 DetailAST lastStmt = slistAst.getLastChild();
655
656 if (lastStmt.getType() == TokenTypes.RCURLY) {
657 lastStmt = lastStmt.getPreviousSibling();
658 }
659
660 while (TokenUtil.isOfType(lastStmt, TokenTypes.SINGLE_LINE_COMMENT,
661 TokenTypes.BLOCK_COMMENT_BEGIN)) {
662 lastStmt = lastStmt.getPreviousSibling();
663 }
664
665 return lastStmt != null
666 && isTerminated(lastStmt, useBreak, useContinue, labels);
667 }
668
669
670
671
672
673
674
675
676
677
678
679 private static boolean checkIf(final DetailAST ast, boolean useBreak,
680 boolean useContinue, Set<String> labels) {
681 final DetailAST thenStmt = getNextNonCommentAst(ast.findFirstToken(TokenTypes.RPAREN));
682
683 final DetailAST elseStmt = getNextNonCommentAst(thenStmt);
684
685 return elseStmt != null
686 && isTerminated(thenStmt, useBreak, useContinue, labels)
687 && isTerminated(elseStmt.getLastChild(), useBreak, useContinue, labels);
688 }
689
690
691
692
693
694
695
696 public static DetailAST getNextNonCommentAst(DetailAST ast) {
697 DetailAST nextSibling = ast.getNextSibling();
698 while (TokenUtil.isOfType(nextSibling, TokenTypes.SINGLE_LINE_COMMENT,
699 TokenTypes.BLOCK_COMMENT_BEGIN)) {
700 nextSibling = nextSibling.getNextSibling();
701 }
702 return nextSibling;
703 }
704
705
706
707
708
709
710
711
712
713 private static boolean checkLoop(final DetailAST ast, Set<String> labels) {
714 final DetailAST loopBody;
715 if (ast.getType() == TokenTypes.LITERAL_DO) {
716 final DetailAST lparen = ast.findFirstToken(TokenTypes.DO_WHILE);
717 loopBody = lparen.getPreviousSibling();
718 }
719 else {
720 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
721 loopBody = rparen.getNextSibling();
722 }
723 return isTerminated(loopBody, false, false, labels);
724 }
725
726
727
728
729
730
731
732
733
734
735
736 private static boolean checkTry(final DetailAST ast, boolean useBreak,
737 boolean useContinue, Set<String> labels) {
738 final DetailAST finalStmt = ast.getLastChild();
739 boolean isTerminated = finalStmt.getType() == TokenTypes.LITERAL_FINALLY
740 && isTerminated(finalStmt.findFirstToken(TokenTypes.SLIST),
741 useBreak, useContinue, labels);
742
743 if (!isTerminated) {
744 DetailAST firstChild = ast.getFirstChild();
745
746 if (firstChild.getType() == TokenTypes.RESOURCE_SPECIFICATION) {
747 firstChild = firstChild.getNextSibling();
748 }
749
750 isTerminated = isTerminated(firstChild,
751 useBreak, useContinue, labels);
752
753 DetailAST catchStmt = ast.findFirstToken(TokenTypes.LITERAL_CATCH);
754 while (catchStmt != null
755 && isTerminated
756 && catchStmt.getType() == TokenTypes.LITERAL_CATCH) {
757 final DetailAST catchBody =
758 catchStmt.findFirstToken(TokenTypes.SLIST);
759 isTerminated = isTerminated(catchBody, useBreak, useContinue, labels);
760 catchStmt = catchStmt.getNextSibling();
761 }
762 }
763 return isTerminated;
764 }
765
766
767
768
769
770
771
772
773
774
775 private static boolean checkSwitch(DetailAST literalSwitchAst,
776 boolean useContinue, Set<String> labels) {
777 DetailAST caseGroup = literalSwitchAst.findFirstToken(TokenTypes.CASE_GROUP);
778 boolean isTerminated = caseGroup != null;
779 while (isTerminated && caseGroup.getType() != TokenTypes.RCURLY) {
780 final DetailAST caseBody =
781 caseGroup.findFirstToken(TokenTypes.SLIST);
782 isTerminated = caseBody != null
783 && isTerminated(caseBody, false, useContinue, labels);
784 caseGroup = caseGroup.getNextSibling();
785 }
786 return isTerminated;
787 }
788
789
790
791
792
793
794
795
796
797
798
799 private static boolean checkSynchronized(final DetailAST synchronizedAst, boolean useBreak,
800 boolean useContinue, Set<String> labels) {
801 return isTerminated(
802 synchronizedAst.findFirstToken(TokenTypes.SLIST), useBreak, useContinue, labels);
803 }
804 }