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