Hi,
While I was designing telemetry streams showing Hackystat module
quality, I said to my self:
I need metric for code complexity. I don't need fancy stuff.
Cyclomatic complexity is good enough.
So I went out looking for tools. I could not find any satisfactory ones
except Borland Together (It computes tons of metrics but it's
super-expensive).
For unknown reason, I downloaded PMD source and took a look. The idea
used by PMD is the same as that in LOCC:
Parse Java source code to generate an abstract syntax tree, and rules
are implemented using visitor pattern.
It only took me 30 min to write my own visitor to compute cyclomatic
complexity. (My code attached in case you are interested).
I know that LOCC did encounter a hell of maintenance headaches while
Mike was trying to support Java 5 syntax. Since there are PMD people
maintaining source code parser, I believe we can resurrect LOCC project
and make it much better.
Following are my thoughts:
(1) Migrate LOCC (Java counter portion) using PMD source code parser.
Basically, we need one visitor for one type of metric.
(2) Add new types of metric. For starters, cyclomatic complexity is
already done. LOC, SLOC, comment lines should be much simpler.
(3) Use LOCC to send Java related metrics and SCLC for other types of
files during our nightly build.
How do you guys think about it?
Thanks.
Cedric
package edu.hawaii.csdl.filemetric;
import net.sourceforge.pmd.*;
import net.sourceforge.pmd.ast.ASTCompilationUnit;
import net.sourceforge.pmd.ast.JavaParser;
import net.sourceforge.pmd.dfa.DataFlowFacade;
import net.sourceforge.pmd.symboltable.SymbolFacade;
import java.io.InputStream;
import java.io.*;
/**
* Based on PMD code, which uses BSD-style license.
* @author Cedric
*/
public class Cmd {
/** Creates a new instance of Cmd */
public Cmd() {
}
public static void main(String[] args) throws Exception {
String inputJavaFilePath = "E:\\test\\test.java";
InputStream ins = new FileInputStream(inputJavaFilePath);
JavaParser parser = new TargetJDK1_5().createParser(ins);
ASTCompilationUnit c = parser.CompilationUnit();
//Thread.yield(); //not sure why PMD guys yield?
//seems it's necessary
SymbolFacade stb = new SymbolFacade();
stb.initializeWith(c);
//seems it's not necessary
DataFlowFacade dff = new DataFlowFacade();
dff.initializeWith(c);
CyclomaticComplexityMetric cc = new CyclomaticComplexityMetric();
cc.visit(c, null);
ins.close();
}
}
package edu.hawaii.csdl.filemetric;
import net.sourceforge.pmd.ast.ASTBlockStatement;
import net.sourceforge.pmd.ast.ASTClassOrInterfaceDeclaration;
import net.sourceforge.pmd.ast.ASTConstructorDeclaration;
import net.sourceforge.pmd.ast.ASTForStatement;
import net.sourceforge.pmd.ast.ASTIfStatement;
import net.sourceforge.pmd.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.ast.ASTMethodDeclarator;
import net.sourceforge.pmd.ast.ASTSwitchLabel;
import net.sourceforge.pmd.ast.ASTSwitchStatement;
import net.sourceforge.pmd.ast.ASTWhileStatement;
import net.sourceforge.pmd.ast.JavaParserVisitorAdapter;
import net.sourceforge.pmd.ast.Node;
import net.sourceforge.pmd.ast.SimpleNode;
import java.util.Stack;
// * Based on PMD code, which uses BSD-style license.
public class CyclomaticComplexityMetric extends JavaParserVisitorAdapter {
private static class Entry {
private SimpleNode node;
private int decisionPoints = 1;
public int highestDecisionPoints;
public int methodCount;
private Entry(SimpleNode node) {
this.node = node;
}
public void bumpDecisionPoints() {
decisionPoints++;
}
public void bumpDecisionPoints(int size) {
decisionPoints += size;
}
public int getComplexityAverage() {
return ((double) methodCount == 0) ? 1 : (int) (Math.rint((double)
decisionPoints / (double) methodCount));
}
}
private Stack entryStack = new Stack();
public Object visit(ASTIfStatement node, Object data) {
((Entry) entryStack.peek()).bumpDecisionPoints();
super.visit(node, data);
return data;
}
public Object visit(ASTForStatement node, Object data) {
((Entry) entryStack.peek()).bumpDecisionPoints();
super.visit(node, data);
return data;
}
public Object visit(ASTSwitchStatement node, Object data) {
Entry entry = (Entry) entryStack.peek();
int childCount = node.jjtGetNumChildren();
int lastIndex = childCount - 1;
for (int n = 0; n < lastIndex; n++) {
Node childNode = node.jjtGetChild(n);
if (childNode instanceof ASTSwitchLabel) {
childNode = node.jjtGetChild(n + 1);
if (childNode instanceof ASTBlockStatement) {
entry.bumpDecisionPoints();
}
}
}
super.visit(node, data);
return data;
}
public Object visit(ASTWhileStatement node, Object data) {
((Entry) entryStack.peek()).bumpDecisionPoints();
super.visit(node, data);
return data;
}
public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
if (node.isInterface()) {
return data;
}
entryStack.push(new Entry(node));
super.visit(node, data);
Entry classEntry = (Entry) entryStack.pop();
//if ((classEntry.getComplexityAverage() >= getIntProperty("reportLevel"))
|| (classEntry.highestDecisionPoints >= getIntProperty("reportLevel"))) {
// addViolation(data, node, new String[]{"class", node.getImage(),
String.valueOf(classEntry.getComplexityAverage()) + " (Highest = " +
String.valueOf(classEntry.highestDecisionPoints) + ")"});
//}
System.out.println("[" + node.getImage() + "] " +
classEntry.getComplexityAverage());
return data;
}
public Object visit(ASTMethodDeclaration node, Object data) {
entryStack.push(new Entry(node));
super.visit(node, data);
Entry methodEntry = (Entry) entryStack.pop();
int methodDecisionPoints = methodEntry.decisionPoints;
Entry classEntry = (Entry) entryStack.peek();
classEntry.methodCount++;
classEntry.bumpDecisionPoints(methodDecisionPoints);
if (methodDecisionPoints > classEntry.highestDecisionPoints) {
classEntry.highestDecisionPoints = methodDecisionPoints;
}
ASTMethodDeclarator methodDeclarator = null;
for (int n = 0; n < node.jjtGetNumChildren(); n++) {
Node childNode = node.jjtGetChild(n);
if (childNode instanceof ASTMethodDeclarator) {
methodDeclarator = (ASTMethodDeclarator) childNode;
break;
}
}
//if (methodEntry.decisionPoints >= getIntProperty("reportLevel")) {
// addViolation(data, node, new String[]{"method", (methodDeclarator ==
null) ? "" : methodDeclarator.getImage(),
String.valueOf(methodEntry.decisionPoints)});
//}
System.out.println("[" + methodDeclarator.getImage() + "] " +
methodEntry.decisionPoints);
return data;
}
public Object visit(ASTConstructorDeclaration node, Object data) {
entryStack.push(new Entry(node));
super.visit(node, data);
Entry constructorEntry = (Entry) entryStack.pop();
int constructorDecisionPointCount = constructorEntry.decisionPoints;
Entry classEntry = (Entry) entryStack.peek();
classEntry.methodCount++;
classEntry.decisionPoints += constructorDecisionPointCount;
if (constructorDecisionPointCount > classEntry.highestDecisionPoints) {
classEntry.highestDecisionPoints = constructorDecisionPointCount;
}
//if (constructorEntry.decisionPoints >= getIntProperty("reportLevel")) {
// addViolation(data, node, new String[]{"constructor",
classEntry.node.getImage(), String.valueOf(constructorDecisionPointCount)});
//}
System.out.println("[" + classEntry.node.getImage() + "] " +
constructorDecisionPointCount);
return data;
}
}