001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2014 Oliver Burn 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019package com.puppycrawl.tools.checkstyle.checks.metrics; 020 021import com.google.common.collect.ImmutableSet; 022import com.google.common.collect.Sets; 023import com.puppycrawl.tools.checkstyle.api.Check; 024import com.puppycrawl.tools.checkstyle.api.DetailAST; 025import com.puppycrawl.tools.checkstyle.api.FastStack; 026import com.puppycrawl.tools.checkstyle.api.FullIdent; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028import com.puppycrawl.tools.checkstyle.checks.CheckUtils; 029 030import java.util.Set; 031 032/** 033 * Base class for coupling calculation. 034 * 035 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a> 036 * @author o_sukhodolsky 037 */ 038public abstract class AbstractClassCouplingCheck extends Check 039{ 040 /** Class names to ignore. */ 041 private static final Set<String> DEFAULT_EXCLUDED_CLASSES = 042 ImmutableSet.<String>builder() 043 // primitives 044 .add("boolean", "byte", "char", "double", "float", "int") 045 .add("long", "short", "void") 046 // wrappers 047 .add("Boolean", "Byte", "Character", "Double", "Float") 048 .add("Integer", "Long", "Short", "Void") 049 // java.lang.* 050 .add("Object", "Class") 051 .add("String", "StringBuffer", "StringBuilder") 052 // Exceptions 053 .add("ArrayIndexOutOfBoundsException", "Exception") 054 .add("RuntimeException", "IllegalArgumentException") 055 .add("IllegalStateException", "IndexOutOfBoundsException") 056 .add("NullPointerException", "Throwable", "SecurityException") 057 .add("UnsupportedOperationException") 058 // java.util.* 059 .add("List", "ArrayList", "Deque", "Queue", "LinkedList") 060 .add("Set", "HashSet", "SortedSet", "TreeSet") 061 .add("Map", "HashMap", "SortedMap", "TreeMap") 062 .build(); 063 /** User-configured class names to ignore. */ 064 private Set<String> mExcludedClasses = DEFAULT_EXCLUDED_CLASSES; 065 /** Allowed complexity. */ 066 private int mMax; 067 /** package of the file we check. */ 068 private String mPackageName; 069 070 /** Stack of contexts. */ 071 private final FastStack<Context> mContextStack = FastStack.newInstance(); 072 /** Current context. */ 073 private Context mContext; 074 075 /** 076 * Creates new instance of the check. 077 * @param aDefaultMax default value for allowed complexity. 078 */ 079 protected AbstractClassCouplingCheck(int aDefaultMax) 080 { 081 setMax(aDefaultMax); 082 } 083 084 @Override 085 public final int[] getDefaultTokens() 086 { 087 return getRequiredTokens(); 088 } 089 090 /** @return allowed complexity. */ 091 public final int getMax() 092 { 093 return mMax; 094 } 095 096 /** 097 * Sets maximul allowed complexity. 098 * @param aMax allowed complexity. 099 */ 100 public final void setMax(int aMax) 101 { 102 mMax = aMax; 103 } 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 mExcludedClasses = ImmutableSet.copyOf(aExcludedClasses); 112 } 113 114 @Override 115 public final void beginTree(DetailAST aAST) 116 { 117 mPackageName = ""; 118 } 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 switch (aAST.getType()) { 127 case TokenTypes.PACKAGE_DEF: 128 visitPackageDef(aAST); 129 break; 130 case TokenTypes.CLASS_DEF: 131 case TokenTypes.INTERFACE_DEF: 132 case TokenTypes.ANNOTATION_DEF: 133 case TokenTypes.ENUM_DEF: 134 visitClassDef(aAST); 135 break; 136 case TokenTypes.TYPE: 137 mContext.visitType(aAST); 138 break; 139 case TokenTypes.LITERAL_NEW: 140 mContext.visitLiteralNew(aAST); 141 break; 142 case TokenTypes.LITERAL_THROWS: 143 mContext.visitLiteralThrows(aAST); 144 break; 145 default: 146 throw new IllegalStateException(aAST.toString()); 147 } 148 } 149 150 @Override 151 public void leaveToken(DetailAST aAST) 152 { 153 switch (aAST.getType()) { 154 case TokenTypes.CLASS_DEF: 155 case TokenTypes.INTERFACE_DEF: 156 case TokenTypes.ANNOTATION_DEF: 157 case TokenTypes.ENUM_DEF: 158 leaveClassDef(); 159 break; 160 default: 161 // Do nothing 162 } 163 } 164 165 /** 166 * Stores package of current class we check. 167 * @param aPkg package definition. 168 */ 169 private void visitPackageDef(DetailAST aPkg) 170 { 171 final FullIdent ident = FullIdent.createFullIdent(aPkg.getLastChild() 172 .getPreviousSibling()); 173 mPackageName = ident.getText(); 174 } 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 mContextStack.push(mContext); 183 final String className = 184 aClassDef.findFirstToken(TokenTypes.IDENT).getText(); 185 mContext = new Context(className, 186 aClassDef.getLineNo(), 187 aClassDef.getColumnNo()); 188 } 189 190 /** Restores previous context. */ 191 private void leaveClassDef() 192 { 193 mContext.checkCoupling(); 194 mContext = mContextStack.pop(); 195 } 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 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 { 226 mClassName = aClassName; 227 mLineNo = aLineNo; 228 mColumnNo = aColumnNo; 229 } 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 for (DetailAST childAST = aThrows.getFirstChild(); 238 childAST != null; 239 childAST = childAST.getNextSibling()) 240 { 241 if (childAST.getType() != TokenTypes.COMMA) { 242 addReferencedClassName(childAST); 243 } 244 } 245 } 246 247 /** 248 * Visits type. 249 * @param aAST type to process. 250 */ 251 public void visitType(DetailAST aAST) 252 { 253 final String className = CheckUtils.createFullType(aAST).getText(); 254 mContext.addReferencedClassName(className); 255 } 256 257 /** 258 * Visits NEW. 259 * @param aAST NEW to process. 260 */ 261 public void visitLiteralNew(DetailAST aAST) 262 { 263 mContext.addReferencedClassName(aAST.getFirstChild()); 264 } 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 final String className = FullIdent.createFullIdent(aAST).getText(); 273 addReferencedClassName(className); 274 } 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 if (isSignificant(aClassName)) { 283 mReferencedClassNames.add(aClassName); 284 } 285 } 286 287 /** Checks if coupling less than allowed or not. */ 288 public void checkCoupling() 289 { 290 mReferencedClassNames.remove(mClassName); 291 mReferencedClassNames.remove(mPackageName + "." + mClassName); 292 293 if (mReferencedClassNames.size() > mMax) { 294 log(mLineNo, mColumnNo, getLogMessageId(), 295 mReferencedClassNames.size(), getMax(), 296 mReferencedClassNames.toString()); 297 } 298 } 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 return (aClassName.length() > 0) 308 && !mExcludedClasses.contains(aClassName) 309 && !aClassName.startsWith("java.lang."); 310 } 311 } 312}