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.List;
27 import java.util.Set;
28 import java.util.function.Predicate;
29 import java.util.regex.Pattern;
30 import java.util.stream.Collectors;
31 import java.util.stream.Stream;
32
33 import com.puppycrawl.tools.checkstyle.api.DetailAST;
34 import com.puppycrawl.tools.checkstyle.api.FullIdent;
35 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
36 import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption;
37
38
39
40
41
42 public final class CheckUtil {
43
44
45
46 private static final int BASE_2 = 2;
47
48
49 private static final int BASE_8 = 8;
50
51
52 private static final int BASE_10 = 10;
53
54
55 private static final int BASE_16 = 16;
56
57
58 private static final Pattern UNDERSCORE_PATTERN = Pattern.compile("_");
59
60
61 private static final Pattern ALL_NEW_LINES = Pattern.compile("\\R");
62
63
64 private static final char PACKAGE_SEPARATOR = '.';
65
66
67 private CheckUtil() {
68 }
69
70
71
72
73
74
75
76
77 public static boolean isEqualsMethod(DetailAST ast) {
78 boolean equalsMethod = false;
79
80 if (ast.getType() == TokenTypes.METHOD_DEF) {
81 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
82 final boolean staticOrAbstract =
83 modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null
84 || modifiers.findFirstToken(TokenTypes.ABSTRACT) != null;
85
86 if (!staticOrAbstract) {
87 final DetailAST nameNode = ast.findFirstToken(TokenTypes.IDENT);
88 final String name = nameNode.getText();
89
90 if ("equals".equals(name)) {
91
92 final DetailAST paramsNode = ast.findFirstToken(TokenTypes.PARAMETERS);
93 equalsMethod = paramsNode.getChildCount() == 1;
94 }
95 }
96 }
97 return equalsMethod;
98 }
99
100
101
102
103
104
105
106
107
108
109 public static double parseDouble(String text, int type) {
110 String txt = UNDERSCORE_PATTERN.matcher(text).replaceAll("");
111
112 return switch (type) {
113 case TokenTypes.NUM_FLOAT, TokenTypes.NUM_DOUBLE -> Double.parseDouble(txt);
114
115 case TokenTypes.NUM_INT, TokenTypes.NUM_LONG -> {
116 int radix = BASE_10;
117 if (txt.startsWith("0x") || txt.startsWith("0X")) {
118 radix = BASE_16;
119 txt = txt.substring(2);
120 }
121 else if (txt.startsWith("0b") || txt.startsWith("0B")) {
122 radix = BASE_2;
123 txt = txt.substring(2);
124 }
125 else if (txt.startsWith("0")) {
126 radix = BASE_8;
127 }
128 yield parseNumber(txt, radix, type);
129 }
130
131 default -> Double.NaN;
132 };
133 }
134
135
136
137
138
139
140
141
142
143
144
145
146
147 private static double parseNumber(final String text, final int radix, final int type) {
148 String txt = text;
149 if (txt.endsWith("L") || txt.endsWith("l")) {
150 txt = txt.substring(0, txt.length() - 1);
151 }
152 final double result;
153
154 final boolean negative = txt.charAt(0) == '-';
155 if (type == TokenTypes.NUM_INT) {
156 if (negative) {
157 result = Integer.parseInt(txt, radix);
158 }
159 else {
160 result = Integer.parseUnsignedInt(txt, radix);
161 }
162 }
163 else {
164 if (negative) {
165 result = Long.parseLong(txt, radix);
166 }
167 else {
168 result = Long.parseUnsignedLong(txt, radix);
169 }
170 }
171
172 return result;
173 }
174
175
176
177
178
179
180
181 public static DetailAST getFirstNode(final DetailAST node) {
182 DetailAST currentNode = node;
183 DetailAST child = node.getFirstChild();
184 while (child != null) {
185 final DetailAST newNode = getFirstNode(child);
186 if (isBeforeInSource(newNode, currentNode)) {
187 currentNode = newNode;
188 }
189 child = child.getNextSibling();
190 }
191
192 return currentNode;
193 }
194
195
196
197
198
199
200
201
202 public static boolean isBeforeInSource(DetailAST ast1, DetailAST ast2) {
203 return ast1.getLineNo() < ast2.getLineNo()
204 || TokenUtil.areOnSameLine(ast1, ast2)
205 && ast1.getColumnNo() < ast2.getColumnNo();
206 }
207
208
209
210
211
212
213
214 public static List<String> getTypeParameterNames(final DetailAST node) {
215 final DetailAST typeParameters =
216 node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
217
218 final List<String> typeParameterNames = new ArrayList<>();
219 if (typeParameters != null) {
220 final DetailAST typeParam =
221 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
222 typeParameterNames.add(
223 typeParam.findFirstToken(TokenTypes.IDENT).getText());
224
225 DetailAST sibling = typeParam.getNextSibling();
226 while (sibling != null) {
227 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
228 typeParameterNames.add(
229 sibling.findFirstToken(TokenTypes.IDENT).getText());
230 }
231 sibling = sibling.getNextSibling();
232 }
233 }
234
235 return typeParameterNames;
236 }
237
238
239
240
241
242
243
244 public static List<DetailAST> getTypeParameters(final DetailAST node) {
245 final DetailAST typeParameters =
246 node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
247
248 final List<DetailAST> typeParams = new ArrayList<>();
249 if (typeParameters != null) {
250 final DetailAST typeParam =
251 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
252 typeParams.add(typeParam);
253
254 DetailAST sibling = typeParam.getNextSibling();
255 while (sibling != null) {
256 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
257 typeParams.add(sibling);
258 }
259 sibling = sibling.getNextSibling();
260 }
261 }
262
263 return typeParams;
264 }
265
266
267
268
269
270
271
272 public static boolean isNonVoidMethod(DetailAST methodDefAst) {
273 boolean returnValue = false;
274 if (methodDefAst.getType() == TokenTypes.METHOD_DEF) {
275 final DetailAST typeAST = methodDefAst.findFirstToken(TokenTypes.TYPE);
276 if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) == null) {
277 returnValue = true;
278 }
279 }
280 return returnValue;
281 }
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 AccessModifierOption getSurroundingAccessModifier(DetailAST node) {
371 AccessModifierOption returnValue = null;
372 for (DetailAST token = node;
373 returnValue == null && !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 = 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 i = 0; i < length && patternClass.charAt(i) == classToBeMatched.charAt(i); ++i) {
492 if (patternClass.charAt(i) == PACKAGE_SEPARATOR) {
493 result = i;
494 }
495 }
496 return result;
497 }
498
499
500
501
502
503
504
505
506
507
508
509 public static String getQualifiedTypeDeclarationName(String packageName,
510 String outerClassQualifiedName,
511 String className) {
512 final String qualifiedClassName;
513
514 if (outerClassQualifiedName == null) {
515 if (packageName == null) {
516 qualifiedClassName = className;
517 }
518 else {
519 qualifiedClassName = packageName + PACKAGE_SEPARATOR + className;
520 }
521 }
522 else {
523 qualifiedClassName = outerClassQualifiedName + PACKAGE_SEPARATOR + className;
524 }
525 return qualifiedClassName;
526 }
527
528
529
530
531
532
533
534
535 public static String extractQualifiedName(DetailAST ast) {
536 return FullIdent.createFullIdent(ast).getText();
537 }
538
539
540
541
542
543
544
545
546
547
548
549
550 public static String getShortNameOfAnonInnerClass(DetailAST literalNewAst) {
551 DetailAST parentAst = literalNewAst;
552 while (TokenUtil.isOfType(parentAst, TokenTypes.LITERAL_NEW, TokenTypes.DOT)) {
553 parentAst = parentAst.getParent();
554 }
555 final DetailAST firstChild = parentAst.getFirstChild();
556 return extractQualifiedName(firstChild);
557 }
558
559
560
561
562
563
564
565 public static boolean isPackageInfo(String filePath) {
566 final Path filename = Path.of(filePath).getFileName();
567 return filename != null && "package-info.java".equals(filename.toString());
568 }
569 }