View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.modifier;
21  
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.Optional;
25  
26  import com.puppycrawl.tools.checkstyle.StatelessCheck;
27  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28  import com.puppycrawl.tools.checkstyle.api.DetailAST;
29  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
31  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
32  
33  /**
34   * <p>
35   * Checks for redundant modifiers.
36   * </p>
37   * <p>
38   * Rationale: The Java Language Specification strongly discourages the usage
39   * of {@code public} and {@code abstract} for method declarations in interface
40   * definitions as a matter of style.
41   * </p>
42   * <p>The check validates:</p>
43   * <ol>
44   * <li>
45   * Interface and annotation definitions.
46   * </li>
47   * <li>
48   * Final modifier on methods of final and anonymous classes.
49   * </li>
50   * <li>
51   * Type declarations nested under interfaces that are declared as {@code public} or {@code static}.
52   * </li>
53   * <li>
54   * Class constructors.
55   * </li>
56   * <li>
57   * Nested {@code enum} definitions that are declared as {@code static}.
58   * </li>
59   * <li>
60   * {@code record} definitions that are declared as {@code final} and nested
61   * {@code record} definitions that are declared as {@code static}.
62   * </li>
63   * <li>
64   * {@code strictfp} modifier when using JDK 17 or later. See reason at
65   * <a href="https://openjdk.org/jeps/306">JEP 306</a>
66   * </li>
67   * <li>
68   * {@code final} modifier on unnamed variables when using JDK 22 or later.
69   * </li>
70   * </ol>
71   * <p>
72   * interfaces by definition are abstract so the {@code abstract} modifier is redundant on them.
73   * </p>
74   * <p>Type declarations nested under interfaces by definition are public and static,
75   * so the {@code public} and {@code static} modifiers on nested type declarations are redundant.
76   * On the other hand, classes inside of interfaces can be abstract or non abstract.
77   * So, {@code abstract} modifier is allowed.
78   * </p>
79   * <p>Fields in interfaces and annotations are automatically
80   * public, static and final, so these modifiers are redundant as
81   * well.</p>
82   *
83   * <p>As annotations are a form of interface, their fields are also
84   * automatically public, static and final just as their
85   * annotation fields are automatically public and abstract.</p>
86   *
87   * <p>A record class is implicitly final and cannot be abstract, these restrictions emphasize
88   * that the API of a record class is defined solely by its state description, and
89   * cannot be enhanced later by another class. Nested records are implicitly static. This avoids an
90   * immediately enclosing instance which would silently add state to the record class.
91   * See <a href="https://openjdk.org/jeps/395">JEP 395</a> for more info.</p>
92   *
93   * <p>Enums by definition are static implicit subclasses of java.lang.Enum&#60;E&#62;.
94   * So, the {@code static} modifier on the enums is redundant. In addition,
95   * if enum is inside of interface, {@code public} modifier is also redundant.</p>
96   *
97   * <p>Enums can also contain abstract methods and methods which can be overridden by the declared
98   * enumeration fields.
99   * See the following example:</p>
100  * <pre>
101  * public enum EnumClass {
102  *   FIELD_1,
103  *   FIELD_2 {
104  *     &#64;Override
105  *     public final void method1() {} // violation expected
106  *   };
107  *
108  *   public void method1() {}
109  *   public final void method2() {} // no violation expected
110  * }
111  * </pre>
112  *
113  * <p>Since these methods can be overridden in these situations, the final methods are not
114  * marked as redundant even though they can't be extended by other classes/enums.</p>
115  * <p>
116  * Nested {@code enum} types are always static by default.
117  * </p>
118  * <p>Final classes by definition cannot be extended so the {@code final}
119  * modifier on the method of a final class is redundant.
120  * </p>
121  * <p>Public modifier for constructors in non-public non-protected classes
122  * is always obsolete: </p>
123  *
124  * <pre>
125  * public class PublicClass {
126  *   public PublicClass() {} // OK
127  * }
128  *
129  * class PackagePrivateClass {
130  *   public PackagePrivateClass() {} // violation expected
131  * }
132  * </pre>
133  *
134  * <p>There is no violation in the following example,
135  * because removing public modifier from ProtectedInnerClass
136  * constructor will make this code not compiling: </p>
137  *
138  * <pre>
139  * package a;
140  * public class ClassExample {
141  *   protected class ProtectedInnerClass {
142  *     public ProtectedInnerClass () {}
143  *   }
144  * }
145  *
146  * package b;
147  * import a.ClassExample;
148  * public class ClassExtending extends ClassExample {
149  *   ProtectedInnerClass pc = new ProtectedInnerClass();
150  * }
151  * </pre>
152  * <ul>
153  * <li>
154  * Property {@code jdkVersion} - Set the JDK version that you are using.
155  * Old JDK version numbering is supported (e.g. 1.8 for Java 8)
156  * as well as just the major JDK version alone (e.g. 8) is supported.
157  * This property only considers features from officially released
158  * Java versions as supported. Features introduced in preview releases are not considered
159  * supported until they are included in a non-preview release.
160  * Type is {@code java.lang.String}.
161  * Default value is {@code "22"}.
162  * </li>
163  * <li>
164  * Property {@code tokens} - tokens to check
165  * Type is {@code java.lang.String[]}.
166  * Validation type is {@code tokenSet}.
167  * Default value is:
168  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
169  * METHOD_DEF</a>,
170  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF">
171  * VARIABLE_DEF</a>,
172  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_FIELD_DEF">
173  * ANNOTATION_FIELD_DEF</a>,
174  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF">
175  * INTERFACE_DEF</a>,
176  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
177  * CTOR_DEF</a>,
178  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF">
179  * CLASS_DEF</a>,
180  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF">
181  * ENUM_DEF</a>,
182  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RESOURCE">
183  * RESOURCE</a>,
184  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_DEF">
185  * ANNOTATION_DEF</a>,
186  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF">
187  * RECORD_DEF</a>,
188  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PATTERN_VARIABLE_DEF">
189  * PATTERN_VARIABLE_DEF</a>,
190  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CATCH">
191  * LITERAL_CATCH</a>,
192  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA">
193  * LAMBDA</a>.
194  * </li>
195  * </ul>
196  * <p>
197  * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
198  * </p>
199  * <p>
200  * Violation Message Keys:
201  * </p>
202  * <ul>
203  * <li>
204  * {@code redundantModifier}
205  * </li>
206  * </ul>
207  *
208  * @since 3.0
209  */
210 @StatelessCheck
211 public class RedundantModifierCheck
212     extends AbstractCheck {
213 
214     /**
215      * A key is pointing to the warning message text in "messages.properties"
216      * file.
217      */
218     public static final String MSG_KEY = "redundantModifier";
219 
220     /**
221      * An array of tokens for interface modifiers.
222      */
223     private static final int[] TOKENS_FOR_INTERFACE_MODIFIERS = {
224         TokenTypes.LITERAL_STATIC,
225         TokenTypes.ABSTRACT,
226     };
227 
228     /**
229      *  Constant for jdk 22 version number.
230      */
231     private static final int JDK_22 = 22;
232 
233     /**
234      *  Constant for jdk 17 version number.
235      *
236      */
237     private static final int JDK_17 = 17;
238 
239     /**
240      * Set the JDK version that you are using.
241      * Old JDK version numbering is supported (e.g. 1.8 for Java 8)
242      * as well as just the major JDK version alone (e.g. 8) is supported.
243      * This property only considers features from officially released
244      * Java versions as supported. Features introduced in preview releases are not considered
245      * supported until they are included in a non-preview release.
246      *
247      */
248     private int jdkVersion = JDK_22;
249 
250     /**
251      * Setter to set the JDK version that you are using.
252      * Old JDK version numbering is supported (e.g. 1.8 for Java 8)
253      * as well as just the major JDK version alone (e.g. 8) is supported.
254      * This property only considers features from officially released
255      * Java versions as supported. Features introduced in preview releases are not considered
256      * supported until they are included in a non-preview release.
257      *
258      * @param jdkVersion the Java version
259      * @since 10.18.0
260      */
261     public void setJdkVersion(String jdkVersion) {
262         final String singleVersionNumber;
263         if (jdkVersion.startsWith("1.")) {
264             singleVersionNumber = jdkVersion.substring(2);
265         }
266         else {
267             singleVersionNumber = jdkVersion;
268         }
269 
270         this.jdkVersion = Integer.parseInt(singleVersionNumber);
271     }
272 
273     @Override
274     public int[] getDefaultTokens() {
275         return getAcceptableTokens();
276     }
277 
278     @Override
279     public int[] getRequiredTokens() {
280         return CommonUtil.EMPTY_INT_ARRAY;
281     }
282 
283     @Override
284     public int[] getAcceptableTokens() {
285         return new int[] {
286             TokenTypes.METHOD_DEF,
287             TokenTypes.VARIABLE_DEF,
288             TokenTypes.ANNOTATION_FIELD_DEF,
289             TokenTypes.INTERFACE_DEF,
290             TokenTypes.CTOR_DEF,
291             TokenTypes.CLASS_DEF,
292             TokenTypes.ENUM_DEF,
293             TokenTypes.RESOURCE,
294             TokenTypes.ANNOTATION_DEF,
295             TokenTypes.RECORD_DEF,
296             TokenTypes.PATTERN_VARIABLE_DEF,
297             TokenTypes.LITERAL_CATCH,
298             TokenTypes.LAMBDA,
299         };
300     }
301 
302     @Override
303     public void visitToken(DetailAST ast) {
304         switch (ast.getType()) {
305             case TokenTypes.INTERFACE_DEF:
306             case TokenTypes.ANNOTATION_DEF:
307                 checkInterfaceModifiers(ast);
308                 break;
309             case TokenTypes.ENUM_DEF:
310                 checkForRedundantModifier(ast, TokenTypes.LITERAL_STATIC);
311                 break;
312             case TokenTypes.CTOR_DEF:
313                 checkConstructorModifiers(ast);
314                 break;
315             case TokenTypes.METHOD_DEF:
316                 processMethods(ast);
317                 break;
318             case TokenTypes.RESOURCE:
319                 processResources(ast);
320                 break;
321             case TokenTypes.RECORD_DEF:
322                 checkForRedundantModifier(ast, TokenTypes.FINAL, TokenTypes.LITERAL_STATIC);
323                 break;
324             case TokenTypes.VARIABLE_DEF:
325             case TokenTypes.PATTERN_VARIABLE_DEF:
326                 checkUnnamedVariables(ast);
327                 break;
328             case TokenTypes.LITERAL_CATCH:
329                 checkUnnamedVariables(ast.findFirstToken(TokenTypes.PARAMETER_DEF));
330                 break;
331             case TokenTypes.LAMBDA:
332                 processLambdaParameters(ast);
333                 break;
334             case TokenTypes.CLASS_DEF:
335             case TokenTypes.ANNOTATION_FIELD_DEF:
336                 break;
337             default:
338                 throw new IllegalStateException("Unexpected token type: " + ast.getType());
339         }
340 
341         if (isInterfaceOrAnnotationMember(ast)) {
342             processInterfaceOrAnnotation(ast);
343         }
344 
345         if (jdkVersion >= JDK_17) {
346             checkForRedundantModifier(ast, TokenTypes.STRICTFP);
347         }
348     }
349 
350     /**
351      * Process lambda parameters.
352      *
353      * @param lambdaAst node of type {@link TokenTypes#LAMBDA}
354      */
355     private void processLambdaParameters(DetailAST lambdaAst) {
356         final DetailAST lambdaParameters = lambdaAst.findFirstToken(TokenTypes.PARAMETERS);
357         if (lambdaParameters != null) {
358             TokenUtil.forEachChild(lambdaParameters, TokenTypes.PARAMETER_DEF,
359                     this::checkUnnamedVariables);
360         }
361     }
362 
363     /**
364      * Check if the variable is unnamed and has redundant final modifier.
365      *
366      * @param ast node of type {@link TokenTypes#VARIABLE_DEF}
367      *     or {@link TokenTypes#PATTERN_VARIABLE_DEF}
368      *     or {@link TokenTypes#PARAMETER_DEF}
369      */
370     private void checkUnnamedVariables(DetailAST ast) {
371         if (jdkVersion >= JDK_22 && isUnnamedVariable(ast)) {
372             checkForRedundantModifier(ast, TokenTypes.FINAL);
373         }
374     }
375 
376     /**
377      * Check if the variable is unnamed.
378      *
379      * @param ast node of type {@link TokenTypes#VARIABLE_DEF}
380      *     or {@link TokenTypes#PATTERN_VARIABLE_DEF}
381      *     or {@link TokenTypes#PARAMETER_DEF}
382      * @return true if the variable is unnamed
383      */
384     private static boolean isUnnamedVariable(DetailAST ast) {
385         return "_".equals(ast.findFirstToken(TokenTypes.IDENT).getText());
386     }
387 
388     /**
389      * Check modifiers of constructor.
390      *
391      * @param ctorDefAst ast node of type {@link TokenTypes#CTOR_DEF}
392      */
393     private void checkConstructorModifiers(DetailAST ctorDefAst) {
394         if (isEnumMember(ctorDefAst)) {
395             checkEnumConstructorModifiers(ctorDefAst);
396         }
397         else {
398             checkClassConstructorModifiers(ctorDefAst);
399         }
400     }
401 
402     /**
403      * Checks if interface has proper modifiers.
404      *
405      * @param ast interface to check
406      */
407     private void checkInterfaceModifiers(DetailAST ast) {
408         final DetailAST modifiers =
409             ast.findFirstToken(TokenTypes.MODIFIERS);
410 
411         for (final int tokenType : TOKENS_FOR_INTERFACE_MODIFIERS) {
412             final DetailAST modifier =
413                     modifiers.findFirstToken(tokenType);
414             if (modifier != null) {
415                 log(modifier, MSG_KEY, modifier.getText());
416             }
417         }
418     }
419 
420     /**
421      * Check if enum constructor has proper modifiers.
422      *
423      * @param ast constructor of enum
424      */
425     private void checkEnumConstructorModifiers(DetailAST ast) {
426         final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
427         TokenUtil.findFirstTokenByPredicate(
428             modifiers, mod -> mod.getType() != TokenTypes.ANNOTATION
429         ).ifPresent(modifier -> log(modifier, MSG_KEY, modifier.getText()));
430     }
431 
432     /**
433      * Do validation of interface of annotation.
434      *
435      * @param ast token AST
436      */
437     private void processInterfaceOrAnnotation(DetailAST ast) {
438         final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
439         DetailAST modifier = modifiers.getFirstChild();
440         while (modifier != null) {
441             // javac does not allow final or static in interface methods
442             // order annotation fields hence no need to check that this
443             // is not a method or annotation field
444 
445             final int type = modifier.getType();
446             if (type == TokenTypes.LITERAL_PUBLIC
447                 || type == TokenTypes.LITERAL_STATIC
448                         && ast.getType() != TokenTypes.METHOD_DEF
449                 || type == TokenTypes.ABSTRACT
450                         && ast.getType() != TokenTypes.CLASS_DEF
451                 || type == TokenTypes.FINAL
452                         && ast.getType() != TokenTypes.CLASS_DEF) {
453                 log(modifier, MSG_KEY, modifier.getText());
454             }
455 
456             modifier = modifier.getNextSibling();
457         }
458     }
459 
460     /**
461      * Process validation of Methods.
462      *
463      * @param ast method AST
464      */
465     private void processMethods(DetailAST ast) {
466         final DetailAST modifiers =
467                         ast.findFirstToken(TokenTypes.MODIFIERS);
468         // private method?
469         boolean checkFinal =
470             modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null;
471         // declared in a final class?
472         DetailAST parent = ast;
473         while (parent != null && !checkFinal) {
474             if (parent.getType() == TokenTypes.CLASS_DEF) {
475                 final DetailAST classModifiers =
476                     parent.findFirstToken(TokenTypes.MODIFIERS);
477                 checkFinal = classModifiers.findFirstToken(TokenTypes.FINAL) != null;
478                 parent = null;
479             }
480             else if (parent.getType() == TokenTypes.LITERAL_NEW
481                     || parent.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
482                 checkFinal = true;
483                 parent = null;
484             }
485             else if (parent.getType() == TokenTypes.ENUM_DEF) {
486                 checkFinal = modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
487                 parent = null;
488             }
489             else {
490                 parent = parent.getParent();
491             }
492         }
493         if (checkFinal && !isAnnotatedWithSafeVarargs(ast)) {
494             checkForRedundantModifier(ast, TokenTypes.FINAL);
495         }
496 
497         if (ast.findFirstToken(TokenTypes.SLIST) == null) {
498             processAbstractMethodParameters(ast);
499         }
500     }
501 
502     /**
503      * Process validation of parameters for Methods with no definition.
504      *
505      * @param ast method AST
506      */
507     private void processAbstractMethodParameters(DetailAST ast) {
508         final DetailAST parameters = ast.findFirstToken(TokenTypes.PARAMETERS);
509         TokenUtil.forEachChild(parameters, TokenTypes.PARAMETER_DEF, paramDef -> {
510             checkForRedundantModifier(paramDef, TokenTypes.FINAL);
511         });
512     }
513 
514     /**
515      * Check if class constructor has proper modifiers.
516      *
517      * @param classCtorAst class constructor ast
518      */
519     private void checkClassConstructorModifiers(DetailAST classCtorAst) {
520         final DetailAST classDef = classCtorAst.getParent().getParent();
521         if (!isClassPublic(classDef) && !isClassProtected(classDef)) {
522             checkForRedundantModifier(classCtorAst, TokenTypes.LITERAL_PUBLIC);
523         }
524     }
525 
526     /**
527      * Checks if given resource has redundant modifiers.
528      *
529      * @param ast ast
530      */
531     private void processResources(DetailAST ast) {
532         checkForRedundantModifier(ast, TokenTypes.FINAL);
533     }
534 
535     /**
536      * Checks if given ast has a redundant modifier.
537      *
538      * @param ast ast
539      * @param modifierTypes The modifiers to check for.
540      */
541     private void checkForRedundantModifier(DetailAST ast, int... modifierTypes) {
542         Optional.ofNullable(ast.findFirstToken(TokenTypes.MODIFIERS))
543             .ifPresent(modifiers -> {
544                 for (DetailAST childAst = modifiers.getFirstChild();
545                      childAst != null; childAst = childAst.getNextSibling()) {
546                     if (TokenUtil.isOfType(childAst, modifierTypes)) {
547                         log(childAst, MSG_KEY, childAst.getText());
548                     }
549                 }
550             });
551     }
552 
553     /**
554      * Checks if given class ast has protected modifier.
555      *
556      * @param classDef class ast
557      * @return true if class is protected, false otherwise
558      */
559     private static boolean isClassProtected(DetailAST classDef) {
560         final DetailAST classModifiers =
561                 classDef.findFirstToken(TokenTypes.MODIFIERS);
562         return classModifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) != null;
563     }
564 
565     /**
566      * Checks if given class is accessible from "public" scope.
567      *
568      * @param ast class def to check
569      * @return true if class is accessible from public scope,false otherwise
570      */
571     private static boolean isClassPublic(DetailAST ast) {
572         boolean isAccessibleFromPublic = false;
573         final DetailAST modifiersAst = ast.findFirstToken(TokenTypes.MODIFIERS);
574         final boolean hasPublicModifier =
575                 modifiersAst.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null;
576 
577         if (TokenUtil.isRootNode(ast.getParent())) {
578             isAccessibleFromPublic = hasPublicModifier;
579         }
580         else {
581             final DetailAST parentClassAst = ast.getParent().getParent();
582 
583             if (hasPublicModifier || parentClassAst.getType() == TokenTypes.INTERFACE_DEF) {
584                 isAccessibleFromPublic = isClassPublic(parentClassAst);
585             }
586         }
587 
588         return isAccessibleFromPublic;
589     }
590 
591     /**
592      * Checks if current AST node is member of Enum.
593      *
594      * @param ast AST node
595      * @return true if it is an enum member
596      */
597     private static boolean isEnumMember(DetailAST ast) {
598         final DetailAST parentTypeDef = ast.getParent().getParent();
599         return parentTypeDef.getType() == TokenTypes.ENUM_DEF;
600     }
601 
602     /**
603      * Checks if current AST node is member of Interface or Annotation, not of their subnodes.
604      *
605      * @param ast AST node
606      * @return true or false
607      */
608     private static boolean isInterfaceOrAnnotationMember(DetailAST ast) {
609         DetailAST parentTypeDef = ast.getParent();
610         parentTypeDef = parentTypeDef.getParent();
611         return parentTypeDef != null
612                 && (parentTypeDef.getType() == TokenTypes.INTERFACE_DEF
613                     || parentTypeDef.getType() == TokenTypes.ANNOTATION_DEF);
614     }
615 
616     /**
617      * Checks if method definition is annotated with.
618      * <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/SafeVarargs.html">
619      * SafeVarargs</a> annotation
620      *
621      * @param methodDef method definition node
622      * @return true or false
623      */
624     private static boolean isAnnotatedWithSafeVarargs(DetailAST methodDef) {
625         boolean result = false;
626         final List<DetailAST> methodAnnotationsList = getMethodAnnotationsList(methodDef);
627         for (DetailAST annotationNode : methodAnnotationsList) {
628             if ("SafeVarargs".equals(annotationNode.getLastChild().getText())) {
629                 result = true;
630                 break;
631             }
632         }
633         return result;
634     }
635 
636     /**
637      * Gets the list of annotations on method definition.
638      *
639      * @param methodDef method definition node
640      * @return List of annotations
641      */
642     private static List<DetailAST> getMethodAnnotationsList(DetailAST methodDef) {
643         final List<DetailAST> annotationsList = new ArrayList<>();
644         final DetailAST modifiers = methodDef.findFirstToken(TokenTypes.MODIFIERS);
645         TokenUtil.forEachChild(modifiers, TokenTypes.ANNOTATION, annotationsList::add);
646         return annotationsList;
647     }
648 
649 }