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 final double result;
112 switch (type) {
113 case TokenTypes.NUM_FLOAT:
114 case TokenTypes.NUM_DOUBLE:
115 result = Double.parseDouble(txt);
116 break;
117 case TokenTypes.NUM_INT:
118 case TokenTypes.NUM_LONG:
119 int radix = BASE_10;
120 if (txt.startsWith("0x") || txt.startsWith("0X")) {
121 radix = BASE_16;
122 txt = txt.substring(2);
123 }
124 else if (txt.startsWith("0b") || txt.startsWith("0B")) {
125 radix = BASE_2;
126 txt = txt.substring(2);
127 }
128 else if (txt.startsWith("0")) {
129 radix = BASE_8;
130 }
131 result = parseNumber(txt, radix, type);
132 break;
133 default:
134 result = Double.NaN;
135 break;
136 }
137 return result;
138 }
139
140
141
142
143
144
145
146
147
148
149
150
151
152 private static double parseNumber(final String text, final int radix, final int type) {
153 String txt = text;
154 if (txt.endsWith("L") || txt.endsWith("l")) {
155 txt = txt.substring(0, txt.length() - 1);
156 }
157 final double result;
158
159 final boolean negative = txt.charAt(0) == '-';
160 if (type == TokenTypes.NUM_INT) {
161 if (negative) {
162 result = Integer.parseInt(txt, radix);
163 }
164 else {
165 result = Integer.parseUnsignedInt(txt, radix);
166 }
167 }
168 else {
169 if (negative) {
170 result = Long.parseLong(txt, radix);
171 }
172 else {
173 result = Long.parseUnsignedLong(txt, radix);
174 }
175 }
176
177 return result;
178 }
179
180
181
182
183
184
185
186 public static DetailAST getFirstNode(final DetailAST node) {
187 DetailAST currentNode = node;
188 DetailAST child = node.getFirstChild();
189 while (child != null) {
190 final DetailAST newNode = getFirstNode(child);
191 if (isBeforeInSource(newNode, currentNode)) {
192 currentNode = newNode;
193 }
194 child = child.getNextSibling();
195 }
196
197 return currentNode;
198 }
199
200
201
202
203
204
205
206
207 public static boolean isBeforeInSource(DetailAST ast1, DetailAST ast2) {
208 return ast1.getLineNo() < ast2.getLineNo()
209 || TokenUtil.areOnSameLine(ast1, ast2)
210 && ast1.getColumnNo() < ast2.getColumnNo();
211 }
212
213
214
215
216
217
218
219 public static List<String> getTypeParameterNames(final DetailAST node) {
220 final DetailAST typeParameters =
221 node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
222
223 final List<String> typeParameterNames = new ArrayList<>();
224 if (typeParameters != null) {
225 final DetailAST typeParam =
226 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
227 typeParameterNames.add(
228 typeParam.findFirstToken(TokenTypes.IDENT).getText());
229
230 DetailAST sibling = typeParam.getNextSibling();
231 while (sibling != null) {
232 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
233 typeParameterNames.add(
234 sibling.findFirstToken(TokenTypes.IDENT).getText());
235 }
236 sibling = sibling.getNextSibling();
237 }
238 }
239
240 return typeParameterNames;
241 }
242
243
244
245
246
247
248
249 public static List<DetailAST> getTypeParameters(final DetailAST node) {
250 final DetailAST typeParameters =
251 node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
252
253 final List<DetailAST> typeParams = new ArrayList<>();
254 if (typeParameters != null) {
255 final DetailAST typeParam =
256 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
257 typeParams.add(typeParam);
258
259 DetailAST sibling = typeParam.getNextSibling();
260 while (sibling != null) {
261 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
262 typeParams.add(sibling);
263 }
264 sibling = sibling.getNextSibling();
265 }
266 }
267
268 return typeParams;
269 }
270
271
272
273
274
275
276
277 public static boolean isNonVoidMethod(DetailAST methodDefAst) {
278 boolean returnValue = false;
279 if (methodDefAst.getType() == TokenTypes.METHOD_DEF) {
280 final DetailAST typeAST = methodDefAst.findFirstToken(TokenTypes.TYPE);
281 if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) == null) {
282 returnValue = true;
283 }
284 }
285 return returnValue;
286 }
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304 public static boolean isReceiverParameter(DetailAST parameterDefAst) {
305 return parameterDefAst.findFirstToken(TokenTypes.IDENT) == null;
306 }
307
308
309
310
311
312
313
314
315
316 public static AccessModifierOption getAccessModifierFromModifiersToken(DetailAST ast) {
317 AccessModifierOption accessModifier;
318 if (ast.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
319 accessModifier = AccessModifierOption.PUBLIC;
320 }
321 else {
322 final DetailAST modsToken = ast.findFirstToken(TokenTypes.MODIFIERS);
323 accessModifier = getAccessModifierFromModifiersTokenDirectly(modsToken);
324 }
325
326 if (accessModifier == AccessModifierOption.PACKAGE) {
327 if (ScopeUtil.isInEnumBlock(ast) && ast.getType() == TokenTypes.CTOR_DEF) {
328 accessModifier = AccessModifierOption.PRIVATE;
329 }
330 else if (ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) {
331 accessModifier = AccessModifierOption.PUBLIC;
332 }
333 }
334
335 return accessModifier;
336 }
337
338
339
340
341
342
343
344
345
346 private static AccessModifierOption getAccessModifierFromModifiersTokenDirectly(
347 DetailAST modifiersToken) {
348 if (modifiersToken == null) {
349 throw new IllegalArgumentException("expected non-null AST-token with type 'MODIFIERS'");
350 }
351
352 AccessModifierOption accessModifier = AccessModifierOption.PACKAGE;
353 for (DetailAST token = modifiersToken.getFirstChild(); token != null;
354 token = token.getNextSibling()) {
355 final int tokenType = token.getType();
356 if (tokenType == TokenTypes.LITERAL_PUBLIC) {
357 accessModifier = AccessModifierOption.PUBLIC;
358 }
359 else if (tokenType == TokenTypes.LITERAL_PROTECTED) {
360 accessModifier = AccessModifierOption.PROTECTED;
361 }
362 else if (tokenType == TokenTypes.LITERAL_PRIVATE) {
363 accessModifier = AccessModifierOption.PRIVATE;
364 }
365 }
366 return accessModifier;
367 }
368
369
370
371
372
373
374
375 public static AccessModifierOption getSurroundingAccessModifier(DetailAST node) {
376 AccessModifierOption returnValue = null;
377 for (DetailAST token = node;
378 returnValue == null && !TokenUtil.isRootNode(token);
379 token = token.getParent()) {
380 final int type = token.getType();
381 if (type == TokenTypes.CLASS_DEF
382 || type == TokenTypes.INTERFACE_DEF
383 || type == TokenTypes.ANNOTATION_DEF
384 || type == TokenTypes.ENUM_DEF) {
385 returnValue = getAccessModifierFromModifiersToken(token);
386 }
387 else if (type == TokenTypes.LITERAL_NEW) {
388 break;
389 }
390 }
391
392 return returnValue;
393 }
394
395
396
397
398
399
400
401 public static Set<String> parseClassNames(String... classNames) {
402 return Arrays.stream(classNames)
403 .flatMap(className -> Stream.of(className, CommonUtil.baseClassName(className)))
404 .filter(Predicate.not(String::isEmpty))
405 .collect(Collectors.toUnmodifiableSet());
406 }
407
408
409
410
411
412
413
414
415
416
417 public static String stripIndentAndInitialNewLineFromTextBlock(String textBlockContent) {
418 final String contentWithInitialNewLineRemoved =
419 ALL_NEW_LINES.matcher(textBlockContent).replaceFirst("");
420 final List<String> lines =
421 Arrays.asList(ALL_NEW_LINES.split(contentWithInitialNewLineRemoved));
422 final int indent = getSmallestIndent(lines);
423 final String suffix = "";
424
425 return lines.stream()
426 .map(line -> stripIndentAndTrailingWhitespaceFromLine(line, indent))
427 .collect(Collectors.joining(System.lineSeparator(), suffix, suffix));
428 }
429
430
431
432
433
434
435
436
437
438 private static String stripIndentAndTrailingWhitespaceFromLine(String line, int indent) {
439 final int lastNonWhitespace = lastIndexOfNonWhitespace(line);
440 String returnString = "";
441 if (lastNonWhitespace > 0) {
442 returnString = line.substring(indent, lastNonWhitespace);
443 }
444 return returnString;
445 }
446
447
448
449
450
451
452
453
454 private static int getSmallestIndent(Collection<String> lines) {
455 return lines.stream()
456 .mapToInt(CommonUtil::indexOfNonWhitespace)
457 .min()
458 .orElse(0);
459 }
460
461
462
463
464
465
466
467 private static int lastIndexOfNonWhitespace(String line) {
468 int length;
469 for (length = line.length(); length > 0; length--) {
470 if (!Character.isWhitespace(line.charAt(length - 1))) {
471 break;
472 }
473 }
474 return length;
475 }
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492 public static int typeDeclarationNameMatchingCount(String patternClass,
493 String classToBeMatched) {
494 final int length = Math.min(classToBeMatched.length(), patternClass.length());
495 int result = 0;
496 for (int i = 0; i < length && patternClass.charAt(i) == classToBeMatched.charAt(i); ++i) {
497 if (patternClass.charAt(i) == PACKAGE_SEPARATOR) {
498 result = i;
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 }