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