2

Background - Question below

I am at the start of implementing a metric suite in Java for Java however I am concerned that my approach is not appropriate.

Currently I am using the JDT's ASTParser for every file within a directory. This started off well and I was able to collect things around line count and average lines per method for each class. This was done via a MethodVisitor class which extends ASTVisitor and contains a method visit(MethodDeclaration node).

I am now trying to calculate Cyclomatic Complexity for every method. I have split out the method body and have a ComplexityVisitor which contains a visit(IfStatement node) and a visit(ReturnStatement node).

Using this structure I know that there is a if statement within the code but I am unsure on how to know how many levels of "if else"s there are. The only method I can find that is helpful is the node.getElseStatement() but this returns what is basically (or seems to me) a string and would therefore have to use regex to know the number of paths the statement could take.

So my question is:

Is there a way to deduce how many levels are in the "if - else if - else" statement when using eclipses ASTParser?

or

Should I be looking for a cleaner solution such as IJavaElement or parsing the code myself putting key words onto a list then looping back through them.

Some sample Code - very much in testing phase

public class Test {

    private static List<ClassInfo> klasses = new ArrayList<ClassInfo>();

    // Called for every file where str is what the file contains
    public static void parse(String str) {
        ASTParser parser = ASTParser.newParser(AST.JLS3);
        parser.setSource(str.toCharArray());
        parser.setKind(ASTParser.K_COMPILATION_UNIT);

        final CompilationUnit cu = (CompilationUnit) parser.createAST(null);     

        ClassVisitor cv = new ClassVisitor();
        cu.accept(cv);

        MethodVisitor methodsVisitor = new MethodVisitor(cu);
        cu.accept(methodsVisitor);


        ClassInfo klass = new ClassInfo(cv.getClassName(),
                cu.getLineNumber(cu.getLength() - 1),
                methodsVisitor.getNumberOfMethods(),
                methodsVisitor.getAverageLinesPerMethod(),
                methodsVisitor.getMethods());

        for(int i = 0; i < klass.methods.size(); i++){
            parser.setSource(klass.methods.get(i).body.toCharArray());
            CyclomaticComplexityVisitor ccv = new CyclomaticComplexityVisitor();

            cu.accept(ccv);
        }
        klasses.add(klass);
    }

-

public class MethodVisitor extends ASTVisitor {

    private CompilationUnit cu;
    private int numberOfMethods;
    private int lineCount;

    private List<MethodInfo> methods = new ArrayList<MethodInfo>();


    public MethodVisitor(CompilationUnit cu){
        this.cu = cu;
    }

    public boolean visit(MethodDeclaration node){
        int startPos = cu.getLineNumber(node.getStartPosition());
        int endPos = cu.getLineNumber(node.getStartPosition() + node.getLength());

        lineCount += (endPos - startPos);
        numberOfMethods++;

        String methodBody = node.getBody().toString();
        MethodInfo m = new MethodInfo(node.getName().getIdentifier(),
                                    (endPos - startPos),
                                    node.getReturnType2());
        m.body = methodBody;
        methods.add(m); 

        return true;
    }

-

public class CyclomaticComplexityVisitor extends ASTVisitor {

    private int complexityScore = 0;
    private int edges = 0;
    private int nodes = 0;
    private int exitPoints = 1;
    private boolean firstReturn = true;

    public boolean visit(IfStatement node){
        System.out.println("THERE WAS AN IF");
        String statement = node.toString();
        System.out.println(statement);

        return true;
    }

    public boolean visit(ReturnStatement node){
        if (firstReturn) {
            firstReturn = false;
        } else {
            exitPoints++;
        }
        return true;
    }

Cheers

  • Please only include what's relevant to the problem at hand. The overall thing you're trying to approach is probably out of scope, though a bit of context so we can know what you're trying to get and suggest alternatives is helpful. In this case, your question is also a bit opinion-based, so if you could add concrete details that'd be great. – Nic Mar 13 '15 at 18:37
  • @QPaysTaxes: Huh? He wants to know how count the nesting depth of IF statements using JDT. This is exactly a programming problem, not an "opinion". (I'm not a JDT expert, so I don't know the answer, but this should be very straightforward to do using the APIs it offers.) – Ira Baxter Mar 14 '15 at 15:07
  • @IraBaxter I'm fairly confident that OP edited the question after I wrote that. Notice the 20-hour time difference. Of course, I could still be wrong; that's why I said "a bit". – Nic Mar 14 '15 at 15:52
  • @IraBaxter - Yes sorry i did edit it after his original post but did not comment after doing so. – Sean Armstrong Mar 14 '15 at 16:32

1 Answers1

3

I'm not sure if this will answer your question, but for calculating McCabe's Cyclomatic Complexity (McCC) metric, you don't need to care about if-else-if nesting levels. You simply need to count the number of "branching" instructions and add 1 in the end. See the definition in the User's Guide of our SourceMeter tool:

McCabe's Cyclomatic Complexity (McCC) Method: complexity of the method expressed as the number of independent control flow paths in it. It represents a lower bound for the number of possible execution paths in the source code and at the same time it is an upper bound for the minimum number of test cases needed for achieving full branch test coverage. The value of the metric is calculated as the number of the following instructions plus 1: if, for, foreach, while, do-while, case label (which belongs to a switch instruction), catch, conditional statement (?:). Moreover, logical “and” (&&) and logical “or” (||) expressions also add 1 to the value because their short-circuit evaluation can cause branching depending on the first operand. The following instructions are not included: else, switch, default label (which belongs to a switch instruction), try, finally.

Rudolf FERENC
  • 289
  • 1
  • 6
  • Thanks for the answer. If this is the case is very much simplifies the implementation for me however I was trying to follow the equation cc = Edges - nodes + exitpoints Is this not actually needed? – Sean Armstrong Mar 14 '15 at 09:53
  • @SeanArmstrong: If you have a structured language (like Java) where control can only enter blocks from the outside, and leave blocks going back to the invoking block, the Rudolf's "simplified" counting scheme works. There's a theorem that says "counting branches+1" is equivalent to McCabe's "full definition" of E-N+X. If you were to process C or BASIC code, in which goto's are more prevalent, you'd need McCabe's full definition, or you'd need to decide the "simpler" version is good enough, which is what happens a lot. McCabe is only an approximate estimates of real complexity anyway. – Ira Baxter Mar 14 '15 at 15:06
  • @IraBaxter - Thanks for the explanation. This answer has solved my issues with calculating Cyclomatic Complexity. The nesting if statement problem may still resurface for other similar metrics but im happy for now – Sean Armstrong Mar 14 '15 at 15:28