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.Optional;
28 import java.util.Set;
29 import java.util.function.Predicate;
30 import java.util.regex.Pattern;
31 import java.util.stream.Collectors;
32 import java.util.stream.Stream;
33
34 import com.puppycrawl.tools.checkstyle.api.DetailAST;
35 import com.puppycrawl.tools.checkstyle.api.FullIdent;
36 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
37 import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption;
38
39
40
41
42
43 public final class CheckUtil {
44
45
46
47 private static final int BASE_2 = 2;
48
49
50 private static final int BASE_8 = 8;
51
52
53 private static final int BASE_10 = 10;
54
55
56 private static final int BASE_16 = 16;
57
58
59 private static final Pattern UNDERSCORE_PATTERN = Pattern.compile("_");
60
61
62 private static final Pattern ALL_NEW_LINES = Pattern.compile("\\R");
63
64
65 private static final char PACKAGE_SEPARATOR = '.';
66
67
68 private CheckUtil() {
69 }
70
71
72
73
74
75
76
77
78 public static boolean isEqualsMethod(DetailAST ast) {
79 boolean equalsMethod = false;
80
81 if (ast.getType() == TokenTypes.METHOD_DEF) {
82 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
83 final boolean staticOrAbstract =
84 modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null
85 || modifiers.findFirstToken(TokenTypes.ABSTRACT) != null;
86
87 if (!staticOrAbstract) {
88 final DetailAST nameNode = ast.findFirstToken(TokenTypes.IDENT);
89 final String name = nameNode.getText();
90
91 if ("equals".equals(name)) {
92
93 final DetailAST paramsNode = ast.findFirstToken(TokenTypes.PARAMETERS);
94 equalsMethod = paramsNode.getChildCount() == 1;
95 }
96 }
97 }
98 return equalsMethod;
99 }
100
101
102
103
104
105
106
107
108
109
110 public static double parseDouble(String text, int type) {
111 String txt = UNDERSCORE_PATTERN.matcher(text).replaceAll("");
112
113 return switch (type) {
114 case TokenTypes.NUM_FLOAT, TokenTypes.NUM_DOUBLE -> Double.parseDouble(txt);
115
116 case TokenTypes.NUM_INT, TokenTypes.NUM_LONG -> {
117 int radix = BASE_10;
118 if (txt.startsWith("0x") || txt.startsWith("0X")) {
119 radix = BASE_16;
120 txt = txt.substring(2);
121 }
122 else if (txt.startsWith("0b") || txt.startsWith("0B")) {
123 radix = BASE_2;
124 txt = txt.substring(2);
125 }
126 else if (txt.startsWith("0")) {
127 radix = BASE_8;
128 }
129 yield parseNumber(txt, radix, type);
130 }
131
132 default -> Double.NaN;
133 };
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
300 public static boolean isReceiverParameter(DetailAST parameterDefAst) {
301 return parameterDefAst.findFirstToken(TokenTypes.IDENT) == null;
302 }
303
304
305
306
307
308
309
310
311
312 public static AccessModifierOption getAccessModifierFromModifiersToken(DetailAST ast) {
313 AccessModifierOption accessModifier;
314 if (ast.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
315 accessModifier = AccessModifierOption.PUBLIC;
316 }
317 else {
318 final DetailAST modsToken = ast.findFirstToken(TokenTypes.MODIFIERS);
319 accessModifier = getAccessModifierFromModifiersTokenDirectly(modsToken);
320 }
321
322 if (accessModifier == AccessModifierOption.PACKAGE) {
323 if (ScopeUtil.isInEnumBlock(ast) && ast.getType() == TokenTypes.CTOR_DEF) {
324 accessModifier = AccessModifierOption.PRIVATE;
325 }
326 else if (ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) {
327 accessModifier = AccessModifierOption.PUBLIC;
328 }
329 }
330
331 return accessModifier;
332 }
333
334
335
336
337
338
339
340
341
342 private static AccessModifierOption getAccessModifierFromModifiersTokenDirectly(
343 DetailAST modifiersToken) {
344 if (modifiersToken == null) {
345 throw new IllegalArgumentException("expected non-null AST-token with type 'MODIFIERS'");
346 }
347
348 AccessModifierOption accessModifier = AccessModifierOption.PACKAGE;
349 for (DetailAST token = modifiersToken.getFirstChild(); token != null;
350 token = token.getNextSibling()) {
351 final int tokenType = token.getType();
352 if (tokenType == TokenTypes.LITERAL_PUBLIC) {
353 accessModifier = AccessModifierOption.PUBLIC;
354 }
355 else if (tokenType == TokenTypes.LITERAL_PROTECTED) {
356 accessModifier = AccessModifierOption.PROTECTED;
357 }
358 else if (tokenType == TokenTypes.LITERAL_PRIVATE) {
359 accessModifier = AccessModifierOption.PRIVATE;
360 }
361 }
362 return accessModifier;
363 }
364
365
366
367
368
369
370
371 public static Optional<AccessModifierOption> getSurroundingAccessModifier(DetailAST node) {
372 Optional<AccessModifierOption> returnValue = Optional.empty();
373 for (DetailAST token = node;
374 returnValue.isEmpty() && !TokenUtil.isRootNode(token);
375 token = token.getParent()) {
376 final int type = token.getType();
377 if (type == TokenTypes.CLASS_DEF
378 || type == TokenTypes.INTERFACE_DEF
379 || type == TokenTypes.ANNOTATION_DEF
380 || type == TokenTypes.ENUM_DEF) {
381 returnValue = Optional.ofNullable(getAccessModifierFromModifiersToken(token));
382 }
383 else if (type == TokenTypes.LITERAL_NEW) {
384 break;
385 }
386 }
387
388 return returnValue;
389 }
390
391
392
393
394
395
396
397 public static Set<String> parseClassNames(String... classNames) {
398 return Arrays.stream(classNames)
399 .flatMap(className -> Stream.of(className, CommonUtil.baseClassName(className)))
400 .filter(Predicate.not(String::isEmpty))
401 .collect(Collectors.toUnmodifiableSet());
402 }
403
404
405
406
407
408
409
410
411
412
413 public static String stripIndentAndInitialNewLineFromTextBlock(String textBlockContent) {
414 final String contentWithInitialNewLineRemoved =
415 ALL_NEW_LINES.matcher(textBlockContent).replaceFirst("");
416 final List<String> lines =
417 Arrays.asList(ALL_NEW_LINES.split(contentWithInitialNewLineRemoved));
418 final int indent = getSmallestIndent(lines);
419 final String suffix = "";
420
421 return lines.stream()
422 .map(line -> stripIndentAndTrailingWhitespaceFromLine(line, indent))
423 .collect(Collectors.joining(System.lineSeparator(), suffix, suffix));
424 }
425
426
427
428
429
430
431
432
433
434 private static String stripIndentAndTrailingWhitespaceFromLine(String line, int indent) {
435 final int lastNonWhitespace = lastIndexOfNonWhitespace(line);
436 String returnString = "";
437 if (lastNonWhitespace > 0) {
438 returnString = line.substring(indent, lastNonWhitespace);
439 }
440 return returnString;
441 }
442
443
444
445
446
447
448
449
450 private static int getSmallestIndent(Collection<String> lines) {
451 return lines.stream()
452 .mapToInt(CommonUtil::indexOfNonWhitespace)
453 .min()
454 .orElse(0);
455 }
456
457
458
459
460
461
462
463 private static int lastIndexOfNonWhitespace(String line) {
464 int length;
465 for (length = line.length(); length > 0; length--) {
466 if (!Character.isWhitespace(line.charAt(length - 1))) {
467 break;
468 }
469 }
470 return length;
471 }
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488 public static int typeDeclarationNameMatchingCount(String patternClass,
489 String classToBeMatched) {
490 final int length = Math.min(classToBeMatched.length(), patternClass.length());
491 int result = 0;
492 for (int i = 0; i < length && patternClass.charAt(i) == classToBeMatched.charAt(i); ++i) {
493 if (patternClass.charAt(i) == PACKAGE_SEPARATOR) {
494 result = i;
495 }
496 }
497 return result;
498 }
499
500
501
502
503
504
505
506
507
508
509
510 public static String getQualifiedTypeDeclarationName(String packageName,
511 String outerClassQualifiedName,
512 String className) {
513 final String qualifiedClassName;
514
515 if (outerClassQualifiedName == null) {
516 if (packageName == null) {
517 qualifiedClassName = className;
518 }
519 else {
520 qualifiedClassName = packageName + PACKAGE_SEPARATOR + className;
521 }
522 }
523 else {
524 qualifiedClassName = outerClassQualifiedName + PACKAGE_SEPARATOR + className;
525 }
526 return qualifiedClassName;
527 }
528
529
530
531
532
533
534
535
536 public static String extractQualifiedName(DetailAST ast) {
537 return FullIdent.createFullIdent(ast).getText();
538 }
539
540
541
542
543
544
545
546
547
548
549
550
551 public static String getShortNameOfAnonInnerClass(DetailAST literalNewAst) {
552 DetailAST parentAst = literalNewAst;
553 while (TokenUtil.isOfType(parentAst, TokenTypes.LITERAL_NEW, TokenTypes.DOT)) {
554 parentAst = parentAst.getParent();
555 }
556 final DetailAST firstChild = parentAst.getFirstChild();
557 return extractQualifiedName(firstChild);
558 }
559
560
561
562
563
564
565
566 public static boolean isPackageInfo(String filePath) {
567 final Path filename = Path.of(filePath).getFileName();
568 return filename != null && "package-info.java".equals(filename.toString());
569 }
570 }