001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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.modifier;
021
022import java.util.ArrayList;
023import java.util.List;
024import java.util.Optional;
025import java.util.Set;
026
027import com.puppycrawl.tools.checkstyle.StatelessCheck;
028import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
029import com.puppycrawl.tools.checkstyle.api.DetailAST;
030import com.puppycrawl.tools.checkstyle.api.FullIdent;
031import com.puppycrawl.tools.checkstyle.api.TokenTypes;
032import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
033import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
034
035/**
036 * <div>
037 * Checks for redundant modifiers.
038 * </div>
039 *
040 * <p>
041 * Rationale: The Java Language Specification strongly discourages the usage
042 * of {@code public} and {@code abstract} for method declarations in interface
043 * definitions as a matter of style.
044 * </p>
045 *
046 * <p>The check validates:</p>
047 * <ol>
048 * <li>
049 * Interface and annotation definitions.
050 * </li>
051 * <li>
052 * Final modifier on methods of final and anonymous classes.
053 * </li>
054 * <li>
055 * Type declarations nested under interfaces that are declared as {@code public} or {@code static}.
056 * </li>
057 * <li>
058 * Class constructors.
059 * </li>
060 * <li>
061 * Nested {@code enum} definitions that are declared as {@code static}.
062 * </li>
063 * <li>
064 * {@code record} definitions that are declared as {@code final} and nested
065 * {@code record} definitions that are declared as {@code static}.
066 * </li>
067 * <li>
068 * {@code strictfp} modifier when using JDK 17 or later. See reason at
069 * <a href="https://openjdk.org/jeps/306">JEP 306</a>
070 * </li>
071 * <li>
072 * {@code final} modifier on unnamed variables when using JDK 22 or later.
073 * </li>
074 * <li>
075 * {@code public} modifier on top-level methods of compact source files.
076 * </li>
077 * </ol>
078 *
079 * <p>
080 * ATTENTION: Except for the {@code public} modifier on top-level methods,
081 * top-level members of compact source files are skipped from validation by this check.
082 * </p>
083 *
084 * <p>The implicitly declared class of a compact source file is final and cannot be
085 * referenced by name from other classes, so the {@code public} modifier on its
086 * top-level methods is redundant. Methods that override methods of {@code java.lang.Object},
087 * such as {@code equals(Object)}, {@code hashCode()} or {@code toString()}, are not reported,
088 * as their access cannot be reduced.
089 * See <a href="https://openjdk.org/jeps/512">JEP 512</a> for more info.</p>
090 *
091 * <p>
092 * interfaces by definition are abstract so the {@code abstract} modifier is redundant on them.
093 * </p>
094 *
095 * <p>Type declarations nested under interfaces by definition are public and static,
096 * so the {@code public} and {@code static} modifiers on nested type declarations are redundant.
097 * On the other hand, classes inside of interfaces can be abstract or non abstract.
098 * So, {@code abstract} modifier is allowed.
099 * </p>
100 *
101 * <p>Fields in interfaces and annotations are automatically
102 * public, static and final, so these modifiers are redundant as well.</p>
103 *
104 * <p>As annotations are a form of interface, their fields are also
105 * automatically public, static and final just as their
106 * annotation fields are automatically public and abstract.</p>
107 *
108 * <p>A record class is implicitly final and cannot be abstract, these restrictions emphasize
109 * that the API of a record class is defined solely by its state description, and
110 * cannot be enhanced later by another class. Nested records are implicitly static. This avoids an
111 * immediately enclosing instance which would silently add state to the record class.
112 * See <a href="https://openjdk.org/jeps/395">JEP 395</a> for more info.</p>
113 *
114 * <p>Enums by definition are static implicit subclasses of java.lang.Enum&#60;E&#62;.
115 * So, the {@code static} modifier on the enums is redundant. In addition,
116 * if enum is inside of interface, {@code public} modifier is also redundant.</p>
117 *
118 * <p>Enums can also contain abstract methods and methods which can be overridden by the declared
119 * enumeration fields.
120 * See the following example:</p>
121 * {@snippet lang="text" :
122 * public enum EnumClass {
123 *   FIELD_1,
124 *   FIELD_2 {
125 *     @Override
126 *     public final void method1() {} // violation expected
127 *   };
128 *
129 *   public void method1() {}
130 *   public final void method2() {} // no violation expected
131 * }
132 * }
133 *
134 * <p>Since these methods can be overridden in these situations, the final methods are not
135 * marked as redundant even though they can't be extended by other classes/enums.</p>
136 *
137 * <p>
138 * Nested {@code enum} types are always static by default.
139 * </p>
140 *
141 * <p>Final classes by definition cannot be extended so the {@code final}
142 * modifier on the method of a final class is redundant.
143 * </p>
144 *
145 * <p>Public modifier for constructors in non-public non-protected classes
146 * is always obsolete: </p>
147 *
148 * {@snippet lang="text" :
149 * public class PublicClass {
150 *   public PublicClass() {} // OK
151 * }
152 *
153 * class PackagePrivateClass {
154 *   public PackagePrivateClass() {} // violation expected
155 * }
156 * }
157 *
158 * <p>There is no violation in the following example,
159 * because removing public modifier from ProtectedInnerClass
160 * constructor will make this code not compiling: </p>
161 *
162 * {@snippet lang="text" :
163 * package a;
164 * public class ClassExample {
165 *   protected class ProtectedInnerClass {
166 *     public ProtectedInnerClass () {}
167 *   }
168 * }
169 *
170 * package b;
171 * import a.ClassExample;
172 * public class ClassExtending extends ClassExample {
173 *   ProtectedInnerClass pc = new ProtectedInnerClass();
174 * }
175 * }
176 *
177 * @since 3.0
178 */
179@StatelessCheck
180public class RedundantModifierCheck extends AbstractCheck {
181
182    /**
183     * A key is pointing to the warning message text in "messages.properties" file.
184     */
185    public static final String MSG_KEY = "redundantModifier";
186
187    /**
188     * An array of tokens for interface modifiers.
189     */
190    private static final int[] TOKENS_FOR_INTERFACE_MODIFIERS = {
191        TokenTypes.LITERAL_STATIC,
192        TokenTypes.ABSTRACT,
193    };
194
195    /**
196     *  Constant for jdk 22 version number.
197     */
198    private static final int JDK_22 = 22;
199
200    /**
201     *  Constant for jdk 17 version number.
202     *
203     */
204    private static final int JDK_17 = 17;
205
206    /**
207     * Names of overridable methods without parameters of {@code java.lang.Object}.
208     */
209    private static final Set<String> OBJECT_METHODS_WITHOUT_PARAMETERS = Set.of(
210        "hashCode", "toString", "clone", "finalize"
211    );
212
213    /**
214     * Set the JDK version that you are using.
215     * Old JDK version numbering is supported (e.g. 1.8 for Java 8)
216     * as well as just the major JDK version alone (e.g. 8) is supported.
217     * This property only considers features from officially released
218     * Java versions as supported. Features introduced in preview releases are not considered
219     * supported until they are included in a non-preview release.
220     *
221     */
222    private int jdkVersion = JDK_22;
223
224    /**
225     * Creates a new {@code RedundantModifierCheck} instance.
226     */
227    public RedundantModifierCheck() {
228        // no code by default
229    }
230
231    /**
232     * Setter to set the JDK version that you are using.
233     * Old JDK version numbering is supported (e.g. 1.8 for Java 8)
234     * as well as just the major JDK version alone (e.g. 8) is supported.
235     * This property only considers features from officially released
236     * Java versions as supported. Features introduced in preview releases are not considered
237     * supported until they are included in a non-preview release.
238     *
239     * @param jdkVersion the Java version
240     * @since 10.18.0
241     */
242    public void setJdkVersion(String jdkVersion) {
243        final String singleVersionNumber;
244        if (jdkVersion.startsWith("1.")) {
245            singleVersionNumber = jdkVersion.substring(2);
246        }
247        else {
248            singleVersionNumber = jdkVersion;
249        }
250
251        this.jdkVersion = Integer.parseInt(singleVersionNumber);
252    }
253
254    @Override
255    public int[] getDefaultTokens() {
256        return getAcceptableTokens();
257    }
258
259    @Override
260    public int[] getRequiredTokens() {
261        return CommonUtil.EMPTY_INT_ARRAY;
262    }
263
264    @Override
265    public int[] getAcceptableTokens() {
266        return new int[] {
267            TokenTypes.METHOD_DEF,
268            TokenTypes.VARIABLE_DEF,
269            TokenTypes.ANNOTATION_FIELD_DEF,
270            TokenTypes.INTERFACE_DEF,
271            TokenTypes.CTOR_DEF,
272            TokenTypes.CLASS_DEF,
273            TokenTypes.ENUM_DEF,
274            TokenTypes.RESOURCE,
275            TokenTypes.ANNOTATION_DEF,
276            TokenTypes.RECORD_DEF,
277            TokenTypes.PATTERN_VARIABLE_DEF,
278            TokenTypes.LITERAL_CATCH,
279            TokenTypes.LAMBDA,
280        };
281    }
282
283    @Override
284    public void visitToken(DetailAST ast) {
285        switch (ast.getType()) {
286            case TokenTypes.INTERFACE_DEF,
287                 TokenTypes.ANNOTATION_DEF ->
288                checkInterfaceModifiers(ast);
289            case TokenTypes.ENUM_DEF -> checkForRedundantModifier(ast, TokenTypes.LITERAL_STATIC);
290            case TokenTypes.CTOR_DEF -> checkConstructorModifiers(ast);
291            case TokenTypes.METHOD_DEF -> processMethods(ast);
292            case TokenTypes.RESOURCE -> processResources(ast);
293            case TokenTypes.RECORD_DEF ->
294                checkForRedundantModifier(ast, TokenTypes.FINAL, TokenTypes.LITERAL_STATIC);
295            case TokenTypes.VARIABLE_DEF,
296                 TokenTypes.PATTERN_VARIABLE_DEF ->
297                checkUnnamedVariables(ast);
298            case TokenTypes.LITERAL_CATCH ->
299                checkUnnamedVariables(ast.findFirstToken(TokenTypes.PARAMETER_DEF));
300            case TokenTypes.LAMBDA -> processLambdaParameters(ast);
301            case TokenTypes.CLASS_DEF,
302                 TokenTypes.ANNOTATION_FIELD_DEF -> {
303                // Nothing extra to do
304            }
305            default -> throw new IllegalStateException("Unexpected token type: " + ast.getType());
306        }
307
308        if (isInterfaceOrAnnotationMember(ast)) {
309            processInterfaceOrAnnotation(ast);
310        }
311
312        if (jdkVersion >= JDK_17) {
313            checkForRedundantModifier(ast, TokenTypes.STRICTFP);
314        }
315    }
316
317    /**
318     * Process lambda parameters.
319     *
320     * @param lambdaAst node of type {@link TokenTypes#LAMBDA}
321     */
322    private void processLambdaParameters(DetailAST lambdaAst) {
323        final DetailAST lambdaParameters = lambdaAst.findFirstToken(TokenTypes.PARAMETERS);
324        if (lambdaParameters != null) {
325            TokenUtil.forEachChild(lambdaParameters, TokenTypes.PARAMETER_DEF,
326                    this::checkUnnamedVariables);
327        }
328    }
329
330    /**
331     * Check if the variable is unnamed and has redundant final modifier.
332     *
333     * @param ast node of type {@link TokenTypes#VARIABLE_DEF}
334     *     or {@link TokenTypes#PATTERN_VARIABLE_DEF} or {@link TokenTypes#PARAMETER_DEF}
335     */
336    private void checkUnnamedVariables(DetailAST ast) {
337        if (jdkVersion >= JDK_22 && isUnnamedVariable(ast)) {
338            checkForRedundantModifier(ast, TokenTypes.FINAL);
339        }
340    }
341
342    /**
343     * Check if the variable is unnamed.
344     *
345     * @param ast node of type {@link TokenTypes#VARIABLE_DEF}
346     *     or {@link TokenTypes#PATTERN_VARIABLE_DEF} or {@link TokenTypes#PARAMETER_DEF}
347     * @return true if the variable is unnamed
348     */
349    private static boolean isUnnamedVariable(DetailAST ast) {
350        return "_".equals(ast.findFirstToken(TokenTypes.IDENT).getText());
351    }
352
353    /**
354     * Check modifiers of constructor.
355     *
356     * @param ctorDefAst ast node of type {@link TokenTypes#CTOR_DEF}
357     */
358    private void checkConstructorModifiers(DetailAST ctorDefAst) {
359        if (isEnumMember(ctorDefAst)) {
360            checkEnumConstructorModifiers(ctorDefAst);
361        }
362        else {
363            checkClassConstructorModifiers(ctorDefAst);
364        }
365    }
366
367    /**
368     * Checks if interface has proper modifiers.
369     *
370     * @param ast interface to check
371     */
372    private void checkInterfaceModifiers(DetailAST ast) {
373        final DetailAST modifiers =
374            ast.findFirstToken(TokenTypes.MODIFIERS);
375
376        for (final int tokenType : TOKENS_FOR_INTERFACE_MODIFIERS) {
377            final DetailAST modifier =
378                    modifiers.findFirstToken(tokenType);
379            if (modifier != null) {
380                log(modifier, MSG_KEY, modifier.getText());
381            }
382        }
383    }
384
385    /**
386     * Check if enum constructor has proper modifiers.
387     *
388     * @param ast constructor of enum
389     */
390    private void checkEnumConstructorModifiers(DetailAST ast) {
391        final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
392        TokenUtil.findFirstTokenByPredicate(
393            modifiers, mod -> mod.getType() != TokenTypes.ANNOTATION
394        ).ifPresent(modifier -> log(modifier, MSG_KEY, modifier.getText()));
395    }
396
397    /**
398     * Do validation of interface of annotation.
399     *
400     * @param ast token AST
401     */
402    private void processInterfaceOrAnnotation(DetailAST ast) {
403        final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
404        DetailAST modifier = modifiers.getFirstChild();
405        while (modifier != null) {
406            // javac does not allow final or static in interface methods
407            // order annotation fields hence no need to check that this
408            // is not a method or annotation field
409
410            final int type = modifier.getType();
411            if (type == TokenTypes.LITERAL_PUBLIC
412                || type == TokenTypes.LITERAL_STATIC
413                        && ast.getType() != TokenTypes.METHOD_DEF
414                || type == TokenTypes.ABSTRACT
415                        && ast.getType() != TokenTypes.CLASS_DEF
416                || type == TokenTypes.FINAL
417                        && ast.getType() != TokenTypes.CLASS_DEF) {
418                log(modifier, MSG_KEY, modifier.getText());
419            }
420
421            modifier = modifier.getNextSibling();
422        }
423    }
424
425    /**
426     * Process validation of Methods.
427     *
428     * @param ast method AST
429     */
430    private void processMethods(DetailAST ast) {
431        final DetailAST modifiers =
432                        ast.findFirstToken(TokenTypes.MODIFIERS);
433        // private method?
434        boolean checkFinal =
435            modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null;
436        // declared in a final class?
437        DetailAST parent = ast;
438        while (parent != null && !checkFinal) {
439            if (parent.getType() == TokenTypes.CLASS_DEF) {
440                final DetailAST classModifiers =
441                    parent.findFirstToken(TokenTypes.MODIFIERS);
442                checkFinal = classModifiers.findFirstToken(TokenTypes.FINAL) != null;
443                parent = null;
444            }
445            else if (parent.getType() == TokenTypes.LITERAL_NEW
446                    || parent.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
447                checkFinal = true;
448                parent = null;
449            }
450            else if (parent.getType() == TokenTypes.ENUM_DEF) {
451                checkFinal = modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
452                parent = null;
453            }
454            else {
455                parent = parent.getParent();
456            }
457        }
458        if (checkFinal && !isAnnotatedWithSafeVarargs(ast)) {
459            checkForRedundantModifier(ast, TokenTypes.FINAL);
460        }
461
462        if (ast.findFirstToken(TokenTypes.SLIST) == null) {
463            processAbstractMethodParameters(ast);
464        }
465
466        checkCompactSourceFileMethodModifiers(ast);
467    }
468
469    /**
470     * Checks if top-level method of compact source file has redundant public modifier.
471     *
472     * @param methodDef method AST
473     */
474    private void checkCompactSourceFileMethodModifiers(DetailAST methodDef) {
475        if (methodDef.getParent().getType() == TokenTypes.COMPACT_COMPILATION_UNIT
476                && !isObjectMethodOverride(methodDef)) {
477            checkForRedundantModifier(methodDef, TokenTypes.LITERAL_PUBLIC);
478        }
479    }
480
481    /**
482     * Checks if method overrides one of the overridable methods of {@code java.lang.Object},
483     * so its {@code public} modifier cannot be removed.
484     *
485     * @param methodDef method AST
486     * @return true if method overrides a method of {@code java.lang.Object}
487     */
488    private static boolean isObjectMethodOverride(DetailAST methodDef) {
489        DetailAST methodName = methodDef.getFirstChild();
490        while (methodName.getType() != TokenTypes.IDENT) {
491            methodName = methodName.getNextSibling();
492        }
493        final String name = methodName.getText();
494        // method name is followed by LPAREN and PARAMETERS
495        final DetailAST parameters = methodName.getNextSibling().getNextSibling();
496        final boolean result;
497        if (parameters.getChildCount() == 0) {
498            result = OBJECT_METHODS_WITHOUT_PARAMETERS.contains(name);
499        }
500        else {
501            result = "equals".equals(name)
502                    && parameters.getChildCount() == 1
503                    && isObjectType(parameters.getFirstChild());
504        }
505        return result;
506    }
507
508    /**
509     * Checks if parameter is of {@code java.lang.Object} type.
510     *
511     * @param parameterDef parameter AST
512     * @return true if parameter is of {@code java.lang.Object} type
513     */
514    private static boolean isObjectType(DetailAST parameterDef) {
515        // parameter type follows MODIFIERS
516        final DetailAST type = parameterDef.getFirstChild().getNextSibling();
517        final String typeName = FullIdent.createFullIdentBelow(type).getText();
518        return "Object".equals(typeName) || "java.lang.Object".equals(typeName);
519    }
520
521    /**
522     * Process validation of parameters for Methods with no definition.
523     *
524     * @param ast method AST
525     */
526    private void processAbstractMethodParameters(DetailAST ast) {
527        final DetailAST parameters = ast.findFirstToken(TokenTypes.PARAMETERS);
528        TokenUtil.forEachChild(parameters, TokenTypes.PARAMETER_DEF, paramDef -> {
529            checkForRedundantModifier(paramDef, TokenTypes.FINAL);
530        });
531    }
532
533    /**
534     * Check if class constructor has proper modifiers.
535     *
536     * @param classCtorAst class constructor ast
537     */
538    private void checkClassConstructorModifiers(DetailAST classCtorAst) {
539        final DetailAST classDef = classCtorAst.getParent().getParent();
540        if (!isClassPublic(classDef) && !isClassProtected(classDef)) {
541            checkForRedundantModifier(classCtorAst, TokenTypes.LITERAL_PUBLIC);
542        }
543    }
544
545    /**
546     * Checks if given resource has redundant modifiers.
547     *
548     * @param ast ast
549     */
550    private void processResources(DetailAST ast) {
551        checkForRedundantModifier(ast, TokenTypes.FINAL);
552    }
553
554    /**
555     * Checks if given ast has a redundant modifier.
556     *
557     * @param ast ast
558     * @param modifierTypes The modifiers to check for.
559     */
560    private void checkForRedundantModifier(DetailAST ast, int... modifierTypes) {
561        Optional.ofNullable(ast.findFirstToken(TokenTypes.MODIFIERS))
562            .ifPresent(modifiers -> {
563                for (DetailAST childAst = modifiers.getFirstChild();
564                     childAst != null; childAst = childAst.getNextSibling()) {
565                    if (TokenUtil.isOfType(childAst, modifierTypes)) {
566                        log(childAst, MSG_KEY, childAst.getText());
567                    }
568                }
569            });
570    }
571
572    /**
573     * Checks if given class ast has protected modifier.
574     *
575     * @param classDef class ast
576     * @return true if class is protected, false otherwise
577     */
578    private static boolean isClassProtected(DetailAST classDef) {
579        final DetailAST classModifiers =
580                classDef.findFirstToken(TokenTypes.MODIFIERS);
581        return classModifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) != null;
582    }
583
584    /**
585     * Checks if given class is accessible from "public" scope.
586     *
587     * @param ast class def to check
588     * @return true if class is accessible from public scope,false otherwise
589     */
590    private static boolean isClassPublic(DetailAST ast) {
591        boolean isAccessibleFromPublic = false;
592        final DetailAST modifiersAst = ast.findFirstToken(TokenTypes.MODIFIERS);
593        final boolean hasPublicModifier =
594                modifiersAst.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null;
595
596        if (TokenUtil.isRootNode(ast.getParent())) {
597            isAccessibleFromPublic = hasPublicModifier;
598        }
599        else {
600            final DetailAST parentClassAst = ast.getParent().getParent();
601
602            if (hasPublicModifier || parentClassAst.getType() == TokenTypes.INTERFACE_DEF) {
603                isAccessibleFromPublic = isClassPublic(parentClassAst);
604            }
605        }
606
607        return isAccessibleFromPublic;
608    }
609
610    /**
611     * Checks if current AST node is member of Enum.
612     *
613     * @param ast AST node
614     * @return true if it is an enum member
615     */
616    private static boolean isEnumMember(DetailAST ast) {
617        final DetailAST parentTypeDef = ast.getParent().getParent();
618        return parentTypeDef.getType() == TokenTypes.ENUM_DEF;
619    }
620
621    /**
622     * Checks if current AST node is member of Interface or Annotation, not of their subnodes.
623     *
624     * @param ast AST node
625     * @return true or false
626     */
627    private static boolean isInterfaceOrAnnotationMember(DetailAST ast) {
628        DetailAST parentTypeDef = ast.getParent();
629        parentTypeDef = parentTypeDef.getParent();
630        return parentTypeDef != null
631                && (parentTypeDef.getType() == TokenTypes.INTERFACE_DEF
632                    || parentTypeDef.getType() == TokenTypes.ANNOTATION_DEF);
633    }
634
635    /**
636     * Checks if method definition is annotated with.
637     * <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/SafeVarargs.html">
638     * SafeVarargs</a> annotation
639     *
640     * @param methodDef method definition node
641     * @return true or false
642     */
643    private static boolean isAnnotatedWithSafeVarargs(DetailAST methodDef) {
644        boolean result = false;
645        final List<DetailAST> methodAnnotationsList = getMethodAnnotationsList(methodDef);
646        for (DetailAST annotationNode : methodAnnotationsList) {
647            if ("SafeVarargs".equals(annotationNode.getLastChild().getText())) {
648                result = true;
649                break;
650            }
651        }
652        return result;
653    }
654
655    /**
656     * Gets the list of annotations on method definition.
657     *
658     * @param methodDef method definition node
659     * @return List of annotations
660     */
661    private static List<DetailAST> getMethodAnnotationsList(DetailAST methodDef) {
662        final List<DetailAST> annotationsList = new ArrayList<>();
663        final DetailAST modifiers = methodDef.findFirstToken(TokenTypes.MODIFIERS);
664        TokenUtil.forEachChild(modifiers, TokenTypes.ANNOTATION, annotationsList::add);
665        return annotationsList;
666    }
667
668}