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