Coverage Report - com.puppycrawl.tools.checkstyle.checks.coding.IllegalInstantiationCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
IllegalInstantiationCheck
94%
88/93
76%
42/55
3.846
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2014  Oliver Burn
 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  
 package com.puppycrawl.tools.checkstyle.checks.coding;
 20  
 
 21  
 import antlr.collections.AST;
 22  
 import com.google.common.collect.Sets;
 23  
 import com.puppycrawl.tools.checkstyle.api.Check;
 24  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 25  
 import com.puppycrawl.tools.checkstyle.api.FullIdent;
 26  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 27  
 import com.puppycrawl.tools.checkstyle.api.Utils;
 28  
 import java.util.Set;
 29  
 import java.util.StringTokenizer;
 30  
 
 31  
 // TODO: Clean up potential duplicate code here and in UnusedImportsCheck
 32  
 /**
 33  
  * <p>
 34  
  * Checks for illegal instantiations where a factory method is preferred.
 35  
  * </p>
 36  
  * <p>
 37  
  * Rationale: Depending on the project, for some classes it might be
 38  
  * preferable to create instances through factory methods rather than
 39  
  * calling the constructor.
 40  
  * </p>
 41  
  * <p>
 42  
  * A simple example is the java.lang.Boolean class, to save memory and CPU
 43  
  * cycles it is preferable to use the predeifined constants TRUE and FALSE.
 44  
  * Constructor invocations should be replaced by calls to Boolean.valueOf().
 45  
  * </p>
 46  
  * <p>
 47  
  * Some extremely performance sensitive projects may require the use of factory
 48  
  * methods for other classes as well, to enforce the usage of number caches or
 49  
  * object pools.
 50  
  * </p>
 51  
  * <p>
 52  
  * Limitations: It is currently not possible to specify array classes.
 53  
  * </p>
 54  
  * <p>
 55  
  * An example of how to configure the check is:
 56  
  * </p>
 57  
  * <pre>
 58  
  * &lt;module name="IllegalInstantiation"/&gt;
 59  
  * </pre>
 60  
  * @author lkuehne
 61  
  */
 62  1
 public class IllegalInstantiationCheck
 63  
     extends Check
 64  
 {
 65  
     /** Set of fully qualified classnames. E.g. "java.lang.Boolean" */
 66  1
     private final Set<String> mIllegalClasses = Sets.newHashSet();
 67  
 
 68  
     /** name of the package */
 69  
     private String mPkgName;
 70  
 
 71  
     /** the imports for the file */
 72  1
     private final Set<FullIdent> mImports = Sets.newHashSet();
 73  
 
 74  
     /** the class names defined in the file */
 75  1
     private final Set<String> mClassNames = Sets.newHashSet();
 76  
 
 77  
     /** the instantiations in the file */
 78  1
     private final Set<DetailAST> mInstantiations = Sets.newHashSet();
 79  
 
 80  
     @Override
 81  
     public int[] getDefaultTokens()
 82  
     {
 83  1
         return new int[] {
 84  
             TokenTypes.IMPORT,
 85  
             TokenTypes.LITERAL_NEW,
 86  
             TokenTypes.PACKAGE_DEF,
 87  
             TokenTypes.CLASS_DEF,
 88  
         };
 89  
     }
 90  
 
 91  
     @Override
 92  
     public int[] getAcceptableTokens()
 93  
     {
 94  
         // Return an empty array to not allow user to change configuration.
 95  0
         return new int[] {};
 96  
     }
 97  
 
 98  
     @Override
 99  
     public int[] getRequiredTokens()
 100  
     {
 101  0
         return new int[] {
 102  
             TokenTypes.IMPORT,
 103  
             TokenTypes.LITERAL_NEW,
 104  
             TokenTypes.PACKAGE_DEF,
 105  
         };
 106  
     }
 107  
 
 108  
     @Override
 109  
     public void beginTree(DetailAST aRootAST)
 110  
     {
 111  1
         super.beginTree(aRootAST);
 112  1
         mPkgName = null;
 113  1
         mImports.clear();
 114  1
         mInstantiations.clear();
 115  1
         mClassNames.clear();
 116  1
     }
 117  
 
 118  
     @Override
 119  
     public void visitToken(DetailAST aAST)
 120  
     {
 121  23
         switch (aAST.getType()) {
 122  
         case TokenTypes.LITERAL_NEW:
 123  12
             processLiteralNew(aAST);
 124  12
             break;
 125  
         case TokenTypes.PACKAGE_DEF:
 126  1
             processPackageDef(aAST);
 127  1
             break;
 128  
         case TokenTypes.IMPORT:
 129  3
             processImport(aAST);
 130  3
             break;
 131  
         case TokenTypes.CLASS_DEF:
 132  7
             processClassDef(aAST);
 133  7
             break;
 134  
         default:
 135  0
             throw new IllegalArgumentException("Unknown type " + aAST);
 136  
         }
 137  23
     }
 138  
 
 139  
     @Override
 140  
     public void finishTree(DetailAST aRootAST)
 141  
     {
 142  1
         for (DetailAST literalNewAST : mInstantiations) {
 143  12
             postprocessLiteralNew(literalNewAST);
 144  
         }
 145  1
     }
 146  
 
 147  
     /**
 148  
      * Collects classes defined in the source file. Required
 149  
      * to avoid false alarms for local vs. java.lang classes.
 150  
      *
 151  
      * @param aAST the classdef token.
 152  
      */
 153  
     private void processClassDef(DetailAST aAST)
 154  
     {
 155  7
         final DetailAST identToken = aAST.findFirstToken(TokenTypes.IDENT);
 156  7
         final String className = identToken.getText();
 157  7
         mClassNames.add(className);
 158  7
     }
 159  
 
 160  
     /**
 161  
      * Perform processing for an import token
 162  
      * @param aAST the import token
 163  
      */
 164  
     private void processImport(DetailAST aAST)
 165  
     {
 166  3
         final FullIdent name = FullIdent.createFullIdentBelow(aAST);
 167  3
         if (name != null) {
 168  
             // Note: different from UnusedImportsCheck.processImport(),
 169  
             // '.*' imports are also added here
 170  3
             mImports.add(name);
 171  
         }
 172  3
     }
 173  
 
 174  
     /**
 175  
      * Perform processing for an package token
 176  
      * @param aAST the package token
 177  
      */
 178  
     private void processPackageDef(DetailAST aAST)
 179  
     {
 180  1
         final DetailAST packageNameAST = aAST.getLastChild()
 181  
                 .getPreviousSibling();
 182  1
         final FullIdent packageIdent =
 183  
                 FullIdent.createFullIdent(packageNameAST);
 184  1
         mPkgName = packageIdent.getText();
 185  1
     }
 186  
 
 187  
     /**
 188  
      * Collects a "new" token.
 189  
      * @param aAST the "new" token
 190  
      */
 191  
     private void processLiteralNew(DetailAST aAST)
 192  
     {
 193  12
         mInstantiations.add(aAST);
 194  12
     }
 195  
 
 196  
     /**
 197  
      * Processes one of the collected "new" tokens when treewalking
 198  
      * has finished.
 199  
      * @param aAST the "new" token.
 200  
      */
 201  
     private void postprocessLiteralNew(DetailAST aAST)
 202  
     {
 203  12
         final DetailAST typeNameAST = aAST.getFirstChild();
 204  12
         final AST nameSibling = typeNameAST.getNextSibling();
 205  12
         if ((nameSibling != null)
 206  
                 && (nameSibling.getType() == TokenTypes.ARRAY_DECLARATOR))
 207  
         {
 208  
             // aAST == "new Boolean[]"
 209  1
             return;
 210  
         }
 211  
 
 212  11
         final FullIdent typeIdent = FullIdent.createFullIdent(typeNameAST);
 213  11
         final String typeName = typeIdent.getText();
 214  11
         final int lineNo = aAST.getLineNo();
 215  11
         final int colNo = aAST.getColumnNo();
 216  11
         final String fqClassName = getIllegalInstantiation(typeName);
 217  11
         if (fqClassName != null) {
 218  6
             log(lineNo, colNo, "instantiation.avoid", fqClassName);
 219  
         }
 220  11
     }
 221  
 
 222  
     /**
 223  
      * Checks illegal instantiations.
 224  
      * @param aClassName instantiated class, may or may not be qualified
 225  
      * @return the fully qualified class name of aClassName
 226  
      * or null if instantiation of aClassName is OK
 227  
      */
 228  
     private String getIllegalInstantiation(String aClassName)
 229  
     {
 230  11
         final String javaLang = "java.lang.";
 231  
 
 232  11
         if (mIllegalClasses.contains(aClassName)) {
 233  1
             return aClassName;
 234  
         }
 235  
 
 236  10
         final int clsNameLen = aClassName.length();
 237  10
         final int pkgNameLen = (mPkgName == null) ? 0 : mPkgName.length();
 238  
 
 239  10
         for (String illegal : mIllegalClasses) {
 240  27
             final int illegalLen = illegal.length();
 241  
 
 242  
             // class from java.lang
 243  27
             if (((illegalLen - javaLang.length()) == clsNameLen)
 244  
                 && illegal.endsWith(aClassName)
 245  
                 && illegal.startsWith(javaLang))
 246  
             {
 247  
                 // java.lang needs no import, but a class without import might
 248  
                 // also come from the same file or be in the same package.
 249  
                 // E.g. if a class defines an inner class "Boolean",
 250  
                 // the expression "new Boolean()" refers to that class,
 251  
                 // not to java.lang.Boolean
 252  
 
 253  2
                 final boolean isSameFile = mClassNames.contains(aClassName);
 254  
 
 255  2
                 boolean isSamePackage = false;
 256  
                 try {
 257  2
                     final ClassLoader classLoader = getClassLoader();
 258  2
                     if (classLoader != null) {
 259  2
                         final String fqName = mPkgName + "." + aClassName;
 260  2
                         classLoader.loadClass(fqName);
 261  
                         // no ClassNotFoundException, fqName is a known class
 262  0
                         isSamePackage = true;
 263  
                     }
 264  
                 }
 265  2
                 catch (final ClassNotFoundException ex) {
 266  
                     // not a class from the same package
 267  2
                     isSamePackage = false;
 268  0
                 }
 269  
 
 270  2
                 if (!(isSameFile || isSamePackage)) {
 271  2
                     return illegal;
 272  
                 }
 273  
             }
 274  
 
 275  
             // class from same package
 276  
 
 277  
             // the toplevel package (mPkgName == null) is covered by the
 278  
             // "illegalInsts.contains(aClassName)" check above
 279  
 
 280  
             // the test is the "no garbage" version of
 281  
             // illegal.equals(mPkgName + "." + aClassName)
 282  25
             if ((mPkgName != null)
 283  
                 && (clsNameLen == illegalLen - pkgNameLen - 1)
 284  
                 && (illegal.charAt(pkgNameLen) == '.')
 285  
                 && illegal.endsWith(aClassName)
 286  
                 && illegal.startsWith(mPkgName))
 287  
             {
 288  1
                 return illegal;
 289  
             }
 290  
             // import statements
 291  24
             for (FullIdent importLineText : mImports) {
 292  71
                 final String importArg = importLineText.getText();
 293  71
                 if (importArg.endsWith(".*")) {
 294  23
                     final String fqClass =
 295  
                         importArg.substring(0, importArg.length() - 1)
 296  
                         + aClassName;
 297  
                     // assume that illegalInsts only contain existing classes
 298  
                     // or else we might create a false alarm here
 299  23
                     if (mIllegalClasses.contains(fqClass)) {
 300  1
                         return fqClass;
 301  
                     }
 302  22
                 }
 303  
                 else {
 304  48
                     if (Utils.baseClassname(importArg).equals(aClassName)
 305  
                         && mIllegalClasses.contains(importArg))
 306  
                     {
 307  1
                         return importArg;
 308  
                     }
 309  
                 }
 310  69
             }
 311  22
         }
 312  5
         return null;
 313  
     }
 314  
 
 315  
     /**
 316  
      * Sets the classes that are illegal to instantiate.
 317  
      * @param aClassNames a comma seperate list of class names
 318  
      */
 319  
     public void setClasses(String aClassNames)
 320  
     {
 321  1
         mIllegalClasses.clear();
 322  1
         final StringTokenizer tok = new StringTokenizer(aClassNames, ",");
 323  5
         while (tok.hasMoreTokens()) {
 324  4
             mIllegalClasses.add(tok.nextToken());
 325  
         }
 326  1
     }
 327  
 }