/* * Sonar Cxx Plugin, open source software quality management tool. * Copyright (C) 2010 - 2011, Neticoa SAS France - Tous droits réservés. * Author(s) : Franck Bonin, Neticoa SAS France. * * Sonar Cxx Plugin is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * Sonar Cxx Plugin 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sonar Cxx Plugin; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 */ package org.sonar.plugins.cxx.cppncss; import java.io.File; import java.text.ParseException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.stream.XMLStreamException; import org.apache.commons.lang.StringUtils; import org.codehaus.staxmate.in.SMHierarchicCursor; import org.codehaus.staxmate.in.SMInputCursor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.batch.Sensor; import org.sonar.api.batch.SensorContext; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.PersistenceMode; import org.sonar.api.measures.RangeDistributionBuilder; import org.sonar.api.resources.Project; import org.sonar.api.utils.StaxParser; import org.sonar.api.utils.XmlParserException; import org.sonar.plugins.cxx.CxxFile; import org.sonar.plugins.cxx.CxxPlugin; import org.sonar.plugins.cxx.utils.ReportsHelper; public class CxxCppNcssSensor extends ReportsHelper implements Sensor { private static Logger logger = LoggerFactory .getLogger(CxxCppNcssSensor.class); public boolean shouldExecuteOnProject(Project project) { return CxxPlugin.KEY.equals(project.getLanguageKey()); } public static final String GROUP_ID = "org.codehaus.mojo"; public static final String ARTIFACT_ID = "cxx-maven-plugin"; public static final String SENSOR_ID = "cppncss"; public static final String DEFAULT_GCOVR_REPORTS_DIR = "cppncss-reports"; public static final String DEFAULT_REPORTS_FILE_PATTERN = "**/cppncss-result-*.xml"; @Override protected String getArtifactId() { return ARTIFACT_ID; } @Override protected String getSensorId() { return SENSOR_ID; } @Override protected String getDefaultReportsDir() { return DEFAULT_GCOVR_REPORTS_DIR; } @Override protected String getDefaultReportsFilePattern() { return DEFAULT_REPORTS_FILE_PATTERN; } @Override protected String getGroupId() { return GROUP_ID; } @Override protected Logger getLogger() { return logger; } public void analyse(Project project, SensorContext context) { File reportDirectory = getReportsDirectory(project); if (reportDirectory != null) { File reports[] = getReports(project, reportDirectory); for (File report : reports) { parseReport(project, report, context); } } } private final static Number[] METHODS_DISTRIB_BOTTOM_LIMITS = { 1, 2, 4, 6, 8, 10, 12 }; private final static Number[] FILE_DISTRIB_BOTTOM_LIMITS = { 0, 5, 10, 20, 30, 60, 90 }; private final static Number[] CLASS_DISTRIB_BOTTOM_LIMITS = { 0, 5, 10, 20, 30, 60, 90 }; private class FileData { FileData(String name) { FileName = name; } private class classData { private Map<String,Integer> ComplexityPerMethod = new HashMap<String, Integer>(); private Integer ClassComplexity = new Integer(0); private Integer NbClassMethod = new Integer(0); private void putComplexityPerMethod(String name, Integer Complexity) { NbClassMethod++; ClassComplexity += Complexity; Integer c = ComplexityPerMethod.get(name); if (c == null) { c = new Integer(Complexity); ComplexityPerMethod.put(name, c); } else { // alert ! } } public Integer getClassComplexity() { return ClassComplexity; } public Collection<Integer> complexityPerMethodValues() { return ComplexityPerMethod.values(); } } public void addMethod(String clazz, String name, Integer Complexity) { NbMethod++; FileComplexity += Complexity; classData ComplexityPerMethod = ComplexityPerClassPerMethod.get(clazz); if (ComplexityPerMethod == null) { ComplexityPerMethod = new classData(); ComplexityPerClassPerMethod.put(clazz, ComplexityPerMethod); } ComplexityPerMethod.putComplexityPerMethod(name, Complexity); } public void saveMetric(Project project, SensorContext context) { CxxFile file = CxxFile.fromFileName(project, FileName, getReportsIncludeSourcePath(project), false); if (context.getResource(file) != null) { RangeDistributionBuilder complexityMethodsDistribution = new RangeDistributionBuilder( CoreMetrics.FUNCTION_COMPLEXITY_DISTRIBUTION, METHODS_DISTRIB_BOTTOM_LIMITS); RangeDistributionBuilder complexityFileDistribution = new RangeDistributionBuilder( CoreMetrics.FILE_COMPLEXITY_DISTRIBUTION, FILE_DISTRIB_BOTTOM_LIMITS); RangeDistributionBuilder complexityClassDistribution = new RangeDistributionBuilder( CoreMetrics.CLASS_COMPLEXITY_DISTRIBUTION, CLASS_DISTRIB_BOTTOM_LIMITS); complexityFileDistribution.add(FileComplexity); for (classData cd : ComplexityPerClassPerMethod.values()) { complexityClassDistribution.add(cd.getClassComplexity()); for (Integer c : cd.complexityPerMethodValues()) { complexityMethodsDistribution.add(c); } } context.saveMeasure(file, CoreMetrics.FUNCTIONS, NbMethod .doubleValue()); context.saveMeasure(file, CoreMetrics.COMPLEXITY, FileComplexity.doubleValue()); //logger.info("File saveMeasure NbMethod={}, FileComplexity={}", NbMethod, FileComplexity); context.saveMeasure(file, complexityMethodsDistribution.build() .setPersistenceMode(PersistenceMode.MEMORY)); //logger.info("File complexityMethodsDistribution={}", complexityMethodsDistribution.build()); context.saveMeasure(file, complexityClassDistribution.build() .setPersistenceMode(PersistenceMode.MEMORY)); //logger.info("File complexityClassDistribution={}", complexityClassDistribution.build()); context.saveMeasure(file, complexityFileDistribution.build() .setPersistenceMode(PersistenceMode.MEMORY)); //logger.info("File complexityFileDistribution={}", complexityFileDistribution.build()); } } private String FileName; private Integer NbMethod = new Integer(0); private Map<String, classData> ComplexityPerClassPerMethod = new HashMap<String, classData>(); private Integer FileComplexity = new Integer(0); } private void parseReport(final Project project, File xmlFile, final SensorContext context) { try { logger.info("parsing {}", xmlFile); StaxParser parser = new StaxParser( new StaxParser.XmlStreamHandler() { public void stream(SMHierarchicCursor rootCursor) throws XMLStreamException { try { Map<String, FileData> fileDataPerFilename = new HashMap<String, FileData>(); rootCursor.advance(); collectMeasure(project, rootCursor .childElementCursor("measure"), fileDataPerFilename, context); for (FileData d : fileDataPerFilename.values()) { d.saveMetric(project, context); } } catch (ParseException e) { throw new XMLStreamException(e); } } }); parser.parse(xmlFile); } catch (XMLStreamException e) { throw new XmlParserException(e); } } private void collectMeasure(Project project, SMInputCursor mesure, Map<String, FileData> fileDataPerFilename, SensorContext context) throws ParseException, XMLStreamException { while (mesure.getNext() != null) { String type = mesure.getAttrValue("type"); logger.debug("collect Mesure type = {}",type); if (!StringUtils.isEmpty(type)) { SMInputCursor mesureChild = mesure.childElementCursor().advance(); // collect labels List<String> valueLabels = new ArrayList<String>(); if (mesureChild.getLocalName().equalsIgnoreCase("labels")) { logger.debug("collect labels"); SMInputCursor itemValueLabels = mesureChild.childElementCursor("label"); while (itemValueLabels.getNext() != null) { String label = itemValueLabels.getElemStringValue(); logger.debug("new label = {}", label); valueLabels.add(label); } } // collect only function metrics if (type.equalsIgnoreCase("Function")) { collectFunctionItems(project, fileDataPerFilename, valueLabels, mesureChild, context); } else if (type.equalsIgnoreCase("File")) { // nothing } } } } private void collectFunctionItems(Project project, Map<String, FileData> fileDataPerFilename, List<String> valueLabels, SMInputCursor items, SensorContext context) throws ParseException, XMLStreamException { while (items.getNext() != null) { String name = items.getAttrValue("name"); //logger.info("collect Funtion name = {}", name); if (!StringUtils.isEmpty(name)) { String loc[] = name.split(" at "); String fullFuncName = loc[0]; String fullFileName = loc[1]; loc = fullFuncName.split("::"); String className = (loc.length > 1)?loc[0]:"GLOBAL"; String funcName = (loc.length > 1)?loc[1]:loc[0]; loc = fullFileName.split(":"); String fileName = loc[0]; Object tab[] = {fileName, className, funcName}; logger.debug("collect Funtion : fileName = {}, className = {}, funcName = {}", tab); FileData data = fileDataPerFilename.get(fileName); if (data == null) { data = new FileData(fileName); fileDataPerFilename.put(fileName, data); } SMInputCursor value = items.childElementCursor("value"); int i = 0; while (value.getNext() != null) { if (valueLabels.get(i).equalsIgnoreCase("CCN")) { String MethodeComplexity = value.getElemStringValue(); if (!StringUtils.isEmpty(MethodeComplexity)) { //logger.info("Found Funtion CCN = {}", MethodeComplexity); data.addMethod(className, funcName, Integer .parseInt(MethodeComplexity.trim())); } } i++; } } } } }