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