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