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> excludedClasses = DEFAULT_EXCLUDED_CLASSES; 065 /** Allowed complexity. */ 066 private int max; 067 /** package of the file we check. */ 068 private String packageName; 069 070 /** Stack of contexts. */ 071 private final FastStack<Context> contextStack = FastStack.newInstance(); 072 /** Current context. */ 073 private Context context; 074 075 /** 076 * Creates new instance of the check. 077 * @param defaultMax default value for allowed complexity. 078 */ 079 protected AbstractClassCouplingCheck(int defaultMax) 080 { 081 setMax(defaultMax); 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 max; 094 } 095 096 /** 097 * Sets maximum allowed complexity. 098 * @param max allowed complexity. 099 */ 100 public final void setMax(int max) 101 { 102 this.max = max; 103 } 104 105 /** 106 * Sets user-excluded classes to ignore. 107 * @param excludedClasses the list of classes to ignore. 108 */ 109 public final void setExcludedClasses(String[] excludedClasses) 110 { 111 this.excludedClasses = ImmutableSet.copyOf(excludedClasses); 112 } 113 114 @Override 115 public final void beginTree(DetailAST ast) 116 { 117 packageName = ""; 118 } 119 120 /** @return message key we use for log violations. */ 121 protected abstract String getLogMessageId(); 122 123 @Override 124 public void visitToken(DetailAST ast) 125 { 126 switch (ast.getType()) { 127 case TokenTypes.PACKAGE_DEF: 128 visitPackageDef(ast); 129 break; 130 case TokenTypes.CLASS_DEF: 131 case TokenTypes.INTERFACE_DEF: 132 case TokenTypes.ANNOTATION_DEF: 133 case TokenTypes.ENUM_DEF: 134 visitClassDef(ast); 135 break; 136 case TokenTypes.TYPE: 137 context.visitType(ast); 138 break; 139 case TokenTypes.LITERAL_NEW: 140 context.visitLiteralNew(ast); 141 break; 142 case TokenTypes.LITERAL_THROWS: 143 context.visitLiteralThrows(ast); 144 break; 145 default: 146 throw new IllegalStateException(ast.toString()); 147 } 148 } 149 150 @Override 151 public void leaveToken(DetailAST ast) 152 { 153 switch (ast.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 pkg package definition. 168 */ 169 private void visitPackageDef(DetailAST pkg) 170 { 171 final FullIdent ident = FullIdent.createFullIdent(pkg.getLastChild() 172 .getPreviousSibling()); 173 packageName = ident.getText(); 174 } 175 176 /** 177 * Creates new context for a given class. 178 * @param classDef class definition node. 179 */ 180 private void visitClassDef(DetailAST classDef) 181 { 182 contextStack.push(context); 183 final String className = 184 classDef.findFirstToken(TokenTypes.IDENT).getText(); 185 context = new Context(className, 186 classDef.getLineNo(), 187 classDef.getColumnNo()); 188 } 189 190 /** Restores previous context. */ 191 private void leaveClassDef() 192 { 193 context.checkCoupling(); 194 context = contextStack.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> referencedClassNames = Sets.newTreeSet(); 210 /** Own class name. */ 211 private final String className; 212 /* Location of own class. (Used to log violations) */ 213 /** Line number of class definition. */ 214 private final int lineNo; 215 /** Column number of class definition. */ 216 private final int columnNo; 217 218 /** 219 * Create new context associated with given class. 220 * @param className name of the given class. 221 * @param lineNo line of class definition. 222 * @param columnNo column of class definition. 223 */ 224 public Context(String className, int lineNo, int columnNo) 225 { 226 this.className = className; 227 this.lineNo = lineNo; 228 this.columnNo = columnNo; 229 } 230 231 /** 232 * Visits throws clause and collects all exceptions we throw. 233 * @param literalThrows throws to process. 234 */ 235 public void visitLiteralThrows(DetailAST literalThrows) 236 { 237 for (DetailAST childAST = literalThrows.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 ast type to process. 250 */ 251 public void visitType(DetailAST ast) 252 { 253 final String className = CheckUtils.createFullType(ast).getText(); 254 context.addReferencedClassName(className); 255 } 256 257 /** 258 * Visits NEW. 259 * @param ast NEW to process. 260 */ 261 public void visitLiteralNew(DetailAST ast) 262 { 263 context.addReferencedClassName(ast.getFirstChild()); 264 } 265 266 /** 267 * Adds new referenced class. 268 * @param ast a node which represents referenced class. 269 */ 270 private void addReferencedClassName(DetailAST ast) 271 { 272 final String className = FullIdent.createFullIdent(ast).getText(); 273 addReferencedClassName(className); 274 } 275 276 /** 277 * Adds new referenced class. 278 * @param className class name of the referenced class. 279 */ 280 private void addReferencedClassName(String className) 281 { 282 if (isSignificant(className)) { 283 referencedClassNames.add(className); 284 } 285 } 286 287 /** Checks if coupling less than allowed or not. */ 288 public void checkCoupling() 289 { 290 referencedClassNames.remove(className); 291 referencedClassNames.remove(packageName + "." + className); 292 293 if (referencedClassNames.size() > max) { 294 log(lineNo, columnNo, getLogMessageId(), 295 referencedClassNames.size(), getMax(), 296 referencedClassNames.toString()); 297 } 298 } 299 300 /** 301 * Checks if given class shouldn't be ignored and not from java.lang. 302 * @param className class to check. 303 * @return true if we should count this class. 304 */ 305 private boolean isSignificant(String className) 306 { 307 return (className.length() > 0) 308 && !excludedClasses.contains(className) 309 && !className.startsWith("java.lang."); 310 } 311 } 312}