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