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.internal;
21
22 import static com.google.common.truth.Truth.assertWithMessage;
23
24 import java.io.File;
25 import java.io.IOException;
26 import java.nio.charset.StandardCharsets;
27 import java.nio.file.Files;
28 import java.nio.file.Path;
29 import java.util.ArrayList;
30 import java.util.Comparator;
31 import java.util.List;
32 import java.util.Objects;
33 import java.util.Set;
34 import java.util.stream.Stream;
35
36 import org.junit.jupiter.api.Test;
37
38 import com.puppycrawl.tools.checkstyle.JavaParser;
39 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
40 import com.puppycrawl.tools.checkstyle.api.DetailAST;
41 import com.puppycrawl.tools.checkstyle.api.FileContents;
42 import com.puppycrawl.tools.checkstyle.api.FileText;
43 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
44 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
45
46
47
48
49
50
51
52
53
54
55
56
57
58 public class XdocsExamplesAstConsistencyTest {
59
60 private static final Path XDOCS_ROOT = Path.of(
61 "src/xdocs-examples/resources/com/puppycrawl/tools/checkstyle"
62 );
63
64 private static final String COMMON_PATH =
65 "src/xdocs-examples/resources/com/puppycrawl/tools/checkstyle/";
66
67 private static final String XDOC_START_MARKER = "// xdoc section -- start";
68 private static final String XDOC_END_MARKER = "// xdoc section -- end";
69
70
71
72
73
74
75 private static final Set<String> UNPARSEABLE_EXAMPLES = Set.of(
76 "checks/regexp/regexponfilename/Example1",
77 "checks/translation/Example1",
78 "filters/suppressionxpathsinglefilter/Example14"
79 );
80
81
82
83
84
85
86
87
88
89 private static final Set<String> SUPPRESSED_EXAMPLES = Set.of(
90 "checks/annotation/annotationonsameline/Example2",
91 "checks/annotation/missingoverride/Example2",
92 "checks/annotation/suppresswarningsholder/Example1",
93 "checks/blocks/emptyblock/Example3",
94 "checks/blocks/emptycatchblock/Example4",
95 "checks/blocks/emptycatchblock/Example5",
96 "checks/blocks/needbraces/Example6",
97 "checks/coding/constructorsdeclarationgrouping/Example2",
98 "checks/coding/covariantequals/Example2",
99 "checks/coding/illegaltoken/Example2",
100 "checks/coding/illegaltokentext/Example3",
101 "checks/coding/illegaltokentext/Example4",
102 "checks/coding/illegaltokentext/Example5",
103 "checks/coding/innerassignment/Example2",
104 "checks/coding/matchxpath/Example2",
105 "checks/coding/matchxpath/Example3",
106 "checks/coding/matchxpath/Example4",
107 "checks/coding/matchxpath/Example5",
108 "checks/coding/missingswitchdefault/Example2",
109 "checks/coding/missingswitchdefault/Example3",
110 "checks/coding/packagedeclaration/Example2",
111 "checks/coding/requirethis/Example5",
112 "checks/coding/requirethis/Example6",
113 "checks/coding/textblockgooglestyleformatting/Example2",
114 "checks/coding/textblockgooglestyleformatting/Example3",
115 "checks/coding/textblockgooglestyleformatting/Example4",
116 "checks/coding/unnecessaryparentheses/Example2",
117 "checks/coding/unnecessaryparentheses/Example3",
118 "checks/coding/unnecessarysemicoloninenumeration/Example2",
119 "checks/coding/useenhancedswitch/Example2",
120 "checks/coding/variabledeclarationusagedistance/Example2",
121 "checks/descendanttoken/Example10",
122 "checks/descendanttoken/Example11",
123 "checks/descendanttoken/Example12",
124 "checks/descendanttoken/Example13",
125 "checks/descendanttoken/Example14",
126 "checks/descendanttoken/Example15",
127 "checks/descendanttoken/Example16",
128 "checks/descendanttoken/Example17",
129 "checks/descendanttoken/Example4",
130 "checks/descendanttoken/Example5",
131 "checks/descendanttoken/Example6",
132 "checks/descendanttoken/Example7",
133 "checks/descendanttoken/Example8",
134 "checks/descendanttoken/Example9",
135 "checks/design/onetoplevelclass/Example3",
136 "checks/design/visibilitymodifier/Example11",
137 "checks/design/visibilitymodifier/Example12",
138 "checks/finalparameters/Example4",
139 "checks/header/header/Example4",
140 "checks/imports/avoidstaticimport/Example2",
141 "checks/imports/customimportorder/Example10",
142 "checks/imports/customimportorder/Example11",
143 "checks/imports/customimportorder/Example12",
144 "checks/imports/customimportorder/Example13",
145 "checks/imports/customimportorder/Example14",
146 "checks/imports/customimportorder/Example15",
147 "checks/imports/customimportorder/Example2",
148 "checks/imports/customimportorder/Example3",
149 "checks/imports/customimportorder/Example4",
150 "checks/imports/customimportorder/Example5",
151 "checks/imports/customimportorder/Example6",
152 "checks/imports/customimportorder/Example7",
153 "checks/imports/customimportorder/Example8",
154 "checks/imports/customimportorder/Example9",
155 "checks/imports/importcontrol/Example12",
156 "checks/imports/importcontrol/Example5",
157 "checks/imports/importcontrol/filters/Example9",
158 "checks/imports/importcontrol/someImports/Example11",
159 "checks/imports/importcontrol/someImports/Example6",
160 "checks/imports/importcontrol/someImports/Example7",
161 "checks/imports/importorder/Example10",
162 "checks/imports/importorder/Example11",
163 "checks/imports/importorder/Example12",
164 "checks/imports/importorder/Example2",
165 "checks/imports/importorder/Example3",
166 "checks/imports/importorder/Example4",
167 "checks/imports/importorder/Example5",
168 "checks/imports/importorder/Example6",
169 "checks/imports/importorder/Example7",
170 "checks/imports/importorder/Example8",
171 "checks/imports/importorder/Example9",
172 "checks/indentation/commentsindentation/Example3",
173 "checks/indentation/commentsindentation/Example4",
174 "checks/indentation/commentsindentation/Example5",
175 "checks/indentation/commentsindentation/Example6",
176 "checks/indentation/commentsindentation/Example7",
177 "checks/indentation/commentsindentation/Example8",
178 "checks/javadoc/javadocleadingasteriskalign/Example2",
179 "checks/javadoc/javadocleadingasteriskalign/Example3",
180 "checks/javadoc/javadocmethod/Example7",
181 "checks/javadoc/javadocmethod/Example8",
182 "checks/javadoc/javadocstyle/Example7",
183 "checks/javadoc/javadoctagcontinuationindentation/Example4",
184 "checks/javadoc/javadocvariable/Example5",
185 "checks/javadoc/missingjavadoctype/Example4",
186 "checks/javadoc/missingjavadoctype/Example5",
187 "checks/metrics/classdataabstractioncoupling/Example11",
188 "checks/metrics/classdataabstractioncoupling/Example2",
189 "checks/metrics/classdataabstractioncoupling/Example3",
190 "checks/metrics/classdataabstractioncoupling/ignore/Example7",
191 "checks/metrics/classdataabstractioncoupling/ignore/Example8",
192 "checks/metrics/classdataabstractioncoupling/ignore/Example9",
193 "checks/metrics/classdataabstractioncoupling/ignore/deeper/Example5",
194 "checks/metrics/classdataabstractioncoupling/ignore/deeper/Example6",
195 "checks/metrics/javancss/Example5",
196 "checks/metrics/npathcomplexity/Example2",
197 "checks/naming/abbreviationaswordinname/Example3",
198 "checks/naming/abbreviationaswordinname/Example4",
199 "checks/naming/abbreviationaswordinname/Example5",
200 "checks/naming/abbreviationaswordinname/Example6",
201 "checks/naming/abbreviationaswordinname/Example7",
202 "checks/naming/interfacetypeparametername/Example2",
203 "checks/naming/lambdaparametername/Example2",
204 "checks/naming/localfinalvariablename/Example3",
205 "checks/naming/localvariablename/Example3",
206 "checks/naming/localvariablename/Example4",
207 "checks/naming/localvariablename/Example5",
208 "checks/naming/membername/Example2",
209 "checks/naming/membername/Example3",
210 "checks/naming/parametername/Example2",
211 "checks/naming/parametername/Example3",
212 "checks/naming/parametername/Example4",
213 "checks/naming/parametername/Example5",
214 "checks/naming/patternvariablename/Example4",
215 "checks/naming/typename/Example2",
216 "checks/naming/typename/Example3",
217 "checks/naming/typename/Example4",
218 "checks/outertypefilename/Example2",
219 "checks/outertypefilename/Example3",
220 "checks/outertypefilename/Example4",
221 "checks/outertypefilename/Example5",
222 "checks/regexp/regexp/Example1",
223 "checks/regexp/regexp/Example10",
224 "checks/regexp/regexp/Example11",
225 "checks/regexp/regexp/Example2",
226 "checks/regexp/regexp/Example3",
227 "checks/regexp/regexp/Example4",
228 "checks/regexp/regexp/Example5",
229 "checks/regexp/regexp/Example6",
230 "checks/regexp/regexp/Example7",
231 "checks/regexp/regexp/Example8",
232 "checks/regexp/regexp/Example9",
233 "checks/regexp/regexpsingleline/Example2",
234 "checks/regexp/regexpsingleline/Example3",
235 "checks/regexp/regexpsingleline/Example4",
236 "checks/regexp/regexpmultiline/Example5",
237 "checks/sizes/lambdabodylength/Example2",
238 "checks/sizes/linelength/Example6",
239 "checks/trailingcomment/Example2",
240 "checks/trailingcomment/Example3",
241 "checks/trailingcomment/Example4",
242 "checks/trailingcomment/Example5",
243 "checks/trailingcomment/Example6",
244 "checks/whitespace/nolinewrap/Example2",
245 "checks/whitespace/nolinewrap/Example3",
246 "checks/whitespace/nolinewrap/Example4",
247 "checks/whitespace/nolinewrap/Example5",
248 "checks/whitespace/nowhitespacebefore/Example2",
249 "checks/whitespace/nowhitespacebefore/Example3",
250 "checks/whitespace/nowhitespacebefore/Example4",
251 "checks/whitespace/operatorwrap/Example2",
252 "checks/whitespace/parenpad/Example2",
253 "checks/whitespace/separatorwrap/Example2",
254 "checks/whitespace/separatorwrap/Example3",
255 "checks/whitespace/singlespaceseparator/Example2",
256 "checks/whitespace/typecastparenpad/Example2",
257 "checks/whitespace/whitespaceafter/Example2",
258 "checks/whitespace/whitespacearound/Example10",
259 "checks/whitespace/whitespacearound/Example2",
260 "checks/whitespace/whitespacearound/Example3",
261 "checks/whitespace/whitespacearound/Example4",
262 "checks/whitespace/whitespacearound/Example5",
263 "checks/whitespace/whitespacearound/Example6",
264 "checks/whitespace/whitespacearound/Example7",
265 "checks/whitespace/whitespacearound/Example8",
266 "checks/whitespace/whitespacearound/Example9",
267 "checks/whitespace/whitespacearound/Example11",
268 "filters/suppressionfilter/Example2",
269 "filters/suppressionfilter/Example3",
270 "filters/suppressionfilter/Example4",
271 "filters/suppressionsinglefilter/Example2",
272 "filters/suppressionsinglefilter/Example3",
273 "filters/suppressionsinglefilter/Example4",
274 "filters/suppressionxpathfilter/Example10",
275 "filters/suppressionxpathfilter/Example11",
276 "filters/suppressionxpathfilter/Example12",
277 "filters/suppressionxpathfilter/Example13",
278 "filters/suppressionxpathfilter/Example14",
279 "filters/suppressionxpathfilter/Example2",
280 "filters/suppressionxpathfilter/Example3",
281 "filters/suppressionxpathfilter/Example4",
282 "filters/suppressionxpathfilter/Example5",
283 "filters/suppressionxpathfilter/Example6",
284 "filters/suppressionxpathfilter/Example7",
285 "filters/suppressionxpathfilter/Example8",
286 "filters/suppressionxpathfilter/Example9",
287 "filters/suppressionxpathsinglefilter/Example10",
288 "filters/suppressionxpathsinglefilter/Example11",
289 "filters/suppressionxpathsinglefilter/Example12",
290 "filters/suppressionxpathsinglefilter/Example13",
291 "filters/suppressionxpathsinglefilter/Example2",
292 "filters/suppressionxpathsinglefilter/Example4",
293 "filters/suppressionxpathsinglefilter/Example5",
294 "filters/suppressionxpathsinglefilter/Example6",
295 "filters/suppressionxpathsinglefilter/Example7",
296 "filters/suppressionxpathsinglefilter/Example8",
297 "filters/suppressionxpathsinglefilter/Example9",
298 "filters/suppresswarningsfilter/Example2",
299 "filters/suppresswithnearbycommentfilter/Example2",
300 "filters/suppresswithnearbycommentfilter/Example3",
301 "filters/suppresswithnearbycommentfilter/Example4",
302 "filters/suppresswithnearbycommentfilter/Example5",
303 "filters/suppresswithnearbycommentfilter/Example6",
304 "filters/suppresswithnearbycommentfilter/Example7",
305 "filters/suppresswithnearbycommentfilter/Example8",
306 "filters/suppresswithnearbytextfilter/Example2",
307 "filters/suppresswithnearbytextfilter/Example3",
308 "filters/suppresswithnearbytextfilter/Example4",
309 "filters/suppresswithnearbytextfilter/Example5",
310 "filters/suppresswithnearbytextfilter/Example6",
311 "filters/suppresswithnearbytextfilter/Example7",
312 "filters/suppresswithnearbytextfilter/Example8",
313 "filters/suppresswithnearbytextfilter/Example9",
314 "filters/suppresswithplaintextcommentfilter/Example5",
315 "filters/suppresswithplaintextcommentfilter/Example9",
316
317 "checks/annotation/missingoverrideonrecordaccessor/Example2",
318
319 "checks/naming/methodname/Example3",
320 "checks/naming/methodname/Example4"
321 );
322
323
324
325
326
327
328
329 @Test
330 public void testExamplesDifferOnlyByComments() throws IOException {
331 final List<String> violations = new ArrayList<>();
332
333 try (Stream<Path> pathStream = Files.walk(XDOCS_ROOT)) {
334 final List<Path> exampleDirs = pathStream
335 .filter(Files::isDirectory)
336 .filter(XdocsExamplesAstConsistencyTest::containsMultipleExamples)
337 .toList();
338
339 for (Path dir : exampleDirs) {
340 final List<String> dirViolations = checkExamplesInDirectory(dir);
341 violations.addAll(dirViolations);
342 }
343 }
344
345 final String message;
346
347 if (violations.isEmpty()) {
348 message = "";
349 }
350 else {
351 final StringBuilder builder = new StringBuilder(1024);
352
353 builder.append("Found ")
354 .append(violations.size())
355 .append(" example files with AST mismatches.\n\n");
356
357 for (String violation : violations) {
358 builder.append(violation)
359 .append("\n\n");
360 }
361
362 builder.append("If these examples have different code intent, "
363 + "add them to SUPPRESSED_EXAMPLES:\n");
364
365 for (String violation : violations) {
366 final String pattern = extractIndependentPattern(violation);
367 if (pattern != null) {
368 builder.append('"').append(pattern).append("\",\n");
369 }
370 }
371
372 message = builder.toString();
373 }
374
375 assertWithMessage(message)
376 .that(violations)
377 .isEmpty();
378 }
379
380
381
382
383
384
385
386 private static String extractIndependentPattern(String violation) {
387 final String dirPath = extractDirectoryPath(violation);
388 final String fileName = extractMismatchFileName(violation);
389 final String result;
390
391 if (dirPath != null && fileName != null) {
392 result = dirPath + "/" + fileName.replace(".java", "");
393 }
394 else {
395 result = null;
396 }
397
398 return result;
399 }
400
401
402
403
404
405
406
407 private static String extractMismatchFileName(String violation) {
408 final String prefix = "Mismatch: ";
409 final int startIndex = violation.indexOf(prefix);
410 final String result;
411
412 if (startIndex == -1) {
413 result = null;
414 }
415 else {
416 final int endIndex = violation.indexOf('\n', startIndex);
417 if (endIndex == -1) {
418 result = violation.substring(startIndex + prefix.length()).trim();
419 }
420 else {
421 result = violation.substring(startIndex + prefix.length(), endIndex).trim();
422 }
423 }
424
425 return result;
426 }
427
428
429
430
431
432
433
434
435 private static boolean isExampleUnparseable(String relativePath, String exampleFileName) {
436 final String exampleName = exampleFileName.replace(".java", "");
437 final String fullPath = relativePath + "/" + exampleName;
438 return UNPARSEABLE_EXAMPLES.contains(fullPath);
439 }
440
441
442
443
444
445
446
447
448 private static boolean isExampleIndependent(String relativePath, String exampleFileName) {
449 final String exampleName = exampleFileName.replace(".java", "");
450 final String fullPath = relativePath + "/" + exampleName;
451 return SUPPRESSED_EXAMPLES.contains(fullPath);
452 }
453
454
455
456
457
458
459
460 private static String getRelativePath(Path dir) {
461 final String fullPath = dir.toString().replace('\\', '/');
462 final String result;
463 if (fullPath.startsWith(COMMON_PATH)) {
464 result = fullPath.substring(COMMON_PATH.length());
465 }
466 else {
467 result = fullPath;
468 }
469 return result;
470 }
471
472
473
474
475
476
477
478 private static String extractDirectoryPath(String violation) {
479 final String prefix = "Directory: ";
480 final int startIndex = violation.indexOf(prefix);
481 final String result;
482
483 if (startIndex == -1) {
484 result = null;
485 }
486 else {
487 final int endIndex = violation.indexOf('\n', startIndex);
488 if (endIndex == -1) {
489 result = null;
490 }
491 else {
492 result = violation.substring(startIndex + prefix.length(), endIndex).trim();
493 }
494 }
495
496 return result;
497 }
498
499
500
501
502
503
504
505 private static boolean containsMultipleExamples(Path dir) {
506 try (Stream<Path> pathStream = Files.list(dir)) {
507 return pathStream
508 .filter(path -> path.getFileName().toString().matches("Example\\d+\\.java"))
509 .count() > 1;
510 }
511 catch (IOException exception) {
512 throw new IllegalStateException("Failed to list files in directory: " + dir,
513 exception);
514 }
515 }
516
517
518
519
520
521
522
523
524 private static List<String> checkExamplesInDirectory(Path dir) throws IOException {
525 final List<String> violations = new ArrayList<>();
526 final List<Path> examples = getExampleFiles(dir);
527
528 if (!examples.isEmpty()) {
529 violations.addAll(compareExamples(dir, examples));
530 }
531
532 return violations;
533 }
534
535
536
537
538
539
540
541
542 private static List<Path> getExampleFiles(Path dir) throws IOException {
543 final List<Path> examples;
544 try (Stream<Path> pathStream = Files.list(dir)) {
545 examples = pathStream
546 .filter(path -> path.getFileName().toString().matches("Example\\d+\\.java"))
547 .sorted(Comparator.comparing(Path::toString))
548 .toList();
549 }
550 return examples;
551 }
552
553
554
555
556
557
558
559
560
561 private static List<String> compareExamples(Path dir, List<Path> examples)
562 throws IOException {
563 final List<String> violations = new ArrayList<>();
564 final String relativePath = getRelativePath(dir);
565
566 final List<Path> regularExamples = new ArrayList<>();
567
568 for (Path example : examples) {
569 final String fileName = example.getFileName().toString();
570 if (!isExampleIndependent(relativePath, fileName)) {
571 regularExamples.add(example);
572 }
573 }
574
575 if (regularExamples.size() > 1) {
576 violations.addAll(validateAllMatch(dir, regularExamples));
577 }
578
579 return violations;
580 }
581
582
583
584
585
586
587
588
589
590 private static List<String> validateAllMatch(Path dir, List<Path> examples)
591 throws IOException {
592 final List<String> violations = new ArrayList<>();
593 final Path reference = examples.getFirst();
594 final String referenceXdocSection = extractXdocSection(reference);
595
596 try {
597 final DetailAST referenceDetailAst = parseContent(referenceXdocSection);
598 if (referenceDetailAst != null) {
599 final StructuralAstNode referenceAst = toStructuralAst(referenceDetailAst);
600
601 for (int index = 1; index < examples.size(); index++) {
602 final Path example = examples.get(index);
603 final String violation = compareSingleExample(
604 dir, example, reference, referenceAst
605 );
606 if (violation != null) {
607 violations.add(violation);
608 }
609 }
610 }
611 }
612 catch (CheckstyleException exception) {
613
614 }
615
616 return violations;
617 }
618
619
620
621
622
623
624
625
626 private static String extractXdocSection(Path file) throws IOException {
627 final List<String> lines = Files.readAllLines(file, StandardCharsets.UTF_8);
628 int startIndex = -1;
629 int endIndex = -1;
630
631 for (int index = 0; index < lines.size(); index++) {
632 final String line = lines.get(index);
633 if (line.contains(XDOC_START_MARKER)) {
634 startIndex = index + 1;
635 }
636 else if (line.contains(XDOC_END_MARKER)) {
637 endIndex = index;
638 break;
639 }
640 }
641
642 final String result;
643 if (startIndex != -1 && endIndex != -1 && startIndex < endIndex) {
644 result = String.join("\n", lines.subList(startIndex, endIndex));
645 }
646 else {
647 result = String.join("\n", lines);
648 }
649
650 return result;
651 }
652
653
654
655
656
657
658
659
660 private static DetailAST parseContent(String content) throws CheckstyleException {
661 final FileText text = new FileText(
662 new File("Example.java").getAbsoluteFile(),
663 content.lines().toList()
664 );
665 final FileContents contents = new FileContents(text);
666 return JavaParser.parse(contents);
667 }
668
669
670
671
672
673
674
675
676
677
678
679 private static String compareSingleExample(Path dir, Path example,
680 Path reference,
681 StructuralAstNode referenceAst)
682 throws IOException {
683 final String exampleXdocSection = extractXdocSection(example);
684 final String relativePath = getRelativePath(dir);
685 final String exampleFileName = example.getFileName().toString();
686
687 DetailAST exampleDetailAst = null;
688 try {
689 exampleDetailAst = parseContent(exampleXdocSection);
690 }
691 catch (CheckstyleException exception) {
692 if (!isExampleUnparseable(relativePath, exampleFileName)) {
693 throw new IllegalStateException(
694 "Failed to parse example: " + example, exception);
695 }
696 }
697
698 final String result;
699 if (exampleDetailAst == null) {
700 result = null;
701 }
702 else {
703 final StructuralAstNode ast = toStructuralAst(exampleDetailAst);
704
705 if (referenceAst.equals(ast)) {
706 result = null;
707 }
708 else {
709 result = "Directory: " + relativePath + "\n"
710 + "Reference: " + reference.getFileName() + "\n"
711 + "Mismatch: " + example.getFileName();
712 }
713 }
714
715 return result;
716 }
717
718
719
720
721
722
723
724 private static StructuralAstNode toStructuralAst(DetailAST ast) {
725 final StructuralAstNode result;
726
727 if (isCommentNode(ast)) {
728 result = null;
729 }
730 else {
731 final StructuralAstNode node = new StructuralAstNode(ast.getType(), ast.getText());
732
733 for (DetailAST child = ast.getFirstChild();
734 child != null;
735 child = child.getNextSibling()) {
736 final StructuralAstNode structuralChild = toStructuralAst(child);
737 if (structuralChild != null) {
738 node.addChild(structuralChild);
739 }
740 }
741 result = node;
742 }
743
744 return result;
745 }
746
747
748
749
750
751
752
753 private static boolean isCommentNode(DetailAST ast) {
754 final int type = ast.getType();
755 return type == TokenTypes.SINGLE_LINE_COMMENT
756 || type == TokenTypes.BLOCK_COMMENT_BEGIN
757 || type == TokenTypes.COMMENT_CONTENT;
758 }
759
760
761
762
763
764
765 private static final class StructuralAstNode {
766 private final int type;
767 private final String text;
768 private final List<StructuralAstNode> children = new ArrayList<>();
769
770 private StructuralAstNode(int type, String text) {
771 this.type = type;
772 if (isLiteralToken(type)) {
773 this.text = text;
774 }
775 else {
776 this.text = null;
777 }
778 }
779
780
781
782
783
784
785
786 private static boolean isLiteralToken(int tokenType) {
787 return switch (tokenType) {
788 case TokenTypes.NUM_INT, TokenTypes.NUM_LONG, TokenTypes.NUM_FLOAT,
789 TokenTypes.NUM_DOUBLE, TokenTypes.STRING_LITERAL,
790 TokenTypes.CHAR_LITERAL, TokenTypes.LITERAL_TRUE,
791 TokenTypes.LITERAL_FALSE, TokenTypes.LITERAL_NULL -> true;
792 default -> false;
793 };
794 }
795
796 private void addChild(StructuralAstNode child) {
797 children.add(child);
798 }
799
800 @Override
801 public boolean equals(Object obj) {
802 final boolean result;
803 if (obj instanceof StructuralAstNode other) {
804 final boolean typeMatch = type == other.type;
805 final boolean textMatch = Objects.equals(text, other.text);
806 final boolean childrenMatch = children.equals(other.children);
807 result = typeMatch && textMatch && childrenMatch;
808 }
809 else {
810 result = false;
811 }
812 return result;
813 }
814
815 @Override
816 public int hashCode() {
817 return Objects.hash(type, text, children);
818 }
819
820 @Override
821 public String toString() {
822 final StringBuilder sb = new StringBuilder(128);
823 sb.append("StructuralAstNode{type=");
824 try {
825 sb.append(TokenUtil.getTokenName(type));
826 }
827 catch (IllegalArgumentException exception) {
828 sb.append(type);
829 }
830 if (text != null) {
831 sb.append(", text='").append(text).append('\'');
832 }
833 if (!children.isEmpty()) {
834 sb.append(", children=").append(children.size());
835 }
836 sb.append('}');
837 return sb.toString();
838 }
839 }
840 }