001: ////////////////////////////////////////////////////////////////////////////////
002: // checkstyle: Checks Java source code for adherence to a set of rules.
003: // Copyright (C) 2001-2007 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: ////////////////////////////////////////////////////////////////////////////////
019: package com.puppycrawl.tools.checkstyle.checks.metrics;
020:
021: import com.puppycrawl.tools.checkstyle.api.Check;
022: import com.puppycrawl.tools.checkstyle.api.DetailAST;
023: import com.puppycrawl.tools.checkstyle.api.FullIdent;
024: import com.puppycrawl.tools.checkstyle.api.TokenTypes;
025:
026: import com.puppycrawl.tools.checkstyle.checks.CheckUtils;
027:
028: import java.util.HashSet;
029: import java.util.Set;
030: import java.util.Stack;
031: import java.util.TreeSet;
032:
033: /**
034: * Base class for coupling calculation.
035: *
036: * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
037: * @author o_sukhodolsky
038: */
039: public abstract class AbstractClassCouplingCheck extends Check {
040: /** Class names to ignore. */
041: private final Set mIgnoredClassNames = new HashSet();
042: /** Allowed complexity. */
043: private int mMax;
044: /** package of the file we check. */
045: private String mPackageName;
046:
047: /** Stack of contexts. */
048: private final Stack mContextStack = new Stack();
049: /** Current context. */
050: private Context mContext;
051:
052: /**
053: * Creates new instance of the check.
054: * @param aDefaultMax default value for allowed complexity.
055: */
056: protected AbstractClassCouplingCheck(int aDefaultMax) {
057: setMax(aDefaultMax);
058:
059: mIgnoredClassNames.add("boolean");
060: mIgnoredClassNames.add("byte");
061: mIgnoredClassNames.add("char");
062: mIgnoredClassNames.add("double");
063: mIgnoredClassNames.add("float");
064: mIgnoredClassNames.add("int");
065: mIgnoredClassNames.add("long");
066: mIgnoredClassNames.add("short");
067: mIgnoredClassNames.add("void");
068: mIgnoredClassNames.add("Boolean");
069: mIgnoredClassNames.add("Byte");
070: mIgnoredClassNames.add("Character");
071: mIgnoredClassNames.add("Double");
072: mIgnoredClassNames.add("Float");
073: mIgnoredClassNames.add("Integer");
074: mIgnoredClassNames.add("Long");
075: mIgnoredClassNames.add("Object");
076: mIgnoredClassNames.add("Short");
077: mIgnoredClassNames.add("String");
078: mIgnoredClassNames.add("StringBuffer");
079: mIgnoredClassNames.add("Void");
080: mIgnoredClassNames.add("ArrayIndexOutOfBoundsException");
081: mIgnoredClassNames.add("Exception");
082: mIgnoredClassNames.add("RuntimeException");
083: mIgnoredClassNames.add("IllegalArgumentException");
084: mIgnoredClassNames.add("IllegalStateException");
085: mIgnoredClassNames.add("IndexOutOfBoundsException");
086: mIgnoredClassNames.add("NullPointerException");
087: mIgnoredClassNames.add("Throwable");
088: mIgnoredClassNames.add("SecurityException");
089: mIgnoredClassNames.add("UnsupportedOperationException");
090: }
091:
092: /** {@inheritDoc} */
093: public final int[] getDefaultTokens() {
094: return getRequiredTokens();
095: }
096:
097: /** @return allowed complexity. */
098: public final int getMax() {
099: return mMax;
100: }
101:
102: /**
103: * Sets maximul allowed complexity.
104: * @param aMax allowed complexity.
105: */
106: public final void setMax(int aMax) {
107: mMax = aMax;
108: }
109:
110: /** {@inheritDoc} */
111: public final void beginTree(DetailAST aAST) {
112: mPackageName = "";
113: }
114:
115: /** @return message key we use for log violations. */
116: protected abstract String getLogMessageId();
117:
118: /** {@inheritDoc} */
119: public void visitToken(DetailAST aAST) {
120: switch (aAST.getType()) {
121: case TokenTypes.PACKAGE_DEF:
122: visitPackageDef(aAST);
123: break;
124: case TokenTypes.CLASS_DEF:
125: case TokenTypes.INTERFACE_DEF:
126: case TokenTypes.ANNOTATION_DEF:
127: case TokenTypes.ENUM_DEF:
128: visitClassDef(aAST);
129: break;
130: case TokenTypes.TYPE:
131: mContext.visitType(aAST);
132: break;
133: case TokenTypes.LITERAL_NEW:
134: mContext.visitLiteralNew(aAST);
135: break;
136: case TokenTypes.LITERAL_THROWS:
137: mContext.visitLiteralThrows(aAST);
138: break;
139: default:
140: throw new IllegalStateException(aAST.toString());
141: }
142: }
143:
144: /** {@inheritDoc} */
145: public void leaveToken(DetailAST aAST) {
146: switch (aAST.getType()) {
147: case TokenTypes.CLASS_DEF:
148: case TokenTypes.INTERFACE_DEF:
149: case TokenTypes.ANNOTATION_DEF:
150: case TokenTypes.ENUM_DEF:
151: leaveClassDef();
152: break;
153: default:
154: // Do nothing
155: }
156: }
157:
158: /**
159: * Stores package of current class we check.
160: * @param aPkg package definition.
161: */
162: private void visitPackageDef(DetailAST aPkg) {
163: final FullIdent ident = FullIdent.createFullIdent(aPkg
164: .getLastChild().getPreviousSibling());
165: mPackageName = ident.getText();
166: }
167:
168: /**
169: * Creates new context for a given class.
170: * @param aClassDef class definition node.
171: */
172: private void visitClassDef(DetailAST aClassDef) {
173: mContextStack.push(mContext);
174: final String className = aClassDef.findFirstToken(
175: TokenTypes.IDENT).getText();
176: mContext = new Context(className, aClassDef.getLineNo(),
177: aClassDef.getColumnNo());
178: }
179:
180: /** Restores previous context. */
181: private void leaveClassDef() {
182: mContext.checkCoupling();
183: mContext = (Context) mContextStack.pop();
184: }
185:
186: /**
187: * Incapsulates information about class coupling.
188: *
189: * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
190: * @author o_sukhodolsky
191: */
192: private class Context {
193: /**
194: * Set of referenced classes.
195: * Sorted by name for predictable error messages in unit tests.
196: */
197: private final Set mReferencedClassNames = new TreeSet();
198: /** Own class name. */
199: private final String mClassName;
200: /* Location of own class. (Used to log violations) */
201: /** Line number of class definition. */
202: private final int mLineNo;
203: /** Column number of class definition. */
204: private final int mColumnNo;
205:
206: /**
207: * Create new context associated with given class.
208: * @param aClassName name of the given class.
209: * @param aLineNo line of class definition.
210: * @param aColumnNo column of class definition.
211: */
212: public Context(String aClassName, int aLineNo, int aColumnNo) {
213: mClassName = aClassName;
214: mLineNo = aLineNo;
215: mColumnNo = aColumnNo;
216: }
217:
218: /**
219: * Visits throws clause and collects all exceptions we throw.
220: * @param aThrows throws to process.
221: */
222: public void visitLiteralThrows(DetailAST aThrows) {
223: for (DetailAST childAST = (DetailAST) aThrows
224: .getFirstChild(); childAST != null; childAST = (DetailAST) childAST
225: .getNextSibling()) {
226: if (childAST.getType() != TokenTypes.COMMA) {
227: addReferencedClassName(childAST);
228: }
229: }
230: }
231:
232: /**
233: * Visits type.
234: * @param aAST type to process.
235: */
236: public void visitType(DetailAST aAST) {
237: final String className = CheckUtils.createFullType(aAST)
238: .getText();
239: mContext.addReferencedClassName(className);
240: }
241:
242: /**
243: * Visits NEW.
244: * @param aAST NEW to process.
245: */
246: public void visitLiteralNew(DetailAST aAST) {
247: mContext.addReferencedClassName((DetailAST) aAST
248: .getFirstChild());
249: }
250:
251: /**
252: * Adds new referenced class.
253: * @param aAST a node which represents referenced class.
254: */
255: private void addReferencedClassName(DetailAST aAST) {
256: final String className = FullIdent.createFullIdent(aAST)
257: .getText();
258: addReferencedClassName(className);
259: }
260:
261: /**
262: * Adds new referenced class.
263: * @param aClassName class name of the referenced class.
264: */
265: private void addReferencedClassName(String aClassName) {
266: if (isSignificant(aClassName)) {
267: mReferencedClassNames.add(aClassName);
268: }
269: }
270:
271: /** Checks if coupling less than allowed or not. */
272: public void checkCoupling() {
273: mReferencedClassNames.remove(mClassName);
274: mReferencedClassNames.remove(mPackageName + "."
275: + mClassName);
276:
277: if (mReferencedClassNames.size() > mMax) {
278: log(mLineNo, mColumnNo, getLogMessageId(),
279: new Object[] {
280: new Integer(mReferencedClassNames
281: .size()),
282: new Integer(getMax()),
283: mReferencedClassNames.toString(), });
284: }
285: }
286:
287: /**
288: * Checks if given class shouldn't be ignored and not from java.lang.
289: * @param aClassName class to check.
290: * @return true if we should count this class.
291: */
292: private boolean isSignificant(String aClassName) {
293: return (aClassName.length() > 0)
294: && !mIgnoredClassNames.contains(aClassName)
295: && !aClassName.startsWith("java.lang.");
296: }
297: }
298: }
|