Java tutorial
/*** * Summer: An enhanced, non-invasive, and easy-to-use IoC container and * LTW-AOP framework enabling brand-new mocking capabilities in combination * with a lightweight rule engine and general meta expression language purely * written in Java. * It provides component composition at run-time as well as brand-new mocking * capabilities that are also applicable to binary third-party libraries, to name * only two of all its features. * There are barely limitations due to the lack of * Java in adding and removing fields and methods to and from a class at run-time. * * Copyright (C) 2011-2013 Sandro Sebastian Koll * * This file is part of Summer. * * Summer is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Summer is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Summer. If not, see <http://www.gnu.org/licenses/>. */ package org.summer.ana; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.analysis.AnalyzerException; import org.objectweb.asm.tree.analysis.BasicInterpreter; import org.objectweb.asm.tree.analysis.Frame; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamWriter; import java.io.File; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; /** * @author Sandro Sebastian Koll */ public class CyclomaticComplexityPrinter extends ClassVisitor { private static final MathContext mc = new MathContext(3, RoundingMode.HALF_UP); private static boolean skipTrivialMethods = false; private static int ccLowerBoundToBeNonTrivial = 2; private static int ccUpperBoundToBeNonCritical = 10; public static void main(String[] args) { if (args == null || args.length < 2) printHelp(); for (int i = 0; i < args.length; ++i) { if (args[i] == null) printHelp(); switch (args[i]) { case "-s": skipTrivialMethods = true; break; case "-l": ccLowerBoundToBeNonTrivial = Integer.parseInt(args[++i].trim()); break; case "-u": ccUpperBoundToBeNonCritical = Integer.parseInt(args[++i].trim()); break; case "-m": StringTokenizer st = new StringTokenizer(args[++i].trim(), ",", false); while (st.hasMoreTokens()) { baseDirs.add(getPath(st.nextToken())); } break; case "-e": st = new StringTokenizer(args[++i].trim(), ",", false); while (st.hasMoreTokens()) { excludes.add(Paths.get(st.nextToken())); } break; case "-o": outputXML = getPath(args[++i].trim()); break; default: printHelp(); } } if (outputXML == null) printHelp(); for (Path path : baseDirs) { if (!Files.exists(path)) { System.out.println("ERROR: Path \"" + path + "\" not found."); printHelp(); } } try { writer = XMLOutputFactory.newInstance().createXMLStreamWriter(Files.newOutputStream(outputXML), "UTF-8"); writer.writeStartDocument(); writer.writeStartElement("results"); writer.writeStartElement("header"); writer.writeStartElement("skip-trivial-methods"); writer.writeCharacters("" + skipTrivialMethods); writer.writeEndElement(); writer.writeStartElement("lower-bound-to-be-non-trivial"); writer.writeCharacters("" + ccLowerBoundToBeNonTrivial); writer.writeEndElement(); writer.writeStartElement("upper-bound-to-be-non-critical"); writer.writeCharacters("" + ccUpperBoundToBeNonCritical); writer.writeEndElement(); writer.writeEndElement(); writer.writeStartElement("modules"); for (Path path : baseDirs) { writer.writeStartElement("module"); String module = path.toString(); writer.writeAttribute("name", module); printModuleCCs(path); writer.writeEndElement(); } writer.writeEndElement(); writer.writeStartElement("critical-methods"); int index; for (int i = 0; i < criticalMethods.size(); ++i) { writer.writeStartElement("critical-method" + (i + 1)); index = criticalMethods.get(i).indexOf(" = "); writer.writeAttribute("cc", criticalMethods.get(i).substring(index + 3)); writer.writeCharacters(criticalMethods.get(i).substring(0, index)); writer.writeEndElement(); } writer.writeEndElement(); writer.writeStartElement("total-stats"); writer.writeAttribute("classCount", "" + totalClassCount); writer.writeAttribute("methodCount", totalMethodCount == 0 ? "-" : "" + totalMethodCount); writer.writeAttribute("sumCC", totalCCSum == 0 ? "-" : "" + totalCCSum); writer.writeAttribute("avgCC", "" + (totalCCSum == 0 || totalMethodCount == 0 ? "-" : new BigDecimal((double) totalCCSum / (double) totalMethodCount).round(mc))); writer.writeEndElement(); writer.writeEndElement(); writer.writeEndDocument(); writer.flush(); } catch (Exception ex) { ex.printStackTrace(); } } private static void printHelp() { System.out .println("Usage: java.exe -jar \"summer-1.0.0-all.jar\" -cc <required-options> <optional-options>"); System.out.println("<required-options>:"); System.out.println(" -m = comma separated list of the modules (maven projects)"); System.out.println(" -o = XML file with the cyclomatic complexity stats of all available methods"); System.out.println("<optional-options>:"); System.out.println( " -e = comma separated list of packages or classes to exclude from the ones specified by -m"); System.out.println(" -s = skip trivial methods (no arguments)"); System.out.println( " -l = lower bound of a method's cyclomatic complexity to be non trivial (positive integer, defaults to 2)"); System.out.println( " -u = upper bound of a method's cyclomatic complexity to be non critical (positive integer, defaults to 10)"); System.out.println("\nExample:"); System.out.println( "java -jar \"summer-1.0.0-all.jar\" -cc -s -l 2 -u 10 -m \"c:/First Project,c:/Second Project\" -e \"org.mycompany.util\" -o \"c:/result.xml\""); System.exit(0); } private static Path getPath(String file) { Path path = new File(file).toPath().normalize(); if (!path.isAbsolute()) path = Paths.get(System.getProperty("user.dir")).resolve(path); return path; } private static void printModuleCCs(Path module) throws Exception { moduleClassCount = 0; moduleMethodCount = 0; moduleCCSum = 0; writer.writeStartElement("classes"); printDirCCs(module); writer.writeEndElement(); writer.writeStartElement("module-stats"); writer.writeAttribute("classCount", "" + moduleClassCount); writer.writeAttribute("methodCount", "" + moduleMethodCount); writer.writeAttribute("sumCC", moduleCCSum == 0 ? "-" : "" + moduleCCSum); writer.writeAttribute("avgCC", "" + (moduleCCSum == 0 || moduleMethodCount == 0 ? "-" : new BigDecimal((double) moduleCCSum / (double) moduleMethodCount).round(mc))); writer.writeEndElement(); } private static void printDirCCs(Path dir) throws Exception { try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) { outer: for (Path path : stream) { if (Files.isDirectory(path)) { for (Path exclude : excludes) { if (path.toString().contains(exclude.toString())) continue outer; } printDirCCs(path); } else if (path.getFileName().toString().toLowerCase(Locale.getDefault()).endsWith(".class")) printClassCCs(path); } } } private static void printClassCCs(Path classFile) throws Exception { String className = null; for (int i = 0; i < classFile.getNameCount(); ++i) { if (classFile.getName(i).toString().equals("classes")) { className = classFile.subpath(i + 1, classFile.getNameCount()).toString(); break; } } if (className == null) { System.out.println("No classes directory found."); printHelp(); } className = className.replace('\\', '.'); className = className.substring(0, className.length() - 6); classMethodCount = 0; classCCSum = 0; ClassReader cr = new ClassReader(className); cr.accept(new CyclomaticComplexityPrinter(), 0); if (classMethodCount > 0) { writer.writeStartElement("class"); writer.writeAttribute("name", className); writer.writeAttribute("methodCount", classMethodCount == 0 ? "-" : "" + classMethodCount); writer.writeAttribute("sumCC", classCCSum == 0 ? "-" : "" + classCCSum); writer.writeAttribute("avgCC", "" + (classCCSum == 0 || classMethodCount == 0 ? "-" : (new BigDecimal((double) classCCSum / (double) classMethodCount).round(mc)))); writer.writeEndElement(); } } public CyclomaticComplexityPrinter() { super(Opcodes.ASM4); } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { this.className = name; isInterface = (access & Opcodes.ACC_INTERFACE) != 0; if (!isInterface) { ++totalClassCount; ++moduleClassCount; } } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { if (!isInterface) return new CyclomaticComplexityPrinter.MethodAnalyzer(className, access, name, desc, null); return null; } private class MethodAnalyzer extends MethodVisitor { public MethodAnalyzer(String owner, int access, String name, String desc, MethodVisitor mv) { super(Opcodes.ASM4, new MethodNode(access, name, desc, null, null)); this.owner = owner; this.methodName = name; } @Override public void visitEnd() { try { int cc = getCyclomaticComplexity(owner, (MethodNode) mv); if (!skipTrivialMethods || cc >= ccLowerBoundToBeNonTrivial) { classCCSum += cc; moduleCCSum += cc; totalCCSum += cc; ++classMethodCount; ++moduleMethodCount; ++totalMethodCount; if (cc > ccUpperBoundToBeNonCritical) { String desc = className.replace('/', '.') + "#" + methodName + " = " + cc; boolean added = false; for (int i = 0; i < criticalMethods.size(); ++i) { String m = criticalMethods.get(i); int mCC = Integer.parseInt(m.substring(m.indexOf(" = ") + 3)); if (mCC > cc) { criticalMethods.add(i, desc); added = true; break; } } if (!added) criticalMethods.add(desc); } } } catch (AnalyzerException ex) { ex.printStackTrace(); } } private int getCyclomaticComplexity(String owner, MethodNode mn) throws AnalyzerException { org.objectweb.asm.tree.analysis.Analyzer ana = new org.objectweb.asm.tree.analysis.Analyzer( new BasicInterpreter()) { @Override protected Frame newFrame(int nLocals, int nStack) { return new CyclomaticComplexityPrinter.FrameNode(nLocals, nStack); } @Override protected Frame newFrame(Frame src) { return new CyclomaticComplexityPrinter.FrameNode(src); } @Override protected void newControlFlowEdge(int src, int dst) { ((CyclomaticComplexityPrinter.FrameNode) getFrames()[src]).successors .add((CyclomaticComplexityPrinter.FrameNode) getFrames()[dst]); } }; ana.analyze(owner, mn); Frame[] frames = ana.getFrames(); int edges = 0; int nodes = 0; for (int i = 0; i < frames.length; ++i) { if (frames[i] != null) { edges += ((CyclomaticComplexityPrinter.FrameNode) frames[i]).successors.size(); ++nodes; } } return edges - nodes + 2; } private String owner; private String methodName; } private static class FrameNode extends Frame { public FrameNode(int nLocals, int nStack) { super(nLocals, nStack); } public FrameNode(Frame src) { super(src); } protected Set<CyclomaticComplexityPrinter.FrameNode> successors = new HashSet<>(); } private static Path outputXML; private static XMLStreamWriter writer; private static List<Path> baseDirs = new LinkedList<>(); private static List<Path> excludes = new LinkedList<>(); private static int totalClassCount = 0; private static int totalMethodCount = 0; private static int totalCCSum = 0; private static int moduleClassCount; private static int moduleMethodCount; private static int moduleCCSum; private static int classMethodCount; private static int classCCSum; private static List<String> criticalMethods = new LinkedList<>(); private String className; private boolean isInterface; }