001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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.ArrayDeque; 023import java.util.Comparator; 024import java.util.Deque; 025import java.util.HashMap; 026import java.util.LinkedHashMap; 027import java.util.Map; 028import java.util.Optional; 029import java.util.function.Function; 030import java.util.function.ToIntFunction; 031 032import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 033import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 034import com.puppycrawl.tools.checkstyle.api.DetailAST; 035import com.puppycrawl.tools.checkstyle.api.TokenTypes; 036import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 037import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 038import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 039import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 040 041/** 042 * <div> 043 * Ensures that identifies classes that can be effectively declared as final are explicitly 044 * marked as final. The following are different types of classes that can be identified: 045 * </div> 046 * <ol> 047 * <li> 048 * Private classes with no declared constructors. 049 * </li> 050 * <li> 051 * Classes with any modifier, and contains only private constructors. 052 * </li> 053 * </ol> 054 * 055 * <p> 056 * Classes are skipped if: 057 * </p> 058 * <ol> 059 * <li> 060 * Class is Super class of some Anonymous inner class. 061 * </li> 062 * <li> 063 * Class is extended by another class in the same file. 064 * </li> 065 * </ol> 066 * 067 * <p> 068 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 069 * </p> 070 * 071 * <p> 072 * Violation Message Keys: 073 * </p> 074 * <ul> 075 * <li> 076 * {@code final.class} 077 * </li> 078 * </ul> 079 * 080 * @since 3.1 081 */ 082@FileStatefulCheck 083public class FinalClassCheck 084 extends AbstractCheck { 085 086 /** 087 * A key is pointing to the warning message text in "messages.properties" 088 * file. 089 */ 090 public static final String MSG_KEY = "final.class"; 091 092 /** 093 * Character separate package names in qualified name of java class. 094 */ 095 private static final String PACKAGE_SEPARATOR = "."; 096 097 /** Keeps ClassDesc objects for all inner classes. */ 098 private Map<String, ClassDesc> innerClasses; 099 100 /** 101 * Maps anonymous inner class's {@link TokenTypes#LITERAL_NEW} node to 102 * the outer type declaration's fully qualified name. 103 */ 104 private Map<DetailAST, String> anonInnerClassToOuterTypeDecl; 105 106 /** Keeps TypeDeclarationDescription object for stack of declared type descriptions. */ 107 private Deque<TypeDeclarationDescription> typeDeclarations; 108 109 /** Full qualified name of the package. */ 110 private String packageName; 111 112 @Override 113 public int[] getDefaultTokens() { 114 return getRequiredTokens(); 115 } 116 117 @Override 118 public int[] getAcceptableTokens() { 119 return getRequiredTokens(); 120 } 121 122 @Override 123 public int[] getRequiredTokens() { 124 return new int[] { 125 TokenTypes.ANNOTATION_DEF, 126 TokenTypes.CLASS_DEF, 127 TokenTypes.ENUM_DEF, 128 TokenTypes.INTERFACE_DEF, 129 TokenTypes.RECORD_DEF, 130 TokenTypes.CTOR_DEF, 131 TokenTypes.PACKAGE_DEF, 132 TokenTypes.LITERAL_NEW, 133 }; 134 } 135 136 @Override 137 public void beginTree(DetailAST rootAST) { 138 typeDeclarations = new ArrayDeque<>(); 139 innerClasses = new LinkedHashMap<>(); 140 anonInnerClassToOuterTypeDecl = new HashMap<>(); 141 packageName = ""; 142 } 143 144 @Override 145 public void visitToken(DetailAST ast) { 146 switch (ast.getType()) { 147 case TokenTypes.PACKAGE_DEF: 148 packageName = CheckUtil.extractQualifiedName(ast.getFirstChild().getNextSibling()); 149 break; 150 151 case TokenTypes.ANNOTATION_DEF: 152 case TokenTypes.ENUM_DEF: 153 case TokenTypes.INTERFACE_DEF: 154 case TokenTypes.RECORD_DEF: 155 final TypeDeclarationDescription description = new TypeDeclarationDescription( 156 extractQualifiedTypeName(ast), 0, ast); 157 typeDeclarations.push(description); 158 break; 159 160 case TokenTypes.CLASS_DEF: 161 visitClass(ast); 162 break; 163 164 case TokenTypes.CTOR_DEF: 165 visitCtor(ast); 166 break; 167 168 case TokenTypes.LITERAL_NEW: 169 if (ast.getFirstChild() != null 170 && ast.getLastChild().getType() == TokenTypes.OBJBLOCK) { 171 anonInnerClassToOuterTypeDecl 172 .put(ast, typeDeclarations.peek().getQualifiedName()); 173 } 174 break; 175 176 default: 177 throw new IllegalStateException(ast.toString()); 178 } 179 } 180 181 /** 182 * Called to process a type definition. 183 * 184 * @param ast the token to process 185 */ 186 private void visitClass(DetailAST ast) { 187 final String qualifiedClassName = extractQualifiedTypeName(ast); 188 final ClassDesc currClass = new ClassDesc(qualifiedClassName, typeDeclarations.size(), ast); 189 typeDeclarations.push(currClass); 190 innerClasses.put(qualifiedClassName, currClass); 191 } 192 193 /** 194 * Called to process a constructor definition. 195 * 196 * @param ast the token to process 197 */ 198 private void visitCtor(DetailAST ast) { 199 if (!ScopeUtil.isInEnumBlock(ast) && !ScopeUtil.isInRecordBlock(ast)) { 200 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 201 if (modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null) { 202 // Can be only of type ClassDesc, preceding if statements guarantee it. 203 final ClassDesc desc = (ClassDesc) typeDeclarations.getFirst(); 204 desc.registerNonPrivateCtor(); 205 } 206 } 207 } 208 209 @Override 210 public void leaveToken(DetailAST ast) { 211 if (TokenUtil.isTypeDeclaration(ast.getType())) { 212 typeDeclarations.pop(); 213 } 214 if (TokenUtil.isRootNode(ast.getParent())) { 215 anonInnerClassToOuterTypeDecl.forEach(this::registerAnonymousInnerClassToSuperClass); 216 // First pass: mark all classes that have derived inner classes 217 innerClasses.forEach(this::registerExtendedClass); 218 // Second pass: report violation for all classes that should be declared as final 219 innerClasses.forEach((qualifiedClassName, classDesc) -> { 220 if (shouldBeDeclaredAsFinal(classDesc)) { 221 final String className = CommonUtil.baseClassName(qualifiedClassName); 222 log(classDesc.getTypeDeclarationAst(), MSG_KEY, className); 223 } 224 }); 225 } 226 } 227 228 /** 229 * Checks whether a class should be declared as final or not. 230 * 231 * @param classDesc description of the class 232 * @return true if given class should be declared as final otherwise false 233 */ 234 private static boolean shouldBeDeclaredAsFinal(ClassDesc classDesc) { 235 final boolean shouldBeFinal; 236 237 final boolean skipClass = classDesc.isDeclaredAsFinal() 238 || classDesc.isDeclaredAsAbstract() 239 || classDesc.isSuperClassOfAnonymousInnerClass() 240 || classDesc.isWithNestedSubclass(); 241 242 if (skipClass) { 243 shouldBeFinal = false; 244 } 245 else if (classDesc.isHasDeclaredConstructor()) { 246 shouldBeFinal = classDesc.isDeclaredAsPrivate(); 247 } 248 else { 249 shouldBeFinal = !classDesc.isWithNonPrivateCtor(); 250 } 251 return shouldBeFinal; 252 } 253 254 /** 255 * Register to outer super class of given classAst that 256 * given classAst is extending them. 257 * 258 * @param qualifiedClassName qualifies class name(with package) of the current class 259 * @param currentClass class which outer super class will be informed about nesting subclass 260 */ 261 private void registerExtendedClass(String qualifiedClassName, 262 ClassDesc currentClass) { 263 final String superClassName = getSuperClassName(currentClass.getTypeDeclarationAst()); 264 if (superClassName != null) { 265 final ToIntFunction<ClassDesc> nestedClassCountProvider = classDesc -> { 266 return CheckUtil.typeDeclarationNameMatchingCount(qualifiedClassName, 267 classDesc.getQualifiedName()); 268 }; 269 getNearestClassWithSameName(superClassName, nestedClassCountProvider) 270 .or(() -> Optional.ofNullable(innerClasses.get(superClassName))) 271 .ifPresent(ClassDesc::registerNestedSubclass); 272 } 273 } 274 275 /** 276 * Register to the super class of anonymous inner class that the given class is instantiated 277 * by an anonymous inner class. 278 * 279 * @param literalNewAst ast node of {@link TokenTypes#LITERAL_NEW} representing anonymous inner 280 * class 281 * @param outerTypeDeclName Fully qualified name of the outer type declaration of anonymous 282 * inner class 283 */ 284 private void registerAnonymousInnerClassToSuperClass(DetailAST literalNewAst, 285 String outerTypeDeclName) { 286 final String superClassName = CheckUtil.getShortNameOfAnonInnerClass(literalNewAst); 287 288 final ToIntFunction<ClassDesc> anonClassCountProvider = classDesc -> { 289 return getAnonSuperTypeMatchingCount(outerTypeDeclName, classDesc.getQualifiedName()); 290 }; 291 getNearestClassWithSameName(superClassName, anonClassCountProvider) 292 .or(() -> Optional.ofNullable(innerClasses.get(superClassName))) 293 .ifPresent(ClassDesc::registerSuperClassOfAnonymousInnerClass); 294 } 295 296 /** 297 * Get the nearest class with same name. 298 * 299 * <p>The parameter {@code countProvider} exists because if the class being searched is the 300 * super class of anonymous inner class, the rules of evaluation are a bit different, 301 * consider the following example- 302 * <pre> 303 * {@code 304 * public class Main { 305 * static class One { 306 * static class Two { 307 * } 308 * } 309 * 310 * class Three { 311 * One.Two object = new One.Two() { // Object of Main.Three.One.Two 312 * // and not of Main.One.Two 313 * }; 314 * 315 * static class One { 316 * static class Two { 317 * } 318 * } 319 * } 320 * } 321 * } 322 * </pre> 323 * If the {@link Function} {@code countProvider} hadn't used 324 * {@link FinalClassCheck#getAnonSuperTypeMatchingCount} to 325 * calculate the matching count then the logic would have falsely evaluated 326 * {@code Main.One.Two} to be the super class of the anonymous inner class. 327 * 328 * @param className name of the class 329 * @param countProvider the function to apply to calculate the name matching count 330 * @return {@link Optional} of {@link ClassDesc} object of the nearest class with the same name. 331 * @noinspection CallToStringConcatCanBeReplacedByOperator 332 * @noinspectionreason CallToStringConcatCanBeReplacedByOperator - operator causes 333 * pitest to fail 334 */ 335 private Optional<ClassDesc> getNearestClassWithSameName(String className, 336 ToIntFunction<ClassDesc> countProvider) { 337 final String dotAndClassName = PACKAGE_SEPARATOR.concat(className); 338 final Comparator<ClassDesc> longestMatch = Comparator.comparingInt(countProvider); 339 return innerClasses.entrySet().stream() 340 .filter(entry -> entry.getKey().endsWith(dotAndClassName)) 341 .map(Map.Entry::getValue) 342 .min(longestMatch.reversed().thenComparingInt(ClassDesc::getDepth)); 343 } 344 345 /** 346 * Extract the qualified type declaration name from given type declaration Ast. 347 * 348 * @param typeDeclarationAst type declaration for which qualified name is being fetched 349 * @return qualified name of a type declaration 350 */ 351 private String extractQualifiedTypeName(DetailAST typeDeclarationAst) { 352 final String className = typeDeclarationAst.findFirstToken(TokenTypes.IDENT).getText(); 353 String outerTypeDeclarationQualifiedName = null; 354 if (!typeDeclarations.isEmpty()) { 355 outerTypeDeclarationQualifiedName = typeDeclarations.peek().getQualifiedName(); 356 } 357 return CheckUtil.getQualifiedTypeDeclarationName(packageName, 358 outerTypeDeclarationQualifiedName, 359 className); 360 } 361 362 /** 363 * Get super class name of given class. 364 * 365 * @param classAst class 366 * @return super class name or null if super class is not specified 367 */ 368 private static String getSuperClassName(DetailAST classAst) { 369 String superClassName = null; 370 final DetailAST classExtend = classAst.findFirstToken(TokenTypes.EXTENDS_CLAUSE); 371 if (classExtend != null) { 372 superClassName = CheckUtil.extractQualifiedName(classExtend.getFirstChild()); 373 } 374 return superClassName; 375 } 376 377 /** 378 * Calculates and returns the type declaration matching count when {@code classToBeMatched} is 379 * considered to be super class of an anonymous inner class. 380 * 381 * <p> 382 * Suppose our pattern class is {@code Main.ClassOne} and class to be matched is 383 * {@code Main.ClassOne.ClassTwo.ClassThree} then type declaration name matching count would 384 * be calculated by comparing every character, and updating main counter when we hit "." or 385 * when it is the last character of the pattern class and certain conditions are met. This is 386 * done so that matching count is 13 instead of 5. This is due to the fact that pattern class 387 * can contain anonymous inner class object of a nested class which isn't true in case of 388 * extending classes as you can't extend nested classes. 389 * </p> 390 * 391 * @param patternTypeDeclaration type declaration against which the given type declaration has 392 * to be matched 393 * @param typeDeclarationToBeMatched type declaration to be matched 394 * @return type declaration matching count 395 */ 396 private static int getAnonSuperTypeMatchingCount(String patternTypeDeclaration, 397 String typeDeclarationToBeMatched) { 398 final int typeDeclarationToBeMatchedLength = typeDeclarationToBeMatched.length(); 399 final int minLength = Math 400 .min(typeDeclarationToBeMatchedLength, patternTypeDeclaration.length()); 401 final char packageSeparator = PACKAGE_SEPARATOR.charAt(0); 402 final boolean shouldCountBeUpdatedAtLastCharacter = 403 typeDeclarationToBeMatchedLength > minLength 404 && typeDeclarationToBeMatched.charAt(minLength) == packageSeparator; 405 406 int result = 0; 407 for (int idx = 0; 408 idx < minLength 409 && patternTypeDeclaration.charAt(idx) == typeDeclarationToBeMatched.charAt(idx); 410 idx++) { 411 412 if (idx == minLength - 1 && shouldCountBeUpdatedAtLastCharacter 413 || patternTypeDeclaration.charAt(idx) == packageSeparator) { 414 result = idx; 415 } 416 } 417 return result; 418 } 419 420 /** 421 * Maintains information about the type of declaration. 422 * Any ast node of type {@link TokenTypes#CLASS_DEF} or {@link TokenTypes#INTERFACE_DEF} 423 * or {@link TokenTypes#ENUM_DEF} or {@link TokenTypes#ANNOTATION_DEF} 424 * or {@link TokenTypes#RECORD_DEF} is considered as a type declaration. 425 * It does not maintain information about classes, a subclass called {@link ClassDesc} 426 * does that job. 427 */ 428 private static class TypeDeclarationDescription { 429 430 /** 431 * Complete type declaration name with package name and outer type declaration name. 432 */ 433 private final String qualifiedName; 434 435 /** 436 * Depth of nesting of type declaration. 437 */ 438 private final int depth; 439 440 /** 441 * Type declaration ast node. 442 */ 443 private final DetailAST typeDeclarationAst; 444 445 /** 446 * Create an instance of TypeDeclarationDescription. 447 * 448 * @param qualifiedName Complete type declaration name with package name and outer type 449 * declaration name. 450 * @param depth Depth of nesting of type declaration 451 * @param typeDeclarationAst Type declaration ast node 452 */ 453 private TypeDeclarationDescription(String qualifiedName, int depth, 454 DetailAST typeDeclarationAst) { 455 this.qualifiedName = qualifiedName; 456 this.depth = depth; 457 this.typeDeclarationAst = typeDeclarationAst; 458 } 459 460 /** 461 * Get the complete type declaration name i.e. type declaration name with package name 462 * and outer type declaration name. 463 * 464 * @return qualified class name 465 */ 466 protected String getQualifiedName() { 467 return qualifiedName; 468 } 469 470 /** 471 * Get the depth of type declaration. 472 * 473 * @return the depth of nesting of type declaration 474 */ 475 protected int getDepth() { 476 return depth; 477 } 478 479 /** 480 * Get the type declaration ast node. 481 * 482 * @return ast node of the type declaration 483 */ 484 protected DetailAST getTypeDeclarationAst() { 485 return typeDeclarationAst; 486 } 487 } 488 489 /** 490 * Maintains information about the class. 491 */ 492 private static final class ClassDesc extends TypeDeclarationDescription { 493 494 /** Is class declared as final. */ 495 private final boolean declaredAsFinal; 496 497 /** Is class declared as abstract. */ 498 private final boolean declaredAsAbstract; 499 500 /** Is class contains private modifier. */ 501 private final boolean declaredAsPrivate; 502 503 /** Does class have implicit constructor. */ 504 private final boolean hasDeclaredConstructor; 505 506 /** Does class have non-private ctors. */ 507 private boolean withNonPrivateCtor; 508 509 /** Does class have nested subclass. */ 510 private boolean withNestedSubclass; 511 512 /** Whether the class is the super class of an anonymous inner class. */ 513 private boolean superClassOfAnonymousInnerClass; 514 515 /** 516 * Create a new ClassDesc instance. 517 * 518 * @param qualifiedName qualified class name(with package) 519 * @param depth class nesting level 520 * @param classAst classAst node 521 */ 522 private ClassDesc(String qualifiedName, int depth, DetailAST classAst) { 523 super(qualifiedName, depth, classAst); 524 final DetailAST modifiers = classAst.findFirstToken(TokenTypes.MODIFIERS); 525 declaredAsFinal = modifiers.findFirstToken(TokenTypes.FINAL) != null; 526 declaredAsAbstract = modifiers.findFirstToken(TokenTypes.ABSTRACT) != null; 527 declaredAsPrivate = modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null; 528 hasDeclaredConstructor = 529 classAst.getLastChild().findFirstToken(TokenTypes.CTOR_DEF) == null; 530 } 531 532 /** Adds non-private ctor. */ 533 private void registerNonPrivateCtor() { 534 withNonPrivateCtor = true; 535 } 536 537 /** Adds nested subclass. */ 538 private void registerNestedSubclass() { 539 withNestedSubclass = true; 540 } 541 542 /** Adds anonymous inner class. */ 543 private void registerSuperClassOfAnonymousInnerClass() { 544 superClassOfAnonymousInnerClass = true; 545 } 546 547 /** 548 * Does class have non-private ctors. 549 * 550 * @return true if class has non-private ctors 551 */ 552 private boolean isWithNonPrivateCtor() { 553 return withNonPrivateCtor; 554 } 555 556 /** 557 * Does class have nested subclass. 558 * 559 * @return true if class has nested subclass 560 */ 561 private boolean isWithNestedSubclass() { 562 return withNestedSubclass; 563 } 564 565 /** 566 * Is class declared as final. 567 * 568 * @return true if class is declared as final 569 */ 570 private boolean isDeclaredAsFinal() { 571 return declaredAsFinal; 572 } 573 574 /** 575 * Is class declared as abstract. 576 * 577 * @return true if class is declared as final 578 */ 579 private boolean isDeclaredAsAbstract() { 580 return declaredAsAbstract; 581 } 582 583 /** 584 * Whether the class is the super class of an anonymous inner class. 585 * 586 * @return {@code true} if the class is the super class of an anonymous inner class. 587 */ 588 private boolean isSuperClassOfAnonymousInnerClass() { 589 return superClassOfAnonymousInnerClass; 590 } 591 592 /** 593 * Does class have implicit constructor. 594 * 595 * @return true if class have implicit constructor 596 */ 597 private boolean isHasDeclaredConstructor() { 598 return hasDeclaredConstructor; 599 } 600 601 /** 602 * Does class is private. 603 * 604 * @return true if class is private 605 */ 606 private boolean isDeclaredAsPrivate() { 607 return declaredAsPrivate; 608 } 609 } 610}