Coverage Report - com.puppycrawl.tools.checkstyle.checks.metrics.AbstractClassCouplingCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractClassCouplingCheck
97%
41/42
50%
7/14
1.95
AbstractClassCouplingCheck$Context
100%
29/29
78%
11/14
1.95
 
 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.metrics;
 20  
 
 21  
 import com.google.common.collect.ImmutableSet;
 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.TokenTypes;
 28  
 import com.puppycrawl.tools.checkstyle.checks.CheckUtils;
 29  
 
 30  
 import java.util.Set;
 31  
 
 32  
 /**
 33  
  * Base class for coupling calculation.
 34  
  *
 35  
  * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
 36  
  * @author o_sukhodolsky
 37  
  */
 38  87
 public abstract class AbstractClassCouplingCheck extends Check
 39  
 {
 40  
     /** Class names to ignore. */
 41  1
     private static final Set<String> DEFAULT_EXCLUDED_CLASSES =
 42  
                 ImmutableSet.<String>builder()
 43  
                 // primitives
 44  
                 .add("boolean", "byte", "char", "double", "float", "int")
 45  
                 .add("long", "short", "void")
 46  
                 // wrappers
 47  
                 .add("Boolean", "Byte", "Character", "Double", "Float")
 48  
                 .add("Integer", "Long", "Short", "Void")
 49  
                 // java.lang.*
 50  
                 .add("Object", "Class")
 51  
                 .add("String", "StringBuffer", "StringBuilder")
 52  
                 // Exceptions
 53  
                 .add("ArrayIndexOutOfBoundsException", "Exception")
 54  
                 .add("RuntimeException", "IllegalArgumentException")
 55  
                 .add("IllegalStateException", "IndexOutOfBoundsException")
 56  
                 .add("NullPointerException", "Throwable", "SecurityException")
 57  
                 .add("UnsupportedOperationException")
 58  
                 // java.util.*
 59  
                 .add("List", "ArrayList", "Deque", "Queue", "LinkedList")
 60  
                 .add("Set", "HashSet", "SortedSet", "TreeSet")
 61  
                 .add("Map", "HashMap", "SortedMap", "TreeMap")
 62  
                 .build();
 63  
     /** User-configured class names to ignore. */
 64  3
     private Set<String> mExcludedClasses = DEFAULT_EXCLUDED_CLASSES;
 65  
     /** Allowed complexity. */
 66  
     private int mMax;
 67  
     /** package of the file we check. */
 68  
     private String mPackageName;
 69  
 
 70  
     /** Stack of contexts. */
 71  3
     private final FastStack<Context> mContextStack = FastStack.newInstance();
 72  
     /** Current context. */
 73  
     private Context mContext;
 74  
 
 75  
     /**
 76  
      * Creates new instance of the check.
 77  
      * @param aDefaultMax default value for allowed complexity.
 78  
      */
 79  
     protected AbstractClassCouplingCheck(int aDefaultMax)
 80  3
     {
 81  3
         setMax(aDefaultMax);
 82  3
     }
 83  
 
 84  
     @Override
 85  
     public final int[] getDefaultTokens()
 86  
     {
 87  3
         return getRequiredTokens();
 88  
     }
 89  
 
 90  
     /** @return allowed complexity. */
 91  
     public final int getMax()
 92  
     {
 93  4
         return mMax;
 94  
     }
 95  
 
 96  
     /**
 97  
      * Sets maximul allowed complexity.
 98  
      * @param aMax allowed complexity.
 99  
      */
 100  
     public final void setMax(int aMax)
 101  
     {
 102  6
         mMax = aMax;
 103  6
     }
 104  
 
 105  
     /**
 106  
      * Sets user-excluded classes to ignore.
 107  
      * @param aExcludedClasses the list of classes to ignore.
 108  
      */
 109  
     public final void setExcludedClasses(String[] aExcludedClasses)
 110  
     {
 111  1
         mExcludedClasses = ImmutableSet.copyOf(aExcludedClasses);
 112  1
     }
 113  
 
 114  
     @Override
 115  
     public final void beginTree(DetailAST aAST)
 116  
     {
 117  3
         mPackageName = "";
 118  3
     }
 119  
 
 120  
     /** @return message key we use for log violations. */
 121  
     protected abstract String getLogMessageId();
 122  
 
 123  
     @Override
 124  
     public void visitToken(DetailAST aAST)
 125  
     {
 126  46
         switch (aAST.getType()) {
 127  
         case TokenTypes.PACKAGE_DEF:
 128  2
             visitPackageDef(aAST);
 129  2
             break;
 130  
         case TokenTypes.CLASS_DEF:
 131  
         case TokenTypes.INTERFACE_DEF:
 132  
         case TokenTypes.ANNOTATION_DEF:
 133  
         case TokenTypes.ENUM_DEF:
 134  13
             visitClassDef(aAST);
 135  13
             break;
 136  
         case TokenTypes.TYPE:
 137  14
             mContext.visitType(aAST);
 138  14
             break;
 139  
         case TokenTypes.LITERAL_NEW:
 140  16
             mContext.visitLiteralNew(aAST);
 141  16
             break;
 142  
         case TokenTypes.LITERAL_THROWS:
 143  1
             mContext.visitLiteralThrows(aAST);
 144  1
             break;
 145  
         default:
 146  0
             throw new IllegalStateException(aAST.toString());
 147  
         }
 148  46
     }
 149  
 
 150  
     @Override
 151  
     public void leaveToken(DetailAST aAST)
 152  
     {
 153  46
         switch (aAST.getType()) {
 154  
         case TokenTypes.CLASS_DEF:
 155  
         case TokenTypes.INTERFACE_DEF:
 156  
         case TokenTypes.ANNOTATION_DEF:
 157  
         case TokenTypes.ENUM_DEF:
 158  13
             leaveClassDef();
 159  13
             break;
 160  
         default:
 161  
             // Do nothing
 162  
         }
 163  46
     }
 164  
 
 165  
     /**
 166  
      * Stores package of current class we check.
 167  
      * @param aPkg package definition.
 168  
      */
 169  
     private void visitPackageDef(DetailAST aPkg)
 170  
     {
 171  2
         final FullIdent ident = FullIdent.createFullIdent(aPkg.getLastChild()
 172  
                 .getPreviousSibling());
 173  2
         mPackageName = ident.getText();
 174  2
     }
 175  
 
 176  
     /**
 177  
      * Creates new context for a given class.
 178  
      * @param aClassDef class definition node.
 179  
      */
 180  
     private void visitClassDef(DetailAST aClassDef)
 181  
     {
 182  13
         mContextStack.push(mContext);
 183  13
         final String className =
 184  
             aClassDef.findFirstToken(TokenTypes.IDENT).getText();
 185  13
         mContext = new Context(className,
 186  
                                aClassDef.getLineNo(),
 187  
                                aClassDef.getColumnNo());
 188  13
     }
 189  
 
 190  
     /** Restores previous context. */
 191  
     private void leaveClassDef()
 192  
     {
 193  13
         mContext.checkCoupling();
 194  13
         mContext = mContextStack.pop();
 195  13
     }
 196  
 
 197  
     /**
 198  
      * Incapsulates information about class coupling.
 199  
      *
 200  
      * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
 201  
      * @author o_sukhodolsky
 202  
      */
 203  
     private class Context
 204  
     {
 205  
         /**
 206  
          * Set of referenced classes.
 207  
          * Sorted by name for predictable error messages in unit tests.
 208  
          */
 209  13
         private final Set<String> mReferencedClassNames = Sets.newTreeSet();
 210  
         /** Own class name. */
 211  
         private final String mClassName;
 212  
         /* Location of own class. (Used to log violations) */
 213  
         /** Line number of class definition. */
 214  
         private final int mLineNo;
 215  
         /** Column number of class definition. */
 216  
         private final int mColumnNo;
 217  
 
 218  
         /**
 219  
          * Create new context associated with given class.
 220  
          * @param aClassName name of the given class.
 221  
          * @param aLineNo line of class definition.
 222  
          * @param aColumnNo column of class definition.
 223  
          */
 224  
         public Context(String aClassName, int aLineNo, int aColumnNo)
 225  13
         {
 226  13
             mClassName = aClassName;
 227  13
             mLineNo = aLineNo;
 228  13
             mColumnNo = aColumnNo;
 229  13
         }
 230  
 
 231  
         /**
 232  
          * Visits throws clause and collects all exceptions we throw.
 233  
          * @param aThrows throws to process.
 234  
          */
 235  
         public void visitLiteralThrows(DetailAST aThrows)
 236  
         {
 237  1
             for (DetailAST childAST = aThrows.getFirstChild();
 238  2
                  childAST != null;
 239  1
                  childAST = childAST.getNextSibling())
 240  
             {
 241  1
                 if (childAST.getType() != TokenTypes.COMMA) {
 242  1
                     addReferencedClassName(childAST);
 243  
                 }
 244  
             }
 245  1
         }
 246  
 
 247  
         /**
 248  
          * Visits type.
 249  
          * @param aAST type to process.
 250  
          */
 251  
         public void visitType(DetailAST aAST)
 252  
         {
 253  14
             final String className = CheckUtils.createFullType(aAST).getText();
 254  14
             mContext.addReferencedClassName(className);
 255  14
         }
 256  
 
 257  
         /**
 258  
          * Visits NEW.
 259  
          * @param aAST NEW to process.
 260  
          */
 261  
         public void visitLiteralNew(DetailAST aAST)
 262  
         {
 263  16
             mContext.addReferencedClassName(aAST.getFirstChild());
 264  16
         }
 265  
 
 266  
         /**
 267  
          * Adds new referenced class.
 268  
          * @param aAST a node which represents referenced class.
 269  
          */
 270  
         private void addReferencedClassName(DetailAST aAST)
 271  
         {
 272  17
             final String className = FullIdent.createFullIdent(aAST).getText();
 273  17
             addReferencedClassName(className);
 274  17
         }
 275  
 
 276  
         /**
 277  
          * Adds new referenced class.
 278  
          * @param aClassName class name of the referenced class.
 279  
          */
 280  
         private void addReferencedClassName(String aClassName)
 281  
         {
 282  31
             if (isSignificant(aClassName)) {
 283  12
                 mReferencedClassNames.add(aClassName);
 284  
             }
 285  31
         }
 286  
 
 287  
         /** Checks if coupling less than allowed or not. */
 288  
         public void checkCoupling()
 289  
         {
 290  13
             mReferencedClassNames.remove(mClassName);
 291  13
             mReferencedClassNames.remove(mPackageName + "." + mClassName);
 292  
 
 293  13
             if (mReferencedClassNames.size() > mMax) {
 294  4
                 log(mLineNo, mColumnNo, getLogMessageId(),
 295  
                         mReferencedClassNames.size(), getMax(),
 296  
                         mReferencedClassNames.toString());
 297  
             }
 298  13
         }
 299  
 
 300  
         /**
 301  
          * Checks if given class shouldn't be ignored and not from java.lang.
 302  
          * @param aClassName class to check.
 303  
          * @return true if we should count this class.
 304  
          */
 305  
         private boolean isSignificant(String aClassName)
 306  
         {
 307  31
             return (aClassName.length() > 0)
 308  
                     && !mExcludedClasses.contains(aClassName)
 309  
                     && !aClassName.startsWith("java.lang.");
 310  
         }
 311  
     }
 312  
 }