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.design; 021 022import java.util.Arrays; 023import java.util.Optional; 024import java.util.Set; 025import java.util.function.Predicate; 026import java.util.stream.Collectors; 027 028import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 029import com.puppycrawl.tools.checkstyle.api.DetailAST; 030import com.puppycrawl.tools.checkstyle.api.Scope; 031import com.puppycrawl.tools.checkstyle.api.TokenTypes; 032import com.puppycrawl.tools.checkstyle.utils.ScopeUtils; 033import com.puppycrawl.tools.checkstyle.utils.TokenUtils; 034 035/** 036 * The check finds classes that are designed for extension (subclass creation). 037 * 038 * <p> 039 * Nothing wrong could be with founded classes. 040 * This check makes sense only for library projects (not an application projects) 041 * which care of ideal OOP-design to make sure that class works in all cases even misusage. 042 * Even in library projects this check most likely will find classes that are designed for extension 043 * by somebody. User needs to use suppressions extensively to got a benefit from this check, 044 * and keep in suppressions all confirmed/known classes that are deigned for inheritance 045 * intentionally to let the check catch only new classes, and bring this to team/user attention. 046 * </p> 047 * 048 * <p> 049 * ATTENTION: Only user can decide whether a class is designed for extension or not. 050 * The check just shows all classes which are possibly designed for extension. 051 * If smth inappropriate is found please use suppression. 052 * </p> 053 * 054 * <p> 055 * ATTENTION: If the method which can be overridden in a subclass has a javadoc comment 056 * (a good practise is to explain its self-use of overridable methods) the check will not 057 * rise a violation. The violation can also be skipped if the method which can be overridden 058 * in a subclass has one or more annotations that are specified in ignoredAnnotations 059 * option. Note, that by default @Override annotation is not included in the 060 * ignoredAnnotations set as in a subclass the method which has the annotation can also be 061 * overridden in its subclass. 062 * </p> 063 * 064 * <p> 065 * More specifically, the check enforces a programming style where superclasses provide empty 066 * "hooks" that can be implemented by subclasses. 067 * </p> 068 * 069 * <p> 070 * The check finds classes that have overridable methods (public or protected methods 071 * that are non-static, not-final, non-abstract) and have non-empty implementation. 072 * </p> 073 * 074 * <p> 075 * This protects superclasses against being broken by subclasses. The downside is that subclasses 076 * are limited in their flexibility, in particular, they cannot prevent execution of code in the 077 * superclass, but that also means that subclasses cannot forget to call their super method. 078 * </p> 079 * 080 * <p> 081 * The check has the following options: 082 * </p> 083 * <ul> 084 * <li> 085 * ignoredAnnotations - annotations which allow the check to skip the method from validation. 086 * Default value is <b>Test, Before, After, BeforeClass, AfterClass</b>. 087 * </li> 088 * </ul> 089 * 090 * @author lkuehne 091 * @author Andrei Selkin 092 */ 093public class DesignForExtensionCheck extends AbstractCheck { 094 095 /** 096 * A key is pointing to the warning message text in "messages.properties" 097 * file. 098 */ 099 public static final String MSG_KEY = "design.forExtension"; 100 101 /** 102 * A set of annotations which allow the check to skip the method from validation. 103 */ 104 private Set<String> ignoredAnnotations = Arrays.stream(new String[] {"Test", "Before", "After", 105 "BeforeClass", "AfterClass", }).collect(Collectors.toSet()); 106 107 /** 108 * Sets annotations which allow the check to skip the method from validation. 109 * @param ignoredAnnotations method annotations. 110 */ 111 public void setIgnoredAnnotations(String... ignoredAnnotations) { 112 this.ignoredAnnotations = Arrays.stream(ignoredAnnotations).collect(Collectors.toSet()); 113 } 114 115 @Override 116 public int[] getDefaultTokens() { 117 return getAcceptableTokens(); 118 } 119 120 @Override 121 public int[] getAcceptableTokens() { 122 // The check does not subscribe to CLASS_DEF token as now it is stateless. If the check 123 // subscribes to CLASS_DEF token it will become stateful, since we need to have additional 124 // stack to hold CLASS_DEF tokens. 125 return new int[] {TokenTypes.METHOD_DEF}; 126 } 127 128 @Override 129 public int[] getRequiredTokens() { 130 return getAcceptableTokens(); 131 } 132 133 @Override 134 public boolean isCommentNodesRequired() { 135 return true; 136 } 137 138 @Override 139 public void visitToken(DetailAST ast) { 140 if (!hasJavadocComment(ast) 141 && canBeOverridden(ast) 142 && (isNativeMethod(ast) 143 || !hasEmptyImplementation(ast)) 144 && !hasIgnoredAnnotation(ast, ignoredAnnotations)) { 145 146 final DetailAST classDef = getNearestClassOrEnumDefinition(ast); 147 if (canBeSubclassed(classDef)) { 148 final String className = classDef.findFirstToken(TokenTypes.IDENT).getText(); 149 final String methodName = ast.findFirstToken(TokenTypes.IDENT).getText(); 150 log(ast.getLineNo(), ast.getColumnNo(), MSG_KEY, className, methodName); 151 } 152 } 153 } 154 155 /** 156 * Checks whether a method has a javadoc comment. 157 * @param methodDef method definition token. 158 * @return true if a method has a javadoc comment. 159 */ 160 private boolean hasJavadocComment(DetailAST methodDef) { 161 final DetailAST modifiers = methodDef.findFirstToken(TokenTypes.MODIFIERS); 162 return modifiers.branchContains(TokenTypes.BLOCK_COMMENT_BEGIN); 163 } 164 165 /** 166 * Checks whether a methods is native. 167 * @param ast method definition token. 168 * @return true if a methods is native. 169 */ 170 private boolean isNativeMethod(DetailAST ast) { 171 final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS); 172 return mods.branchContains(TokenTypes.LITERAL_NATIVE); 173 } 174 175 /** 176 * Checks whether a method has only comments in the body (has an empty implementation). 177 * Method is OK if its implementation is empty. 178 * @param ast method definition token. 179 * @return true if a method has only comments in the body. 180 */ 181 private static boolean hasEmptyImplementation(DetailAST ast) { 182 boolean hasEmptyBody = true; 183 final DetailAST methodImplOpenBrace = ast.findFirstToken(TokenTypes.SLIST); 184 final DetailAST methodImplCloseBrace = methodImplOpenBrace.getLastChild(); 185 final Predicate<DetailAST> predicate = currentNode -> { 186 return currentNode != methodImplCloseBrace 187 && !TokenUtils.isCommentType(currentNode.getType()); 188 }; 189 final Optional<DetailAST> methodBody = 190 TokenUtils.findFirstTokenByPredicate(methodImplOpenBrace, predicate); 191 if (methodBody.isPresent()) { 192 hasEmptyBody = false; 193 } 194 return hasEmptyBody; 195 } 196 197 /** 198 * Checks whether a method can be overridden. 199 * Method can be overridden if it is not private, abstract, final or static. 200 * Note that the check has nothing to do for interfaces. 201 * @param methodDef method definition token. 202 * @return true if a method can be overridden in a subclass. 203 */ 204 private boolean canBeOverridden(DetailAST methodDef) { 205 final DetailAST modifiers = methodDef.findFirstToken(TokenTypes.MODIFIERS); 206 return ScopeUtils.getSurroundingScope(methodDef).isIn(Scope.PROTECTED) 207 && !ScopeUtils.isInInterfaceOrAnnotationBlock(methodDef) 208 && !modifiers.branchContains(TokenTypes.LITERAL_PRIVATE) 209 && !modifiers.branchContains(TokenTypes.ABSTRACT) 210 && !modifiers.branchContains(TokenTypes.FINAL) 211 && !modifiers.branchContains(TokenTypes.LITERAL_STATIC); 212 } 213 214 /** 215 * Checks whether a method has any of ignored annotations. 216 * @param methodDef method definition token. 217 * @param annotations a set of ignored annotations. 218 * @return true if a method has any of ignored annotations. 219 */ 220 private static boolean hasIgnoredAnnotation(DetailAST methodDef, Set<String> annotations) { 221 final DetailAST modifiers = methodDef.findFirstToken(TokenTypes.MODIFIERS); 222 boolean hasIgnoredAnnotation = false; 223 if (modifiers.branchContains(TokenTypes.ANNOTATION)) { 224 final Optional<DetailAST> annotation = TokenUtils.findFirstTokenByPredicate(modifiers, 225 currentToken -> { 226 return currentToken.getType() == TokenTypes.ANNOTATION 227 && annotations.contains(getAnnotationName(currentToken)); 228 }); 229 if (annotation.isPresent()) { 230 hasIgnoredAnnotation = true; 231 } 232 } 233 return hasIgnoredAnnotation; 234 } 235 236 /** 237 * Gets the name of the annotation. 238 * @param annotation to get name of. 239 * @return the name of the annotation. 240 */ 241 private static String getAnnotationName(DetailAST annotation) { 242 final DetailAST dotAst = annotation.findFirstToken(TokenTypes.DOT); 243 final String name; 244 if (dotAst == null) { 245 name = annotation.findFirstToken(TokenTypes.IDENT).getText(); 246 } 247 else { 248 name = dotAst.findFirstToken(TokenTypes.IDENT).getText(); 249 } 250 return name; 251 } 252 253 /** 254 * Returns CLASS_DEF or ENUM_DEF token which is the nearest to the given ast node. 255 * Searches the tree towards the root until it finds a CLASS_DEF or ENUM_DEF node. 256 * @param ast the start node for searching. 257 * @return the CLASS_DEF or ENUM_DEF token. 258 */ 259 private static DetailAST getNearestClassOrEnumDefinition(DetailAST ast) { 260 DetailAST searchAST = ast; 261 while (searchAST.getType() != TokenTypes.CLASS_DEF 262 && searchAST.getType() != TokenTypes.ENUM_DEF) { 263 searchAST = searchAST.getParent(); 264 } 265 return searchAST; 266 } 267 268 /** 269 * Checks if the given class (given CLASS_DEF node) can be subclassed. 270 * @param classDef class definition token. 271 * @return true if the containing class can be subclassed. 272 */ 273 private static boolean canBeSubclassed(DetailAST classDef) { 274 final DetailAST modifiers = classDef.findFirstToken(TokenTypes.MODIFIERS); 275 return classDef.getType() != TokenTypes.ENUM_DEF 276 && !modifiers.branchContains(TokenTypes.FINAL) 277 && hasDefaultOrExplicitNonPrivateCtor(classDef); 278 } 279 280 /** 281 * Checks whether a class has default or explicit non-private constructor. 282 * @param classDef class ast token. 283 * @return true if a class has default or explicit non-private constructor. 284 */ 285 private static boolean hasDefaultOrExplicitNonPrivateCtor(DetailAST classDef) { 286 // check if subclassing is prevented by having only private ctors 287 final DetailAST objBlock = classDef.findFirstToken(TokenTypes.OBJBLOCK); 288 289 boolean hasDefaultConstructor = true; 290 boolean hasExplicitNonPrivateCtor = false; 291 292 DetailAST candidate = objBlock.getFirstChild(); 293 294 while (candidate != null) { 295 if (candidate.getType() == TokenTypes.CTOR_DEF) { 296 hasDefaultConstructor = false; 297 298 final DetailAST ctorMods = 299 candidate.findFirstToken(TokenTypes.MODIFIERS); 300 if (!ctorMods.branchContains(TokenTypes.LITERAL_PRIVATE)) { 301 hasExplicitNonPrivateCtor = true; 302 break; 303 } 304 } 305 candidate = candidate.getNextSibling(); 306 } 307 308 return hasDefaultConstructor || hasExplicitNonPrivateCtor; 309 } 310}