Coverage Report - com.puppycrawl.tools.checkstyle.checks.AbstractTypeAwareCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractTypeAwareCheck
86%
86/100
86%
52/60
1.949
AbstractTypeAwareCheck$1
N/A
N/A
1.949
AbstractTypeAwareCheck$ClassAlias
80%
4/5
N/A
1.949
AbstractTypeAwareCheck$ClassInfo
83%
5/6
50%
1/2
1.949
AbstractTypeAwareCheck$RegularClass
92%
13/14
66%
4/6
1.949
AbstractTypeAwareCheck$Token
92%
13/14
N/A
1.949
 
 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;
 20  
 
 21  
 import com.google.common.collect.Maps;
 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.FastStack;
 26  
 import com.puppycrawl.tools.checkstyle.api.FullIdent;
 27  
 import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
 28  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 29  
 import java.util.Map;
 30  
 import java.util.Set;
 31  
 
 32  
 /**
 33  
  * Abstract class that endeavours to maintain type information for the Java
 34  
  * file being checked. It provides helper methods for performing type
 35  
  * information functions.
 36  
  *
 37  
  * @author Oliver Burn
 38  
  * @version 1.0
 39  
  */
 40  35
 public abstract class AbstractTypeAwareCheck extends Check
 41  
 {
 42  
     /** imports details **/
 43  35
     private final Set<String> mImports = Sets.newHashSet();
 44  
 
 45  
     /** full identifier for package of the method **/
 46  
     private FullIdent mPackageFullIdent;
 47  
 
 48  
     /** Name of current class. */
 49  
     private String mCurrentClass;
 50  
 
 51  
     /** <code>ClassResolver</code> instance for current tree. */
 52  
     private ClassResolver mClassResolver;
 53  
 
 54  
     /** Stack of maps for type params. */
 55  35
     private final FastStack<Map<String, ClassInfo>> mTypeParams =
 56  
         FastStack.newInstance();
 57  
 
 58  
     /**
 59  
      * Whether to log class loading errors to the checkstyle report
 60  
      * instead of throwing a RTE.
 61  
      *
 62  
      * Logging errors will avoid stopping checkstyle completely
 63  
      * because of a typo in javadoc. However, with modern IDEs that
 64  
      * support automated refactoring and generate javadoc this will
 65  
      * occur rarely, so by default we assume a configuration problem
 66  
      * in the checkstyle classpath and throw an execption.
 67  
      *
 68  
      * This configuration option was triggered by bug 1422462.
 69  
      */
 70  35
     private boolean mLogLoadErrors = true;
 71  
 
 72  
     /**
 73  
      * Controls whether to log class loading errors to the checkstyle report
 74  
      * instead of throwing a RTE.
 75  
      *
 76  
      * @param aLogLoadErrors true if errors should be logged
 77  
      */
 78  
     public final void setLogLoadErrors(boolean aLogLoadErrors)
 79  
     {
 80  0
         mLogLoadErrors = aLogLoadErrors;
 81  0
     }
 82  
 
 83  
     /**
 84  
      * Whether to show class loading errors in the checkstyle report.
 85  
      * Request ID 1491630
 86  
      */
 87  
     private boolean mSuppressLoadErrors;
 88  
 
 89  
     /**
 90  
      * Controls whether to show class loading errors in the checkstyle report.
 91  
      *
 92  
      * @param aSuppressLoadErrors true if errors shouldn't be shown
 93  
      */
 94  
     public final void setSuppressLoadErrors(boolean aSuppressLoadErrors)
 95  
     {
 96  0
         mSuppressLoadErrors = aSuppressLoadErrors;
 97  0
     }
 98  
 
 99  
     /**
 100  
      * Called to process an AST when visiting it.
 101  
      * @param aAST the AST to process. Guaranteed to not be PACKAGE_DEF or
 102  
      *             IMPORT tokens.
 103  
      */
 104  
     protected abstract void processAST(DetailAST aAST);
 105  
 
 106  
     @Override
 107  
     public final int[] getRequiredTokens()
 108  
     {
 109  0
         return new int[] {
 110  
             TokenTypes.PACKAGE_DEF,
 111  
             TokenTypes.IMPORT,
 112  
             TokenTypes.CLASS_DEF,
 113  
             TokenTypes.ENUM_DEF,
 114  
         };
 115  
     }
 116  
 
 117  
     @Override
 118  
     public void beginTree(DetailAST aRootAST)
 119  
     {
 120  35
         mPackageFullIdent = FullIdent.createFullIdent(null);
 121  35
         mImports.clear();
 122  
         // add java.lang.* since it's always imported
 123  35
         mImports.add("java.lang.*");
 124  35
         mClassResolver = null;
 125  35
         mCurrentClass = "";
 126  35
         mTypeParams.clear();
 127  35
     }
 128  
 
 129  
     @Override
 130  
     public final void visitToken(DetailAST aAST)
 131  
     {
 132  579
         if (aAST.getType() == TokenTypes.PACKAGE_DEF) {
 133  20
             processPackage(aAST);
 134  
         }
 135  559
         else if (aAST.getType() == TokenTypes.IMPORT) {
 136  16
             processImport(aAST);
 137  
         }
 138  543
         else if ((aAST.getType() == TokenTypes.CLASS_DEF)
 139  
                  || (aAST.getType() == TokenTypes.ENUM_DEF))
 140  
         {
 141  107
             processClass(aAST);
 142  
         }
 143  
         else {
 144  436
             if (aAST.getType() == TokenTypes.METHOD_DEF) {
 145  390
                 processTypeParams(aAST);
 146  
             }
 147  436
             processAST(aAST);
 148  
         }
 149  579
     }
 150  
 
 151  
     @Override
 152  
     public final void leaveToken(DetailAST aAST)
 153  
     {
 154  579
         if ((aAST.getType() == TokenTypes.CLASS_DEF)
 155  
             || (aAST.getType() == TokenTypes.ENUM_DEF))
 156  
         {
 157  
             // perhaps it was inner class
 158  107
             int dotIdx = mCurrentClass.lastIndexOf("$");
 159  107
             if (dotIdx == -1) {
 160  
                 // perhaps just a class
 161  48
                 dotIdx = mCurrentClass.lastIndexOf(".");
 162  
             }
 163  107
             if (dotIdx == -1) {
 164  
                 // looks like a topmost class
 165  48
                 mCurrentClass = "";
 166  
             }
 167  
             else {
 168  59
                 mCurrentClass = mCurrentClass.substring(0, dotIdx);
 169  
             }
 170  107
             mTypeParams.pop();
 171  107
         }
 172  472
         else if (aAST.getType() == TokenTypes.METHOD_DEF) {
 173  390
             mTypeParams.pop();
 174  
         }
 175  82
         else if ((aAST.getType() != TokenTypes.PACKAGE_DEF)
 176  
                  && (aAST.getType() != TokenTypes.IMPORT))
 177  
         {
 178  46
             leaveAST(aAST);
 179  
         }
 180  579
     }
 181  
 
 182  
     /**
 183  
      * Called when exiting an AST. A no-op by default, extending classes
 184  
      * may choose to override this to augment their processing.
 185  
      * @param aAST the AST we are departing. Guaranteed to not be PACKAGE_DEF,
 186  
      *             CLASS_DEF, or IMPORT
 187  
      */
 188  
     protected void leaveAST(DetailAST aAST)
 189  
     {
 190  46
     }
 191  
 
 192  
     /**
 193  
      * Is exception is unchecked (subclass of <code>RuntimeException</code>
 194  
      * or <code>Error</code>
 195  
      *
 196  
      * @param aException <code>Class</code> of exception to check
 197  
      * @return true  if exception is unchecked
 198  
      *         false if exception is checked
 199  
      */
 200  
     protected boolean isUnchecked(Class<?> aException)
 201  
     {
 202  46
         return isSubclass(aException, RuntimeException.class)
 203  
             || isSubclass(aException, Error.class);
 204  
     }
 205  
 
 206  
     /**
 207  
      * Checks if one class is subclass of another
 208  
      *
 209  
      * @param aChild <code>Class</code> of class
 210  
      *               which should be child
 211  
      * @param aParent <code>Class</code> of class
 212  
      *                which should be parent
 213  
      * @return true  if aChild is subclass of aParent
 214  
      *         false otherwise
 215  
      */
 216  
     protected boolean isSubclass(Class<?> aChild, Class<?> aParent)
 217  
     {
 218  106
         return (aParent != null) && (aChild != null)
 219  
             &&  aParent.isAssignableFrom(aChild);
 220  
     }
 221  
 
 222  
     /** @return <code>ClassResolver</code> for current tree. */
 223  
     private ClassResolver getClassResolver()
 224  
     {
 225  121
         if (mClassResolver == null) {
 226  17
             mClassResolver =
 227  
                 new ClassResolver(getClassLoader(),
 228  
                                   mPackageFullIdent.getText(),
 229  
                                   mImports);
 230  
         }
 231  121
         return mClassResolver;
 232  
     }
 233  
 
 234  
     /**
 235  
      * Attempts to resolve the Class for a specified name.
 236  
      * @param aClassName name of the class to resolve
 237  
      * @param aCurrentClass name of surrounding class.
 238  
      * @return the resolved class or <code>null</code>
 239  
      *          if unable to resolve the class.
 240  
      */
 241  
     protected final Class<?> resolveClass(String aClassName,
 242  
             String aCurrentClass)
 243  
     {
 244  
         try {
 245  121
             return getClassResolver().resolve(aClassName, aCurrentClass);
 246  
         }
 247  0
         catch (final ClassNotFoundException e) {
 248  0
             return null;
 249  
         }
 250  
     }
 251  
 
 252  
     /**
 253  
      * Tries to load class. Logs error if unable.
 254  
      * @param aIdent name of class which we try to load.
 255  
      * @param aCurrentClass name of surrounding class.
 256  
      * @return <code>Class</code> for a ident.
 257  
      */
 258  
     protected final Class<?> tryLoadClass(Token aIdent, String aCurrentClass)
 259  
     {
 260  121
         final Class<?> clazz = resolveClass(aIdent.getText(), aCurrentClass);
 261  121
         if (clazz == null) {
 262  0
             logLoadError(aIdent);
 263  
         }
 264  121
         return clazz;
 265  
     }
 266  
 
 267  
     /**
 268  
      * Logs error if unable to load class information.
 269  
      * Abstract, should be overrided in subclasses.
 270  
      * @param aIdent class name for which we can no load class.
 271  
      */
 272  
     protected abstract void logLoadError(Token aIdent);
 273  
 
 274  
     /**
 275  
      * Common implementation for logLoadError() method.
 276  
      * @param aLineNo line number of the problem.
 277  
      * @param aColumnNo column number of the problem.
 278  
      * @param aMsgKey message key to use.
 279  
      * @param aValues values to fill the message out.
 280  
      */
 281  
     protected final void logLoadErrorImpl(int aLineNo, int aColumnNo,
 282  
                                           String aMsgKey, Object... aValues)
 283  
     {
 284  0
         if (!mLogLoadErrors) {
 285  0
             final LocalizedMessage msg = new LocalizedMessage(aLineNo,
 286  
                                                     aColumnNo,
 287  
                                                     getMessageBundle(),
 288  
                                                     aMsgKey,
 289  
                                                     aValues,
 290  
                                                     getSeverityLevel(),
 291  
                                                     getId(),
 292  
                                                     this.getClass(),
 293  
                                                     null);
 294  0
             throw new RuntimeException(msg.getMessage());
 295  
         }
 296  
 
 297  0
         if (!mSuppressLoadErrors) {
 298  0
             log(aLineNo, aColumnNo, aMsgKey, aValues);
 299  
         }
 300  0
     }
 301  
 
 302  
     /**
 303  
      * Collects the details of a package.
 304  
      * @param aAST node containing the package details
 305  
      */
 306  
     private void processPackage(DetailAST aAST)
 307  
     {
 308  20
         final DetailAST nameAST = aAST.getLastChild().getPreviousSibling();
 309  20
         mPackageFullIdent = FullIdent.createFullIdent(nameAST);
 310  20
     }
 311  
 
 312  
     /**
 313  
      * Collects the details of imports.
 314  
      * @param aAST node containing the import details
 315  
      */
 316  
     private void processImport(DetailAST aAST)
 317  
     {
 318  16
         final FullIdent name = FullIdent.createFullIdentBelow(aAST);
 319  16
         if (name != null) {
 320  16
             mImports.add(name.getText());
 321  
         }
 322  16
     }
 323  
 
 324  
     /**
 325  
      * Process type params (if any) for given class, enum or method.
 326  
      * @param aAST class, enum or method to process.
 327  
      */
 328  
     private void processTypeParams(DetailAST aAST)
 329  
     {
 330  497
         final DetailAST typeParams =
 331  
             aAST.findFirstToken(TokenTypes.TYPE_PARAMETERS);
 332  
 
 333  497
         final Map<String, ClassInfo> paramsMap = Maps.newHashMap();
 334  497
         mTypeParams.push(paramsMap);
 335  
 
 336  497
         if (typeParams == null) {
 337  481
             return;
 338  
         }
 339  
 
 340  16
         for (DetailAST child = typeParams.getFirstChild();
 341  78
              child != null;
 342  62
              child = child.getNextSibling())
 343  
         {
 344  62
             if (child.getType() == TokenTypes.TYPE_PARAMETER) {
 345  23
                 final DetailAST param = child;
 346  23
                 final String alias =
 347  
                     param.findFirstToken(TokenTypes.IDENT).getText();
 348  23
                 final DetailAST bounds =
 349  
                     param.findFirstToken(TokenTypes.TYPE_UPPER_BOUNDS);
 350  23
                 if (bounds != null) {
 351  17
                     final FullIdent name =
 352  
                         FullIdent.createFullIdentBelow(bounds);
 353  17
                     final ClassInfo ci =
 354  
                         createClassInfo(new Token(name), getCurrentClassName());
 355  17
                     paramsMap.put(alias, ci);
 356  
                 }
 357  
             }
 358  
         }
 359  16
     }
 360  
 
 361  
     /**
 362  
      * Processes class definition.
 363  
      * @param aAST class definition to process.
 364  
      */
 365  
     private void processClass(DetailAST aAST)
 366  
     {
 367  107
         final DetailAST ident = aAST.findFirstToken(TokenTypes.IDENT);
 368  107
         mCurrentClass += ("".equals(mCurrentClass) ? "" : "$")
 369  
             + ident.getText();
 370  
 
 371  107
         processTypeParams(aAST);
 372  107
     }
 373  
 
 374  
     /**
 375  
      * Returns current class.
 376  
      * @return name of current class.
 377  
      */
 378  
     protected final String getCurrentClassName()
 379  
     {
 380  238
         return mCurrentClass;
 381  
     }
 382  
 
 383  
     /**
 384  
      * Creates class info for given name.
 385  
      * @param aName name of type.
 386  
      * @param aSurroundingClass name of surrounding class.
 387  
      * @return class infor for given name.
 388  
      */
 389  
     protected final ClassInfo createClassInfo(final Token aName,
 390  
                                               final String aSurroundingClass)
 391  
     {
 392  238
         final ClassInfo ci = findClassAlias(aName.getText());
 393  238
         if (ci != null) {
 394  56
             return new ClassAlias(aName, ci);
 395  
         }
 396  182
         return new RegularClass(aName, aSurroundingClass, this);
 397  
     }
 398  
 
 399  
     /**
 400  
      * Looking if a given name is alias.
 401  
      * @param aName given name
 402  
      * @return ClassInfo for alias if it exists, null otherwise
 403  
      */
 404  
     protected final ClassInfo findClassAlias(final String aName)
 405  
     {
 406  238
         ClassInfo ci = null;
 407  648
         for (int i = mTypeParams.size() - 1; i >= 0; i--) {
 408  466
             final Map<String, ClassInfo> paramMap = mTypeParams.peek(i);
 409  466
             ci = paramMap.get(aName);
 410  466
             if (ci != null) {
 411  56
                 break;
 412  
             }
 413  
         }
 414  238
         return ci;
 415  
     }
 416  
 
 417  
     /**
 418  
      * Contains class's <code>Token</code>.
 419  
      */
 420  
     protected abstract static class ClassInfo
 421  
     {
 422  
         /** <code>FullIdent</code> associated with this class. */
 423  
         private final Token mName;
 424  
 
 425  
         /** @return class name */
 426  
         public final Token getName()
 427  
         {
 428  362
             return mName;
 429  
         }
 430  
 
 431  
         /** @return <code>Class</code> associated with an object. */
 432  
         public abstract Class<?> getClazz();
 433  
 
 434  
         /**
 435  
          * Creates new instance of class inforamtion object.
 436  
          * @param aName token which represents class name.
 437  
          */
 438  
         protected ClassInfo(final Token aName)
 439  238
         {
 440  238
             if (aName == null) {
 441  0
                 throw new NullPointerException(
 442  
                     "ClassInfo's name should be non-null");
 443  
             }
 444  238
             mName = aName;
 445  238
         }
 446  
     }
 447  
 
 448  
     /** Represents regular classes/enumes. */
 449  182
     private static final class RegularClass extends ClassInfo
 450  
     {
 451  
         /** name of surrounding class. */
 452  
         private final String mSurroundingClass;
 453  
         /** is class loadable. */
 454  182
         private boolean mIsLoadable = true;
 455  
         /** <code>Class</code> object of this class if it's loadable. */
 456  
         private Class<?> mClass;
 457  
         /** the check we use to resolve classes. */
 458  
         private final AbstractTypeAwareCheck mCheck;
 459  
 
 460  
         /**
 461  
          * Creates new instance of of class information object.
 462  
          * @param aName <code>FullIdent</code> associated with new object.
 463  
          * @param aSurroundingClass name of current surrounding class.
 464  
          * @param aCheck the check we use to load class.
 465  
          */
 466  
         private RegularClass(final Token aName,
 467  
                              final String aSurroundingClass,
 468  
                              final AbstractTypeAwareCheck aCheck)
 469  
         {
 470  182
             super(aName);
 471  182
             mSurroundingClass = aSurroundingClass;
 472  182
             mCheck = aCheck;
 473  182
         }
 474  
         /** @return if class is loadable ot not. */
 475  
         private boolean isLoadable()
 476  
         {
 477  232
             return mIsLoadable;
 478  
         }
 479  
 
 480  
         @Override
 481  
         public Class<?> getClazz()
 482  
         {
 483  232
             if (isLoadable() && (mClass == null)) {
 484  121
                 setClazz(mCheck.tryLoadClass(getName(), mSurroundingClass));
 485  
             }
 486  232
             return mClass;
 487  
         }
 488  
 
 489  
         /**
 490  
          * Associates <code> Class</code> with an object.
 491  
          * @param aClass <code>Class</code> to associate with.
 492  
          */
 493  
         private void setClazz(Class<?> aClass)
 494  
         {
 495  121
             mClass = aClass;
 496  121
             mIsLoadable = (mClass != null);
 497  121
         }
 498  
 
 499  
         @Override
 500  
         public String toString()
 501  
         {
 502  0
             return "RegularClass[name=" + getName()
 503  
                 + ", in class=" + mSurroundingClass
 504  
                 + ", loadable=" + mIsLoadable
 505  
                 + ", class=" + mClass + "]";
 506  
         }
 507  
     }
 508  
 
 509  
     /** Represents type param which is "alias" for real type. */
 510  
     private static class ClassAlias extends ClassInfo
 511  
     {
 512  
         /** Class information associated with the alias. */
 513  
         private final ClassInfo mClassInfo;
 514  
 
 515  
         /**
 516  
          * Creates nnew instance of the class.
 517  
          * @param aName token which represents name of class alias.
 518  
          * @param aClassInfo class information associated with the alias.
 519  
          */
 520  
         ClassAlias(final Token aName, ClassInfo aClassInfo)
 521  
         {
 522  56
             super(aName);
 523  56
             mClassInfo = aClassInfo;
 524  56
         }
 525  
 
 526  
         @Override
 527  
         public final Class<?> getClazz()
 528  
         {
 529  59
             return mClassInfo.getClazz();
 530  
         }
 531  
 
 532  
         @Override
 533  
         public String toString()
 534  
         {
 535  0
             return "ClassAlias[alias " + getName()
 536  
                 + " for " + mClassInfo + "]";
 537  
         }
 538  
     }
 539  
 
 540  
     /**
 541  
      * Represents text element with location in the text.
 542  
      */
 543  35
     protected static class Token
 544  
     {
 545  
         /** token's column number. */
 546  
         private final int mColumn;
 547  
         /** token's line number. */
 548  
         private final int mLine;
 549  
         /** token's text. */
 550  
         private final String mText;
 551  
 
 552  
         /**
 553  
          * Creates token.
 554  
          * @param aText token's text
 555  
          * @param aLine token's line number
 556  
          * @param aColumn token's column number
 557  
          */
 558  
         public Token(String aText, int aLine, int aColumn)
 559  82
         {
 560  82
             mText = aText;
 561  82
             mLine = aLine;
 562  82
             mColumn = aColumn;
 563  82
         }
 564  
 
 565  
         /**
 566  
          * Converts FullIdent to Token.
 567  
          * @param aFullIdent full ident to convert.
 568  
          */
 569  
         public Token(FullIdent aFullIdent)
 570  156
         {
 571  156
             mText = aFullIdent.getText();
 572  156
             mLine = aFullIdent.getLineNo();
 573  156
             mColumn = aFullIdent.getColumnNo();
 574  156
         }
 575  
 
 576  
         /** @return line number of the token */
 577  
         public int getLineNo()
 578  
         {
 579  28
             return mLine;
 580  
         }
 581  
 
 582  
         /** @return column number of the token */
 583  
         public int getColumnNo()
 584  
         {
 585  28
             return mColumn;
 586  
         }
 587  
 
 588  
         /** @return text of the token */
 589  
         public String getText()
 590  
         {
 591  586
             return mText;
 592  
         }
 593  
 
 594  
         @Override
 595  
         public String toString()
 596  
         {
 597  0
             return "Token[" + getText() + "(" + getLineNo()
 598  
                 + "x" + getColumnNo() + ")]";
 599  
         }
 600  
     }
 601  
 }