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