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