001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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 int[]}. 141 * Default value is: 142 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 143 * METHOD_DEF</a>, 144 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF"> 145 * VARIABLE_DEF</a>, 146 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_FIELD_DEF"> 147 * ANNOTATION_FIELD_DEF</a>, 148 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF"> 149 * INTERFACE_DEF</a>, 150 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 151 * CTOR_DEF</a>, 152 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF"> 153 * CLASS_DEF</a>, 154 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF"> 155 * ENUM_DEF</a>, 156 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RESOURCE"> 157 * RESOURCE</a>. 158 * </li> 159 * </ul> 160 * <p> 161 * To configure the check: 162 * </p> 163 * <pre> 164 * <module name="RedundantModifier"/> 165 * </pre> 166 * <p> 167 * To configure the check to check only methods and not variables: 168 * </p> 169 * <pre> 170 * <module name="RedundantModifier"> 171 * <property name="tokens" value="METHOD_DEF"/> 172 * </module> 173 * </pre> 174 * <p> 175 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 176 * </p> 177 * <p> 178 * Violation Message Keys: 179 * </p> 180 * <ul> 181 * <li> 182 * {@code redundantModifier} 183 * </li> 184 * </ul> 185 * 186 * @since 3.0 187 */ 188@StatelessCheck 189public class RedundantModifierCheck 190 extends AbstractCheck { 191 192 /** 193 * A key is pointing to the warning message text in "messages.properties" 194 * file. 195 */ 196 public static final String MSG_KEY = "redundantModifier"; 197 198 /** 199 * An array of tokens for interface modifiers. 200 */ 201 private static final int[] TOKENS_FOR_INTERFACE_MODIFIERS = { 202 TokenTypes.LITERAL_STATIC, 203 TokenTypes.ABSTRACT, 204 }; 205 206 @Override 207 public int[] getDefaultTokens() { 208 return getAcceptableTokens(); 209 } 210 211 @Override 212 public int[] getRequiredTokens() { 213 return CommonUtil.EMPTY_INT_ARRAY; 214 } 215 216 @Override 217 public int[] getAcceptableTokens() { 218 return new int[] { 219 TokenTypes.METHOD_DEF, 220 TokenTypes.VARIABLE_DEF, 221 TokenTypes.ANNOTATION_FIELD_DEF, 222 TokenTypes.INTERFACE_DEF, 223 TokenTypes.CTOR_DEF, 224 TokenTypes.CLASS_DEF, 225 TokenTypes.ENUM_DEF, 226 TokenTypes.RESOURCE, 227 }; 228 } 229 230 @Override 231 public void visitToken(DetailAST ast) { 232 if (ast.getType() == TokenTypes.INTERFACE_DEF) { 233 checkInterfaceModifiers(ast); 234 } 235 else if (ast.getType() == TokenTypes.ENUM_DEF) { 236 checkEnumDef(ast); 237 } 238 else { 239 if (ast.getType() == TokenTypes.CTOR_DEF) { 240 if (isEnumMember(ast)) { 241 checkEnumConstructorModifiers(ast); 242 } 243 else { 244 checkClassConstructorModifiers(ast); 245 } 246 } 247 else if (ast.getType() == TokenTypes.METHOD_DEF) { 248 processMethods(ast); 249 } 250 else if (ast.getType() == TokenTypes.RESOURCE) { 251 processResources(ast); 252 } 253 254 if (isInterfaceOrAnnotationMember(ast)) { 255 processInterfaceOrAnnotation(ast); 256 } 257 } 258 } 259 260 /** 261 * Checks if interface has proper modifiers. 262 * 263 * @param ast interface to check 264 */ 265 private void checkInterfaceModifiers(DetailAST ast) { 266 final DetailAST modifiers = 267 ast.findFirstToken(TokenTypes.MODIFIERS); 268 269 for (final int tokenType : TOKENS_FOR_INTERFACE_MODIFIERS) { 270 final DetailAST modifier = 271 modifiers.findFirstToken(tokenType); 272 if (modifier != null) { 273 log(modifier, MSG_KEY, modifier.getText()); 274 } 275 } 276 } 277 278 /** 279 * Check if enum constructor has proper modifiers. 280 * 281 * @param ast constructor of enum 282 */ 283 private void checkEnumConstructorModifiers(DetailAST ast) { 284 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 285 TokenUtil.findFirstTokenByPredicate( 286 modifiers, mod -> mod.getType() != TokenTypes.ANNOTATION 287 ).ifPresent(modifier -> log(modifier, MSG_KEY, modifier.getText())); 288 } 289 290 /** 291 * Checks whether enum has proper modifiers. 292 * 293 * @param ast enum definition. 294 */ 295 private void checkEnumDef(DetailAST ast) { 296 if (isInterfaceOrAnnotationMember(ast)) { 297 processInterfaceOrAnnotation(ast); 298 } 299 else { 300 checkForRedundantModifier(ast, TokenTypes.LITERAL_STATIC); 301 } 302 } 303 304 /** 305 * Do validation of interface of annotation. 306 * 307 * @param ast token AST 308 */ 309 private void processInterfaceOrAnnotation(DetailAST ast) { 310 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 311 DetailAST modifier = modifiers.getFirstChild(); 312 while (modifier != null) { 313 // javac does not allow final or static in interface methods 314 // order annotation fields hence no need to check that this 315 // is not a method or annotation field 316 317 final int type = modifier.getType(); 318 if (type == TokenTypes.LITERAL_PUBLIC 319 || type == TokenTypes.LITERAL_STATIC 320 && ast.getType() != TokenTypes.METHOD_DEF 321 || type == TokenTypes.ABSTRACT 322 && ast.getType() != TokenTypes.CLASS_DEF 323 || type == TokenTypes.FINAL 324 && ast.getType() != TokenTypes.CLASS_DEF) { 325 log(modifier, MSG_KEY, modifier.getText()); 326 break; 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 boolean isMostOuterScope = ast.getParent() == null; 444 final DetailAST modifiersAst = ast.findFirstToken(TokenTypes.MODIFIERS); 445 final boolean hasPublicModifier = 446 modifiersAst.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null; 447 448 if (isMostOuterScope) { 449 isAccessibleFromPublic = hasPublicModifier; 450 } 451 else { 452 final DetailAST parentClassAst = ast.getParent().getParent(); 453 454 if (hasPublicModifier || parentClassAst.getType() == TokenTypes.INTERFACE_DEF) { 455 isAccessibleFromPublic = isClassPublic(parentClassAst); 456 } 457 } 458 459 return isAccessibleFromPublic; 460 } 461 462 /** 463 * Checks if current AST node is member of Enum. 464 * 465 * @param ast AST node 466 * @return true if it is an enum member 467 */ 468 private static boolean isEnumMember(DetailAST ast) { 469 final DetailAST parentTypeDef = ast.getParent().getParent(); 470 return parentTypeDef.getType() == TokenTypes.ENUM_DEF; 471 } 472 473 /** 474 * Checks if current AST node is member of Interface or Annotation, not of their subnodes. 475 * 476 * @param ast AST node 477 * @return true or false 478 */ 479 private static boolean isInterfaceOrAnnotationMember(DetailAST ast) { 480 DetailAST parentTypeDef = ast.getParent(); 481 482 if (parentTypeDef != null) { 483 parentTypeDef = parentTypeDef.getParent(); 484 } 485 return parentTypeDef != null 486 && (parentTypeDef.getType() == TokenTypes.INTERFACE_DEF 487 || parentTypeDef.getType() == TokenTypes.ANNOTATION_DEF); 488 } 489 490 /** 491 * Checks if method definition is annotated with. 492 * <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/SafeVarargs.html"> 493 * SafeVarargs</a> annotation 494 * 495 * @param methodDef method definition node 496 * @return true or false 497 */ 498 private static boolean isAnnotatedWithSafeVarargs(DetailAST methodDef) { 499 boolean result = false; 500 final List<DetailAST> methodAnnotationsList = getMethodAnnotationsList(methodDef); 501 for (DetailAST annotationNode : methodAnnotationsList) { 502 if ("SafeVarargs".equals(annotationNode.getLastChild().getText())) { 503 result = true; 504 break; 505 } 506 } 507 return result; 508 } 509 510 /** 511 * Gets the list of annotations on method definition. 512 * 513 * @param methodDef method definition node 514 * @return List of annotations 515 */ 516 private static List<DetailAST> getMethodAnnotationsList(DetailAST methodDef) { 517 final List<DetailAST> annotationsList = new ArrayList<>(); 518 final DetailAST modifiers = methodDef.findFirstToken(TokenTypes.MODIFIERS); 519 TokenUtil.forEachChild(modifiers, TokenTypes.ANNOTATION, annotationsList::add); 520 return annotationsList; 521 } 522 523}