Coverage Report - com.puppycrawl.tools.checkstyle.checks.coding.IllegalTypeCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
IllegalTypeCheck
100%
126/126
100%
67/67
2.36
 
 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.coding;
 21  
 
 22  
 import java.util.ArrayList;
 23  
 import java.util.Collections;
 24  
 import java.util.HashSet;
 25  
 import java.util.List;
 26  
 import java.util.Set;
 27  
 import java.util.regex.Pattern;
 28  
 
 29  
 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
 30  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 31  
 import com.puppycrawl.tools.checkstyle.api.FullIdent;
 32  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 33  
 import com.puppycrawl.tools.checkstyle.utils.CheckUtils;
 34  
 import com.puppycrawl.tools.checkstyle.utils.TokenUtils;
 35  
 
 36  
 /**
 37  
  * Checks that particular class are never used as types in variable
 38  
  * declarations, return values or parameters.
 39  
  *
 40  
  * <p>Rationale:
 41  
  * Helps reduce coupling on concrete classes.
 42  
  *
 43  
  * <p>Check has following properties:
 44  
  *
 45  
  * <p><b>format</b> - Pattern for illegal class names.
 46  
  *
 47  
  * <p><b>legalAbstractClassNames</b> - Abstract classes that may be used as types.
 48  
  *
 49  
  * <p><b>illegalClassNames</b> - Classes that should not be used as types in variable
 50  
    declarations, return values or parameters.
 51  
  * It is possible to set illegal class names via short or
 52  
  * <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.7">
 53  
  *  canonical</a> name.
 54  
  *  Specifying illegal type invokes analyzing imports and Check puts violations at
 55  
  *   corresponding declarations
 56  
  *  (of variables, methods or parameters). This helps to avoid ambiguous cases, e.g.:
 57  
  *
 58  
  * <p>{@code java.awt.List} was set as illegal class name, then, code like:
 59  
  *
 60  
  * <p>{@code
 61  
  * import java.util.List;<br>
 62  
  * ...<br>
 63  
  * List list; //No violation here
 64  
  * }
 65  
  *
 66  
  * <p>will be ok.
 67  
  *
 68  
  * <p><b>validateAbstractClassNames</b> - controls whether to validate abstract class names.
 69  
  * Default value is <b>false</b>
 70  
  * </p>
 71  
  *
 72  
  * <p><b>ignoredMethodNames</b> - Methods that should not be checked.
 73  
  *
 74  
  * <p><b>memberModifiers</b> - To check only methods and fields with only specified modifiers.
 75  
  *
 76  
  * <p>In most cases it's justified to put following classes to <b>illegalClassNames</b>:
 77  
  * <ul>
 78  
  * <li>GregorianCalendar</li>
 79  
  * <li>Hashtable</li>
 80  
  * <li>ArrayList</li>
 81  
  * <li>LinkedList</li>
 82  
  * <li>Vector</li>
 83  
  * </ul>
 84  
  *
 85  
  * <p>as methods that are differ from interface methods are rear used, so in most cases user will
 86  
  *  benefit from checking for them.
 87  
  * </p>
 88  
  *
 89  
  * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
 90  
  * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
 91  
  * @author <a href="mailto:andreyselkin@gmail.com">Andrei Selkin</a>
 92  
  */
 93  
 public final class IllegalTypeCheck extends AbstractCheck {
 94  
 
 95  
     /**
 96  
      * A key is pointing to the warning message text in "messages.properties"
 97  
      * file.
 98  
      */
 99  
     public static final String MSG_KEY = "illegal.type";
 100  
 
 101  
     /** Abstract classes legal by default. */
 102  1
     private static final String[] DEFAULT_LEGAL_ABSTRACT_NAMES = {};
 103  
     /** Types illegal by default. */
 104  1
     private static final String[] DEFAULT_ILLEGAL_TYPES = {
 105  
         "HashSet",
 106  
         "HashMap",
 107  
         "LinkedHashMap",
 108  
         "LinkedHashSet",
 109  
         "TreeSet",
 110  
         "TreeMap",
 111  
         "java.util.HashSet",
 112  
         "java.util.HashMap",
 113  
         "java.util.LinkedHashMap",
 114  
         "java.util.LinkedHashSet",
 115  
         "java.util.TreeSet",
 116  
         "java.util.TreeMap",
 117  
     };
 118  
 
 119  
     /** Default ignored method names. */
 120  1
     private static final String[] DEFAULT_IGNORED_METHOD_NAMES = {
 121  
         "getInitialContext",
 122  
         "getEnvironment",
 123  
     };
 124  
 
 125  
     /** Illegal classes. */
 126  22
     private final Set<String> illegalClassNames = new HashSet<>();
 127  
     /** Illegal short classes. */
 128  22
     private final Set<String> illegalShortClassNames = new HashSet<>();
 129  
     /** Legal abstract classes. */
 130  22
     private final Set<String> legalAbstractClassNames = new HashSet<>();
 131  
     /** Methods which should be ignored. */
 132  22
     private final Set<String> ignoredMethodNames = new HashSet<>();
 133  
     /** Check methods and fields with only corresponding modifiers. */
 134  
     private List<Integer> memberModifiers;
 135  
 
 136  
     /** The regexp to match against. */
 137  22
     private Pattern format = Pattern.compile("^(.*[.])?Abstract.*$");
 138  
 
 139  
     /**
 140  
      * Controls whether to validate abstract class names.
 141  
      */
 142  
     private boolean validateAbstractClassNames;
 143  
 
 144  
     /** Creates new instance of the check. */
 145  22
     public IllegalTypeCheck() {
 146  22
         setIllegalClassNames(DEFAULT_ILLEGAL_TYPES);
 147  22
         setLegalAbstractClassNames(DEFAULT_LEGAL_ABSTRACT_NAMES);
 148  22
         setIgnoredMethodNames(DEFAULT_IGNORED_METHOD_NAMES);
 149  22
     }
 150  
 
 151  
     /**
 152  
      * Set the format for the specified regular expression.
 153  
      * @param pattern a pattern.
 154  
      */
 155  
     public void setFormat(Pattern pattern) {
 156  1
         format = pattern;
 157  1
     }
 158  
 
 159  
     /**
 160  
      * Sets whether to validate abstract class names.
 161  
      * @param validateAbstractClassNames whether abstract class names must be ignored.
 162  
      */
 163  
     public void setValidateAbstractClassNames(boolean validateAbstractClassNames) {
 164  5
         this.validateAbstractClassNames = validateAbstractClassNames;
 165  5
     }
 166  
 
 167  
     @Override
 168  
     public int[] getDefaultTokens() {
 169  36
         return getAcceptableTokens();
 170  
     }
 171  
 
 172  
     @Override
 173  
     public int[] getAcceptableTokens() {
 174  42
         return new int[] {
 175  
             TokenTypes.VARIABLE_DEF,
 176  
             TokenTypes.PARAMETER_DEF,
 177  
             TokenTypes.METHOD_DEF,
 178  
             TokenTypes.IMPORT,
 179  
         };
 180  
     }
 181  
 
 182  
     @Override
 183  
     public void beginTree(DetailAST rootAST) {
 184  14
         illegalShortClassNames.clear();
 185  
 
 186  14
         for (String s : illegalClassNames) {
 187  108
             if (s.indexOf('.') == -1) {
 188  55
                 illegalShortClassNames.add(s);
 189  
             }
 190  108
         }
 191  14
     }
 192  
 
 193  
     @Override
 194  
     public int[] getRequiredTokens() {
 195  37
         return new int[] {TokenTypes.IMPORT};
 196  
     }
 197  
 
 198  
     @Override
 199  
     public void visitToken(DetailAST ast) {
 200  136
         switch (ast.getType()) {
 201  
             case TokenTypes.METHOD_DEF:
 202  38
                 if (isVerifiable(ast)) {
 203  37
                     visitMethodDef(ast);
 204  
                 }
 205  
                 break;
 206  
             case TokenTypes.VARIABLE_DEF:
 207  56
                 if (isVerifiable(ast)) {
 208  53
                     visitVariableDef(ast);
 209  
                 }
 210  
                 break;
 211  
             case TokenTypes.PARAMETER_DEF:
 212  14
                 visitParameterDef(ast);
 213  14
                 break;
 214  
             case TokenTypes.IMPORT:
 215  27
                 visitImport(ast);
 216  27
                 break;
 217  
             default:
 218  1
                 throw new IllegalStateException(ast.toString());
 219  
         }
 220  135
     }
 221  
 
 222  
     /**
 223  
      * Checks if current method's return type or variable's type is verifiable
 224  
      * according to <b>memberModifiers</b> option.
 225  
      * @param methodOrVariableDef METHOD_DEF or VARIABLE_DEF ast node.
 226  
      * @return true if member is verifiable according to <b>memberModifiers</b> option.
 227  
      */
 228  
     private boolean isVerifiable(DetailAST methodOrVariableDef) {
 229  94
         boolean result = true;
 230  94
         if (memberModifiers != null) {
 231  13
             final DetailAST modifiersAst = methodOrVariableDef
 232  13
                     .findFirstToken(TokenTypes.MODIFIERS);
 233  13
             result = isContainVerifiableType(modifiersAst);
 234  
         }
 235  94
         return result;
 236  
     }
 237  
 
 238  
     /**
 239  
      * Checks is modifiers contain verifiable type.
 240  
      *
 241  
      * @param modifiers
 242  
      *            parent node for all modifiers
 243  
      * @return true if method or variable can be verified
 244  
      */
 245  
     private boolean isContainVerifiableType(DetailAST modifiers) {
 246  13
         boolean result = false;
 247  13
         if (modifiers.getFirstChild() != null) {
 248  10
             for (DetailAST modifier = modifiers.getFirstChild(); modifier != null;
 249  3
                      modifier = modifier.getNextSibling()) {
 250  12
                 if (memberModifiers.contains(modifier.getType())) {
 251  9
                     result = true;
 252  9
                     break;
 253  
                 }
 254  
             }
 255  
         }
 256  13
         return result;
 257  
     }
 258  
 
 259  
     /**
 260  
      * Checks return type of a given method.
 261  
      * @param methodDef method for check.
 262  
      */
 263  
     private void visitMethodDef(DetailAST methodDef) {
 264  37
         if (isCheckedMethod(methodDef)) {
 265  34
             checkClassName(methodDef);
 266  
         }
 267  37
     }
 268  
 
 269  
     /**
 270  
      * Checks type of parameters.
 271  
      * @param parameterDef parameter list for check.
 272  
      */
 273  
     private void visitParameterDef(DetailAST parameterDef) {
 274  14
         final DetailAST grandParentAST = parameterDef.getParent().getParent();
 275  
 
 276  14
         if (grandParentAST.getType() == TokenTypes.METHOD_DEF
 277  9
             && isCheckedMethod(grandParentAST)) {
 278  8
             checkClassName(parameterDef);
 279  
         }
 280  14
     }
 281  
 
 282  
     /**
 283  
      * Checks type of given variable.
 284  
      * @param variableDef variable to check.
 285  
      */
 286  
     private void visitVariableDef(DetailAST variableDef) {
 287  53
         checkClassName(variableDef);
 288  53
     }
 289  
 
 290  
     /**
 291  
      * Checks imported type (as static and star imports are not supported by Check,
 292  
      *  only type is in the consideration).<br>
 293  
      * If this type is illegal due to Check's options - puts violation on it.
 294  
      * @param importAst {@link TokenTypes#IMPORT Import}
 295  
      */
 296  
     private void visitImport(DetailAST importAst) {
 297  27
         if (!isStarImport(importAst)) {
 298  21
             final String canonicalName = getImportedTypeCanonicalName(importAst);
 299  21
             extendIllegalClassNamesWithShortName(canonicalName);
 300  
         }
 301  27
     }
 302  
 
 303  
     /**
 304  
      * Checks if current import is star import. E.g.:
 305  
      * <p>
 306  
      * {@code
 307  
      * import java.util.*;
 308  
      * }
 309  
      * </p>
 310  
      * @param importAst {@link TokenTypes#IMPORT Import}
 311  
      * @return true if it is star import
 312  
      */
 313  
     private static boolean isStarImport(DetailAST importAst) {
 314  27
         boolean result = false;
 315  27
         DetailAST toVisit = importAst;
 316  248
         while (toVisit != null) {
 317  227
             toVisit = getNextSubTreeNode(toVisit, importAst);
 318  227
             if (toVisit != null && toVisit.getType() == TokenTypes.STAR) {
 319  6
                 result = true;
 320  6
                 break;
 321  
             }
 322  
         }
 323  27
         return result;
 324  
     }
 325  
 
 326  
     /**
 327  
      * Checks type of given method, parameter or variable.
 328  
      * @param ast node to check.
 329  
      */
 330  
     private void checkClassName(DetailAST ast) {
 331  95
         final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
 332  95
         final FullIdent ident = CheckUtils.createFullType(type);
 333  
 
 334  95
         if (isMatchingClassName(ident.getText())) {
 335  72
             log(ident.getLineNo(), ident.getColumnNo(),
 336  36
                 MSG_KEY, ident.getText());
 337  
         }
 338  95
     }
 339  
 
 340  
     /**
 341  
      * Returns true if given class name is one of illegal classes or else false.
 342  
      * @param className class name to check.
 343  
      * @return true if given class name is one of illegal classes
 344  
      *         or if it matches to abstract class names pattern.
 345  
      */
 346  
     private boolean isMatchingClassName(String className) {
 347  95
         final String shortName = className.substring(className.lastIndexOf('.') + 1);
 348  190
         return illegalClassNames.contains(className)
 349  71
                 || illegalShortClassNames.contains(shortName)
 350  
                 || validateAbstractClassNames
 351  25
                     && !legalAbstractClassNames.contains(className)
 352  24
                     && format.matcher(className).find();
 353  
     }
 354  
 
 355  
     /**
 356  
      * Extends illegal class names set via imported short type name.
 357  
      * @param canonicalName
 358  
      *  <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.7">
 359  
      *  Canonical</a> name of imported type.
 360  
      */
 361  
     private void extendIllegalClassNamesWithShortName(String canonicalName) {
 362  21
         if (illegalClassNames.contains(canonicalName)) {
 363  6
             final String shortName = canonicalName
 364  6
                 .substring(canonicalName.lastIndexOf('.') + 1);
 365  6
             illegalShortClassNames.add(shortName);
 366  
         }
 367  21
     }
 368  
 
 369  
     /**
 370  
      * Gets imported type's
 371  
      * <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.7">
 372  
      *  canonical name</a>.
 373  
      * @param importAst {@link TokenTypes#IMPORT Import}
 374  
      * @return Imported canonical type's name.
 375  
      */
 376  
     private static String getImportedTypeCanonicalName(DetailAST importAst) {
 377  21
         final StringBuilder canonicalNameBuilder = new StringBuilder(256);
 378  21
         DetailAST toVisit = importAst;
 379  218
         while (toVisit != null) {
 380  197
             toVisit = getNextSubTreeNode(toVisit, importAst);
 381  197
             if (toVisit != null && toVisit.getType() == TokenTypes.IDENT) {
 382  88
                 canonicalNameBuilder.append(toVisit.getText());
 383  88
                 final DetailAST nextSubTreeNode = getNextSubTreeNode(toVisit, importAst);
 384  88
                 if (nextSubTreeNode.getType() != TokenTypes.SEMI) {
 385  67
                     canonicalNameBuilder.append('.');
 386  
                 }
 387  88
             }
 388  
         }
 389  21
         return canonicalNameBuilder.toString();
 390  
     }
 391  
 
 392  
     /**
 393  
      * Gets the next node of a syntactical tree (child of a current node or
 394  
      * sibling of a current node, or sibling of a parent of a current node).
 395  
      * @param currentNodeAst Current node in considering
 396  
      * @param subTreeRootAst SubTree root
 397  
      * @return Current node after bypassing, if current node reached the root of a subtree
 398  
      *        method returns null
 399  
      */
 400  
     private static DetailAST
 401  
         getNextSubTreeNode(DetailAST currentNodeAst, DetailAST subTreeRootAst) {
 402  512
         DetailAST currentNode = currentNodeAst;
 403  512
         DetailAST toVisitAst = currentNode.getFirstChild();
 404  995
         while (toVisitAst == null) {
 405  525
             toVisitAst = currentNode.getNextSibling();
 406  525
             if (toVisitAst == null) {
 407  249
                 if (currentNode.getParent().equals(subTreeRootAst)) {
 408  42
                     break;
 409  
                 }
 410  207
                 currentNode = currentNode.getParent();
 411  
             }
 412  
         }
 413  512
         return toVisitAst;
 414  
     }
 415  
 
 416  
     /**
 417  
      * Returns true if method has to be checked or false.
 418  
      * @param ast method def to check.
 419  
      * @return true if we should check this method.
 420  
      */
 421  
     private boolean isCheckedMethod(DetailAST ast) {
 422  46
         final String methodName =
 423  46
             ast.findFirstToken(TokenTypes.IDENT).getText();
 424  46
         return !ignoredMethodNames.contains(methodName);
 425  
     }
 426  
 
 427  
     /**
 428  
      * Set the list of illegal variable types.
 429  
      * @param classNames array of illegal variable types
 430  
      * @noinspection WeakerAccess
 431  
      */
 432  
     public void setIllegalClassNames(String... classNames) {
 433  27
         illegalClassNames.clear();
 434  27
         Collections.addAll(illegalClassNames, classNames);
 435  27
     }
 436  
 
 437  
     /**
 438  
      * Set the list of ignore method names.
 439  
      * @param methodNames array of ignored method names
 440  
      * @noinspection WeakerAccess
 441  
      */
 442  
     public void setIgnoredMethodNames(String... methodNames) {
 443  25
         ignoredMethodNames.clear();
 444  25
         Collections.addAll(ignoredMethodNames, methodNames);
 445  25
     }
 446  
 
 447  
     /**
 448  
      * Set the list of legal abstract class names.
 449  
      * @param classNames array of legal abstract class names
 450  
      * @noinspection WeakerAccess
 451  
      */
 452  
     public void setLegalAbstractClassNames(String... classNames) {
 453  23
         legalAbstractClassNames.clear();
 454  23
         Collections.addAll(legalAbstractClassNames, classNames);
 455  23
     }
 456  
 
 457  
     /**
 458  
      * Set the list of member modifiers (of methods and fields) which should be checked.
 459  
      * @param modifiers String contains modifiers.
 460  
      */
 461  
     public void setMemberModifiers(String modifiers) {
 462  2
         final List<Integer> modifiersList = new ArrayList<>();
 463  8
         for (String modifier : modifiers.split(",")) {
 464  6
             modifiersList.add(TokenUtils.getTokenId(modifier.trim()));
 465  
         }
 466  2
         memberModifiers = modifiersList;
 467  2
     }
 468  
 }