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<E>. 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}