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