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