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.CommonUtils; 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 * @author lkuehne 114 * @author <a href="mailto:piotr.listkiewicz@gmail.com">liscju</a> 115 * @author <a href="mailto:andreyselkin@gmail.com">Andrei Selkin</a> 116 * @author Vladislav Lisetskiy 117 */ 118@StatelessCheck 119public class RedundantModifierCheck 120 extends AbstractCheck { 121 122 /** 123 * A key is pointing to the warning message text in "messages.properties" 124 * file. 125 */ 126 public static final String MSG_KEY = "redundantModifier"; 127 128 /** 129 * An array of tokens for interface modifiers. 130 */ 131 private static final int[] TOKENS_FOR_INTERFACE_MODIFIERS = { 132 TokenTypes.LITERAL_STATIC, 133 TokenTypes.ABSTRACT, 134 }; 135 136 @Override 137 public int[] getDefaultTokens() { 138 return getAcceptableTokens(); 139 } 140 141 @Override 142 public int[] getRequiredTokens() { 143 return CommonUtils.EMPTY_INT_ARRAY; 144 } 145 146 @Override 147 public int[] getAcceptableTokens() { 148 return new int[] { 149 TokenTypes.METHOD_DEF, 150 TokenTypes.VARIABLE_DEF, 151 TokenTypes.ANNOTATION_FIELD_DEF, 152 TokenTypes.INTERFACE_DEF, 153 TokenTypes.CTOR_DEF, 154 TokenTypes.CLASS_DEF, 155 TokenTypes.ENUM_DEF, 156 TokenTypes.RESOURCE, 157 }; 158 } 159 160 @Override 161 public void visitToken(DetailAST ast) { 162 if (ast.getType() == TokenTypes.INTERFACE_DEF) { 163 checkInterfaceModifiers(ast); 164 } 165 else if (ast.getType() == TokenTypes.ENUM_DEF) { 166 checkEnumDef(ast); 167 } 168 else { 169 if (ast.getType() == TokenTypes.CTOR_DEF) { 170 if (isEnumMember(ast)) { 171 checkEnumConstructorModifiers(ast); 172 } 173 else { 174 checkClassConstructorModifiers(ast); 175 } 176 } 177 else if (ast.getType() == TokenTypes.METHOD_DEF) { 178 processMethods(ast); 179 } 180 else if (ast.getType() == TokenTypes.RESOURCE) { 181 processResources(ast); 182 } 183 184 if (isInterfaceOrAnnotationMember(ast)) { 185 processInterfaceOrAnnotation(ast); 186 } 187 } 188 } 189 190 /** 191 * Checks if interface has proper modifiers. 192 * @param ast interface to check 193 */ 194 private void checkInterfaceModifiers(DetailAST ast) { 195 final DetailAST modifiers = 196 ast.findFirstToken(TokenTypes.MODIFIERS); 197 198 for (final int tokenType : TOKENS_FOR_INTERFACE_MODIFIERS) { 199 final DetailAST modifier = 200 modifiers.findFirstToken(tokenType); 201 if (modifier != null) { 202 log(modifier.getLineNo(), modifier.getColumnNo(), 203 MSG_KEY, modifier.getText()); 204 } 205 } 206 } 207 208 /** 209 * Check if enum constructor has proper modifiers. 210 * @param ast constructor of enum 211 */ 212 private void checkEnumConstructorModifiers(DetailAST ast) { 213 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 214 final DetailAST modifier = getFirstModifierAst(modifiers); 215 216 if (modifier != null) { 217 log(modifier.getLineNo(), modifier.getColumnNo(), 218 MSG_KEY, modifier.getText()); 219 } 220 } 221 222 /** 223 * Retrieves the first modifier that is not an annotation. 224 * @param modifiers The ast to examine. 225 * @return The first modifier or {@code null} if none found. 226 */ 227 private static DetailAST getFirstModifierAst(DetailAST modifiers) { 228 DetailAST modifier = modifiers.getFirstChild(); 229 230 while (modifier != null && modifier.getType() == TokenTypes.ANNOTATION) { 231 modifier = modifier.getNextSibling(); 232 } 233 234 return modifier; 235 } 236 237 /** 238 * Checks whether enum has proper modifiers. 239 * @param ast enum definition. 240 */ 241 private void checkEnumDef(DetailAST ast) { 242 if (isInterfaceOrAnnotationMember(ast)) { 243 processInterfaceOrAnnotation(ast); 244 } 245 else if (ast.getParent() != null) { 246 checkForRedundantModifier(ast, TokenTypes.LITERAL_STATIC); 247 } 248 } 249 250 /** 251 * Do validation of interface of annotation. 252 * @param ast token AST 253 */ 254 private void processInterfaceOrAnnotation(DetailAST ast) { 255 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 256 DetailAST modifier = modifiers.getFirstChild(); 257 while (modifier != null) { 258 // javac does not allow final or static in interface methods 259 // order annotation fields hence no need to check that this 260 // is not a method or annotation field 261 262 final int type = modifier.getType(); 263 if (type == TokenTypes.LITERAL_PUBLIC 264 || type == TokenTypes.LITERAL_STATIC 265 && ast.getType() != TokenTypes.METHOD_DEF 266 || type == TokenTypes.ABSTRACT 267 && ast.getType() != TokenTypes.CLASS_DEF 268 || type == TokenTypes.FINAL 269 && ast.getType() != TokenTypes.CLASS_DEF) { 270 log(modifier.getLineNo(), modifier.getColumnNo(), 271 MSG_KEY, modifier.getText()); 272 break; 273 } 274 275 modifier = modifier.getNextSibling(); 276 } 277 } 278 279 /** 280 * Process validation of Methods. 281 * @param ast method AST 282 */ 283 private void processMethods(DetailAST ast) { 284 final DetailAST modifiers = 285 ast.findFirstToken(TokenTypes.MODIFIERS); 286 // private method? 287 boolean checkFinal = 288 modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null; 289 // declared in a final class? 290 DetailAST parent = ast.getParent(); 291 while (parent != null && !checkFinal) { 292 if (parent.getType() == TokenTypes.CLASS_DEF) { 293 final DetailAST classModifiers = 294 parent.findFirstToken(TokenTypes.MODIFIERS); 295 checkFinal = classModifiers.findFirstToken(TokenTypes.FINAL) != null; 296 parent = null; 297 } 298 else if (parent.getType() == TokenTypes.LITERAL_NEW 299 || parent.getType() == TokenTypes.ENUM_CONSTANT_DEF) { 300 checkFinal = true; 301 parent = null; 302 } 303 else if (parent.getType() == TokenTypes.ENUM_DEF) { 304 checkFinal = modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null; 305 parent = null; 306 } 307 else { 308 parent = parent.getParent(); 309 } 310 } 311 if (checkFinal && !isAnnotatedWithSafeVarargs(ast)) { 312 checkForRedundantModifier(ast, TokenTypes.FINAL); 313 } 314 315 if (ast.findFirstToken(TokenTypes.SLIST) == null) { 316 processAbstractMethodParameters(ast); 317 } 318 } 319 320 /** 321 * Process validation of parameters for Methods with no definition. 322 * @param ast method AST 323 */ 324 private void processAbstractMethodParameters(DetailAST ast) { 325 final DetailAST parameters = ast.findFirstToken(TokenTypes.PARAMETERS); 326 327 for (DetailAST child = parameters.getFirstChild(); child != null; child = child 328 .getNextSibling()) { 329 if (child.getType() == TokenTypes.PARAMETER_DEF) { 330 checkForRedundantModifier(child, TokenTypes.FINAL); 331 } 332 } 333 } 334 335 /** 336 * Check if class constructor has proper modifiers. 337 * @param classCtorAst class constructor ast 338 */ 339 private void checkClassConstructorModifiers(DetailAST classCtorAst) { 340 final DetailAST classDef = classCtorAst.getParent().getParent(); 341 if (!isClassPublic(classDef) && !isClassProtected(classDef)) { 342 checkForRedundantModifier(classCtorAst, TokenTypes.LITERAL_PUBLIC); 343 } 344 } 345 346 /** 347 * Checks if given resource has redundant modifiers. 348 * @param ast ast 349 */ 350 private void processResources(DetailAST ast) { 351 checkForRedundantModifier(ast, TokenTypes.FINAL); 352 } 353 354 /** 355 * Checks if given ast has a redundant modifier. 356 * @param ast ast 357 * @param modifierType The modifier to check for. 358 */ 359 private void checkForRedundantModifier(DetailAST ast, int modifierType) { 360 final DetailAST astModifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 361 DetailAST astModifier = astModifiers.getFirstChild(); 362 while (astModifier != null) { 363 if (astModifier.getType() == modifierType) { 364 log(astModifier.getLineNo(), astModifier.getColumnNo(), 365 MSG_KEY, astModifier.getText()); 366 } 367 368 astModifier = astModifier.getNextSibling(); 369 } 370 } 371 372 /** 373 * Checks if given class ast has protected modifier. 374 * @param classDef class ast 375 * @return true if class is protected, false otherwise 376 */ 377 private static boolean isClassProtected(DetailAST classDef) { 378 final DetailAST classModifiers = 379 classDef.findFirstToken(TokenTypes.MODIFIERS); 380 return classModifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) != null; 381 } 382 383 /** 384 * Checks if given class is accessible from "public" scope. 385 * @param ast class def to check 386 * @return true if class is accessible from public scope,false otherwise 387 */ 388 private static boolean isClassPublic(DetailAST ast) { 389 boolean isAccessibleFromPublic = false; 390 final boolean isMostOuterScope = ast.getParent() == null; 391 final DetailAST modifiersAst = ast.findFirstToken(TokenTypes.MODIFIERS); 392 final boolean hasPublicModifier = 393 modifiersAst.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null; 394 395 if (isMostOuterScope) { 396 isAccessibleFromPublic = hasPublicModifier; 397 } 398 else { 399 final DetailAST parentClassAst = ast.getParent().getParent(); 400 401 if (hasPublicModifier || parentClassAst.getType() == TokenTypes.INTERFACE_DEF) { 402 isAccessibleFromPublic = isClassPublic(parentClassAst); 403 } 404 } 405 406 return isAccessibleFromPublic; 407 } 408 409 /** 410 * Checks if current AST node is member of Enum. 411 * @param ast AST node 412 * @return true if it is an enum member 413 */ 414 private static boolean isEnumMember(DetailAST ast) { 415 final DetailAST parentTypeDef = ast.getParent().getParent(); 416 return parentTypeDef.getType() == TokenTypes.ENUM_DEF; 417 } 418 419 /** 420 * Checks if current AST node is member of Interface or Annotation, not of their subnodes. 421 * @param ast AST node 422 * @return true or false 423 */ 424 private static boolean isInterfaceOrAnnotationMember(DetailAST ast) { 425 DetailAST parentTypeDef = ast.getParent(); 426 427 if (parentTypeDef != null) { 428 parentTypeDef = parentTypeDef.getParent(); 429 } 430 return parentTypeDef != null 431 && (parentTypeDef.getType() == TokenTypes.INTERFACE_DEF 432 || parentTypeDef.getType() == TokenTypes.ANNOTATION_DEF); 433 } 434 435 /** 436 * Checks if method definition is annotated with. 437 * <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/SafeVarargs.html"> 438 * SafeVarargs</a> annotation 439 * @param methodDef method definition node 440 * @return true or false 441 */ 442 private static boolean isAnnotatedWithSafeVarargs(DetailAST methodDef) { 443 boolean result = false; 444 final List<DetailAST> methodAnnotationsList = getMethodAnnotationsList(methodDef); 445 for (DetailAST annotationNode : methodAnnotationsList) { 446 if ("SafeVarargs".equals(annotationNode.getLastChild().getText())) { 447 result = true; 448 break; 449 } 450 } 451 return result; 452 } 453 454 /** 455 * Gets the list of annotations on method definition. 456 * @param methodDef method definition node 457 * @return List of annotations 458 */ 459 private static List<DetailAST> getMethodAnnotationsList(DetailAST methodDef) { 460 final List<DetailAST> annotationsList = new ArrayList<>(); 461 final DetailAST modifiers = methodDef.findFirstToken(TokenTypes.MODIFIERS); 462 DetailAST modifier = modifiers.getFirstChild(); 463 while (modifier != null) { 464 if (modifier.getType() == TokenTypes.ANNOTATION) { 465 annotationsList.add(modifier); 466 } 467 modifier = modifier.getNextSibling(); 468 } 469 return annotationsList; 470 } 471 472}