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