001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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; 024 025import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 026import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.FullIdent; 029import com.puppycrawl.tools.checkstyle.api.TokenTypes; 030import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 031 032/** 033 * <p> 034 * Checks that a class which has only private constructors 035 * is declared as final. Doesn't check for classes nested in interfaces 036 * or annotations, as they are always {@code final} there. 037 * </p> 038 * <p> 039 * To configure the check: 040 * </p> 041 * <pre> 042 * <module name="FinalClass"/> 043 * </pre> 044 * <p> 045 * Example: 046 * </p> 047 * <pre> 048 * final class MyClass { // OK 049 * private MyClass() { } 050 * } 051 * 052 * class MyClass { // violation, class should be declared final 053 * private MyClass() { } 054 * } 055 * 056 * class MyClass { // OK, since it has a public constructor 057 * int field1; 058 * String field2; 059 * private MyClass(int value) { 060 * this.field1 = value; 061 * this.field2 = " "; 062 * } 063 * public MyClass(String value) { 064 * this.field2 = value; 065 * this.field1 = 0; 066 * } 067 * } 068 * 069 * interface CheckInterface 070 * { 071 * class MyClass { // OK, nested class in interface is always final 072 * private MyClass() {} 073 * } 074 * } 075 * 076 * public @interface Test { 077 * public boolean enabled() 078 * default true; 079 * class MyClass { // OK, class nested in an annotation is always final 080 * private MyClass() { } 081 * } 082 * } 083 * </pre> 084 * <p> 085 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 086 * </p> 087 * <p> 088 * Violation Message Keys: 089 * </p> 090 * <ul> 091 * <li> 092 * {@code final.class} 093 * </li> 094 * </ul> 095 * 096 * @since 3.1 097 */ 098@FileStatefulCheck 099public class FinalClassCheck 100 extends AbstractCheck { 101 102 /** 103 * A key is pointing to the warning message text in "messages.properties" 104 * file. 105 */ 106 public static final String MSG_KEY = "final.class"; 107 108 /** 109 * Character separate package names in qualified name of java class. 110 */ 111 private static final String PACKAGE_SEPARATOR = "."; 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[] {TokenTypes.CLASS_DEF, TokenTypes.CTOR_DEF, TokenTypes.PACKAGE_DEF}; 132 } 133 134 @Override 135 public void beginTree(DetailAST rootAST) { 136 classes = new ArrayDeque<>(); 137 packageName = ""; 138 } 139 140 @Override 141 public void visitToken(DetailAST ast) { 142 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 143 144 switch (ast.getType()) { 145 case TokenTypes.PACKAGE_DEF: 146 packageName = extractQualifiedName(ast.getFirstChild().getNextSibling()); 147 break; 148 149 case TokenTypes.CLASS_DEF: 150 registerNestedSubclassToOuterSuperClasses(ast); 151 152 final boolean isFinal = modifiers.findFirstToken(TokenTypes.FINAL) != null; 153 final boolean isAbstract = modifiers.findFirstToken(TokenTypes.ABSTRACT) != null; 154 155 final String qualifiedClassName = getQualifiedClassName(ast); 156 classes.push(new ClassDesc(qualifiedClassName, isFinal, isAbstract)); 157 break; 158 159 case TokenTypes.CTOR_DEF: 160 if (!ScopeUtil.isInEnumBlock(ast) && !ScopeUtil.isInRecordBlock(ast)) { 161 final ClassDesc desc = classes.peek(); 162 if (modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null) { 163 desc.registerNonPrivateCtor(); 164 } 165 else { 166 desc.registerPrivateCtor(); 167 } 168 } 169 break; 170 171 default: 172 throw new IllegalStateException(ast.toString()); 173 } 174 } 175 176 @Override 177 public void leaveToken(DetailAST ast) { 178 if (ast.getType() == TokenTypes.CLASS_DEF) { 179 final ClassDesc desc = classes.pop(); 180 if (desc.isWithPrivateCtor() 181 && !desc.isDeclaredAsAbstract() 182 && !desc.isDeclaredAsFinal() 183 && !desc.isWithNonPrivateCtor() 184 && !desc.isWithNestedSubclass() 185 && !ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) { 186 final String qualifiedName = desc.getQualifiedName(); 187 final String className = getClassNameFromQualifiedName(qualifiedName); 188 log(ast, MSG_KEY, className); 189 } 190 } 191 } 192 193 /** 194 * Get name of class (with qualified package if specified) in {@code ast}. 195 * 196 * @param ast ast to extract class name from 197 * @return qualified name 198 */ 199 private static String extractQualifiedName(DetailAST ast) { 200 return FullIdent.createFullIdent(ast).getText(); 201 } 202 203 /** 204 * Register to outer super classes of given classAst that 205 * given classAst is extending them. 206 * 207 * @param classAst class which outer super classes will be 208 * informed about nesting subclass 209 */ 210 private void registerNestedSubclassToOuterSuperClasses(DetailAST classAst) { 211 final String currentAstSuperClassName = getSuperClassName(classAst); 212 if (currentAstSuperClassName != null) { 213 for (ClassDesc classDesc : classes) { 214 final String classDescQualifiedName = classDesc.getQualifiedName(); 215 if (doesNameInExtendMatchSuperClassName(classDescQualifiedName, 216 currentAstSuperClassName)) { 217 classDesc.registerNestedSubclass(); 218 } 219 } 220 } 221 } 222 223 /** 224 * Get qualified class name from given class Ast. 225 * 226 * @param classAst class to get qualified class name 227 * @return qualified class name of a class 228 */ 229 private String getQualifiedClassName(DetailAST classAst) { 230 final String className = classAst.findFirstToken(TokenTypes.IDENT).getText(); 231 String outerClassQualifiedName = null; 232 if (!classes.isEmpty()) { 233 outerClassQualifiedName = classes.peek().getQualifiedName(); 234 } 235 return getQualifiedClassName(packageName, outerClassQualifiedName, className); 236 } 237 238 /** 239 * Calculate qualified class name(package + class name) laying inside given 240 * outer class. 241 * 242 * @param packageName package name, empty string on default package 243 * @param outerClassQualifiedName qualified name(package + class) of outer class, 244 * null if doesn't exist 245 * @param className class name 246 * @return qualified class name(package + class name) 247 */ 248 private static String getQualifiedClassName(String packageName, String outerClassQualifiedName, 249 String className) { 250 final String qualifiedClassName; 251 252 if (outerClassQualifiedName == null) { 253 if (packageName.isEmpty()) { 254 qualifiedClassName = className; 255 } 256 else { 257 qualifiedClassName = packageName + PACKAGE_SEPARATOR + className; 258 } 259 } 260 else { 261 qualifiedClassName = outerClassQualifiedName + PACKAGE_SEPARATOR + className; 262 } 263 return qualifiedClassName; 264 } 265 266 /** 267 * Get super class name of given class. 268 * 269 * @param classAst class 270 * @return super class name or null if super class is not specified 271 */ 272 private static String getSuperClassName(DetailAST classAst) { 273 String superClassName = null; 274 final DetailAST classExtend = classAst.findFirstToken(TokenTypes.EXTENDS_CLAUSE); 275 if (classExtend != null) { 276 superClassName = extractQualifiedName(classExtend.getFirstChild()); 277 } 278 return superClassName; 279 } 280 281 /** 282 * Checks if given super class name in extend clause match super class qualified name. 283 * 284 * @param superClassQualifiedName super class qualified name (with package) 285 * @param superClassInExtendClause name in extend clause 286 * @return true if given super class name in extend clause match super class qualified name, 287 * false otherwise 288 */ 289 private static boolean doesNameInExtendMatchSuperClassName(String superClassQualifiedName, 290 String superClassInExtendClause) { 291 String superClassNormalizedName = superClassQualifiedName; 292 if (!superClassInExtendClause.contains(PACKAGE_SEPARATOR)) { 293 superClassNormalizedName = getClassNameFromQualifiedName(superClassQualifiedName); 294 } 295 return superClassNormalizedName.equals(superClassInExtendClause); 296 } 297 298 /** 299 * Get class name from qualified name. 300 * 301 * @param qualifiedName qualified class name 302 * @return class name 303 */ 304 private static String getClassNameFromQualifiedName(String qualifiedName) { 305 return qualifiedName.substring(qualifiedName.lastIndexOf(PACKAGE_SEPARATOR) + 1); 306 } 307 308 /** Maintains information about class' ctors. */ 309 private static final class ClassDesc { 310 311 /** Qualified class name(with package). */ 312 private final String qualifiedName; 313 314 /** Is class declared as final. */ 315 private final boolean declaredAsFinal; 316 317 /** Is class declared as abstract. */ 318 private final boolean declaredAsAbstract; 319 320 /** Does class have non-private ctors. */ 321 private boolean withNonPrivateCtor; 322 323 /** Does class have private ctors. */ 324 private boolean withPrivateCtor; 325 326 /** Does class have nested subclass. */ 327 private boolean withNestedSubclass; 328 329 /** 330 * Create a new ClassDesc instance. 331 * 332 * @param qualifiedName qualified class name(with package) 333 * @param declaredAsFinal indicates if the 334 * class declared as final 335 * @param declaredAsAbstract indicates if the 336 * class declared as abstract 337 */ 338 /* package */ ClassDesc(String qualifiedName, boolean declaredAsFinal, 339 boolean declaredAsAbstract) { 340 this.qualifiedName = qualifiedName; 341 this.declaredAsFinal = declaredAsFinal; 342 this.declaredAsAbstract = declaredAsAbstract; 343 } 344 345 /** 346 * Get qualified class name. 347 * 348 * @return qualified class name 349 */ 350 private String getQualifiedName() { 351 return qualifiedName; 352 } 353 354 /** Adds private ctor. */ 355 private void registerPrivateCtor() { 356 withPrivateCtor = true; 357 } 358 359 /** Adds non-private ctor. */ 360 private void registerNonPrivateCtor() { 361 withNonPrivateCtor = true; 362 } 363 364 /** Adds nested subclass. */ 365 private void registerNestedSubclass() { 366 withNestedSubclass = true; 367 } 368 369 /** 370 * Does class have private ctors. 371 * 372 * @return true if class has private ctors 373 */ 374 private boolean isWithPrivateCtor() { 375 return withPrivateCtor; 376 } 377 378 /** 379 * Does class have non-private ctors. 380 * 381 * @return true if class has non-private ctors 382 */ 383 private boolean isWithNonPrivateCtor() { 384 return withNonPrivateCtor; 385 } 386 387 /** 388 * Does class have nested subclass. 389 * 390 * @return true if class has nested subclass 391 */ 392 private boolean isWithNestedSubclass() { 393 return withNestedSubclass; 394 } 395 396 /** 397 * Is class declared as final. 398 * 399 * @return true if class is declared as final 400 */ 401 private boolean isDeclaredAsFinal() { 402 return declaredAsFinal; 403 } 404 405 /** 406 * Is class declared as abstract. 407 * 408 * @return true if class is declared as final 409 */ 410 private boolean isDeclaredAsAbstract() { 411 return declaredAsAbstract; 412 } 413 414 } 415 416}