package com.ikokoon.serenity.instrumentation.complexity; import org.apache.log4j.Logger; import org.objectweb.asm.Label; import org.objectweb.asm.MethodAdapter; import org.objectweb.asm.MethodVisitor; import com.ikokoon.serenity.Collector; import com.ikokoon.serenity.instrumentation.coverage.CoverageMethodAdapter; import com.ikokoon.toolkit.Toolkit; /** * TODO - add the interesting methods to the collection of the complexity. Do we need to add try catch? And what about multiple catch? One for each * potential exception thrown? No? * * This class just visits the byte code in the classes and collects the complexity metrics for the class. Complexity is calculated by adding one every * time there is a jump instruction. * * @author Michael Couck * @since 12.07.09 * @version 01.00 */ public class ComplexityMethodAdapter extends MethodAdapter { /** The logger for the class. */ private Logger logger = Logger.getLogger(CoverageMethodAdapter.class); /** The name of the class that this method adapter is enhancing the methods for. */ private String className; /** The name of the method that is being enhanced. */ private String methodName; /** The description of the method being enhanced. */ private String methodDescription; /** * The complexity counter, start with one and increment for each jump instruction/decision point. This will give the approximate value of the * McCabe method:<br> * * M = E − N + 2P where * * M = cyclomatic complexity <br> * E = the number of edges of the graph<br> * N = the number of nodes of the graph<br> * P = the number of connected components.<br> */ private int complexityCounter = 1; /** * The constructor initialises a {@link ComplexityMethodAdapter} that takes all the interesting items for the method that is to be enhanced * including the parent method visitor. * * @param methodVisitor * the method visitor of the parent * @param className * the name of the class the method belongs to * @param methodName * the name of the method that will be collected for complexity * @param methodDescription * the description of the method, i.e. the byte code signature */ public ComplexityMethodAdapter(MethodVisitor methodVisitor, Integer access, String className, String methodName, String methodDescription) { super(methodVisitor); this.className = Toolkit.slashToDot(className); this.methodName = methodName; this.methodDescription = methodDescription; if (logger.isDebugEnabled()) { logger.debug("Class name : " + className + ", name : " + methodName + ", desc : " + methodDescription); } } /** * {@inheritDoc} */ public void visitLineNumber(int lineNumber, Label label) { if (logger.isDebugEnabled()) { logger.debug("visitLineNumber : " + lineNumber + ", " + label + ", " + label.getOffset() + ", " + className + ", " + methodName); } this.mv.visitLineNumber(lineNumber, label); } /** * {@inheritDoc} */ public void visitJumpInsn(int opcode, Label paramLabel) { if (logger.isDebugEnabled()) { logger.debug("visitJumpInsn:" + opcode); } complexityCounter++; this.mv.visitJumpInsn(opcode, paramLabel); } /** * {@inheritDoc} */ public void visitEnd() { if (logger.isDebugEnabled()) { logger.debug("visitEnd:" + className + ", " + methodName + ", " + methodDescription/* + ", " + lineCounter */); } Collector.collectComplexity(className, methodName, methodDescription, complexityCounter/* , lineCounter */); this.mv.visitEnd(); } /** * {@inheritDoc} */ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { logger.debug("visitTryCatchBlock : " + className + ", " + methodName); complexityCounter++; this.mv.visitTryCatchBlock(start, end, handler, type); } /** * {@inheritDoc} */ public void visitLookupSwitchInsn(Label dflt, int keys[], Label labels[]) { logger.debug("visitlookupSwitchInst : " + className + ", " + methodName); complexityCounter++; this.mv.visitLookupSwitchInsn(dflt, keys, labels); } /** * {@inheritDoc} */ public void visitTableSwitchInsn(int min, int max, Label dflt, Label labels[]) { logger.debug("visitTableSwitchInst : " + className + ", " + methodName); complexityCounter++; this.mv.visitTableSwitchInsn(min, max, dflt, labels); } /** * {@inheritDoc} */ public void visitInsn(int opcode) { logger.debug("visitInst : " + className + ", " + methodName); // I could be an ATHROW. Do we count as a jump instruction? this.mv.visitInsn(opcode); } }