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 java.util.Stack;
022:
023: import com.puppycrawl.tools.checkstyle.api.Check;
024: import com.puppycrawl.tools.checkstyle.api.DetailAST;
025: import com.puppycrawl.tools.checkstyle.api.TokenTypes;
026:
027: /**
028: * This check calculates the Non Commenting Source Statements (NCSS) metric for
029: * java source files and methods. The check adheres to the <a
030: * href="http://www.kclee.com/clemens/java/javancss/">JavaNCSS specification
031: * </a> and gives the same results as the JavaNCSS tool.
032: *
033: * The NCSS-metric tries to determine complexity of methods, classes and files
034: * by counting the non commenting lines. Roughly said this is (nearly)
035: * equivalent to counting the semicolons and opening curly braces.
036: *
037: * @author Lars Ködderitzsch
038: */
039: public class JavaNCSSCheck extends Check {
040: /** default constant for max file ncss */
041: private static final int FILE_MAX_NCSS = 2000;
042:
043: /** default constant for max file ncss */
044: private static final int CLASS_MAX_NCSS = 1500;
045:
046: /** default constant for max method ncss */
047: private static final int METHOD_MAX_NCSS = 50;
048:
049: /** maximum ncss for a complete source file */
050: private int mFileMax = FILE_MAX_NCSS;
051:
052: /** maximum ncss for a class */
053: private int mClassMax = CLASS_MAX_NCSS;
054:
055: /** maximum ncss for a method */
056: private int mMethodMax = METHOD_MAX_NCSS;
057:
058: /** list containing the stacked counters */
059: private Stack mCounters;
060:
061: /**
062: * {@inheritDoc}
063: */
064: public int[] getDefaultTokens() {
065: return new int[] { TokenTypes.CLASS_DEF,
066: TokenTypes.INTERFACE_DEF, TokenTypes.METHOD_DEF,
067: TokenTypes.CTOR_DEF, TokenTypes.INSTANCE_INIT,
068: TokenTypes.STATIC_INIT, TokenTypes.PACKAGE_DEF,
069: TokenTypes.IMPORT, TokenTypes.VARIABLE_DEF,
070: TokenTypes.CTOR_CALL, TokenTypes.SUPER_CTOR_CALL,
071: TokenTypes.LITERAL_IF, TokenTypes.LITERAL_ELSE,
072: TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO,
073: TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_SWITCH,
074: TokenTypes.LITERAL_BREAK, TokenTypes.LITERAL_CONTINUE,
075: TokenTypes.LITERAL_RETURN, TokenTypes.LITERAL_THROW,
076: TokenTypes.LITERAL_SYNCHRONIZED,
077: TokenTypes.LITERAL_CATCH, TokenTypes.LITERAL_FINALLY,
078: TokenTypes.EXPR, TokenTypes.LABELED_STAT,
079: TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT, };
080: }
081:
082: /**
083: * {@inheritDoc}
084: */
085: public int[] getRequiredTokens() {
086: return new int[] { TokenTypes.CLASS_DEF,
087: TokenTypes.INTERFACE_DEF, TokenTypes.METHOD_DEF,
088: TokenTypes.CTOR_DEF, TokenTypes.INSTANCE_INIT,
089: TokenTypes.STATIC_INIT, TokenTypes.PACKAGE_DEF,
090: TokenTypes.IMPORT, TokenTypes.VARIABLE_DEF,
091: TokenTypes.CTOR_CALL, TokenTypes.SUPER_CTOR_CALL,
092: TokenTypes.LITERAL_IF, TokenTypes.LITERAL_ELSE,
093: TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO,
094: TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_SWITCH,
095: TokenTypes.LITERAL_BREAK, TokenTypes.LITERAL_CONTINUE,
096: TokenTypes.LITERAL_RETURN, TokenTypes.LITERAL_THROW,
097: TokenTypes.LITERAL_SYNCHRONIZED,
098: TokenTypes.LITERAL_CATCH, TokenTypes.LITERAL_FINALLY,
099: TokenTypes.EXPR, TokenTypes.LABELED_STAT,
100: TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT, };
101: }
102:
103: /**
104: * {@inheritDoc}
105: */
106: public void beginTree(DetailAST aRootAST) {
107: mCounters = new Stack();
108:
109: //add a counter for the file
110: mCounters.push(new Counter());
111: }
112:
113: /**
114: * {@inheritDoc}
115: */
116: public void visitToken(DetailAST aAST) {
117: final int tokenType = aAST.getType();
118:
119: if ((TokenTypes.CLASS_DEF == tokenType)
120: || (TokenTypes.METHOD_DEF == tokenType)
121: || (TokenTypes.CTOR_DEF == tokenType)
122: || (TokenTypes.STATIC_INIT == tokenType)
123: || (TokenTypes.INSTANCE_INIT == tokenType)) {
124: //add a counter for this class/method
125: mCounters.push(new Counter());
126: }
127:
128: //check if token is countable
129: if (isCountable(aAST)) {
130: //increment the stacked counters
131: final int size = mCounters.size();
132: for (int i = 0; i < size; i++) {
133: ((Counter) mCounters.get(i)).increment();
134: }
135: }
136: }
137:
138: /**
139: * {@inheritDoc}
140: */
141: public void leaveToken(DetailAST aAST) {
142: final int tokenType = aAST.getType();
143: if ((TokenTypes.METHOD_DEF == tokenType)
144: || (TokenTypes.CTOR_DEF == tokenType)
145: || (TokenTypes.STATIC_INIT == tokenType)
146: || (TokenTypes.INSTANCE_INIT == tokenType)) {
147: //pop counter from the stack
148: final Counter counter = (Counter) mCounters.pop();
149:
150: final int count = counter.getCount();
151: if (count > mMethodMax) {
152: log(aAST.getLineNo(), aAST.getColumnNo(),
153: "ncss.method", new Integer(count), new Integer(
154: mMethodMax));
155: }
156: } else if (TokenTypes.CLASS_DEF == tokenType) {
157: //pop counter from the stack
158: final Counter counter = (Counter) mCounters.pop();
159:
160: final int count = counter.getCount();
161: if (count > mClassMax) {
162: log(aAST.getLineNo(), aAST.getColumnNo(), "ncss.class",
163: new Integer(count), new Integer(mClassMax));
164: }
165: }
166: }
167:
168: /**
169: * {@inheritDoc}
170: */
171: public void finishTree(DetailAST aRootAST) {
172: //pop counter from the stack
173: final Counter counter = (Counter) mCounters.pop();
174:
175: final int count = counter.getCount();
176: if (count > mFileMax) {
177: log(aRootAST.getLineNo(), aRootAST.getColumnNo(),
178: "ncss.file", new Integer(count), new Integer(
179: mMethodMax));
180: }
181: }
182:
183: /**
184: * Sets the maximum ncss for a file.
185: *
186: * @param aFileMax
187: * the maximum ncss
188: */
189: public void setFileMaximum(int aFileMax) {
190: mFileMax = aFileMax;
191: }
192:
193: /**
194: * Sets the maximum ncss for a class.
195: *
196: * @param aClassMax
197: * the maximum ncss
198: */
199: public void setClassMaximum(int aClassMax) {
200: mClassMax = aClassMax;
201: }
202:
203: /**
204: * Sets the maximum ncss for a method.
205: *
206: * @param aMethodMax
207: * the maximum ncss
208: */
209: public void setMethodMaximum(int aMethodMax) {
210: mMethodMax = aMethodMax;
211: }
212:
213: /**
214: * Checks if a token is countable for the ncss metric
215: *
216: * @param aAST
217: * the AST
218: * @return true if the token is countable
219: */
220: private boolean isCountable(DetailAST aAST) {
221: boolean countable = true;
222:
223: final int tokenType = aAST.getType();
224:
225: //check if an expression is countable
226: if (TokenTypes.EXPR == tokenType) {
227: countable = isExpressionCountable(aAST);
228: }
229: //check if an variable definition is countable
230: else if (TokenTypes.VARIABLE_DEF == tokenType) {
231: countable = isVariableDefCountable(aAST);
232: }
233: return countable;
234: }
235:
236: /**
237: * Checks if a variable definition is countable.
238: *
239: * @param aAST the AST
240: * @return true if the variable definition is countable, false otherwise
241: */
242: private boolean isVariableDefCountable(DetailAST aAST) {
243: boolean countable = false;
244:
245: //count variable defs only if they are direct child to a slist or
246: // object block
247: final int parentType = aAST.getParent().getType();
248:
249: if ((TokenTypes.SLIST == parentType)
250: || (TokenTypes.OBJBLOCK == parentType)) {
251: final DetailAST prevSibling = aAST.getPreviousSibling();
252:
253: //is countable if no previous sibling is found or
254: //the sibling is no COMMA.
255: //This is done because multiple assignment on one line are countes
256: // as 1
257: countable = (prevSibling == null)
258: || (TokenTypes.COMMA != prevSibling.getType());
259: }
260:
261: return countable;
262: }
263:
264: /**
265: * Checks if an expression is countable for the ncss metric.
266: *
267: * @param aAST the AST
268: * @return true if the expression is countable, false otherwise
269: */
270: private boolean isExpressionCountable(DetailAST aAST) {
271: boolean countable = true;
272:
273: //count expressions only if they are direct child to a slist (method
274: // body, for loop...)
275: //or direct child of label,if,else,do,while,for
276: final int parentType = aAST.getParent().getType();
277: switch (parentType) {
278: case TokenTypes.SLIST:
279: case TokenTypes.LABELED_STAT:
280: case TokenTypes.LITERAL_FOR:
281: case TokenTypes.LITERAL_DO:
282: case TokenTypes.LITERAL_WHILE:
283: case TokenTypes.LITERAL_IF:
284: case TokenTypes.LITERAL_ELSE:
285: //don't count if or loop conditions
286: final DetailAST prevSibling = aAST.getPreviousSibling();
287: countable = (prevSibling == null)
288: || (TokenTypes.LPAREN != prevSibling.getType());
289: break;
290: default:
291: countable = false;
292: break;
293: }
294: return countable;
295: }
296:
297: /**
298: * @author Lars Ködderitzsch
299: *
300: * Class representing a counter,
301: */
302: private class Counter {
303: /** the counters internal integer */
304: private int mIvCount;
305:
306: /**
307: * Increments the counter.
308: */
309: public void increment() {
310: mIvCount++;
311: }
312:
313: /**
314: * Gets the counters value
315: *
316: * @return the counter
317: */
318: public int getCount() {
319: return mIvCount;
320: }
321: }
322: }
|