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.CTOR_DEF) { 145 if (isEnumMember(ast)) { 146 checkEnumConstructorModifiers(ast); 147 } 148 else { 149 checkClassConstructorModifiers(ast); 150 } 151 } 152 else if (ast.getType() == TokenTypes.ENUM_DEF) { 153 checkEnumDef(ast); 154 } 155 else if (isInterfaceOrAnnotationMember(ast)) { 156 processInterfaceOrAnnotation(ast); 157 } 158 else if (ast.getType() == TokenTypes.METHOD_DEF) { 159 processMethods(ast); 160 } 161 else if (ast.getType() == TokenTypes.RESOURCE) { 162 processResources(ast); 163 } 164 } 165 166 /** 167 * Checks if interface has proper modifiers. 168 * @param ast interface to check 169 */ 170 private void checkInterfaceModifiers(DetailAST ast) { 171 final DetailAST modifiers = 172 ast.findFirstToken(TokenTypes.MODIFIERS); 173 174 for (final int tokenType : TOKENS_FOR_INTERFACE_MODIFIERS) { 175 final DetailAST modifier = 176 modifiers.findFirstToken(tokenType); 177 if (modifier != null) { 178 log(modifier.getLineNo(), modifier.getColumnNo(), 179 MSG_KEY, modifier.getText()); 180 } 181 } 182 } 183 184 /** 185 * Check if enum constructor has proper modifiers. 186 * @param ast constructor of enum 187 */ 188 private void checkEnumConstructorModifiers(DetailAST ast) { 189 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 190 final DetailAST modifier = modifiers.getFirstChild(); 191 if (modifier != null) { 192 log(modifier.getLineNo(), modifier.getColumnNo(), 193 MSG_KEY, modifier.getText()); 194 } 195 } 196 197 /** 198 * Checks whether enum has proper modifiers. 199 * @param ast enum definition. 200 */ 201 private void checkEnumDef(DetailAST ast) { 202 if (isInterfaceOrAnnotationMember(ast)) { 203 processInterfaceOrAnnotation(ast); 204 } 205 else if (ast.getParent() != null) { 206 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 207 final DetailAST staticModifier = modifiers.findFirstToken(TokenTypes.LITERAL_STATIC); 208 if (staticModifier != null) { 209 log(staticModifier.getLineNo(), staticModifier.getColumnNo(), 210 MSG_KEY, staticModifier.getText()); 211 } 212 } 213 } 214 215 /** 216 * Do validation of interface of annotation. 217 * @param ast token AST 218 */ 219 private void processInterfaceOrAnnotation(DetailAST ast) { 220 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 221 DetailAST modifier = modifiers.getFirstChild(); 222 while (modifier != null) { 223 224 // javac does not allow final or static in interface methods 225 // order annotation fields hence no need to check that this 226 // is not a method or annotation field 227 228 final int type = modifier.getType(); 229 if (type == TokenTypes.LITERAL_PUBLIC 230 || type == TokenTypes.LITERAL_STATIC 231 && ast.getType() != TokenTypes.METHOD_DEF 232 || type == TokenTypes.ABSTRACT 233 && ast.getType() != TokenTypes.CLASS_DEF 234 || type == TokenTypes.FINAL 235 && ast.getType() != TokenTypes.CLASS_DEF) { 236 log(modifier.getLineNo(), modifier.getColumnNo(), 237 MSG_KEY, modifier.getText()); 238 break; 239 } 240 241 modifier = modifier.getNextSibling(); 242 } 243 } 244 245 /** 246 * Process validation ofMethods. 247 * @param ast method AST 248 */ 249 private void processMethods(DetailAST ast) { 250 final DetailAST modifiers = 251 ast.findFirstToken(TokenTypes.MODIFIERS); 252 // private method? 253 boolean checkFinal = 254 modifiers.branchContains(TokenTypes.LITERAL_PRIVATE); 255 // declared in a final class? 256 DetailAST parent = ast.getParent(); 257 while (parent != null) { 258 if (parent.getType() == TokenTypes.CLASS_DEF) { 259 final DetailAST classModifiers = 260 parent.findFirstToken(TokenTypes.MODIFIERS); 261 checkFinal = checkFinal || classModifiers.branchContains(TokenTypes.FINAL); 262 parent = null; 263 } 264 else if (parent.getType() == TokenTypes.LITERAL_NEW) { 265 checkFinal = true; 266 parent = null; 267 } 268 else { 269 parent = parent.getParent(); 270 } 271 } 272 if (checkFinal && !isAnnotatedWithSafeVarargs(ast)) { 273 DetailAST modifier = modifiers.getFirstChild(); 274 while (modifier != null) { 275 final int type = modifier.getType(); 276 if (type == TokenTypes.FINAL) { 277 log(modifier.getLineNo(), modifier.getColumnNo(), 278 MSG_KEY, modifier.getText()); 279 break; 280 } 281 modifier = modifier.getNextSibling(); 282 } 283 } 284 } 285 286 /** 287 * Check if class constructor has proper modifiers. 288 * @param classCtorAst class constructor ast 289 */ 290 private void checkClassConstructorModifiers(DetailAST classCtorAst) { 291 final DetailAST classDef = classCtorAst.getParent().getParent(); 292 if (!isClassPublic(classDef) && !isClassProtected(classDef)) { 293 checkForRedundantPublicModifier(classCtorAst); 294 } 295 } 296 297 /** 298 * Checks if given resource has redundant modifiers. 299 * @param ast ast 300 */ 301 private void processResources(DetailAST ast) { 302 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 303 DetailAST modifier = modifiers.getFirstChild(); 304 305 while (modifier != null) { 306 final int type = modifier.getType(); 307 308 if (type == TokenTypes.FINAL) { 309 log(modifier.getLineNo(), modifier.getColumnNo(), MSG_KEY, modifier.getText()); 310 break; 311 } 312 313 modifier = modifier.getNextSibling(); 314 } 315 } 316 317 /** 318 * Checks if given ast has redundant public modifier. 319 * @param ast ast 320 */ 321 private void checkForRedundantPublicModifier(DetailAST ast) { 322 final DetailAST astModifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 323 DetailAST astModifier = astModifiers.getFirstChild(); 324 while (astModifier != null) { 325 if (astModifier.getType() == TokenTypes.LITERAL_PUBLIC) { 326 log(astModifier.getLineNo(), astModifier.getColumnNo(), 327 MSG_KEY, astModifier.getText()); 328 } 329 330 astModifier = astModifier.getNextSibling(); 331 } 332 } 333 334 /** 335 * Checks if given class ast has protected modifier. 336 * @param classDef class ast 337 * @return true if class is protected, false otherwise 338 */ 339 private static boolean isClassProtected(DetailAST classDef) { 340 final DetailAST classModifiers = 341 classDef.findFirstToken(TokenTypes.MODIFIERS); 342 return classModifiers.branchContains(TokenTypes.LITERAL_PROTECTED); 343 } 344 345 /** 346 * Checks if given class is accessible from "public" scope. 347 * @param ast class def to check 348 * @return true if class is accessible from public scope,false otherwise 349 */ 350 private static boolean isClassPublic(DetailAST ast) { 351 boolean isAccessibleFromPublic = false; 352 final boolean isMostOuterScope = ast.getParent() == null; 353 final DetailAST modifiersAst = ast.findFirstToken(TokenTypes.MODIFIERS); 354 final boolean hasPublicModifier = modifiersAst.branchContains(TokenTypes.LITERAL_PUBLIC); 355 356 if (isMostOuterScope) { 357 isAccessibleFromPublic = hasPublicModifier; 358 } 359 else { 360 final DetailAST parentClassAst = ast.getParent().getParent(); 361 362 if (hasPublicModifier || parentClassAst.getType() == TokenTypes.INTERFACE_DEF) { 363 isAccessibleFromPublic = isClassPublic(parentClassAst); 364 } 365 } 366 367 return isAccessibleFromPublic; 368 } 369 370 /** 371 * Checks if current AST node is member of Enum. 372 * @param ast AST node 373 * @return true if it is an enum member 374 */ 375 private static boolean isEnumMember(DetailAST ast) { 376 final DetailAST parentTypeDef = ast.getParent().getParent(); 377 return parentTypeDef.getType() == TokenTypes.ENUM_DEF; 378 } 379 380 /** 381 * Checks if current AST node is member of Interface or Annotation, not of their subnodes. 382 * @param ast AST node 383 * @return true or false 384 */ 385 private static boolean isInterfaceOrAnnotationMember(DetailAST ast) { 386 DetailAST parentTypeDef = ast.getParent(); 387 388 if (parentTypeDef != null) { 389 parentTypeDef = parentTypeDef.getParent(); 390 } 391 return parentTypeDef != null 392 && (parentTypeDef.getType() == TokenTypes.INTERFACE_DEF 393 || parentTypeDef.getType() == TokenTypes.ANNOTATION_DEF); 394 } 395 396 /** 397 * Checks if method definition is annotated with 398 * <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/SafeVarargs.html"> 399 * SafeVarargs</a> annotation 400 * @param methodDef method definition node 401 * @return true or false 402 */ 403 private static boolean isAnnotatedWithSafeVarargs(DetailAST methodDef) { 404 boolean result = false; 405 final List<DetailAST> methodAnnotationsList = getMethodAnnotationsList(methodDef); 406 for (DetailAST annotationNode : methodAnnotationsList) { 407 if ("SafeVarargs".equals(annotationNode.getLastChild().getText())) { 408 result = true; 409 break; 410 } 411 } 412 return result; 413 } 414 415 /** 416 * Gets the list of annotations on method definition. 417 * @param methodDef method definition node 418 * @return List of annotations 419 */ 420 private static List<DetailAST> getMethodAnnotationsList(DetailAST methodDef) { 421 final List<DetailAST> annotationsList = new ArrayList<>(); 422 final DetailAST modifiers = methodDef.findFirstToken(TokenTypes.MODIFIERS); 423 DetailAST modifier = modifiers.getFirstChild(); 424 while (modifier != null) { 425 if (modifier.getType() == TokenTypes.ANNOTATION) { 426 annotationsList.add(modifier); 427 } 428 modifier = modifier.getNextSibling(); 429 } 430 return annotationsList; 431 } 432}