001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code 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.Deque; 024import java.util.HashMap; 025import java.util.Map; 026import java.util.Optional; 027 028import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 029import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 030import com.puppycrawl.tools.checkstyle.api.DetailAST; 031import com.puppycrawl.tools.checkstyle.api.TokenTypes; 032import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 033import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 034import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 035 036/** 037 * <p> 038 * Checks that a class that has only private constructors and has no descendant 039 * classes is declared as final. 040 * </p> 041 * <p> 042 * To configure the check: 043 * </p> 044 * <pre> 045 * <module name="FinalClass"/> 046 * </pre> 047 * <p> 048 * Example: 049 * </p> 050 * <pre> 051 * final class MyClass { // OK 052 * private MyClass() { } 053 * } 054 * 055 * class MyClass { // violation, class should be declared final 056 * private MyClass() { } 057 * } 058 * 059 * class MyClass { // OK, since it has a public constructor 060 * int field1; 061 * String field2; 062 * private MyClass(int value) { 063 * this.field1 = value; 064 * this.field2 = " "; 065 * } 066 * public MyClass(String value) { 067 * this.field2 = value; 068 * this.field1 = 0; 069 * } 070 * } 071 * 072 * class TestAnonymousInnerClasses { // OK, class has an anonymous inner class. 073 * public static final TestAnonymousInnerClasses ONE = new TestAnonymousInnerClasses() { 074 * 075 * }; 076 * 077 * private TestAnonymousInnerClasses() { 078 * } 079 * } 080 * </pre> 081 * <p> 082 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 083 * </p> 084 * <p> 085 * Violation Message Keys: 086 * </p> 087 * <ul> 088 * <li> 089 * {@code final.class} 090 * </li> 091 * </ul> 092 * 093 * @since 3.1 094 */ 095@FileStatefulCheck 096public class FinalClassCheck 097 extends AbstractCheck { 098 099 /** 100 * A key is pointing to the warning message text in "messages.properties" 101 * file. 102 */ 103 public static final String MSG_KEY = "final.class"; 104 105 /** 106 * Character separate package names in qualified name of java class. 107 */ 108 private static final String PACKAGE_SEPARATOR = "."; 109 110 /** Keeps ClassDesc objects for all inner classes. */ 111 private Map<String, ClassDesc> innerClasses; 112 113 /** Keeps ClassDesc objects for stack of declared classes. */ 114 private Deque<ClassDesc> classes; 115 116 /** Full qualified name of the package. */ 117 private String packageName; 118 119 @Override 120 public int[] getDefaultTokens() { 121 return getRequiredTokens(); 122 } 123 124 @Override 125 public int[] getAcceptableTokens() { 126 return getRequiredTokens(); 127 } 128 129 @Override 130 public int[] getRequiredTokens() { 131 return new int[] { 132 TokenTypes.ANNOTATION_DEF, 133 TokenTypes.CLASS_DEF, 134 TokenTypes.ENUM_DEF, 135 TokenTypes.INTERFACE_DEF, 136 TokenTypes.RECORD_DEF, 137 TokenTypes.CTOR_DEF, 138 TokenTypes.PACKAGE_DEF, 139 TokenTypes.LITERAL_NEW, 140 }; 141 } 142 143 @Override 144 public void beginTree(DetailAST rootAST) { 145 classes = new ArrayDeque<>(); 146 innerClasses = new HashMap<>(); 147 packageName = ""; 148 } 149 150 @Override 151 public void visitToken(DetailAST ast) { 152 switch (ast.getType()) { 153 case TokenTypes.PACKAGE_DEF: 154 packageName = CheckUtil.extractQualifiedName(ast.getFirstChild().getNextSibling()); 155 break; 156 157 case TokenTypes.ANNOTATION_DEF: 158 case TokenTypes.ENUM_DEF: 159 case TokenTypes.INTERFACE_DEF: 160 case TokenTypes.RECORD_DEF: 161 break; 162 163 case TokenTypes.CLASS_DEF: 164 visitClass(ast); 165 break; 166 167 case TokenTypes.CTOR_DEF: 168 visitCtor(ast); 169 break; 170 171 case TokenTypes.LITERAL_NEW: 172 if (ast.getFirstChild() != null 173 && ast.getLastChild().getType() == TokenTypes.OBJBLOCK) { 174 for (ClassDesc classDesc : classes) { 175 if (doesNameOfClassMatchAnonymousInnerClassName(ast, classDesc)) { 176 classDesc.registerAnonymousInnerClass(); 177 } 178 } 179 } 180 break; 181 182 default: 183 throw new IllegalStateException(ast.toString()); 184 } 185 } 186 187 /** 188 * Called to process a type definition. 189 * 190 * @param ast the token to process 191 */ 192 private void visitClass(DetailAST ast) { 193 final String qualifiedClassName = getQualifiedClassName(ast); 194 final ClassDesc currClass = new ClassDesc(qualifiedClassName, classes.size(), ast); 195 classes.push(currClass); 196 innerClasses.put(qualifiedClassName, currClass); 197 } 198 199 /** 200 * Called to process a constructor definition. 201 * 202 * @param ast the token to process 203 */ 204 private void visitCtor(DetailAST ast) { 205 if (!ScopeUtil.isInEnumBlock(ast) && !ScopeUtil.isInRecordBlock(ast)) { 206 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 207 final ClassDesc desc = classes.getFirst(); 208 if (modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null) { 209 desc.registerNonPrivateCtor(); 210 } 211 else { 212 desc.registerPrivateCtor(); 213 } 214 } 215 } 216 217 @Override 218 public void leaveToken(DetailAST ast) { 219 if (ast.getType() == TokenTypes.CLASS_DEF) { 220 classes.pop(); 221 } 222 if (TokenUtil.isRootNode(ast.getParent())) { 223 // First pass: mark all classes that have derived inner classes 224 innerClasses.forEach(this::registerNestedSubclassToOuterSuperClasses); 225 // Second pass: report violation for all classes that should be declared as final 226 innerClasses.forEach((qualifiedClassName, classDesc) -> { 227 if (shouldBeDeclaredAsFinal(classDesc)) { 228 final String className = getClassNameFromQualifiedName(qualifiedClassName); 229 log(classDesc.getClassAst(), MSG_KEY, className); 230 } 231 }); 232 } 233 } 234 235 /** 236 * Checks whether a class should be declared as final or not. 237 * 238 * @param desc description of the class 239 * @return true if given class should be declared as final otherwise false 240 */ 241 private static boolean shouldBeDeclaredAsFinal(ClassDesc desc) { 242 return desc.isWithPrivateCtor() 243 && !(desc.isDeclaredAsAbstract() 244 || desc.isWithAnonymousInnerClass()) 245 && !desc.isDeclaredAsFinal() 246 && !desc.isWithNonPrivateCtor() 247 && !desc.isWithNestedSubclass(); 248 } 249 250 /** 251 * Register to outer super class of given classAst that 252 * given classAst is extending them. 253 * 254 * @param qualifiedClassName qualifies class name(with package) of the current class 255 * @param currentClass class which outer super class will be informed about nesting subclass 256 */ 257 private void registerNestedSubclassToOuterSuperClasses(String qualifiedClassName, 258 ClassDesc currentClass) { 259 final String superClassName = getSuperClassName(currentClass.getClassAst()); 260 if (superClassName != null) { 261 final ClassDesc nearest = 262 getNearestClassWithSameName(superClassName, qualifiedClassName); 263 if (nearest == null) { 264 Optional.ofNullable(innerClasses.get(superClassName)) 265 .ifPresent(ClassDesc::registerNestedSubclass); 266 } 267 else { 268 nearest.registerNestedSubclass(); 269 } 270 } 271 } 272 273 /** 274 * Checks if there is a class with same name. 275 * 276 * @param className name of the class 277 * @param superClassName name of the super class 278 * @return true if there is another class with same name. 279 * @noinspection CallToStringConcatCanBeReplacedByOperator 280 */ 281 private ClassDesc getNearestClassWithSameName(String className, String superClassName) { 282 final String dotAndClassName = PACKAGE_SEPARATOR.concat(className); 283 return innerClasses.entrySet().stream() 284 .filter(entry -> entry.getKey().endsWith(dotAndClassName)) 285 .map(Map.Entry::getValue) 286 .min((first, second) -> { 287 return getClassDeclarationNameMatchingCountDiff(superClassName, first, second); 288 }) 289 .orElse(null); 290 } 291 292 /** 293 * Get the difference between class declaration name matching count. If the difference between 294 * them is zero, then their depth is compared to obtain the result. 295 * 296 * @param superClassName name of the super class 297 * @param firstClass first input class 298 * @param secondClass second input class 299 * @return difference between class declaration name matching count 300 */ 301 private static int getClassDeclarationNameMatchingCountDiff(String superClassName, 302 ClassDesc firstClass, 303 ClassDesc secondClass) { 304 int diff = Integer.compare( 305 CheckUtil 306 .typeDeclarationNameMatchingCount(superClassName, secondClass.getQualifiedName()), 307 CheckUtil 308 .typeDeclarationNameMatchingCount(superClassName, firstClass.getQualifiedName())); 309 if (diff == 0) { 310 diff = Integer.compare(firstClass.getDepth(), secondClass.getDepth()); 311 } 312 return diff; 313 } 314 315 /** 316 * Check if class name matches with anonymous inner class name. 317 * 318 * @param ast current ast. 319 * @param classDesc class to match. 320 * @return true if current class name matches anonymous inner 321 * class name. 322 */ 323 private static boolean doesNameOfClassMatchAnonymousInnerClassName(DetailAST ast, 324 ClassDesc classDesc) { 325 final String[] className = classDesc.getQualifiedName().split("\\."); 326 return ast.getFirstChild().getText().equals(className[className.length - 1]); 327 } 328 329 /** 330 * Get qualified class name from given class Ast. 331 * 332 * @param classAst class to get qualified class name 333 * @return qualified class name of a class 334 */ 335 private String getQualifiedClassName(DetailAST classAst) { 336 final String className = classAst.findFirstToken(TokenTypes.IDENT).getText(); 337 String outerClassQualifiedName = null; 338 if (!classes.isEmpty()) { 339 outerClassQualifiedName = classes.peek().getQualifiedName(); 340 } 341 return CheckUtil 342 .getQualifiedTypeDeclarationName(packageName, outerClassQualifiedName, className); 343 } 344 345 /** 346 * Get super class name of given class. 347 * 348 * @param classAst class 349 * @return super class name or null if super class is not specified 350 */ 351 private static String getSuperClassName(DetailAST classAst) { 352 String superClassName = null; 353 final DetailAST classExtend = classAst.findFirstToken(TokenTypes.EXTENDS_CLAUSE); 354 if (classExtend != null) { 355 superClassName = CheckUtil.extractQualifiedName(classExtend.getFirstChild()); 356 } 357 return superClassName; 358 } 359 360 /** 361 * Get class name from qualified name. 362 * 363 * @param qualifiedName qualified class name 364 * @return class name 365 */ 366 private static String getClassNameFromQualifiedName(String qualifiedName) { 367 return qualifiedName.substring(qualifiedName.lastIndexOf(PACKAGE_SEPARATOR) + 1); 368 } 369 370 /** Maintains information about class' ctors. */ 371 private static final class ClassDesc { 372 373 /** Corresponding node. */ 374 private final DetailAST classAst; 375 376 /** Qualified class name(with package). */ 377 private final String qualifiedName; 378 379 /** Is class declared as final. */ 380 private final boolean declaredAsFinal; 381 382 /** Is class declared as abstract. */ 383 private final boolean declaredAsAbstract; 384 385 /** Class nesting level. */ 386 private final int depth; 387 388 /** Does class have non-private ctors. */ 389 private boolean withNonPrivateCtor; 390 391 /** Does class have private ctors. */ 392 private boolean withPrivateCtor; 393 394 /** Does class have nested subclass. */ 395 private boolean withNestedSubclass; 396 397 /** Does class have anonymous inner class. */ 398 private boolean withAnonymousInnerClass; 399 400 /** 401 * Create a new ClassDesc instance. 402 * 403 * @param qualifiedName qualified class name(with package) 404 * @param depth class nesting level 405 * @param classAst classAst node 406 */ 407 /* package */ ClassDesc(String qualifiedName, int depth, DetailAST classAst) { 408 this.qualifiedName = qualifiedName; 409 this.depth = depth; 410 this.classAst = classAst; 411 final DetailAST modifiers = classAst.findFirstToken(TokenTypes.MODIFIERS); 412 declaredAsFinal = modifiers.findFirstToken(TokenTypes.FINAL) != null; 413 declaredAsAbstract = modifiers.findFirstToken(TokenTypes.ABSTRACT) != null; 414 } 415 416 /** 417 * Get qualified class name. 418 * 419 * @return qualified class name 420 */ 421 private String getQualifiedName() { 422 return qualifiedName; 423 } 424 425 /** 426 * Get the classAst node. 427 * 428 * @return classAst node 429 */ 430 public DetailAST getClassAst() { 431 return classAst; 432 } 433 434 /** Adds private ctor. */ 435 private void registerPrivateCtor() { 436 withPrivateCtor = true; 437 } 438 439 /** Adds non-private ctor. */ 440 private void registerNonPrivateCtor() { 441 withNonPrivateCtor = true; 442 } 443 444 /** Adds nested subclass. */ 445 private void registerNestedSubclass() { 446 withNestedSubclass = true; 447 } 448 449 /** Adds anonymous inner class. */ 450 private void registerAnonymousInnerClass() { 451 withAnonymousInnerClass = true; 452 } 453 454 /** 455 * Returns class nesting level. 456 * 457 * @return class nesting level 458 */ 459 private int getDepth() { 460 return depth; 461 } 462 463 /** 464 * Does class have private ctors. 465 * 466 * @return true if class has private ctors 467 */ 468 private boolean isWithPrivateCtor() { 469 return withPrivateCtor; 470 } 471 472 /** 473 * Does class have non-private ctors. 474 * 475 * @return true if class has non-private ctors 476 */ 477 private boolean isWithNonPrivateCtor() { 478 return withNonPrivateCtor; 479 } 480 481 /** 482 * Does class have nested subclass. 483 * 484 * @return true if class has nested subclass 485 */ 486 private boolean isWithNestedSubclass() { 487 return withNestedSubclass; 488 } 489 490 /** 491 * Is class declared as final. 492 * 493 * @return true if class is declared as final 494 */ 495 private boolean isDeclaredAsFinal() { 496 return declaredAsFinal; 497 } 498 499 /** 500 * Is class declared as abstract. 501 * 502 * @return true if class is declared as final 503 */ 504 private boolean isDeclaredAsAbstract() { 505 return declaredAsAbstract; 506 } 507 508 /** 509 * Does class have an anonymous inner class. 510 * 511 * @return true if class has anonymous inner class 512 */ 513 private boolean isWithAnonymousInnerClass() { 514 return withAnonymousInnerClass; 515 } 516 517 } 518}