org.sonar.plugins.cxx.cppcheck.CxxCppCheckSensor.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.plugins.cxx.cppcheck.CxxCppCheckSensor.java

Source

/*
 * Sonar Cxx Plugin, open source software quality management tool.
 * Copyright (C) 2010 - 2011, Neticoa SAS France - Tous droits rservs.
 * Author(s) : Franck Bonin, Neticoa SAS France.
 * Copyright (C) 2010 Vincent Hardion
 *
 * 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.cppcheck;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;

import javax.xml.stream.XMLStreamException;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.io.input.TeeInputStream;

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.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;
import org.sonar.api.rules.Rule;
import org.sonar.api.rules.RuleFinder;
import org.sonar.api.rules.Violation;

/**
 * Sensor for CppCheck external tool.
 * 
 * CppCheck is an equivalent to FindBug but for C++
 * 
 * @author fbonin
 * @author vhardion
 * @todo enable include dirs (-I)
 * @todo allow configuration of path to analyze
 */
public class CxxCppCheckSensor extends ReportsHelper implements Sensor {

    private static final String EXEC = "cppcheck";
    private static final String ARGS = "--enable=all -v --quiet --xml";

    private static final String GROUP_ID = "org.codehaus.mojo";
    private static final String ARTIFACT_ID = "cxx-maven-plugin";
    private static final String SENSOR_ID = "cppcheck";
    private static final String DEFAULT_CPPCHECK_REPORTS_DIR = "cppcheck-reports";
    private static final String DEFAULT_REPORTS_FILE_PATTERN = "**/cppcheck-result-*.xml";

    private RuleFinder ruleFinder;
    private boolean dynamicAnalysis = false;

    public CxxCppCheckSensor(RuleFinder ruleFinder, Configuration conf) {
        this.ruleFinder = ruleFinder;
        this.dynamicAnalysis = conf.getBoolean("sonar.cxx.cppcheck.runAnalysis", this.dynamicAnalysis);
    }

    private static Logger logger = LoggerFactory.getLogger(CxxCppCheckSensor.class);

    /**
     * This plugin should be executed on C++ project
     * 
     * @param project
     * @return
     */
    public boolean shouldExecuteOnProject(Project project) {
        return CxxPlugin.KEY.equals(project.getLanguageKey());
    }

    @Override
    protected String getArtifactId() {
        return ARTIFACT_ID;
    }

    @Override
    protected String getSensorId() {
        return SENSOR_ID;
    }

    @Override
    protected String getDefaultReportsDir() {
        return DEFAULT_CPPCHECK_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) {
        if (dynamicAnalysis) {
            Process p;

            try {
                String cmd = EXEC + " " + ARGS + " " + project.getPom().getBuild().getSourceDirectory();
                logger.debug(cmd);
                p = Runtime.getRuntime().exec(cmd);
                p.waitFor();

                // Write result in local file
                File resultOutputFile = new File(
                        project.getFileSystem().getSonarWorkingDirectory() + "/cppcheck-result.xml");
                // resultOutputFile.createNewFile();
                logger.debug("Output result to " + resultOutputFile.getAbsolutePath());

                // Becarefull ... CppCheck print its result into Error output !
                // TeeInputStream is used to read result from stream and both
                // write it to a file
                FileOutputStream fos = new FileOutputStream(resultOutputFile);
                TeeInputStream tis = new TeeInputStream(p.getErrorStream(), fos, true);
                parseReport(project, tis, context);

            } catch (InterruptedException ex) {
                logger.error("Analysis can't wait for the end of the process", ex);
            } catch (IOException ex) {
                logger.error("IO EXCEPTION", ex);
            }

        } else {
            File reportDirectory = getReportsDirectory(project);
            if (reportDirectory != null) {
                File reports[] = getReports(project, reportDirectory);
                for (File report : reports) {
                    parseReport(project, report, context);
                }
            }
        }
    }

    private void parseReport(final Project project, File xmlFile, final SensorContext context) {
        try {
            parseReport(project, new FileInputStream(xmlFile), context);
        } catch (FileNotFoundException ex) {
            logger.error("CppCheck Report not found : " + xmlFile.getAbsoluteFile(), ex);
        }
    }

    /**
     * Parse the stream of CppCheck XML report
     * 
     * @param project
     * @param xmlStream
     *            - This stream will be closed at the end of this method
     * @param context
     */
    private void parseReport(final Project project, InputStream xmlStream, final SensorContext context) {
        try {
            logger.info("parsing CppCheck XML stream{}");
            StaxParser parser = new StaxParser(new StaxParser.XmlStreamHandler() {

                public void stream(SMHierarchicCursor rootCursor) throws XMLStreamException {
                    try {
                        rootCursor.advance();
                        collectError(project, rootCursor.childElementCursor("error"), context);
                    } catch (ParseException e) {
                        throw new XMLStreamException(e);
                    }
                }
            });
            parser.parse(xmlStream);
        } catch (XMLStreamException e) {
            throw new XmlParserException(e);
        } finally {
            try {
                if (xmlStream != null) {
                    xmlStream.close();
                }
            } catch (IOException ex) {
                logger.error("Can't close the xml stream", ex);
            }
        }
    }

    private void collectError(Project project, SMInputCursor error, SensorContext context)
            throws ParseException, XMLStreamException {
        while (error.getNext() != null) {
            // logger.info("collectError nodename = {} {}",
            // error.getPrefixedName(), error.getAttrCount());

            String id = error.getAttrValue("id");
            String msg = error.getAttrValue("msg");
            String file = error.getAttrValue("file");
            String line = error.getAttrValue("line");
            if (StringUtils.isEmpty(line)) {
                line = "0";
            }
            if (!StringUtils.isEmpty(file)) {
                CxxFile ressource = CxxFile.fromFileName(project, file, getReportsIncludeSourcePath(project),
                        false);
                if (fileExist(context, ressource)) {
                    Rule rule = ruleFinder.findByKey(CxxCppCheckRuleRepository.REPOSITORY_KEY, id);
                    if (rule != null) {
                        Object t[] = { id, msg, line, ressource.getKey() };
                        logger.debug("error id={} msg={} found at line {} from ressource {}", t);

                        Violation violation = Violation.create(rule, ressource);
                        violation.setMessage(msg);
                        violation.setLineId(Integer.parseInt(line));
                        context.saveViolation(violation);
                    } else {
                        Object t[] = { id, msg, line, file };
                        logger.warn("No rule for error id={} msg={} found at line {} from file {}", t);
                    }
                } else {
                    Object t[] = { id, msg, line, file };
                    logger.warn("error id={} msg={} found at line {} from file {} has no ressource associated", t);
                }
            } else {
                Object t[] = { id, msg, line };
                logger.warn("error id={} msg={} found at line {} has no file associated", t);
            }
        }
    }

    private boolean fileExist(SensorContext context, CxxFile file) {
        return context.getResource(file) != null;
    }
}