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