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