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