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