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