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