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