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