001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2023 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.javadoc;
021
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.Collection;
025import java.util.HashSet;
026import java.util.Iterator;
027import java.util.List;
028import java.util.ListIterator;
029import java.util.Set;
030import java.util.regex.MatchResult;
031import java.util.regex.Matcher;
032import java.util.regex.Pattern;
033
034import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
035import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
036import com.puppycrawl.tools.checkstyle.api.DetailAST;
037import com.puppycrawl.tools.checkstyle.api.FileContents;
038import com.puppycrawl.tools.checkstyle.api.FullIdent;
039import com.puppycrawl.tools.checkstyle.api.TextBlock;
040import com.puppycrawl.tools.checkstyle.api.TokenTypes;
041import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption;
042import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
043import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
044import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
045
046/**
047 * <p>
048 * Checks the Javadoc of a method or constructor.
049 * </p>
050 * <p>
051 * Violates parameters and type parameters for which no param tags are present can
052 * be suppressed by defining property {@code allowMissingParamTags}.
053 * </p>
054 * <p>
055 * Violates methods which return non-void but for which no return tag is present can
056 * be suppressed by defining property {@code allowMissingReturnTag}.
057 * </p>
058 * <p>
059 * Violates exceptions which are declared to be thrown (by {@code throws} in the method
060 * signature or by {@code throw new} in the method body), but for which no throws tag is
061 * present by activation of property {@code validateThrows}.
062 * Note that {@code throw new} is not checked in the following places:
063 * </p>
064 * <ul>
065 * <li>
066 * Inside a try block (with catch). It is not possible to determine if the thrown
067 * exception can be caught by the catch block as there is no knowledge of the
068 * inheritance hierarchy, so the try block is ignored entirely. However, catch
069 * and finally blocks, as well as try blocks without catch, are still checked.
070 * </li>
071 * <li>
072 * Local classes, anonymous classes and lambda expressions. It is not known when the
073 * throw statements inside such classes are going to be evaluated, so they are ignored.
074 * </li>
075 * </ul>
076 * <p>
077 * ATTENTION: Checkstyle does not have information about hierarchy of exception types
078 * so usage of base class is considered as separate exception type.
079 * As workaround, you need to specify both types in javadoc (parent and exact type).
080 * </p>
081 * <p>
082 * Javadoc is not required on a method that is tagged with the {@code @Override}
083 * annotation. However, under Java 5 it is not possible to mark a method required
084 * for an interface (this was <i>corrected</i> under Java 6). Hence, Checkstyle
085 * supports using the convention of using a single {@code {@inheritDoc}} tag
086 * instead of all the other tags.
087 * </p>
088 * <p>
089 * Note that only inheritable items will allow the {@code {@inheritDoc}}
090 * tag to be used in place of comments. Static methods at all visibilities,
091 * private non-static methods and constructors are not inheritable.
092 * </p>
093 * <p>
094 * For example, if the following method is implementing a method required by
095 * an interface, then the Javadoc could be done as:
096 * </p>
097 * <pre>
098 * &#47;** {&#64;inheritDoc} *&#47;
099 * public int checkReturnTag(final int aTagIndex,
100 *                           JavadocTag[] aTags,
101 *                           int aLineNo)
102 * </pre>
103 * <ul>
104 * <li>
105 * Property {@code allowedAnnotations} - Specify annotations that allow missed documentation.
106 * Type is {@code java.lang.String[]}.
107 * Default value is {@code Override}.
108 * </li>
109 * <li>
110 * Property {@code validateThrows} - Control whether to validate {@code throws} tags.
111 * Type is {@code boolean}.
112 * Default value is {@code false}.
113 * </li>
114 * <li>
115 * Property {@code accessModifiers} - Specify the access modifiers where Javadoc comments are
116 * checked.
117 * Type is {@code com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption[]}.
118 * Default value is {@code public, protected, package, private}.
119 * </li>
120 * <li>
121 * Property {@code allowMissingParamTags} - Control whether to ignore violations
122 * when a method has parameters but does not have matching {@code param} tags in the javadoc.
123 * Type is {@code boolean}.
124 * Default value is {@code false}.
125 * </li>
126 * <li>
127 * Property {@code allowMissingReturnTag} - Control whether to ignore violations
128 * when a method returns non-void type and does not have a {@code return} tag in the javadoc.
129 * Type is {@code boolean}.
130 * Default value is {@code false}.
131 * </li>
132 * <li>
133 * Property {@code tokens} - tokens to check
134 * Type is {@code java.lang.String[]}.
135 * Validation type is {@code tokenSet}.
136 * Default value is:
137 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
138 * METHOD_DEF</a>,
139 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
140 * CTOR_DEF</a>,
141 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_FIELD_DEF">
142 * ANNOTATION_FIELD_DEF</a>,
143 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF">
144 * COMPACT_CTOR_DEF</a>.
145 * </li>
146 * </ul>
147 * <p>
148 * To configure the default check:
149 * </p>
150 * <pre>
151 * &lt;module name="JavadocMethod"/&gt;
152 * </pre>
153 * <p>Example:</p>
154 * <pre>
155 * public class Test {
156 *
157 *  &#47;**
158 *   *
159 *   *&#47;
160 *  Test(int x) {            // violation, param tag missing for x
161 *  }
162 *
163 *  &#47;**
164 *   *
165 *   *&#47;
166 *  public int foo(int p1) { // violation, param tag missing for p1
167 *    return p1;             // violation, return tag missing
168 *  }
169 *
170 *  &#47;**
171 *   *
172 *   * &#64;param p1 The first number
173 *   *&#47;
174 *  &#64;Deprecated
175 *  private int boo(int p1) {
176 *    return p1;             // violation, return tag missing
177 *  }
178 *
179 *  &#47;**
180 *   *
181 *   *&#47;
182 *  void bar(int p1) {       // violation, param tag missing for p1
183 *  }                        // ok, no return tag for void method
184 * }
185 * </pre>
186 * <p>
187 * To configure the check for only {@code public} modifier, ignoring any missing param tags is:
188 * </p>
189 * <pre>
190 * &lt;module name="JavadocMethod"&gt;
191 *   &lt;property name="accessModifiers" value="public"/&gt;
192 *   &lt;property name="allowMissingParamTags" value="true"/&gt;
193 * &lt;/module&gt;
194 * </pre>
195 * <p>Example:</p>
196 * <pre>
197 * public class Test {
198 *
199 *  &#47;**
200 *   *
201 *   *&#47;
202 *  Test(int x) {            // ok, only public methods checked
203 *  }
204 *
205 *  &#47;**
206 *   *
207 *   *&#47;
208 *  public int foo(int p1) { // ok, missing param tags allowed
209 *    return p1;             // violation, return tag missing
210 *  }
211 *
212 *  &#47;**
213 *   *
214 *   * &#64;param p1 The first number
215 *   *&#47;
216 *  &#64;Deprecated
217 *  private int boo(int p1) {
218 *    return p1;             // ok, only public methods checked
219 *  }
220 *
221 *  &#47;**
222 *   *
223 *   *&#47;
224 *  void bar(int p1) {       // ok, missing param tags allowed
225 *  }                        // ok, no return tag for void method
226 * }
227 * </pre>
228 * <p>
229 * To configure the check for methods which are in {@code private} and {@code package},
230 * but not any other modifier:
231 * </p>
232 * <pre>
233 * &lt;module name="JavadocMethod"&gt;
234 *   &lt;property name="accessModifiers" value="private, package"/&gt;
235 * &lt;/module&gt;
236 * </pre>
237 * <p>Example:</p>
238 * <pre>
239 * class Test {
240 *
241 *  &#47;**
242 *   *
243 *   *&#47;
244 *  Test(int x) {            // violation, param tag missing for x
245 *  }
246 *
247 *  &#47;**
248 *   *
249 *   *&#47;
250 *  public int foo(int p1) { // ok, public methods not checked
251 *    return p1;
252 *  }
253 *
254 *  &#47;**
255 *   *
256 *   * &#64;param p1 The first number
257 *   *&#47;
258 *  &#64;Deprecated
259 *  private int boo(int p1) {
260 *    return p1;             // violation, return tag missing
261 *  }
262 *
263 *  &#47;**
264 *   *
265 *   *&#47;
266 *  void bar(int p1) {       // violation, param tag missing for p1
267 *  }                        // ok, no return tag for void method
268 * }
269 * </pre>
270 * <p>
271 * To configure the check to ignore any missing return tags:
272 * </p>
273 * <pre>
274 * &lt;module name="JavadocMethod"&gt;
275 *   &lt;property name="allowMissingReturnTag" value="true"/&gt;
276 * &lt;/module&gt;
277 * </pre>
278 * <p>Example:</p>
279 * <pre>
280 * public class Test {
281 *
282 *  &#47;**
283 *   *
284 *   *&#47;
285 *  Test(int x) {            // violation, param tag missing for x
286 *  }
287 *
288 *  &#47;**
289 *   *
290 *   *&#47;
291 *  public int foo(int p1) { // violation, param tag missing for p1
292 *    return p1;             // ok, missing return tag allowed
293 *  }
294 *
295 *  &#47;**
296 *   *
297 *   * &#64;param p1 The first number
298 *   *&#47;
299 *  &#64;Deprecated
300 *  private int boo(int p1) {
301 *    return p1;             // ok, missing return tag allowed
302 *  }
303 *
304 *  &#47;**
305 *   *
306 *   *&#47;
307 *  void bar(int p1) {       // violation, param tag missing for p1
308 *  }                        // ok, no return tag for void method
309 * }
310 * </pre>
311 * <p>
312 *  To configure the check to ignore Methods with annotation {@code Deprecated}:
313 * </p>
314 * <pre>
315 * &lt;module name="JavadocMethod"&gt;
316 *   &lt;property name="allowedAnnotations" value="Deprecated"/&gt;
317 * &lt;/module&gt;
318 * </pre>
319 * <p>Example:</p>
320 * <pre>
321 * public class Test {
322 *
323 *  &#47;**
324 *   *
325 *   *&#47;
326 *  Test(int x) {            // violation, param tag missing for x
327 *  }
328 *
329 *  &#47;**
330 *   *
331 *   *&#47;
332 *  public int foo(int p1) { // violation, param tag missing for p1
333 *    return p1;             // violation, return tag missing
334 *  }
335 *
336 *  &#47;**
337 *   *
338 *   * &#64;param p1 The first number
339 *   *&#47;
340 *  &#64;Deprecated
341 *  private int boo(int p1) {
342 *    return p1;             // ok, Deprecated methods not checked
343 *  }
344 *
345 *  &#47;**
346 *   *
347 *   *&#47;
348 *  void bar(int p1) {       // violation, param tag missing for p1
349 *  }                        // ok, no return tag for void method
350 * }
351 * </pre>
352 * <p>
353 *     To configure the check only for tokens which are Constructor Definitions:
354 * </p>
355 * <pre>
356 * &lt;module name="JavadocMethod"&gt;
357 *   &lt;property name="tokens" value="CTOR_DEF"/&gt;
358 * &lt;/module&gt;
359 * </pre>
360 * <p>Example:</p>
361 * <pre>
362 * public class Test {
363 *
364 *  &#47;**
365 *   *
366 *   *&#47;
367 *  Test(int x) {            // violation, param tag missing for x
368 *  }
369 *
370 *  &#47;**
371 *   *
372 *   *&#47;
373 *  public int foo(int p1) { // ok, method not checked
374 *    return p1;             // ok, method not checked
375 *  }
376 *
377 *  &#47;**
378 *   *
379 *   * &#64;param p1 The first number
380 *   *&#47;
381 *  &#64;Deprecated
382 *  private int boo(int p1) {
383 *    return p1;             // ok, method not checked
384 *  }
385 *
386 *  &#47;**
387 *   *
388 *   *&#47;
389 *  void bar(int p1) {       // ok, method not checked
390 *  }
391 * }
392 * </pre>
393 * <p>
394 * To configure the check to validate {@code throws} tags, you can use following config.
395 * </p>
396 * <pre>
397 * &lt;module name="JavadocMethod"&gt;
398 *   &lt;property name="validateThrows" value="true"/&gt;
399 * &lt;/module&gt;
400 * </pre>
401 * <p>Example:</p>
402 * <pre>
403 * &#47;**
404 *  * Actual exception thrown is child class of class that is declared in throws.
405 *  * It is limitation of checkstyle (as checkstyle does not know type hierarchy).
406 *  * Javadoc is valid not declaring FileNotFoundException
407 *  * BUT checkstyle can not distinguish relationship between exceptions.
408 *  * &#64;param file some file
409 *  * &#64;throws IOException if some problem
410 *  *&#47;
411 * public void doSomething8(File file) throws IOException {
412 *     if (file == null) {
413 *         throw new FileNotFoundException(); // violation
414 *     }
415 * }
416 *
417 * &#47;**
418 *  * Exact throw type referencing in javadoc even first is parent of second type.
419 *  * It is a limitation of checkstyle (as checkstyle does not know type hierarchy).
420 *  * This javadoc is valid for checkstyle and for javadoc tool.
421 *  * &#64;param file some file
422 *  * &#64;throws IOException if some problem
423 *  * &#64;throws FileNotFoundException if file is not found
424 *  *&#47;
425 * public void doSomething9(File file) throws IOException {
426 *     if (file == null) {
427 *         throw new FileNotFoundException();
428 *     }
429 * }
430 *
431 * &#47;**
432 *  * Ignore try block, but keep catch and finally blocks.
433 *  *
434 *  * &#64;param s String to parse
435 *  * &#64;return A positive integer
436 *  *&#47;
437 * public int parsePositiveInt(String s) {
438 *     try {
439 *         int value = Integer.parseInt(s);
440 *         if (value &lt;= 0) {
441 *             throw new NumberFormatException(value + " is negative/zero"); // ok, try
442 *         }
443 *         return value;
444 *     } catch (NumberFormatException ex) {
445 *         throw new IllegalArgumentException("Invalid number", ex); // violation, catch
446 *     } finally {
447 *         throw new IllegalStateException("Should never reach here"); // violation, finally
448 *     }
449 * }
450 *
451 * &#47;**
452 *  * Try block without catch is not ignored.
453 *  *
454 *  * &#64;return a String from standard input, if there is one
455 *  *&#47;
456 * public String readLine() {
457 *     try (Scanner sc = new Scanner(System.in)) {
458 *         if (!sc.hasNext()) {
459 *             throw new IllegalStateException("Empty input"); // violation, not caught
460 *         }
461 *         return sc.next();
462 *     }
463 * }
464 *
465 * &#47;**
466 *  * Lambda expressions are ignored as we do not know when the exception will be thrown.
467 *  *
468 *  * &#64;param s a String to be printed at some point in the future
469 *  * &#64;return a Runnable to be executed when the string is to be printed
470 *  *&#47;
471 * public Runnable printLater(String s) {
472 *     return () -&gt; {
473 *         if (s == null) {
474 *             throw new NullPointerException(); // ok
475 *         }
476 *         System.out.println(s);
477 *     };
478 * }
479 * </pre>
480 * <p>
481 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
482 * </p>
483 * <p>
484 * Violation Message Keys:
485 * </p>
486 * <ul>
487 * <li>
488 * {@code javadoc.classInfo}
489 * </li>
490 * <li>
491 * {@code javadoc.duplicateTag}
492 * </li>
493 * <li>
494 * {@code javadoc.expectedTag}
495 * </li>
496 * <li>
497 * {@code javadoc.invalidInheritDoc}
498 * </li>
499 * <li>
500 * {@code javadoc.return.expected}
501 * </li>
502 * <li>
503 * {@code javadoc.unusedTag}
504 * </li>
505 * <li>
506 * {@code javadoc.unusedTagGeneral}
507 * </li>
508 * </ul>
509 *
510 * @since 3.0
511 */
512@FileStatefulCheck
513public class JavadocMethodCheck extends AbstractCheck {
514
515    /**
516     * A key is pointing to the warning message text in "messages.properties"
517     * file.
518     */
519    public static final String MSG_CLASS_INFO = "javadoc.classInfo";
520
521    /**
522     * A key is pointing to the warning message text in "messages.properties"
523     * file.
524     */
525    public static final String MSG_UNUSED_TAG_GENERAL = "javadoc.unusedTagGeneral";
526
527    /**
528     * A key is pointing to the warning message text in "messages.properties"
529     * file.
530     */
531    public static final String MSG_INVALID_INHERIT_DOC = "javadoc.invalidInheritDoc";
532
533    /**
534     * A key is pointing to the warning message text in "messages.properties"
535     * file.
536     */
537    public static final String MSG_UNUSED_TAG = "javadoc.unusedTag";
538
539    /**
540     * A key is pointing to the warning message text in "messages.properties"
541     * file.
542     */
543    public static final String MSG_EXPECTED_TAG = "javadoc.expectedTag";
544
545    /**
546     * A key is pointing to the warning message text in "messages.properties"
547     * file.
548     */
549    public static final String MSG_RETURN_EXPECTED = "javadoc.return.expected";
550
551    /**
552     * A key is pointing to the warning message text in "messages.properties"
553     * file.
554     */
555    public static final String MSG_DUPLICATE_TAG = "javadoc.duplicateTag";
556
557    /** Compiled regexp to match Javadoc tags that take an argument. */
558    private static final Pattern MATCH_JAVADOC_ARG = CommonUtil.createPattern(
559            "^\\s*(?>\\*|\\/\\*\\*)?\\s*@(throws|exception|param)\\s+(\\S+)\\s+\\S*");
560    /** Compiled regexp to match Javadoc tags with argument but with missing description. */
561    private static final Pattern MATCH_JAVADOC_ARG_MISSING_DESCRIPTION =
562        CommonUtil.createPattern("^\\s*(?>\\*|\\/\\*\\*)?\\s*@(throws|exception|param)\\s+"
563            + "(\\S[^*]*)(?:(\\s+|\\*\\/))?");
564
565    /** Compiled regexp to look for a continuation of the comment. */
566    private static final Pattern MATCH_JAVADOC_MULTILINE_CONT =
567            CommonUtil.createPattern("(\\*\\/|@|[^\\s\\*])");
568
569    /** Multiline finished at end of comment. */
570    private static final String END_JAVADOC = "*/";
571    /** Multiline finished at next Javadoc. */
572    private static final String NEXT_TAG = "@";
573
574    /** Compiled regexp to match Javadoc tags with no argument. */
575    private static final Pattern MATCH_JAVADOC_NOARG =
576            CommonUtil.createPattern("^\\s*(?>\\*|\\/\\*\\*)?\\s*@(return|see)\\s+\\S");
577    /** Compiled regexp to match first part of multilineJavadoc tags. */
578    private static final Pattern MATCH_JAVADOC_NOARG_MULTILINE_START =
579            CommonUtil.createPattern("^\\s*(?>\\*|\\/\\*\\*)?\\s*@(return|see)\\s*$");
580    /** Compiled regexp to match Javadoc tags with no argument and {}. */
581    private static final Pattern MATCH_JAVADOC_NOARG_CURLY =
582            CommonUtil.createPattern("\\{\\s*@(inheritDoc)\\s*\\}");
583
584    /** Name of current class. */
585    private String currentClassName;
586
587    /** Specify the access modifiers where Javadoc comments are checked. */
588    private AccessModifierOption[] accessModifiers = {
589        AccessModifierOption.PUBLIC,
590        AccessModifierOption.PROTECTED,
591        AccessModifierOption.PACKAGE,
592        AccessModifierOption.PRIVATE,
593    };
594
595    /**
596     * Control whether to validate {@code throws} tags.
597     */
598    private boolean validateThrows;
599
600    /**
601     * Control whether to ignore violations when a method has parameters but does
602     * not have matching {@code param} tags in the javadoc.
603     */
604    private boolean allowMissingParamTags;
605
606    /**
607     * Control whether to ignore violations when a method returns non-void type
608     * and does not have a {@code return} tag in the javadoc.
609     */
610    private boolean allowMissingReturnTag;
611
612    /** Specify annotations that allow missed documentation. */
613    private Set<String> allowedAnnotations = Set.of("Override");
614
615    /**
616     * Setter to control whether to validate {@code throws} tags.
617     *
618     * @param value user's value.
619     */
620    public void setValidateThrows(boolean value) {
621        validateThrows = value;
622    }
623
624    /**
625     * Setter to specify annotations that allow missed documentation.
626     *
627     * @param userAnnotations user's value.
628     */
629    public void setAllowedAnnotations(String... userAnnotations) {
630        allowedAnnotations = Set.of(userAnnotations);
631    }
632
633    /**
634     * Setter to specify the access modifiers where Javadoc comments are checked.
635     *
636     * @param accessModifiers access modifiers.
637     */
638    public void setAccessModifiers(AccessModifierOption... accessModifiers) {
639        this.accessModifiers =
640            Arrays.copyOf(accessModifiers, accessModifiers.length);
641    }
642
643    /**
644     * Setter to control whether to ignore violations when a method has parameters
645     * but does not have matching {@code param} tags in the javadoc.
646     *
647     * @param flag a {@code Boolean} value
648     */
649    public void setAllowMissingParamTags(boolean flag) {
650        allowMissingParamTags = flag;
651    }
652
653    /**
654     * Setter to control whether to ignore violations when a method returns non-void type
655     * and does not have a {@code return} tag in the javadoc.
656     *
657     * @param flag a {@code Boolean} value
658     */
659    public void setAllowMissingReturnTag(boolean flag) {
660        allowMissingReturnTag = flag;
661    }
662
663    @Override
664    public final int[] getRequiredTokens() {
665        return new int[] {
666            TokenTypes.CLASS_DEF,
667            TokenTypes.INTERFACE_DEF,
668            TokenTypes.ENUM_DEF,
669            TokenTypes.RECORD_DEF,
670        };
671    }
672
673    @Override
674    public int[] getDefaultTokens() {
675        return getAcceptableTokens();
676    }
677
678    @Override
679    public int[] getAcceptableTokens() {
680        return new int[] {
681            TokenTypes.CLASS_DEF,
682            TokenTypes.ENUM_DEF,
683            TokenTypes.INTERFACE_DEF,
684            TokenTypes.METHOD_DEF,
685            TokenTypes.CTOR_DEF,
686            TokenTypes.ANNOTATION_FIELD_DEF,
687            TokenTypes.RECORD_DEF,
688            TokenTypes.COMPACT_CTOR_DEF,
689        };
690    }
691
692    @Override
693    public void beginTree(DetailAST rootAST) {
694        currentClassName = "";
695    }
696
697    @Override
698    public final void visitToken(DetailAST ast) {
699        if (ast.getType() == TokenTypes.CLASS_DEF
700                 || ast.getType() == TokenTypes.INTERFACE_DEF
701                 || ast.getType() == TokenTypes.ENUM_DEF
702                 || ast.getType() == TokenTypes.RECORD_DEF) {
703            processClass(ast);
704        }
705        else {
706            processAST(ast);
707        }
708    }
709
710    @Override
711    public final void leaveToken(DetailAST ast) {
712        if (ast.getType() == TokenTypes.CLASS_DEF
713            || ast.getType() == TokenTypes.INTERFACE_DEF
714            || ast.getType() == TokenTypes.ENUM_DEF
715            || ast.getType() == TokenTypes.RECORD_DEF) {
716            // perhaps it was inner class
717            final int dotIdx = currentClassName.lastIndexOf('$');
718            currentClassName = currentClassName.substring(0, dotIdx);
719        }
720    }
721
722    /**
723     * Called to process an AST when visiting it.
724     *
725     * @param ast the AST to process. Guaranteed to not be PACKAGE_DEF or
726     *             IMPORT tokens.
727     */
728    // suppress deprecation until https://github.com/checkstyle/checkstyle/issues/11166
729    @SuppressWarnings("deprecation")
730    private void processAST(DetailAST ast) {
731        if (shouldCheck(ast)) {
732            final FileContents contents = getFileContents();
733            final TextBlock textBlock = contents.getJavadocBefore(ast.getLineNo());
734
735            if (textBlock != null) {
736                checkComment(ast, textBlock);
737            }
738        }
739    }
740
741    /**
742     * Whether we should check this node.
743     *
744     * @param ast a given node.
745     * @return whether we should check a given node.
746     */
747    private boolean shouldCheck(final DetailAST ast) {
748        final AccessModifierOption surroundingAccessModifier = CheckUtil
749                .getSurroundingAccessModifier(ast);
750        final AccessModifierOption accessModifier = CheckUtil
751                .getAccessModifierFromModifiersToken(ast);
752        return surroundingAccessModifier != null
753                && Arrays.stream(accessModifiers)
754                        .anyMatch(modifier -> modifier == surroundingAccessModifier)
755                && Arrays.stream(accessModifiers).anyMatch(modifier -> modifier == accessModifier);
756    }
757
758    /**
759     * Checks the Javadoc for a method.
760     *
761     * @param ast the token for the method
762     * @param comment the Javadoc comment
763     */
764    private void checkComment(DetailAST ast, TextBlock comment) {
765        final List<JavadocTag> tags = getMethodTags(comment);
766
767        if (!hasShortCircuitTag(ast, tags)) {
768            if (ast.getType() == TokenTypes.ANNOTATION_FIELD_DEF) {
769                checkReturnTag(tags, ast.getLineNo(), true);
770            }
771            else {
772                final Iterator<JavadocTag> it = tags.iterator();
773                // Check for inheritDoc
774                boolean hasInheritDocTag = false;
775                while (!hasInheritDocTag && it.hasNext()) {
776                    hasInheritDocTag = it.next().isInheritDocTag();
777                }
778                final boolean reportExpectedTags = !hasInheritDocTag
779                    && !AnnotationUtil.containsAnnotation(ast, allowedAnnotations);
780
781                // COMPACT_CTOR_DEF has no parameters
782                if (ast.getType() != TokenTypes.COMPACT_CTOR_DEF) {
783                    checkParamTags(tags, ast, reportExpectedTags);
784                }
785                final List<ExceptionInfo> throwed =
786                    combineExceptionInfo(getThrows(ast), getThrowed(ast));
787                checkThrowsTags(tags, throwed, reportExpectedTags);
788                if (CheckUtil.isNonVoidMethod(ast)) {
789                    checkReturnTag(tags, ast.getLineNo(), reportExpectedTags);
790                }
791
792            }
793
794            // Dump out all unused tags
795            tags.stream().filter(javadocTag -> !javadocTag.isSeeOrInheritDocTag())
796                .forEach(javadocTag -> log(javadocTag.getLineNo(), MSG_UNUSED_TAG_GENERAL));
797        }
798    }
799
800    /**
801     * Validates whether the Javadoc has a short circuit tag. Currently, this is
802     * the inheritTag. Any violations are logged.
803     *
804     * @param ast the construct being checked
805     * @param tags the list of Javadoc tags associated with the construct
806     * @return true if the construct has a short circuit tag.
807     */
808    private boolean hasShortCircuitTag(final DetailAST ast, final List<JavadocTag> tags) {
809        boolean result = true;
810        // Check if it contains {@inheritDoc} tag
811        if (tags.size() == 1
812                && tags.get(0).isInheritDocTag()) {
813            // Invalid if private, a constructor, or a static method
814            if (!JavadocTagInfo.INHERIT_DOC.isValidOn(ast)) {
815                log(ast, MSG_INVALID_INHERIT_DOC);
816            }
817        }
818        else {
819            result = false;
820        }
821        return result;
822    }
823
824    /**
825     * Returns the tags in a javadoc comment. Only finds throws, exception,
826     * param, return and see tags.
827     *
828     * @param comment the Javadoc comment
829     * @return the tags found
830     */
831    private static List<JavadocTag> getMethodTags(TextBlock comment) {
832        final String[] lines = comment.getText();
833        final List<JavadocTag> tags = new ArrayList<>();
834        int currentLine = comment.getStartLineNo() - 1;
835        final int startColumnNumber = comment.getStartColNo();
836
837        for (int i = 0; i < lines.length; i++) {
838            currentLine++;
839            final Matcher javadocArgMatcher =
840                MATCH_JAVADOC_ARG.matcher(lines[i]);
841            final Matcher javadocArgMissingDescriptionMatcher =
842                MATCH_JAVADOC_ARG_MISSING_DESCRIPTION.matcher(lines[i]);
843            final Matcher javadocNoargMatcher =
844                MATCH_JAVADOC_NOARG.matcher(lines[i]);
845            final Matcher noargCurlyMatcher =
846                MATCH_JAVADOC_NOARG_CURLY.matcher(lines[i]);
847            final Matcher noargMultilineStart =
848                MATCH_JAVADOC_NOARG_MULTILINE_START.matcher(lines[i]);
849
850            if (javadocArgMatcher.find()) {
851                final int col = calculateTagColumn(javadocArgMatcher, i, startColumnNumber);
852                tags.add(new JavadocTag(currentLine, col, javadocArgMatcher.group(1),
853                        javadocArgMatcher.group(2)));
854            }
855            else if (javadocArgMissingDescriptionMatcher.find()) {
856                final int col = calculateTagColumn(javadocArgMissingDescriptionMatcher, i,
857                    startColumnNumber);
858                tags.add(new JavadocTag(currentLine, col,
859                    javadocArgMissingDescriptionMatcher.group(1),
860                    javadocArgMissingDescriptionMatcher.group(2)));
861            }
862            else if (javadocNoargMatcher.find()) {
863                final int col = calculateTagColumn(javadocNoargMatcher, i, startColumnNumber);
864                tags.add(new JavadocTag(currentLine, col, javadocNoargMatcher.group(1)));
865            }
866            else if (noargCurlyMatcher.find()) {
867                final int col = calculateTagColumn(noargCurlyMatcher, i, startColumnNumber);
868                tags.add(new JavadocTag(currentLine, col, noargCurlyMatcher.group(1)));
869            }
870            else if (noargMultilineStart.find()) {
871                tags.addAll(getMultilineNoArgTags(noargMultilineStart, lines, i, currentLine));
872            }
873        }
874        return tags;
875    }
876
877    /**
878     * Calculates column number using Javadoc tag matcher.
879     *
880     * @param javadocTagMatchResult found javadoc tag match result
881     * @param lineNumber line number of Javadoc tag in comment
882     * @param startColumnNumber column number of Javadoc comment beginning
883     * @return column number
884     */
885    private static int calculateTagColumn(MatchResult javadocTagMatchResult,
886            int lineNumber, int startColumnNumber) {
887        int col = javadocTagMatchResult.start(1) - 1;
888        if (lineNumber == 0) {
889            col += startColumnNumber;
890        }
891        return col;
892    }
893
894    /**
895     * Gets multiline Javadoc tags with no arguments.
896     *
897     * @param noargMultilineStart javadoc tag Matcher
898     * @param lines comment text lines
899     * @param lineIndex line number that contains the javadoc tag
900     * @param tagLine javadoc tag line number in file
901     * @return javadoc tags with no arguments
902     */
903    private static List<JavadocTag> getMultilineNoArgTags(final Matcher noargMultilineStart,
904            final String[] lines, final int lineIndex, final int tagLine) {
905        int remIndex = lineIndex;
906        Matcher multilineCont;
907
908        do {
909            remIndex++;
910            multilineCont = MATCH_JAVADOC_MULTILINE_CONT.matcher(lines[remIndex]);
911        } while (!multilineCont.find());
912
913        final List<JavadocTag> tags = new ArrayList<>();
914        final String lFin = multilineCont.group(1);
915        if (!NEXT_TAG.equals(lFin)
916            && !END_JAVADOC.equals(lFin)) {
917            final String param1 = noargMultilineStart.group(1);
918            final int col = noargMultilineStart.start(1) - 1;
919
920            tags.add(new JavadocTag(tagLine, col, param1));
921        }
922
923        return tags;
924    }
925
926    /**
927     * Computes the parameter nodes for a method.
928     *
929     * @param ast the method node.
930     * @return the list of parameter nodes for ast.
931     */
932    private static List<DetailAST> getParameters(DetailAST ast) {
933        final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
934        final List<DetailAST> returnValue = new ArrayList<>();
935
936        DetailAST child = params.getFirstChild();
937        while (child != null) {
938            if (child.getType() == TokenTypes.PARAMETER_DEF) {
939                final DetailAST ident = child.findFirstToken(TokenTypes.IDENT);
940                if (ident != null) {
941                    returnValue.add(ident);
942                }
943            }
944            child = child.getNextSibling();
945        }
946        return returnValue;
947    }
948
949    /**
950     * Computes the exception nodes for a method.
951     *
952     * @param ast the method node.
953     * @return the list of exception nodes for ast.
954     */
955    private static List<ExceptionInfo> getThrows(DetailAST ast) {
956        final List<ExceptionInfo> returnValue = new ArrayList<>();
957        final DetailAST throwsAST = ast
958                .findFirstToken(TokenTypes.LITERAL_THROWS);
959        if (throwsAST != null) {
960            DetailAST child = throwsAST.getFirstChild();
961            while (child != null) {
962                if (child.getType() == TokenTypes.IDENT
963                        || child.getType() == TokenTypes.DOT) {
964                    returnValue.add(getExceptionInfo(child));
965                }
966                child = child.getNextSibling();
967            }
968        }
969        return returnValue;
970    }
971
972    /**
973     * Get ExceptionInfo for all exceptions that throws in method code by 'throw new'.
974     *
975     * @param methodAst method DetailAST object where to find exceptions
976     * @return list of ExceptionInfo
977     */
978    private static List<ExceptionInfo> getThrowed(DetailAST methodAst) {
979        final List<ExceptionInfo> returnValue = new ArrayList<>();
980        final DetailAST blockAst = methodAst.findFirstToken(TokenTypes.SLIST);
981        if (blockAst != null) {
982            final List<DetailAST> throwLiterals = findTokensInAstByType(blockAst,
983                    TokenTypes.LITERAL_THROW);
984            for (DetailAST throwAst : throwLiterals) {
985                if (!isInIgnoreBlock(blockAst, throwAst)) {
986                    final DetailAST newAst = throwAst.getFirstChild().getFirstChild();
987                    if (newAst.getType() == TokenTypes.LITERAL_NEW) {
988                        final DetailAST child = newAst.getFirstChild();
989                        returnValue.add(getExceptionInfo(child));
990                    }
991                }
992            }
993        }
994        return returnValue;
995    }
996
997    /**
998     * Get ExceptionInfo instance.
999     *
1000     * @param ast DetailAST object where to find exceptions node;
1001     * @return ExceptionInfo
1002     */
1003    private static ExceptionInfo getExceptionInfo(DetailAST ast) {
1004        final FullIdent ident = FullIdent.createFullIdent(ast);
1005        final DetailAST firstClassNameNode = getFirstClassNameNode(ast);
1006        return new ExceptionInfo(firstClassNameNode,
1007                new ClassInfo(new Token(ident)));
1008    }
1009
1010    /**
1011     * Get node where class name of exception starts.
1012     *
1013     * @param ast DetailAST object where to find exceptions node;
1014     * @return exception node where class name starts
1015     */
1016    private static DetailAST getFirstClassNameNode(DetailAST ast) {
1017        DetailAST startNode = ast;
1018        while (startNode.getType() == TokenTypes.DOT) {
1019            startNode = startNode.getFirstChild();
1020        }
1021        return startNode;
1022    }
1023
1024    /**
1025     * Checks if a 'throw' usage is contained within a block that should be ignored.
1026     * Such blocks consist of try (with catch) blocks, local classes, anonymous classes,
1027     * and lambda expressions. Note that a try block without catch is not considered.
1028     *
1029     * @param methodBodyAst DetailAST node representing the method body
1030     * @param throwAst DetailAST node representing the 'throw' literal
1031     * @return true if throwAst is inside a block that should be ignored
1032     */
1033    private static boolean isInIgnoreBlock(DetailAST methodBodyAst, DetailAST throwAst) {
1034        DetailAST ancestor = throwAst.getParent();
1035        while (ancestor != methodBodyAst) {
1036            if (ancestor.getType() == TokenTypes.LITERAL_TRY
1037                    && ancestor.findFirstToken(TokenTypes.LITERAL_CATCH) != null
1038                    || ancestor.getType() == TokenTypes.LAMBDA
1039                    || ancestor.getType() == TokenTypes.OBJBLOCK) {
1040                // throw is inside a try block, and there is a catch block,
1041                // or throw is inside a lambda expression/anonymous class/local class
1042                break;
1043            }
1044            if (ancestor.getType() == TokenTypes.LITERAL_CATCH
1045                    || ancestor.getType() == TokenTypes.LITERAL_FINALLY) {
1046                // if the throw is inside a catch or finally block,
1047                // skip the immediate ancestor (try token)
1048                ancestor = ancestor.getParent();
1049            }
1050            ancestor = ancestor.getParent();
1051        }
1052        return ancestor != methodBodyAst;
1053    }
1054
1055    /**
1056     * Combine ExceptionInfo collections together by matching names.
1057     *
1058     * @param first the first collection of ExceptionInfo
1059     * @param second the second collection of ExceptionInfo
1060     * @return combined list of ExceptionInfo
1061     */
1062    private static List<ExceptionInfo> combineExceptionInfo(Collection<ExceptionInfo> first,
1063                                                            Iterable<ExceptionInfo> second) {
1064        final List<ExceptionInfo> result = new ArrayList<>(first);
1065        for (ExceptionInfo exceptionInfo : second) {
1066            if (result.stream().noneMatch(item -> isExceptionInfoSame(item, exceptionInfo))) {
1067                result.add(exceptionInfo);
1068            }
1069        }
1070        return result;
1071    }
1072
1073    /**
1074     * Finds node of specified type among root children, siblings, siblings children
1075     * on any deep level.
1076     *
1077     * @param root    DetailAST
1078     * @param astType value of TokenType
1079     * @return {@link List} of {@link DetailAST} nodes which matches the predicate.
1080     */
1081    public static List<DetailAST> findTokensInAstByType(DetailAST root, int astType) {
1082        final List<DetailAST> result = new ArrayList<>();
1083        // iterative preorder depth-first search
1084        DetailAST curNode = root;
1085        do {
1086            // process curNode
1087            if (curNode.getType() == astType) {
1088                result.add(curNode);
1089            }
1090            // process children (if any)
1091            if (curNode.hasChildren()) {
1092                curNode = curNode.getFirstChild();
1093                continue;
1094            }
1095            // backtrack to parent if last child, stopping at root
1096            while (curNode != root && curNode.getNextSibling() == null) {
1097                curNode = curNode.getParent();
1098            }
1099            // explore siblings if not root
1100            if (curNode != root) {
1101                curNode = curNode.getNextSibling();
1102            }
1103        } while (curNode != root);
1104        return result;
1105    }
1106
1107    /**
1108     * Checks a set of tags for matching parameters.
1109     *
1110     * @param tags the tags to check
1111     * @param parent the node which takes the parameters
1112     * @param reportExpectedTags whether we should report if do not find
1113     *            expected tag
1114     */
1115    private void checkParamTags(final List<JavadocTag> tags,
1116            final DetailAST parent, boolean reportExpectedTags) {
1117        final List<DetailAST> params = getParameters(parent);
1118        final List<DetailAST> typeParams = CheckUtil
1119                .getTypeParameters(parent);
1120
1121        // Loop over the tags, checking to see they exist in the params.
1122        final ListIterator<JavadocTag> tagIt = tags.listIterator();
1123        while (tagIt.hasNext()) {
1124            final JavadocTag tag = tagIt.next();
1125
1126            if (!tag.isParamTag()) {
1127                continue;
1128            }
1129
1130            tagIt.remove();
1131
1132            final String arg1 = tag.getFirstArg();
1133            boolean found = removeMatchingParam(params, arg1);
1134
1135            if (CommonUtil.startsWithChar(arg1, '<') && CommonUtil.endsWithChar(arg1, '>')) {
1136                found = searchMatchingTypeParameter(typeParams,
1137                        arg1.substring(1, arg1.length() - 1));
1138            }
1139
1140            // Handle extra JavadocTag
1141            if (!found) {
1142                log(tag.getLineNo(), tag.getColumnNo(), MSG_UNUSED_TAG,
1143                        "@param", arg1);
1144            }
1145        }
1146
1147        // Now dump out all type parameters/parameters without tags :- unless
1148        // the user has chosen to suppress these problems
1149        if (!allowMissingParamTags && reportExpectedTags) {
1150            for (DetailAST param : params) {
1151                log(param, MSG_EXPECTED_TAG,
1152                    JavadocTagInfo.PARAM.getText(), param.getText());
1153            }
1154
1155            for (DetailAST typeParam : typeParams) {
1156                log(typeParam, MSG_EXPECTED_TAG,
1157                    JavadocTagInfo.PARAM.getText(),
1158                    "<" + typeParam.findFirstToken(TokenTypes.IDENT).getText()
1159                    + ">");
1160            }
1161        }
1162    }
1163
1164    /**
1165     * Returns true if required type found in type parameters.
1166     *
1167     * @param typeParams
1168     *            collection of type parameters
1169     * @param requiredTypeName
1170     *            name of required type
1171     * @return true if required type found in type parameters.
1172     */
1173    private static boolean searchMatchingTypeParameter(Iterable<DetailAST> typeParams,
1174            String requiredTypeName) {
1175        // Loop looking for matching type param
1176        final Iterator<DetailAST> typeParamsIt = typeParams.iterator();
1177        boolean found = false;
1178        while (typeParamsIt.hasNext()) {
1179            final DetailAST typeParam = typeParamsIt.next();
1180            if (typeParam.findFirstToken(TokenTypes.IDENT).getText()
1181                    .equals(requiredTypeName)) {
1182                found = true;
1183                typeParamsIt.remove();
1184                break;
1185            }
1186        }
1187        return found;
1188    }
1189
1190    /**
1191     * Remove parameter from params collection by name.
1192     *
1193     * @param params collection of DetailAST parameters
1194     * @param paramName name of parameter
1195     * @return true if parameter found and removed
1196     */
1197    private static boolean removeMatchingParam(Iterable<DetailAST> params, String paramName) {
1198        boolean found = false;
1199        final Iterator<DetailAST> paramIt = params.iterator();
1200        while (paramIt.hasNext()) {
1201            final DetailAST param = paramIt.next();
1202            if (param.getText().equals(paramName)) {
1203                found = true;
1204                paramIt.remove();
1205                break;
1206            }
1207        }
1208        return found;
1209    }
1210
1211    /**
1212     * Checks for only one return tag. All return tags will be removed from the
1213     * supplied list.
1214     *
1215     * @param tags the tags to check
1216     * @param lineNo the line number of the expected tag
1217     * @param reportExpectedTags whether we should report if do not find
1218     *            expected tag
1219     */
1220    private void checkReturnTag(List<JavadocTag> tags, int lineNo,
1221        boolean reportExpectedTags) {
1222        // Loop over tags finding return tags. After the first one, report a
1223        // violation.
1224        boolean found = false;
1225        final ListIterator<JavadocTag> it = tags.listIterator();
1226        while (it.hasNext()) {
1227            final JavadocTag javadocTag = it.next();
1228            if (javadocTag.isReturnTag()) {
1229                if (found) {
1230                    log(javadocTag.getLineNo(), javadocTag.getColumnNo(),
1231                            MSG_DUPLICATE_TAG,
1232                            JavadocTagInfo.RETURN.getText());
1233                }
1234                found = true;
1235                it.remove();
1236            }
1237        }
1238
1239        // Handle there being no @return tags :- unless
1240        // the user has chosen to suppress these problems
1241        if (!found && !allowMissingReturnTag && reportExpectedTags) {
1242            log(lineNo, MSG_RETURN_EXPECTED);
1243        }
1244    }
1245
1246    /**
1247     * Checks a set of tags for matching throws.
1248     *
1249     * @param tags the tags to check
1250     * @param throwsList the throws to check
1251     * @param reportExpectedTags whether we should report if do not find
1252     *            expected tag
1253     */
1254    private void checkThrowsTags(List<JavadocTag> tags,
1255            List<ExceptionInfo> throwsList, boolean reportExpectedTags) {
1256        // Loop over the tags, checking to see they exist in the throws.
1257        // The foundThrows used for performance only
1258        final Set<String> foundThrows = new HashSet<>();
1259        final ListIterator<JavadocTag> tagIt = tags.listIterator();
1260        while (tagIt.hasNext()) {
1261            final JavadocTag tag = tagIt.next();
1262
1263            if (!tag.isThrowsTag()) {
1264                continue;
1265            }
1266            tagIt.remove();
1267
1268            // Loop looking for matching throw
1269            final Token token = new Token(tag.getFirstArg(), tag.getLineNo(), tag
1270                    .getColumnNo());
1271            final ClassInfo documentedClassInfo = new ClassInfo(token);
1272            processThrows(throwsList, documentedClassInfo, foundThrows);
1273        }
1274        // Now dump out all throws without tags :- unless
1275        // the user has chosen to suppress these problems
1276        if (validateThrows && reportExpectedTags) {
1277            throwsList.stream().filter(exceptionInfo -> !exceptionInfo.isFound())
1278                .forEach(exceptionInfo -> {
1279                    final Token token = exceptionInfo.getName();
1280                    log(exceptionInfo.getAst(),
1281                        MSG_EXPECTED_TAG,
1282                        JavadocTagInfo.THROWS.getText(), token.getText());
1283                });
1284        }
1285    }
1286
1287    /**
1288     * Verifies that documented exception is in throws.
1289     *
1290     * @param throwsIterable collection of throws
1291     * @param documentedClassInfo documented exception class info
1292     * @param foundThrows previously found throws
1293     */
1294    private static void processThrows(Iterable<ExceptionInfo> throwsIterable,
1295                                      ClassInfo documentedClassInfo, Set<String> foundThrows) {
1296        ExceptionInfo foundException = null;
1297
1298        // First look for matches on the exception name
1299        for (ExceptionInfo exceptionInfo : throwsIterable) {
1300            if (isClassNamesSame(exceptionInfo.getName().getText(),
1301                    documentedClassInfo.getName().getText())) {
1302                foundException = exceptionInfo;
1303                break;
1304            }
1305        }
1306
1307        if (foundException != null) {
1308            foundException.setFound();
1309            foundThrows.add(documentedClassInfo.getName().getText());
1310        }
1311    }
1312
1313    /**
1314     * Check that ExceptionInfo objects are same by name.
1315     *
1316     * @param info1 ExceptionInfo object
1317     * @param info2 ExceptionInfo object
1318     * @return true is ExceptionInfo object have the same name
1319     */
1320    private static boolean isExceptionInfoSame(ExceptionInfo info1, ExceptionInfo info2) {
1321        return isClassNamesSame(info1.getName().getText(),
1322                                    info2.getName().getText());
1323    }
1324
1325    /**
1326     * Check that class names are same by short name of class. If some class name is fully
1327     * qualified it is cut to short name.
1328     *
1329     * @param class1 class name
1330     * @param class2 class name
1331     * @return true is ExceptionInfo object have the same name
1332     */
1333    private static boolean isClassNamesSame(String class1, String class2) {
1334        boolean result = false;
1335        if (class1.equals(class2)) {
1336            result = true;
1337        }
1338        else {
1339            final String separator = ".";
1340            if (class1.contains(separator) || class2.contains(separator)) {
1341                final String class1ShortName = class1
1342                        .substring(class1.lastIndexOf('.') + 1);
1343                final String class2ShortName = class2
1344                        .substring(class2.lastIndexOf('.') + 1);
1345                result = class1ShortName.equals(class2ShortName);
1346            }
1347        }
1348        return result;
1349    }
1350
1351    /**
1352     * Processes class definition.
1353     *
1354     * @param ast class definition to process.
1355     */
1356    private void processClass(DetailAST ast) {
1357        final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
1358        String innerClass = ident.getText();
1359
1360        innerClass = "$" + innerClass;
1361        currentClassName += innerClass;
1362    }
1363
1364    /**
1365     * Contains class's {@code Token}.
1366     */
1367    private static class ClassInfo {
1368
1369        /** {@code FullIdent} associated with this class. */
1370        private final Token name;
1371
1372        /**
1373         * Creates new instance of class information object.
1374         *
1375         * @param className token which represents class name.
1376         * @throws IllegalArgumentException when className is nulls
1377         */
1378        protected ClassInfo(final Token className) {
1379            name = className;
1380        }
1381
1382        /**
1383         * Gets class name.
1384         *
1385         * @return class name
1386         */
1387        public final Token getName() {
1388            return name;
1389        }
1390
1391    }
1392
1393    /**
1394     * Represents text element with location in the text.
1395     */
1396    private static final class Token {
1397
1398        /** Token's column number. */
1399        private final int columnNo;
1400        /** Token's line number. */
1401        private final int lineNo;
1402        /** Token's text. */
1403        private final String text;
1404
1405        /**
1406         * Creates token.
1407         *
1408         * @param text token's text
1409         * @param lineNo token's line number
1410         * @param columnNo token's column number
1411         */
1412        private Token(String text, int lineNo, int columnNo) {
1413            this.text = text;
1414            this.lineNo = lineNo;
1415            this.columnNo = columnNo;
1416        }
1417
1418        /**
1419         * Converts FullIdent to Token.
1420         *
1421         * @param fullIdent full ident to convert.
1422         */
1423        private Token(FullIdent fullIdent) {
1424            text = fullIdent.getText();
1425            lineNo = fullIdent.getLineNo();
1426            columnNo = fullIdent.getColumnNo();
1427        }
1428
1429        /**
1430         * Gets text of the token.
1431         *
1432         * @return text of the token
1433         */
1434        public String getText() {
1435            return text;
1436        }
1437
1438        @Override
1439        public String toString() {
1440            return "Token[" + text + "(" + lineNo
1441                + "x" + columnNo + ")]";
1442        }
1443
1444    }
1445
1446    /** Stores useful information about declared exception. */
1447    private static final class ExceptionInfo {
1448
1449        /** AST node representing this exception. */
1450        private final DetailAST ast;
1451
1452        /** Class information associated with this exception. */
1453        private final ClassInfo classInfo;
1454        /** Does the exception have throws tag associated with. */
1455        private boolean found;
1456
1457        /**
1458         * Creates new instance for {@code FullIdent}.
1459         *
1460         * @param ast AST node representing this exception
1461         * @param classInfo class info
1462         */
1463        private ExceptionInfo(DetailAST ast, ClassInfo classInfo) {
1464            this.ast = ast;
1465            this.classInfo = classInfo;
1466        }
1467
1468        /**
1469         * Gets the AST node representing this exception.
1470         *
1471         * @return the AST node representing this exception
1472         */
1473        private DetailAST getAst() {
1474            return ast;
1475        }
1476
1477        /** Mark that the exception has associated throws tag. */
1478        private void setFound() {
1479            found = true;
1480        }
1481
1482        /**
1483         * Checks that the exception has throws tag associated with it.
1484         *
1485         * @return whether the exception has throws tag associated with
1486         */
1487        private boolean isFound() {
1488            return found;
1489        }
1490
1491        /**
1492         * Gets exception name.
1493         *
1494         * @return exception's name
1495         */
1496        private Token getName() {
1497            return classInfo.getName();
1498        }
1499
1500    }
1501
1502}