Coverage Report - com.puppycrawl.tools.checkstyle.checks.design.FinalClassCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
FinalClassCheck
100%
78/78
100%
46/46
2
FinalClassCheck$ClassDesc
100%
18/18
N/A
2
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2017 the original author or authors.
 4  
 //
 5  
 // This library is free software; you can redistribute it and/or
 6  
 // modify it under the terms of the GNU Lesser General Public
 7  
 // License as published by the Free Software Foundation; either
 8  
 // version 2.1 of the License, or (at your option) any later version.
 9  
 //
 10  
 // This library is distributed in the hope that it will be useful,
 11  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 // Lesser General Public License for more details.
 14  
 //
 15  
 // You should have received a copy of the GNU Lesser General Public
 16  
 // License along with this library; if not, write to the Free Software
 17  
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 ////////////////////////////////////////////////////////////////////////////////
 19  
 
 20  
 package com.puppycrawl.tools.checkstyle.checks.design;
 21  
 
 22  
 import java.util.ArrayDeque;
 23  
 import java.util.Deque;
 24  
 import java.util.LinkedList;
 25  
 import java.util.List;
 26  
 
 27  
 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
 28  
 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
 29  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 30  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 31  
 import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
 32  
 
 33  
 /**
 34  
  * <p>
 35  
  * Checks that class which has only private ctors
 36  
  * is declared as final. Doesn't check for classes nested in interfaces
 37  
  * or annotations, as they are always {@code final} there.
 38  
  * </p>
 39  
  * <p>
 40  
  * An example of how to configure the check is:
 41  
  * </p>
 42  
  * <pre>
 43  
  * &lt;module name="FinalClass"/&gt;
 44  
  * </pre>
 45  
  * @author o_sukhodolsky
 46  
  */
 47  
 @FileStatefulCheck
 48  13
 public class FinalClassCheck
 49  
     extends AbstractCheck {
 50  
 
 51  
     /**
 52  
      * A key is pointing to the warning message text in "messages.properties"
 53  
      * file.
 54  
      */
 55  
     public static final String MSG_KEY = "final.class";
 56  
 
 57  
     /**
 58  
      * Character separate package names in qualified name of java class.
 59  
      */
 60  
     private static final String PACKAGE_SEPARATOR = ".";
 61  
 
 62  
     /** Keeps ClassDesc objects for stack of declared classes. */
 63  
     private Deque<ClassDesc> classes;
 64  
 
 65  
     /** Full qualified name of the package. */
 66  
     private String packageName;
 67  
 
 68  
     @Override
 69  
     public int[] getDefaultTokens() {
 70  14
         return getRequiredTokens();
 71  
     }
 72  
 
 73  
     @Override
 74  
     public int[] getAcceptableTokens() {
 75  5
         return getRequiredTokens();
 76  
     }
 77  
 
 78  
     @Override
 79  
     public int[] getRequiredTokens() {
 80  34
         return new int[] {TokenTypes.CLASS_DEF, TokenTypes.CTOR_DEF, TokenTypes.PACKAGE_DEF};
 81  
     }
 82  
 
 83  
     @Override
 84  
     public void beginTree(DetailAST rootAST) {
 85  4
         classes = new ArrayDeque<>();
 86  4
         packageName = "";
 87  4
     }
 88  
 
 89  
     @Override
 90  
     public void visitToken(DetailAST ast) {
 91  45
         final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
 92  
 
 93  45
         switch (ast.getType()) {
 94  
 
 95  
             case TokenTypes.PACKAGE_DEF:
 96  3
                 packageName = extractQualifiedName(ast);
 97  3
                 break;
 98  
 
 99  
             case TokenTypes.CLASS_DEF:
 100  24
                 registerNestedSubclassToOuterSuperClasses(ast);
 101  
 
 102  24
                 final boolean isFinal = modifiers.findFirstToken(TokenTypes.FINAL) != null;
 103  24
                 final boolean isAbstract = modifiers.findFirstToken(TokenTypes.ABSTRACT) != null;
 104  
 
 105  24
                 final String qualifiedClassName = getQualifiedClassName(ast);
 106  24
                 classes.push(new ClassDesc(qualifiedClassName, isFinal, isAbstract));
 107  24
                 break;
 108  
 
 109  
             case TokenTypes.CTOR_DEF:
 110  17
                 if (!ScopeUtils.isInEnumBlock(ast)) {
 111  16
                     final ClassDesc desc = classes.peek();
 112  16
                     if (modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null) {
 113  2
                         desc.registerNonPrivateCtor();
 114  
                     }
 115  
                     else {
 116  14
                         desc.registerPrivateCtor();
 117  
                     }
 118  16
                 }
 119  
                 break;
 120  
 
 121  
             default:
 122  1
                 throw new IllegalStateException(ast.toString());
 123  
         }
 124  44
     }
 125  
 
 126  
     @Override
 127  
     public void leaveToken(DetailAST ast) {
 128  44
         if (ast.getType() == TokenTypes.CLASS_DEF) {
 129  24
             final ClassDesc desc = classes.pop();
 130  24
             if (desc.isWithPrivateCtor()
 131  14
                 && !desc.isDeclaredAsAbstract()
 132  12
                 && !desc.isDeclaredAsFinal()
 133  11
                 && !desc.isWithNonPrivateCtor()
 134  10
                 && !desc.isWithNestedSubclass()
 135  7
                 && !ScopeUtils.isInInterfaceOrAnnotationBlock(ast)) {
 136  5
                 final String qualifiedName = desc.getQualifiedName();
 137  5
                 final String className = getClassNameFromQualifiedName(qualifiedName);
 138  5
                 log(ast.getLineNo(), MSG_KEY, className);
 139  
             }
 140  
         }
 141  44
     }
 142  
 
 143  
     /**
 144  
      * Get name of class(with qualified package if specified) in extend clause.
 145  
      * @param classExtend extend clause to extract class name
 146  
      * @return super class name
 147  
      */
 148  
     private static String extractQualifiedName(DetailAST classExtend) {
 149  
         final String className;
 150  
 
 151  8
         if (classExtend.findFirstToken(TokenTypes.IDENT) == null) {
 152  
             // Name specified with packages, have to traverse DOT
 153  6
             final DetailAST firstChild = classExtend.findFirstToken(TokenTypes.DOT);
 154  6
             final List<String> qualifiedNameParts = new LinkedList<>();
 155  
 
 156  6
             qualifiedNameParts.add(0, firstChild.findFirstToken(TokenTypes.IDENT).getText());
 157  6
             DetailAST traverse = firstChild.findFirstToken(TokenTypes.DOT);
 158  31
             while (traverse != null) {
 159  25
                 qualifiedNameParts.add(0, traverse.findFirstToken(TokenTypes.IDENT).getText());
 160  25
                 traverse = traverse.findFirstToken(TokenTypes.DOT);
 161  
             }
 162  6
             className = String.join(PACKAGE_SEPARATOR, qualifiedNameParts);
 163  6
         }
 164  
         else {
 165  2
             className = classExtend.findFirstToken(TokenTypes.IDENT).getText();
 166  
         }
 167  
 
 168  8
         return className;
 169  
     }
 170  
 
 171  
     /**
 172  
      * Register to outer super classes of given classAst that
 173  
      * given classAst is extending them.
 174  
      * @param classAst class which outer super classes will be
 175  
      *                 informed about nesting subclass
 176  
      */
 177  
     private void registerNestedSubclassToOuterSuperClasses(DetailAST classAst) {
 178  24
         final String currentAstSuperClassName = getSuperClassName(classAst);
 179  24
         if (currentAstSuperClassName != null) {
 180  5
             for (ClassDesc classDesc : classes) {
 181  10
                 final String classDescQualifiedName = classDesc.getQualifiedName();
 182  10
                 if (doesNameInExtendMatchSuperClassName(classDescQualifiedName,
 183  
                         currentAstSuperClassName)) {
 184  3
                     classDesc.registerNestedSubclass();
 185  
                 }
 186  10
             }
 187  
         }
 188  24
     }
 189  
 
 190  
     /**
 191  
      * Get qualified class name from given class Ast.
 192  
      * @param classAst class to get qualified class name
 193  
      * @return qualified class name of a class
 194  
      */
 195  
     private String getQualifiedClassName(DetailAST classAst) {
 196  24
         final String className = classAst.findFirstToken(TokenTypes.IDENT).getText();
 197  24
         String outerClassQualifiedName = null;
 198  24
         if (!classes.isEmpty()) {
 199  11
             outerClassQualifiedName = classes.peek().getQualifiedName();
 200  
         }
 201  24
         return getQualifiedClassName(packageName, outerClassQualifiedName, className);
 202  
     }
 203  
 
 204  
     /**
 205  
      * Calculate qualified class name(package + class name) laying inside given
 206  
      * outer class.
 207  
      * @param packageName package name, empty string on default package
 208  
      * @param outerClassQualifiedName qualified name(package + class) of outer class,
 209  
      *                           null if doesn't exist
 210  
      * @param className class name
 211  
      * @return qualified class name(package + class name)
 212  
      */
 213  
     private static String getQualifiedClassName(String packageName, String outerClassQualifiedName,
 214  
                                                 String className) {
 215  
         final String qualifiedClassName;
 216  
 
 217  24
         if (outerClassQualifiedName == null) {
 218  13
             if (packageName.isEmpty()) {
 219  1
                 qualifiedClassName = className;
 220  
             }
 221  
             else {
 222  12
                 qualifiedClassName = packageName + PACKAGE_SEPARATOR + className;
 223  
             }
 224  
         }
 225  
         else {
 226  11
             qualifiedClassName = outerClassQualifiedName + PACKAGE_SEPARATOR + className;
 227  
         }
 228  24
         return qualifiedClassName;
 229  
     }
 230  
 
 231  
     /**
 232  
      * Get super class name of given class.
 233  
      * @param classAst class
 234  
      * @return super class name or null if super class is not specified
 235  
      */
 236  
     private static String getSuperClassName(DetailAST classAst) {
 237  24
         String superClassName = null;
 238  24
         final DetailAST classExtend = classAst.findFirstToken(TokenTypes.EXTENDS_CLAUSE);
 239  24
         if (classExtend != null) {
 240  5
             superClassName = extractQualifiedName(classExtend);
 241  
         }
 242  24
         return superClassName;
 243  
     }
 244  
 
 245  
     /**
 246  
      * Checks if given super class name in extend clause match super class qualified name.
 247  
      * @param superClassQualifiedName super class qualified name (with package)
 248  
      * @param superClassInExtendClause name in extend clause
 249  
      * @return true if given super class name in extend clause match super class qualified name,
 250  
      *         false otherwise
 251  
      */
 252  
     private static boolean doesNameInExtendMatchSuperClassName(String superClassQualifiedName,
 253  
                                                                String superClassInExtendClause) {
 254  10
         String superClassNormalizedName = superClassQualifiedName;
 255  10
         if (!superClassInExtendClause.contains(PACKAGE_SEPARATOR)) {
 256  4
             superClassNormalizedName = getClassNameFromQualifiedName(superClassQualifiedName);
 257  
         }
 258  10
         return superClassNormalizedName.equals(superClassInExtendClause);
 259  
     }
 260  
 
 261  
     /**
 262  
      * Get class name from qualified name.
 263  
      * @param qualifiedName qualified class name
 264  
      * @return class name
 265  
      */
 266  
     private static String getClassNameFromQualifiedName(String qualifiedName) {
 267  9
         return qualifiedName.substring(qualifiedName.lastIndexOf(PACKAGE_SEPARATOR) + 1);
 268  
     }
 269  
 
 270  
     /** Maintains information about class' ctors. */
 271  116
     private static final class ClassDesc {
 272  
         /** Qualified class name(with package). */
 273  
         private final String qualifiedName;
 274  
 
 275  
         /** Is class declared as final. */
 276  
         private final boolean declaredAsFinal;
 277  
 
 278  
         /** Is class declared as abstract. */
 279  
         private final boolean declaredAsAbstract;
 280  
 
 281  
         /** Does class have non-private ctors. */
 282  
         private boolean withNonPrivateCtor;
 283  
 
 284  
         /** Does class have private ctors. */
 285  
         private boolean withPrivateCtor;
 286  
 
 287  
         /** Does class have nested subclass. */
 288  
         private boolean withNestedSubclass;
 289  
 
 290  
         /**
 291  
          *  Create a new ClassDesc instance.
 292  
          *  @param qualifiedName qualified class name(with package)
 293  
          *  @param declaredAsFinal indicates if the
 294  
          *         class declared as final
 295  
          *  @param declaredAsAbstract indicates if the
 296  
          *         class declared as abstract
 297  
          */
 298  24
         ClassDesc(String qualifiedName, boolean declaredAsFinal, boolean declaredAsAbstract) {
 299  24
             this.qualifiedName = qualifiedName;
 300  24
             this.declaredAsFinal = declaredAsFinal;
 301  24
             this.declaredAsAbstract = declaredAsAbstract;
 302  24
         }
 303  
 
 304  
         /**
 305  
          * Get qualified class name.
 306  
          * @return qualified class name
 307  
          */
 308  
         private String getQualifiedName() {
 309  26
             return qualifiedName;
 310  
         }
 311  
 
 312  
         /** Adds private ctor. */
 313  
         private void registerPrivateCtor() {
 314  14
             withPrivateCtor = true;
 315  14
         }
 316  
 
 317  
         /** Adds non-private ctor. */
 318  
         private void registerNonPrivateCtor() {
 319  2
             withNonPrivateCtor = true;
 320  2
         }
 321  
 
 322  
         /** Adds nested subclass. */
 323  
         private void registerNestedSubclass() {
 324  3
             withNestedSubclass = true;
 325  3
         }
 326  
 
 327  
         /**
 328  
          *  Does class have private ctors.
 329  
          *  @return true if class has private ctors
 330  
          */
 331  
         private boolean isWithPrivateCtor() {
 332  24
             return withPrivateCtor;
 333  
         }
 334  
 
 335  
         /**
 336  
          *  Does class have non-private ctors.
 337  
          *  @return true if class has non-private ctors
 338  
          */
 339  
         private boolean isWithNonPrivateCtor() {
 340  11
             return withNonPrivateCtor;
 341  
         }
 342  
 
 343  
         /**
 344  
          * Does class have nested subclass.
 345  
          * @return true if class has nested subclass
 346  
          */
 347  
         private boolean isWithNestedSubclass() {
 348  10
             return withNestedSubclass;
 349  
         }
 350  
 
 351  
         /**
 352  
          *  Is class declared as final.
 353  
          *  @return true if class is declared as final
 354  
          */
 355  
         private boolean isDeclaredAsFinal() {
 356  12
             return declaredAsFinal;
 357  
         }
 358  
 
 359  
         /**
 360  
          *  Is class declared as abstract.
 361  
          *  @return true if class is declared as final
 362  
          */
 363  
         private boolean isDeclaredAsAbstract() {
 364  14
             return declaredAsAbstract;
 365  
         }
 366  
     }
 367  
 }