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