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