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