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