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.checks.javadoc;
21
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collection;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.ListIterator;
28 import java.util.Optional;
29 import java.util.Set;
30
31 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
32 import com.puppycrawl.tools.checkstyle.api.DetailAST;
33 import com.puppycrawl.tools.checkstyle.api.DetailNode;
34 import com.puppycrawl.tools.checkstyle.api.FullIdent;
35 import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
36 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
37 import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption;
38 import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
39 import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
40 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
41 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
42 import com.puppycrawl.tools.checkstyle.utils.UnmodifiableCollectionUtil;
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111 @FileStatefulCheck
112 public class JavadocMethodCheck extends AbstractJavadocCheck {
113
114
115
116
117
118 public static final String MSG_CLASS_INFO = "javadoc.classInfo";
119
120
121
122
123
124 public static final String MSG_UNUSED_TAG_GENERAL = "javadoc.unusedTagGeneral";
125
126
127
128
129
130 public static final String MSG_INVALID_INHERIT_DOC = "javadoc.invalidInheritDoc";
131
132
133
134
135
136 public static final String MSG_UNUSED_TAG = "javadoc.unusedTag";
137
138
139
140
141
142 public static final String MSG_EXPECTED_TAG = "javadoc.expectedTag";
143
144
145
146
147
148 public static final String MSG_RETURN_EXPECTED = "javadoc.return.expected";
149
150
151
152
153
154 public static final String MSG_DUPLICATE_TAG = "javadoc.duplicateTag";
155
156
157 private static final String ELEMENT_START = "<";
158
159
160 private static final String ELEMENT_END = ">";
161
162
163 private final List<JavadocTag> javadocTags = new ArrayList<>();
164
165
166
167
168 private boolean allowInlineReturn;
169
170
171 private AccessModifierOption[] accessModifiers = {
172 AccessModifierOption.PUBLIC,
173 AccessModifierOption.PROTECTED,
174 AccessModifierOption.PACKAGE,
175 AccessModifierOption.PRIVATE,
176 };
177
178
179
180
181 private boolean validateThrows;
182
183
184
185
186
187 private boolean allowMissingParamTags;
188
189
190
191
192
193 private boolean allowMissingReturnTag;
194
195
196 private Set<String> allowedAnnotations = Set.of("Override");
197
198
199 private DetailAST currentAst;
200
201
202
203
204 public JavadocMethodCheck() {
205
206 }
207
208
209
210
211
212
213
214 public void setAllowInlineReturn(boolean value) {
215 allowInlineReturn = value;
216 }
217
218
219
220
221
222
223
224 public void setValidateThrows(boolean value) {
225 validateThrows = value;
226 }
227
228
229
230
231
232
233
234 public void setAllowedAnnotations(String... userAnnotations) {
235 allowedAnnotations = Set.of(userAnnotations);
236 }
237
238
239
240
241
242
243
244 public void setAccessModifiers(AccessModifierOption... accessModifiers) {
245 this.accessModifiers =
246 UnmodifiableCollectionUtil.copyOfArray(accessModifiers, accessModifiers.length);
247 }
248
249
250
251
252
253
254
255
256 public void setAllowMissingParamTags(boolean flag) {
257 allowMissingParamTags = flag;
258 }
259
260
261
262
263
264
265
266
267 public void setAllowMissingReturnTag(boolean flag) {
268 allowMissingReturnTag = flag;
269 }
270
271
272
273
274
275
276
277
278
279
280
281 @Override
282 public void setViolateExecutionOnNonTightHtml(boolean shouldReportViolation) {
283 super.setViolateExecutionOnNonTightHtml(shouldReportViolation);
284 }
285
286 @Override
287 public final int[] getRequiredTokens() {
288 return CommonUtil.EMPTY_INT_ARRAY;
289 }
290
291 @Override
292 public int[] getDefaultTokens() {
293 return getAcceptableTokens();
294 }
295
296 @Override
297 public int[] getAcceptableTokens() {
298 return new int[] {
299 TokenTypes.METHOD_DEF,
300 TokenTypes.CTOR_DEF,
301 TokenTypes.ANNOTATION_FIELD_DEF,
302 TokenTypes.COMPACT_CTOR_DEF,
303 };
304 }
305
306 @Override
307 public final void visitToken(DetailAST ast) {
308 if (shouldCheck(ast)) {
309 final DetailAST blockCommentNode = JavadocUtil.getAttachedJavadocComment(ast);
310 if (blockCommentNode != null) {
311 currentAst = ast;
312 super.visitToken(blockCommentNode);
313 }
314 }
315 }
316
317 @Override
318 public void beginJavadocTree(DetailNode rootAst) {
319 javadocTags.clear();
320 }
321
322 @Override
323 public void finishJavadocTree(DetailNode rootAst) {
324 checkCollectedTags();
325 }
326
327 @Override
328 public int[] getDefaultJavadocTokens() {
329 return getRequiredJavadocTokens();
330 }
331
332 @Override
333 public int[] getRequiredJavadocTokens() {
334 return new int[] {
335 JavadocCommentsTokenTypes.PARAM_BLOCK_TAG,
336 JavadocCommentsTokenTypes.RETURN_BLOCK_TAG,
337 JavadocCommentsTokenTypes.RETURN_INLINE_TAG,
338 JavadocCommentsTokenTypes.THROWS_BLOCK_TAG,
339 JavadocCommentsTokenTypes.EXCEPTION_BLOCK_TAG,
340 JavadocCommentsTokenTypes.INHERIT_DOC_INLINE_TAG,
341 };
342 }
343
344 @Override
345 public void visitJavadocToken(DetailNode ast) {
346 switch (ast.getType()) {
347 case JavadocCommentsTokenTypes.RETURN_BLOCK_TAG -> collectReturn(ast);
348 case JavadocCommentsTokenTypes.RETURN_INLINE_TAG -> {
349 if (allowInlineReturn) {
350 collectReturn(ast);
351 }
352 }
353 case JavadocCommentsTokenTypes.INHERIT_DOC_INLINE_TAG -> collectInheritDoc();
354 case JavadocCommentsTokenTypes.PARAM_BLOCK_TAG -> collectParam(ast);
355 case JavadocCommentsTokenTypes.THROWS_BLOCK_TAG -> collectThrows(ast, "throws");
356 case JavadocCommentsTokenTypes.EXCEPTION_BLOCK_TAG -> collectThrows(ast, "exception");
357 default -> throw new IllegalArgumentException("Unknown javadoc token type " + ast);
358 }
359 }
360
361
362
363
364
365
366 private void collectReturn(DetailNode ast) {
367 if (JavadocUtil.findFirstToken(ast, JavadocCommentsTokenTypes.DESCRIPTION) != null) {
368 javadocTags.add(new JavadocTag(ast.getLineNumber(), ast.getColumnNumber(), "return"));
369 }
370 }
371
372
373
374
375 private void collectInheritDoc() {
376 javadocTags.add(new JavadocTag(0, 0, "inheritDoc"));
377 }
378
379
380
381
382
383
384 private void collectParam(DetailNode ast) {
385 final DetailNode parameterName = JavadocUtil.findFirstToken(
386 ast, JavadocCommentsTokenTypes.PARAMETER_NAME);
387 if (parameterName != null) {
388 javadocTags.add(new JavadocTag(ast.getLineNumber(), ast.getColumnNumber(),
389 "param", parameterName.getText()));
390 }
391 }
392
393
394
395
396
397
398
399 private void collectThrows(DetailNode ast, String tagName) {
400 final DetailNode identifier = JavadocUtil.findFirstToken(
401 ast, JavadocCommentsTokenTypes.IDENTIFIER);
402 if (identifier != null) {
403 javadocTags.add(new JavadocTag(0, 0,
404 tagName, identifier.getText()));
405 }
406 }
407
408
409
410
411 private void checkCollectedTags() {
412 final List<JavadocTag> tagsToCheck = new ArrayList<>(javadocTags);
413 if (!hasShortCircuitTag(currentAst, tagsToCheck)) {
414 if (currentAst.getType() == TokenTypes.ANNOTATION_FIELD_DEF) {
415 checkReturnTag(tagsToCheck, currentAst.getLineNo(), true);
416 }
417 else {
418 boolean hasInheritDocTag = false;
419 final Iterator<JavadocTag> iterator = tagsToCheck.iterator();
420 while (!hasInheritDocTag && iterator.hasNext()) {
421 hasInheritDocTag = iterator.next().isInheritDocTag();
422 }
423 final boolean reportExpectedTags = !hasInheritDocTag
424 && !AnnotationUtil.containsAnnotation(currentAst, allowedAnnotations);
425 if (currentAst.getType() == TokenTypes.COMPACT_CTOR_DEF) {
426 checkRecordParamTags(tagsToCheck, currentAst, reportExpectedTags);
427 }
428 else {
429 checkParamTags(tagsToCheck, currentAst, reportExpectedTags);
430 }
431 final List<ExceptionInfo> thrown =
432 combineExceptionInfo(getThrows(currentAst), getThrowed(currentAst));
433 checkThrowsTags(tagsToCheck, thrown, reportExpectedTags);
434 if (CheckUtil.isNonVoidMethod(currentAst)) {
435 checkReturnTag(tagsToCheck, currentAst.getLineNo(), reportExpectedTags);
436 }
437 }
438 }
439 tagsToCheck.stream()
440 .filter(javadocTag -> !javadocTag.isInheritDocTag())
441 .forEach(javadocTag -> log(javadocTag.getLineNo(), MSG_UNUSED_TAG_GENERAL));
442 }
443
444
445
446
447
448
449
450
451
452
453
454
455 private boolean shouldCheck(final DetailAST ast) {
456 final Optional<AccessModifierOption> surroundingAccessModifier = CheckUtil
457 .getSurroundingAccessModifier(ast);
458 final AccessModifierOption accessModifier = CheckUtil
459 .getAccessModifierFromModifiersToken(ast);
460 return surroundingAccessModifier.isPresent() && Arrays.stream(accessModifiers)
461 .anyMatch(modifier -> modifier == surroundingAccessModifier.get())
462 && Arrays.stream(accessModifiers).anyMatch(modifier -> modifier == accessModifier);
463 }
464
465
466
467
468
469
470
471 private static List<DetailAST> getRecordComponents(final DetailAST recordDef) {
472 final List<DetailAST> components = new ArrayList<>();
473 final DetailAST recordDecl = recordDef.findFirstToken(TokenTypes.RECORD_COMPONENTS);
474
475 DetailAST child = recordDecl.getFirstChild();
476 while (child != null) {
477 if (child.getType() == TokenTypes.RECORD_COMPONENT_DEF) {
478 components.add(child.findFirstToken(TokenTypes.IDENT));
479 }
480 child = child.getNextSibling();
481 }
482 return components;
483 }
484
485
486
487
488
489
490
491 private static DetailAST getRecordDef(DetailAST ast) {
492 DetailAST current = ast;
493 while (current.getType() != TokenTypes.RECORD_DEF) {
494 current = current.getParent();
495 }
496 return current;
497 }
498
499
500
501
502
503
504
505
506
507 private boolean hasShortCircuitTag(final DetailAST ast, final List<JavadocTag> tags) {
508 boolean result = true;
509
510 if (tags.size() == 1
511 && tags.getFirst().isInheritDocTag()) {
512
513 if (!JavadocTagInfo.INHERIT_DOC.isValidOn(ast)) {
514 log(ast, MSG_INVALID_INHERIT_DOC);
515 }
516 }
517 else {
518 result = false;
519 }
520 return result;
521 }
522
523
524
525
526
527
528
529 private static List<DetailAST> getParameters(DetailAST ast) {
530 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
531 final List<DetailAST> returnValue = new ArrayList<>();
532
533 DetailAST child = params.getFirstChild();
534 while (child != null) {
535 final DetailAST ident = child.findFirstToken(TokenTypes.IDENT);
536 if (ident != null) {
537 returnValue.add(ident);
538 }
539 child = child.getNextSibling();
540 }
541 return returnValue;
542 }
543
544
545
546
547
548
549
550 private static List<ExceptionInfo> getThrows(DetailAST ast) {
551 final List<ExceptionInfo> returnValue = new ArrayList<>();
552 final DetailAST throwsAST = ast
553 .findFirstToken(TokenTypes.LITERAL_THROWS);
554 if (throwsAST != null) {
555 DetailAST child = throwsAST.getFirstChild();
556 while (child != null) {
557 if (child.getType() == TokenTypes.IDENT
558 || child.getType() == TokenTypes.DOT) {
559 returnValue.add(getExceptionInfo(child));
560 }
561 child = child.getNextSibling();
562 }
563 }
564 return returnValue;
565 }
566
567
568
569
570
571
572
573 private static List<ExceptionInfo> getThrowed(DetailAST methodAst) {
574 final List<ExceptionInfo> returnValue = new ArrayList<>();
575 final List<DetailAST> throwLiterals = findTokensInAstByType(methodAst,
576 TokenTypes.LITERAL_THROW);
577 for (DetailAST throwAst : throwLiterals) {
578 if (!isInIgnoreBlock(methodAst, throwAst)) {
579 final DetailAST newAst = throwAst.getFirstChild().getFirstChild();
580 if (newAst.getType() == TokenTypes.LITERAL_NEW) {
581 final DetailAST child = newAst.getFirstChild();
582 returnValue.add(getExceptionInfo(child));
583 }
584 }
585 }
586 return returnValue;
587 }
588
589
590
591
592
593
594
595 private static ExceptionInfo getExceptionInfo(DetailAST ast) {
596 final FullIdent ident = FullIdent.createFullIdent(ast);
597 final DetailAST firstClassNameNode = getFirstClassNameNode(ast);
598 return new ExceptionInfo(firstClassNameNode,
599 new ClassInfo(new Token(ident)));
600 }
601
602
603
604
605
606
607
608 private static DetailAST getFirstClassNameNode(DetailAST ast) {
609 DetailAST startNode = ast;
610 while (startNode.getType() == TokenTypes.DOT) {
611 startNode = startNode.getFirstChild();
612 }
613 return startNode;
614 }
615
616
617
618
619
620
621
622
623
624
625 private static boolean isInIgnoreBlock(DetailAST methodBodyAst, DetailAST throwAst) {
626 DetailAST ancestor = throwAst;
627 while (ancestor != methodBodyAst) {
628 if (ancestor.getType() == TokenTypes.LAMBDA
629 || ancestor.getType() == TokenTypes.OBJBLOCK
630 || ancestor.findFirstToken(TokenTypes.LITERAL_CATCH) != null) {
631
632
633 break;
634 }
635 if (ancestor.getType() == TokenTypes.LITERAL_CATCH
636 || ancestor.getType() == TokenTypes.LITERAL_FINALLY) {
637
638
639 ancestor = ancestor.getParent();
640 }
641 ancestor = ancestor.getParent();
642 }
643 return ancestor != methodBodyAst;
644 }
645
646
647
648
649
650
651
652
653 private static List<ExceptionInfo> combineExceptionInfo(Collection<ExceptionInfo> first,
654 Iterable<ExceptionInfo> second) {
655 final List<ExceptionInfo> result = new ArrayList<>(first);
656 for (ExceptionInfo exceptionInfo : second) {
657 if (result.stream().noneMatch(item -> isExceptionInfoSame(item, exceptionInfo))) {
658 result.add(exceptionInfo);
659 }
660 }
661 return result;
662 }
663
664
665
666
667
668
669
670
671
672 public static List<DetailAST> findTokensInAstByType(DetailAST root, int astType) {
673 final List<DetailAST> result = new ArrayList<>();
674
675 DetailAST curNode = root;
676 do {
677
678 if (curNode.getType() == astType) {
679 result.add(curNode);
680 }
681
682 if (curNode.hasChildren()) {
683 curNode = curNode.getFirstChild();
684 continue;
685 }
686
687 while (curNode.getNextSibling() == null) {
688 curNode = curNode.getParent();
689 }
690
691 if (curNode != root) {
692 curNode = curNode.getNextSibling();
693 }
694 } while (curNode != root);
695 return result;
696 }
697
698
699
700
701
702
703
704
705
706
707 private void checkRecordParamTags(final List<JavadocTag> tags,
708 final DetailAST compactDef, boolean reportExpectedTags) {
709
710 final DetailAST parent = getRecordDef(compactDef);
711 final List<DetailAST> params = getRecordComponents(parent);
712
713 for (JavadocTag tag : tags) {
714 if (!tag.isParamTag()) {
715 continue;
716 }
717
718 if (isDuplicateParamTag(tags, tag)) {
719 log(tag.getLineNo(), tag.getColumnNo(), MSG_DUPLICATE_TAG,
720 JavadocTagInfo.PARAM.getText());
721 }
722 else {
723 final String arg1 = tag.getFirstArg();
724 final boolean found = removeMatchingParam(params, arg1);
725
726 if (!found) {
727 log(tag.getLineNo(), tag.getColumnNo(), MSG_UNUSED_TAG,
728 JavadocTagInfo.PARAM.getText(), arg1);
729 }
730 }
731 }
732 tags.removeIf(JavadocTag::isParamTag);
733
734 if (!allowMissingParamTags && reportExpectedTags) {
735 for (DetailAST param : params) {
736 log(compactDef, MSG_EXPECTED_TAG,
737 JavadocTagInfo.PARAM.getText(), param.getText());
738 }
739 }
740 }
741
742
743
744
745
746
747
748
749
750 private void checkParamTags(final List<JavadocTag> tags,
751 final DetailAST parent, boolean reportExpectedTags) {
752 final List<DetailAST> params = getParameters(parent);
753 final List<DetailAST> typeParams = CheckUtil
754 .getTypeParameters(parent);
755
756
757 for (JavadocTag tag : tags) {
758 if (!tag.isParamTag()) {
759 continue;
760 }
761
762 if (isDuplicateParamTag(tags, tag)) {
763 log(tag.getLineNo(), tag.getColumnNo(), MSG_DUPLICATE_TAG,
764 JavadocTagInfo.PARAM.getText());
765 }
766 else {
767 final String arg1 = tag.getFirstArg();
768 boolean found = removeMatchingParam(params, arg1);
769
770 if (arg1.endsWith(ELEMENT_END)) {
771 found = searchMatchingTypeParameter(typeParams,
772 arg1.substring(1, arg1.length() - 1));
773 }
774
775
776 if (!found) {
777 log(tag.getLineNo(), tag.getColumnNo(), MSG_UNUSED_TAG,
778 JavadocTagInfo.PARAM.getText(), arg1);
779 }
780 }
781 }
782 tags.removeIf(JavadocTag::isParamTag);
783
784
785
786 if (!allowMissingParamTags && reportExpectedTags) {
787 for (DetailAST param : params) {
788 log(param, MSG_EXPECTED_TAG,
789 JavadocTagInfo.PARAM.getText(), param.getText());
790 }
791
792 for (DetailAST typeParam : typeParams) {
793 log(typeParam, MSG_EXPECTED_TAG,
794 JavadocTagInfo.PARAM.getText(),
795 ELEMENT_START + typeParam.findFirstToken(TokenTypes.IDENT).getText()
796 + ELEMENT_END);
797 }
798 }
799 }
800
801
802
803
804
805
806
807
808 private static boolean isDuplicateParamTag(List<JavadocTag> tags, JavadocTag tag) {
809 final int tagIndex = tags.indexOf(tag);
810 boolean result = false;
811 for (int tagPosition = 0; tagPosition < tagIndex; tagPosition++) {
812 final JavadocTag currentTag = tags.get(tagPosition);
813 if (currentTag.isParamTag()
814 && currentTag.getFirstArg().equals(tag.getFirstArg())) {
815 result = true;
816 }
817 }
818 return result;
819 }
820
821
822
823
824
825
826
827
828
829
830 private static boolean searchMatchingTypeParameter(Iterable<DetailAST> typeParams,
831 String requiredTypeName) {
832
833 final Iterator<DetailAST> typeParamsIt = typeParams.iterator();
834 boolean found = false;
835 while (typeParamsIt.hasNext()) {
836 final DetailAST typeParam = typeParamsIt.next();
837 if (typeParam.findFirstToken(TokenTypes.IDENT).getText()
838 .equals(requiredTypeName)) {
839 found = true;
840 typeParamsIt.remove();
841 break;
842 }
843 }
844 return found;
845 }
846
847
848
849
850
851
852
853
854 private static boolean removeMatchingParam(Iterable<DetailAST> params, String paramName) {
855 boolean found = false;
856 final Iterator<DetailAST> paramIt = params.iterator();
857 while (paramIt.hasNext()) {
858 final DetailAST param = paramIt.next();
859 if (param.getText().equals(paramName)) {
860 found = true;
861 paramIt.remove();
862 break;
863 }
864 }
865 return found;
866 }
867
868
869
870
871
872
873
874
875
876
877 private void checkReturnTag(List<JavadocTag> tags, int lineNo,
878 boolean reportExpectedTags) {
879
880 boolean found = false;
881 final ListIterator<JavadocTag> it = tags.listIterator();
882 while (it.hasNext()) {
883 final JavadocTag javadocTag = it.next();
884 if (javadocTag.isReturnTag()) {
885 if (found) {
886 log(javadocTag.getLineNo(), javadocTag.getColumnNo(),
887 MSG_DUPLICATE_TAG,
888 JavadocTagInfo.RETURN.getText());
889 }
890 found = true;
891 it.remove();
892 }
893 }
894
895
896
897 if (!found && !allowMissingReturnTag && reportExpectedTags) {
898 log(lineNo, MSG_RETURN_EXPECTED);
899 }
900 }
901
902
903
904
905
906
907
908
909
910 private void checkThrowsTags(List<JavadocTag> tags,
911 List<ExceptionInfo> throwsList, boolean reportExpectedTags) {
912
913 final ListIterator<JavadocTag> tagIt = tags.listIterator();
914 while (tagIt.hasNext()) {
915 final JavadocTag tag = tagIt.next();
916
917 if (!tag.isThrowsTag()) {
918 continue;
919 }
920 tagIt.remove();
921
922
923 processThrows(throwsList, tag.getFirstArg());
924 }
925
926
927 if (validateThrows && reportExpectedTags) {
928 throwsList.stream().filter(exceptionInfo -> !exceptionInfo.isFound())
929 .forEach(exceptionInfo -> {
930 final Token token = exceptionInfo.getName();
931 log(exceptionInfo.getAst(),
932 MSG_EXPECTED_TAG,
933 JavadocTagInfo.THROWS.getText(), token.text());
934 });
935 }
936 }
937
938
939
940
941
942
943
944 private static void processThrows(Iterable<ExceptionInfo> throwsIterable,
945 String documentedClassName) {
946 for (ExceptionInfo exceptionInfo : throwsIterable) {
947 if (isClassNamesSame(exceptionInfo.getName().text(),
948 documentedClassName)) {
949 exceptionInfo.setFound();
950 break;
951 }
952 }
953 }
954
955
956
957
958
959
960
961
962 private static boolean isExceptionInfoSame(ExceptionInfo info1, ExceptionInfo info2) {
963 return isClassNamesSame(info1.getName().text(),
964 info2.getName().text());
965 }
966
967
968
969
970
971
972
973
974
975 private static boolean isClassNamesSame(String class1, String class2) {
976 final String class1ShortName = class1
977 .substring(class1.lastIndexOf('.') + 1);
978 final String class2ShortName = class2
979 .substring(class2.lastIndexOf('.') + 1);
980 return class1ShortName.equals(class2ShortName);
981 }
982
983
984
985
986
987
988 private record ClassInfo(Token name) {
989 }
990
991
992
993
994
995
996 private record Token(String text) {
997
998
999
1000
1001
1002
1003 private Token(FullIdent fullIdent) {
1004 this(fullIdent.getText());
1005 }
1006 }
1007
1008
1009 private static final class ExceptionInfo {
1010
1011
1012 private final DetailAST ast;
1013
1014
1015 private final ClassInfo classInfo;
1016
1017 private boolean found;
1018
1019
1020
1021
1022
1023
1024
1025 private ExceptionInfo(DetailAST ast, ClassInfo classInfo) {
1026 this.ast = ast;
1027 this.classInfo = classInfo;
1028 }
1029
1030
1031
1032
1033
1034
1035 private DetailAST getAst() {
1036 return ast;
1037 }
1038
1039
1040 private void setFound() {
1041 found = true;
1042 }
1043
1044
1045
1046
1047
1048
1049 private boolean isFound() {
1050 return found;
1051 }
1052
1053
1054
1055
1056
1057
1058 private Token getName() {
1059 return classInfo.name();
1060 }
1061
1062 }
1063
1064 }