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