001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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.StatelessCheck; 029import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 030import com.puppycrawl.tools.checkstyle.api.DetailAST; 031import com.puppycrawl.tools.checkstyle.api.Scope; 032import com.puppycrawl.tools.checkstyle.api.TokenTypes; 033import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 034import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 035import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 036 037/** 038 * <p> 039 * Checks that classes are designed for extension (subclass creation). 040 * </p> 041 * <p> 042 * Nothing wrong could be with founded classes. 043 * This check makes sense only for library projects (not application projects) 044 * which care of ideal OOP-design to make sure that class works in all cases even misusage. 045 * Even in library projects this check most likely will find classes that are designed for extension 046 * by somebody. User needs to use suppressions extensively to got a benefit from this check, 047 * and keep in suppressions all confirmed/known classes that are deigned for inheritance 048 * intentionally to let the check catch only new classes, and bring this to team/user attention. 049 * </p> 050 * 051 * <p> 052 * ATTENTION: Only user can decide whether a class is designed for extension or not. 053 * The check just shows all classes which are possibly designed for extension. 054 * If smth inappropriate is found please use suppression. 055 * </p> 056 * 057 * <p> 058 * ATTENTION: If the method which can be overridden in a subclass has a javadoc comment 059 * (a good practice is to explain its self-use of overridable methods) the check will not 060 * rise a violation. The violation can also be skipped if the method which can be overridden 061 * in a subclass has one or more annotations that are specified in ignoredAnnotations 062 * option. Note, that by default @Override annotation is not included in the 063 * ignoredAnnotations set as in a subclass the method which has the annotation can also be 064 * overridden in its subclass. 065 * </p> 066 * <p> 067 * Problem is described at "Effective Java, 2nd Edition by Joshua Bloch" book, chapter 068 * "Item 17: Design and document for inheritance or else prohibit it". 069 * </p> 070 * <p> 071 * Some quotes from book: 072 * </p> 073 * <blockquote>The class must document its self-use of overridable methods. 074 * By convention, a method that invokes overridable methods contains a description 075 * of these invocations at the end of its documentation comment. The description 076 * begins with the phrase “This implementation.” 077 * </blockquote> 078 * <blockquote> 079 * The best solution to this problem is to prohibit subclassing in classes that 080 * are not designed and documented to be safely subclassed. 081 * </blockquote> 082 * <blockquote> 083 * If a concrete class does not implement a standard interface, then you may 084 * inconvenience some programmers by prohibiting inheritance. If you feel that you 085 * must allow inheritance from such a class, one reasonable approach is to ensure 086 * that the class never invokes any of its overridable methods and to document this 087 * fact. In other words, eliminate the class’s self-use of overridable methods entirely. 088 * In doing so, you’ll create a class that is reasonably safe to subclass. Overriding a 089 * method will never affect the behavior of any other method. 090 * </blockquote> 091 * <p> 092 * The check finds classes that have overridable methods (public or protected methods 093 * that are non-static, not-final, non-abstract) and have non-empty implementation. 094 * </p> 095 * <p> 096 * Rationale: This library design style protects superclasses against being broken 097 * by subclasses. The downside is that subclasses are limited in their flexibility, 098 * in particular they cannot prevent execution of code in the superclass, but that 099 * also means that subclasses cannot corrupt the state of the superclass by forgetting 100 * to call the superclass's method. 101 * </p> 102 * <p> 103 * More specifically, it enforces a programming style where superclasses provide 104 * empty "hooks" that can be implemented by subclasses. 105 * </p> 106 * <p> 107 * Example of code that cause violation as it is designed for extension: 108 * </p> 109 * <pre> 110 * public abstract class Plant { 111 * private String roots; 112 * private String trunk; 113 * 114 * protected void validate() { 115 * if (roots == null) throw new IllegalArgumentException("No roots!"); 116 * if (trunk == null) throw new IllegalArgumentException("No trunk!"); 117 * } 118 * 119 * public abstract void grow(); 120 * } 121 * 122 * public class Tree extends Plant { 123 * private List leaves; 124 * 125 * @Overrides 126 * protected void validate() { 127 * super.validate(); 128 * if (leaves == null) throw new IllegalArgumentException("No leaves!"); 129 * } 130 * 131 * public void grow() { 132 * validate(); 133 * } 134 * } 135 * </pre> 136 * <p> 137 * Example of code without violation: 138 * </p> 139 * <pre> 140 * public abstract class Plant { 141 * private String roots; 142 * private String trunk; 143 * 144 * private void validate() { 145 * if (roots == null) throw new IllegalArgumentException("No roots!"); 146 * if (trunk == null) throw new IllegalArgumentException("No trunk!"); 147 * validateEx(); 148 * } 149 * 150 * protected void validateEx() { } 151 * 152 * public abstract void grow(); 153 * } 154 * </pre> 155 * <ul> 156 * <li> 157 * Property {@code ignoredAnnotations} - Specify annotations which allow the check to 158 * skip the method from validation. 159 * Type is {@code java.lang.String[]}. 160 * Default value is {@code After, AfterClass, Before, BeforeClass, Test}. 161 * </li> 162 * </ul> 163 * <p> 164 * To configure the check: 165 * </p> 166 * <pre> 167 * <module name="DesignForExtension"/> 168 * </pre> 169 * <p> 170 * To configure the check to allow methods which have @Override and @Test annotations 171 * to be designed for extension. 172 * </p> 173 * <pre> 174 * <module name="DesignForExtension"> 175 * <property name="ignoredAnnotations" value="Override, Test"/> 176 * </module> 177 * </pre> 178 * <pre> 179 * public class A extends B { 180 * @Override 181 * public int foo() { 182 * return 2; 183 * } 184 * 185 * public int foo2() {return 8;} // violation 186 * } 187 * 188 * public class B { 189 * /** 190 * * This implementation ... 191 * @return some int value. 192 * */ 193 * public int foo() { 194 * return 1; 195 * } 196 * 197 * public int foo3() {return 3;} // violation 198 * } 199 * 200 * public class FooTest { 201 * @Test 202 * public void testFoo() { 203 * final B b = new A(); 204 * assertEquals(2, b.foo()); 205 * } 206 * 207 * public int foo4() {return 4;} // violation 208 * } 209 * </pre> 210 * <p> 211 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 212 * </p> 213 * <p> 214 * Violation Message Keys: 215 * </p> 216 * <ul> 217 * <li> 218 * {@code design.forExtension} 219 * </li> 220 * </ul> 221 * 222 * @since 3.1 223 */ 224@StatelessCheck 225public class DesignForExtensionCheck extends AbstractCheck { 226 227 /** 228 * A key is pointing to the warning message text in "messages.properties" 229 * file. 230 */ 231 public static final String MSG_KEY = "design.forExtension"; 232 233 /** 234 * Specify annotations which allow the check to skip the method from validation. 235 */ 236 private Set<String> ignoredAnnotations = Arrays.stream(new String[] {"Test", "Before", "After", 237 "BeforeClass", "AfterClass", }).collect(Collectors.toSet()); 238 239 /** 240 * Setter to specify annotations which allow the check to skip the method from validation. 241 * 242 * @param ignoredAnnotations method annotations. 243 */ 244 public void setIgnoredAnnotations(String... ignoredAnnotations) { 245 this.ignoredAnnotations = Arrays.stream(ignoredAnnotations).collect(Collectors.toSet()); 246 } 247 248 @Override 249 public int[] getDefaultTokens() { 250 return getRequiredTokens(); 251 } 252 253 @Override 254 public int[] getAcceptableTokens() { 255 return getRequiredTokens(); 256 } 257 258 @Override 259 public int[] getRequiredTokens() { 260 // The check does not subscribe to CLASS_DEF token as now it is stateless. If the check 261 // subscribes to CLASS_DEF token it will become stateful, since we need to have additional 262 // stack to hold CLASS_DEF tokens. 263 return new int[] {TokenTypes.METHOD_DEF}; 264 } 265 266 @Override 267 public boolean isCommentNodesRequired() { 268 return true; 269 } 270 271 @Override 272 public void visitToken(DetailAST ast) { 273 if (!hasJavadocComment(ast) 274 && canBeOverridden(ast) 275 && (isNativeMethod(ast) 276 || !hasEmptyImplementation(ast)) 277 && !hasIgnoredAnnotation(ast, ignoredAnnotations) 278 && !ScopeUtil.isInRecordBlock(ast)) { 279 final DetailAST classDef = getNearestClassOrEnumDefinition(ast); 280 if (canBeSubclassed(classDef)) { 281 final String className = classDef.findFirstToken(TokenTypes.IDENT).getText(); 282 final String methodName = ast.findFirstToken(TokenTypes.IDENT).getText(); 283 log(ast, MSG_KEY, className, methodName); 284 } 285 } 286 } 287 288 /** 289 * Checks whether a method has a javadoc comment. 290 * 291 * @param methodDef method definition token. 292 * @return true if a method has a javadoc comment. 293 */ 294 private static boolean hasJavadocComment(DetailAST methodDef) { 295 return hasJavadocCommentOnToken(methodDef, TokenTypes.MODIFIERS) 296 || hasJavadocCommentOnToken(methodDef, TokenTypes.TYPE); 297 } 298 299 /** 300 * Checks whether a token has a javadoc comment. 301 * 302 * @param methodDef method definition token. 303 * @param tokenType token type. 304 * @return true if a token has a javadoc comment. 305 */ 306 private static boolean hasJavadocCommentOnToken(DetailAST methodDef, int tokenType) { 307 final DetailAST token = methodDef.findFirstToken(tokenType); 308 return branchContainsJavadocComment(token); 309 } 310 311 /** 312 * Checks whether a javadoc comment exists under the token. 313 * 314 * @param token tree token. 315 * @return true if a javadoc comment exists under the token. 316 */ 317 private static boolean branchContainsJavadocComment(DetailAST token) { 318 boolean result = false; 319 DetailAST curNode = token; 320 while (curNode != null) { 321 if (curNode.getType() == TokenTypes.BLOCK_COMMENT_BEGIN 322 && JavadocUtil.isJavadocComment(curNode)) { 323 result = true; 324 break; 325 } 326 327 DetailAST toVisit = curNode.getFirstChild(); 328 while (toVisit == null) { 329 if (curNode == token) { 330 break; 331 } 332 333 toVisit = curNode.getNextSibling(); 334 curNode = curNode.getParent(); 335 } 336 curNode = toVisit; 337 } 338 339 return result; 340 } 341 342 /** 343 * Checks whether a methods is native. 344 * 345 * @param ast method definition token. 346 * @return true if a methods is native. 347 */ 348 private static boolean isNativeMethod(DetailAST ast) { 349 final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS); 350 return mods.findFirstToken(TokenTypes.LITERAL_NATIVE) != null; 351 } 352 353 /** 354 * Checks whether a method has only comments in the body (has an empty implementation). 355 * Method is OK if its implementation is empty. 356 * 357 * @param ast method definition token. 358 * @return true if a method has only comments in the body. 359 */ 360 private static boolean hasEmptyImplementation(DetailAST ast) { 361 boolean hasEmptyBody = true; 362 final DetailAST methodImplOpenBrace = ast.findFirstToken(TokenTypes.SLIST); 363 final DetailAST methodImplCloseBrace = methodImplOpenBrace.getLastChild(); 364 final Predicate<DetailAST> predicate = currentNode -> { 365 return currentNode != methodImplCloseBrace 366 && !TokenUtil.isCommentType(currentNode.getType()); 367 }; 368 final Optional<DetailAST> methodBody = 369 TokenUtil.findFirstTokenByPredicate(methodImplOpenBrace, predicate); 370 if (methodBody.isPresent()) { 371 hasEmptyBody = false; 372 } 373 return hasEmptyBody; 374 } 375 376 /** 377 * Checks whether a method can be overridden. 378 * Method can be overridden if it is not private, abstract, final or static. 379 * Note that the check has nothing to do for interfaces. 380 * 381 * @param methodDef method definition token. 382 * @return true if a method can be overridden in a subclass. 383 */ 384 private static boolean canBeOverridden(DetailAST methodDef) { 385 final DetailAST modifiers = methodDef.findFirstToken(TokenTypes.MODIFIERS); 386 return ScopeUtil.getSurroundingScope(methodDef).isIn(Scope.PROTECTED) 387 && !ScopeUtil.isInInterfaceOrAnnotationBlock(methodDef) 388 && modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null 389 && modifiers.findFirstToken(TokenTypes.ABSTRACT) == null 390 && modifiers.findFirstToken(TokenTypes.FINAL) == null 391 && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) == null; 392 } 393 394 /** 395 * Checks whether a method has any of ignored annotations. 396 * 397 * @param methodDef method definition token. 398 * @param annotations a set of ignored annotations. 399 * @return true if a method has any of ignored annotations. 400 */ 401 private static boolean hasIgnoredAnnotation(DetailAST methodDef, Set<String> annotations) { 402 final DetailAST modifiers = methodDef.findFirstToken(TokenTypes.MODIFIERS); 403 final Optional<DetailAST> annotation = TokenUtil.findFirstTokenByPredicate(modifiers, 404 currentToken -> { 405 return currentToken.getType() == TokenTypes.ANNOTATION 406 && annotations.contains(getAnnotationName(currentToken)); 407 }); 408 return annotation.isPresent(); 409 } 410 411 /** 412 * Gets the name of the annotation. 413 * 414 * @param annotation to get name of. 415 * @return the name of the annotation. 416 */ 417 private static String getAnnotationName(DetailAST annotation) { 418 final DetailAST dotAst = annotation.findFirstToken(TokenTypes.DOT); 419 final String name; 420 if (dotAst == null) { 421 name = annotation.findFirstToken(TokenTypes.IDENT).getText(); 422 } 423 else { 424 name = dotAst.findFirstToken(TokenTypes.IDENT).getText(); 425 } 426 return name; 427 } 428 429 /** 430 * Returns CLASS_DEF or ENUM_DEF token which is the nearest to the given ast node. 431 * Searches the tree towards the root until it finds a CLASS_DEF or ENUM_DEF node. 432 * 433 * @param ast the start node for searching. 434 * @return the CLASS_DEF or ENUM_DEF token. 435 */ 436 private static DetailAST getNearestClassOrEnumDefinition(DetailAST ast) { 437 DetailAST searchAST = ast; 438 while (searchAST.getType() != TokenTypes.CLASS_DEF 439 && searchAST.getType() != TokenTypes.ENUM_DEF) { 440 searchAST = searchAST.getParent(); 441 } 442 return searchAST; 443 } 444 445 /** 446 * Checks if the given class (given CLASS_DEF node) can be subclassed. 447 * 448 * @param classDef class definition token. 449 * @return true if the containing class can be subclassed. 450 */ 451 private static boolean canBeSubclassed(DetailAST classDef) { 452 final DetailAST modifiers = classDef.findFirstToken(TokenTypes.MODIFIERS); 453 return classDef.getType() != TokenTypes.ENUM_DEF 454 && modifiers.findFirstToken(TokenTypes.FINAL) == null 455 && hasDefaultOrExplicitNonPrivateCtor(classDef); 456 } 457 458 /** 459 * Checks whether a class has default or explicit non-private constructor. 460 * 461 * @param classDef class ast token. 462 * @return true if a class has default or explicit non-private constructor. 463 */ 464 private static boolean hasDefaultOrExplicitNonPrivateCtor(DetailAST classDef) { 465 // check if subclassing is prevented by having only private ctors 466 final DetailAST objBlock = classDef.findFirstToken(TokenTypes.OBJBLOCK); 467 468 boolean hasDefaultConstructor = true; 469 boolean hasExplicitNonPrivateCtor = false; 470 471 DetailAST candidate = objBlock.getFirstChild(); 472 473 while (candidate != null) { 474 if (candidate.getType() == TokenTypes.CTOR_DEF) { 475 hasDefaultConstructor = false; 476 477 final DetailAST ctorMods = 478 candidate.findFirstToken(TokenTypes.MODIFIERS); 479 if (ctorMods.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null) { 480 hasExplicitNonPrivateCtor = true; 481 break; 482 } 483 } 484 candidate = candidate.getNextSibling(); 485 } 486 487 return hasDefaultConstructor || hasExplicitNonPrivateCtor; 488 } 489 490}