001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 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 * </code></pre></div> 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 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 133 * public class PublicClass { 134 * public PublicClass() {} // OK 135 * } 136 * 137 * class PackagePrivateClass { 138 * public PackagePrivateClass() {} // violation expected 139 * } 140 * </code></pre></div> 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 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 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 * </code></pre></div> 160 * 161 * @since 3.0 162 */ 163@StatelessCheck 164public class RedundantModifierCheck 165 extends AbstractCheck { 166 167 /** 168 * A key is pointing to the warning message text in "messages.properties" 169 * file. 170 */ 171 public static final String MSG_KEY = "redundantModifier"; 172 173 /** 174 * An array of tokens for interface modifiers. 175 */ 176 private static final int[] TOKENS_FOR_INTERFACE_MODIFIERS = { 177 TokenTypes.LITERAL_STATIC, 178 TokenTypes.ABSTRACT, 179 }; 180 181 /** 182 * Constant for jdk 22 version number. 183 */ 184 private static final int JDK_22 = 22; 185 186 /** 187 * Constant for jdk 17 version number. 188 * 189 */ 190 private static final int JDK_17 = 17; 191 192 /** 193 * Set the JDK version that you are using. 194 * Old JDK version numbering is supported (e.g. 1.8 for Java 8) 195 * as well as just the major JDK version alone (e.g. 8) is supported. 196 * This property only considers features from officially released 197 * Java versions as supported. Features introduced in preview releases are not considered 198 * supported until they are included in a non-preview release. 199 * 200 */ 201 private int jdkVersion = JDK_22; 202 203 /** 204 * Setter to set the JDK version that you are using. 205 * Old JDK version numbering is supported (e.g. 1.8 for Java 8) 206 * as well as just the major JDK version alone (e.g. 8) is supported. 207 * This property only considers features from officially released 208 * Java versions as supported. Features introduced in preview releases are not considered 209 * supported until they are included in a non-preview release. 210 * 211 * @param jdkVersion the Java version 212 * @since 10.18.0 213 */ 214 public void setJdkVersion(String jdkVersion) { 215 final String singleVersionNumber; 216 if (jdkVersion.startsWith("1.")) { 217 singleVersionNumber = jdkVersion.substring(2); 218 } 219 else { 220 singleVersionNumber = jdkVersion; 221 } 222 223 this.jdkVersion = Integer.parseInt(singleVersionNumber); 224 } 225 226 @Override 227 public int[] getDefaultTokens() { 228 return getAcceptableTokens(); 229 } 230 231 @Override 232 public int[] getRequiredTokens() { 233 return CommonUtil.EMPTY_INT_ARRAY; 234 } 235 236 @Override 237 public int[] getAcceptableTokens() { 238 return new int[] { 239 TokenTypes.METHOD_DEF, 240 TokenTypes.VARIABLE_DEF, 241 TokenTypes.ANNOTATION_FIELD_DEF, 242 TokenTypes.INTERFACE_DEF, 243 TokenTypes.CTOR_DEF, 244 TokenTypes.CLASS_DEF, 245 TokenTypes.ENUM_DEF, 246 TokenTypes.RESOURCE, 247 TokenTypes.ANNOTATION_DEF, 248 TokenTypes.RECORD_DEF, 249 TokenTypes.PATTERN_VARIABLE_DEF, 250 TokenTypes.LITERAL_CATCH, 251 TokenTypes.LAMBDA, 252 }; 253 } 254 255 @Override 256 public void visitToken(DetailAST ast) { 257 switch (ast.getType()) { 258 case TokenTypes.INTERFACE_DEF: 259 case TokenTypes.ANNOTATION_DEF: 260 checkInterfaceModifiers(ast); 261 break; 262 case TokenTypes.ENUM_DEF: 263 checkForRedundantModifier(ast, TokenTypes.LITERAL_STATIC); 264 break; 265 case TokenTypes.CTOR_DEF: 266 checkConstructorModifiers(ast); 267 break; 268 case TokenTypes.METHOD_DEF: 269 processMethods(ast); 270 break; 271 case TokenTypes.RESOURCE: 272 processResources(ast); 273 break; 274 case TokenTypes.RECORD_DEF: 275 checkForRedundantModifier(ast, TokenTypes.FINAL, TokenTypes.LITERAL_STATIC); 276 break; 277 case TokenTypes.VARIABLE_DEF: 278 case TokenTypes.PATTERN_VARIABLE_DEF: 279 checkUnnamedVariables(ast); 280 break; 281 case TokenTypes.LITERAL_CATCH: 282 checkUnnamedVariables(ast.findFirstToken(TokenTypes.PARAMETER_DEF)); 283 break; 284 case TokenTypes.LAMBDA: 285 processLambdaParameters(ast); 286 break; 287 case TokenTypes.CLASS_DEF: 288 case TokenTypes.ANNOTATION_FIELD_DEF: 289 break; 290 default: 291 throw new IllegalStateException("Unexpected token type: " + ast.getType()); 292 } 293 294 if (isInterfaceOrAnnotationMember(ast)) { 295 processInterfaceOrAnnotation(ast); 296 } 297 298 if (jdkVersion >= JDK_17) { 299 checkForRedundantModifier(ast, TokenTypes.STRICTFP); 300 } 301 } 302 303 /** 304 * Process lambda parameters. 305 * 306 * @param lambdaAst node of type {@link TokenTypes#LAMBDA} 307 */ 308 private void processLambdaParameters(DetailAST lambdaAst) { 309 final DetailAST lambdaParameters = lambdaAst.findFirstToken(TokenTypes.PARAMETERS); 310 if (lambdaParameters != null) { 311 TokenUtil.forEachChild(lambdaParameters, TokenTypes.PARAMETER_DEF, 312 this::checkUnnamedVariables); 313 } 314 } 315 316 /** 317 * Check if the variable is unnamed and has redundant final modifier. 318 * 319 * @param ast node of type {@link TokenTypes#VARIABLE_DEF} 320 * or {@link TokenTypes#PATTERN_VARIABLE_DEF} 321 * or {@link TokenTypes#PARAMETER_DEF} 322 */ 323 private void checkUnnamedVariables(DetailAST ast) { 324 if (jdkVersion >= JDK_22 && isUnnamedVariable(ast)) { 325 checkForRedundantModifier(ast, TokenTypes.FINAL); 326 } 327 } 328 329 /** 330 * Check if the variable is unnamed. 331 * 332 * @param ast node of type {@link TokenTypes#VARIABLE_DEF} 333 * or {@link TokenTypes#PATTERN_VARIABLE_DEF} 334 * or {@link TokenTypes#PARAMETER_DEF} 335 * @return true if the variable is unnamed 336 */ 337 private static boolean isUnnamedVariable(DetailAST ast) { 338 return "_".equals(ast.findFirstToken(TokenTypes.IDENT).getText()); 339 } 340 341 /** 342 * Check modifiers of constructor. 343 * 344 * @param ctorDefAst ast node of type {@link TokenTypes#CTOR_DEF} 345 */ 346 private void checkConstructorModifiers(DetailAST ctorDefAst) { 347 if (isEnumMember(ctorDefAst)) { 348 checkEnumConstructorModifiers(ctorDefAst); 349 } 350 else { 351 checkClassConstructorModifiers(ctorDefAst); 352 } 353 } 354 355 /** 356 * Checks if interface has proper modifiers. 357 * 358 * @param ast interface to check 359 */ 360 private void checkInterfaceModifiers(DetailAST ast) { 361 final DetailAST modifiers = 362 ast.findFirstToken(TokenTypes.MODIFIERS); 363 364 for (final int tokenType : TOKENS_FOR_INTERFACE_MODIFIERS) { 365 final DetailAST modifier = 366 modifiers.findFirstToken(tokenType); 367 if (modifier != null) { 368 log(modifier, MSG_KEY, modifier.getText()); 369 } 370 } 371 } 372 373 /** 374 * Check if enum constructor has proper modifiers. 375 * 376 * @param ast constructor of enum 377 */ 378 private void checkEnumConstructorModifiers(DetailAST ast) { 379 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 380 TokenUtil.findFirstTokenByPredicate( 381 modifiers, mod -> mod.getType() != TokenTypes.ANNOTATION 382 ).ifPresent(modifier -> log(modifier, MSG_KEY, modifier.getText())); 383 } 384 385 /** 386 * Do validation of interface of annotation. 387 * 388 * @param ast token AST 389 */ 390 private void processInterfaceOrAnnotation(DetailAST ast) { 391 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 392 DetailAST modifier = modifiers.getFirstChild(); 393 while (modifier != null) { 394 // javac does not allow final or static in interface methods 395 // order annotation fields hence no need to check that this 396 // is not a method or annotation field 397 398 final int type = modifier.getType(); 399 if (type == TokenTypes.LITERAL_PUBLIC 400 || type == TokenTypes.LITERAL_STATIC 401 && ast.getType() != TokenTypes.METHOD_DEF 402 || type == TokenTypes.ABSTRACT 403 && ast.getType() != TokenTypes.CLASS_DEF 404 || type == TokenTypes.FINAL 405 && ast.getType() != TokenTypes.CLASS_DEF) { 406 log(modifier, MSG_KEY, modifier.getText()); 407 } 408 409 modifier = modifier.getNextSibling(); 410 } 411 } 412 413 /** 414 * Process validation of Methods. 415 * 416 * @param ast method AST 417 */ 418 private void processMethods(DetailAST ast) { 419 final DetailAST modifiers = 420 ast.findFirstToken(TokenTypes.MODIFIERS); 421 // private method? 422 boolean checkFinal = 423 modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null; 424 // declared in a final class? 425 DetailAST parent = ast; 426 while (parent != null && !checkFinal) { 427 if (parent.getType() == TokenTypes.CLASS_DEF) { 428 final DetailAST classModifiers = 429 parent.findFirstToken(TokenTypes.MODIFIERS); 430 checkFinal = classModifiers.findFirstToken(TokenTypes.FINAL) != null; 431 parent = null; 432 } 433 else if (parent.getType() == TokenTypes.LITERAL_NEW 434 || parent.getType() == TokenTypes.ENUM_CONSTANT_DEF) { 435 checkFinal = true; 436 parent = null; 437 } 438 else if (parent.getType() == TokenTypes.ENUM_DEF) { 439 checkFinal = modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null; 440 parent = null; 441 } 442 else { 443 parent = parent.getParent(); 444 } 445 } 446 if (checkFinal && !isAnnotatedWithSafeVarargs(ast)) { 447 checkForRedundantModifier(ast, TokenTypes.FINAL); 448 } 449 450 if (ast.findFirstToken(TokenTypes.SLIST) == null) { 451 processAbstractMethodParameters(ast); 452 } 453 } 454 455 /** 456 * Process validation of parameters for Methods with no definition. 457 * 458 * @param ast method AST 459 */ 460 private void processAbstractMethodParameters(DetailAST ast) { 461 final DetailAST parameters = ast.findFirstToken(TokenTypes.PARAMETERS); 462 TokenUtil.forEachChild(parameters, TokenTypes.PARAMETER_DEF, paramDef -> { 463 checkForRedundantModifier(paramDef, TokenTypes.FINAL); 464 }); 465 } 466 467 /** 468 * Check if class constructor has proper modifiers. 469 * 470 * @param classCtorAst class constructor ast 471 */ 472 private void checkClassConstructorModifiers(DetailAST classCtorAst) { 473 final DetailAST classDef = classCtorAst.getParent().getParent(); 474 if (!isClassPublic(classDef) && !isClassProtected(classDef)) { 475 checkForRedundantModifier(classCtorAst, TokenTypes.LITERAL_PUBLIC); 476 } 477 } 478 479 /** 480 * Checks if given resource has redundant modifiers. 481 * 482 * @param ast ast 483 */ 484 private void processResources(DetailAST ast) { 485 checkForRedundantModifier(ast, TokenTypes.FINAL); 486 } 487 488 /** 489 * Checks if given ast has a redundant modifier. 490 * 491 * @param ast ast 492 * @param modifierTypes The modifiers to check for. 493 */ 494 private void checkForRedundantModifier(DetailAST ast, int... modifierTypes) { 495 Optional.ofNullable(ast.findFirstToken(TokenTypes.MODIFIERS)) 496 .ifPresent(modifiers -> { 497 for (DetailAST childAst = modifiers.getFirstChild(); 498 childAst != null; childAst = childAst.getNextSibling()) { 499 if (TokenUtil.isOfType(childAst, modifierTypes)) { 500 log(childAst, MSG_KEY, childAst.getText()); 501 } 502 } 503 }); 504 } 505 506 /** 507 * Checks if given class ast has protected modifier. 508 * 509 * @param classDef class ast 510 * @return true if class is protected, false otherwise 511 */ 512 private static boolean isClassProtected(DetailAST classDef) { 513 final DetailAST classModifiers = 514 classDef.findFirstToken(TokenTypes.MODIFIERS); 515 return classModifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) != null; 516 } 517 518 /** 519 * Checks if given class is accessible from "public" scope. 520 * 521 * @param ast class def to check 522 * @return true if class is accessible from public scope,false otherwise 523 */ 524 private static boolean isClassPublic(DetailAST ast) { 525 boolean isAccessibleFromPublic = false; 526 final DetailAST modifiersAst = ast.findFirstToken(TokenTypes.MODIFIERS); 527 final boolean hasPublicModifier = 528 modifiersAst.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null; 529 530 if (TokenUtil.isRootNode(ast.getParent())) { 531 isAccessibleFromPublic = hasPublicModifier; 532 } 533 else { 534 final DetailAST parentClassAst = ast.getParent().getParent(); 535 536 if (hasPublicModifier || parentClassAst.getType() == TokenTypes.INTERFACE_DEF) { 537 isAccessibleFromPublic = isClassPublic(parentClassAst); 538 } 539 } 540 541 return isAccessibleFromPublic; 542 } 543 544 /** 545 * Checks if current AST node is member of Enum. 546 * 547 * @param ast AST node 548 * @return true if it is an enum member 549 */ 550 private static boolean isEnumMember(DetailAST ast) { 551 final DetailAST parentTypeDef = ast.getParent().getParent(); 552 return parentTypeDef.getType() == TokenTypes.ENUM_DEF; 553 } 554 555 /** 556 * Checks if current AST node is member of Interface or Annotation, not of their subnodes. 557 * 558 * @param ast AST node 559 * @return true or false 560 */ 561 private static boolean isInterfaceOrAnnotationMember(DetailAST ast) { 562 DetailAST parentTypeDef = ast.getParent(); 563 parentTypeDef = parentTypeDef.getParent(); 564 return parentTypeDef != null 565 && (parentTypeDef.getType() == TokenTypes.INTERFACE_DEF 566 || parentTypeDef.getType() == TokenTypes.ANNOTATION_DEF); 567 } 568 569 /** 570 * Checks if method definition is annotated with. 571 * <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/SafeVarargs.html"> 572 * SafeVarargs</a> annotation 573 * 574 * @param methodDef method definition node 575 * @return true or false 576 */ 577 private static boolean isAnnotatedWithSafeVarargs(DetailAST methodDef) { 578 boolean result = false; 579 final List<DetailAST> methodAnnotationsList = getMethodAnnotationsList(methodDef); 580 for (DetailAST annotationNode : methodAnnotationsList) { 581 if ("SafeVarargs".equals(annotationNode.getLastChild().getText())) { 582 result = true; 583 break; 584 } 585 } 586 return result; 587 } 588 589 /** 590 * Gets the list of annotations on method definition. 591 * 592 * @param methodDef method definition node 593 * @return List of annotations 594 */ 595 private static List<DetailAST> getMethodAnnotationsList(DetailAST methodDef) { 596 final List<DetailAST> annotationsList = new ArrayList<>(); 597 final DetailAST modifiers = methodDef.findFirstToken(TokenTypes.MODIFIERS); 598 TokenUtil.forEachChild(modifiers, TokenTypes.ANNOTATION, annotationsList::add); 599 return annotationsList; 600 } 601 602}