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