/* * Sonar Clover Plugin * Copyright (C) 2008 SonarSource * dev@sonar.codehaus.org * * This program 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. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 */ package org.sonar.plugins.clover; import org.apache.commons.lang.StringUtils; import org.codehaus.staxmate.in.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.batch.SensorContext; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.CoverageMeasuresBuilder; import org.sonar.api.measures.Measure; import org.sonar.api.resources.JavaFile; import org.sonar.api.resources.JavaPackage; import org.sonar.api.resources.Resource; import org.sonar.api.utils.ParsingUtils; import org.sonar.api.utils.StaxParser; import org.sonar.api.utils.XmlParserException; import javax.xml.stream.XMLStreamException; import java.io.File; import java.text.ParseException; import static org.sonar.api.utils.ParsingUtils.scaleValue; public class XmlReportParser { private static final Logger LOG = LoggerFactory.getLogger(XmlReportParser.class); private SensorContext context; final CoverageMeasuresBuilder fileMeasuresBuilder = CoverageMeasuresBuilder.create(); public XmlReportParser(SensorContext context) { this.context = context; } private boolean reportExists(File report) { return report != null && report.exists() && report.isFile(); } protected void collect(File xmlFile) { try { if (reportExists(xmlFile)) { LOG.info("Parsing " + xmlFile.getCanonicalPath()); StaxParser parser = new StaxParser(new StaxParser.XmlStreamHandler() { public void stream(SMHierarchicCursor rootCursor) throws XMLStreamException { try { collectProjectMeasures(rootCursor.advance()); } catch (ParseException e) { throw new XMLStreamException(e); } } }); parser.parse(xmlFile); } } catch (Exception e) { throw new XmlParserException(e); } } private void collectProjectMeasures(SMInputCursor rootCursor) throws ParseException, XMLStreamException { SMInputCursor projectCursor = rootCursor.descendantElementCursor("project"); SMInputCursor projectChildrenCursor = projectCursor.advance().childElementCursor(); projectChildrenCursor.setFilter(new SimpleFilter(SMEvent.START_ELEMENT)); SMInputCursor metricsCursor = projectChildrenCursor.advance(); analyseMetricsNode(null, metricsCursor); collectPackageMeasures(projectChildrenCursor); } private void analyseMetricsNode(Resource resource, SMInputCursor metricsCursor) throws ParseException, XMLStreamException { int elements = (int) ParsingUtils.parseNumber(metricsCursor.getAttrValue("elements")); if (elements == 0) { return; } int statements = (int) ParsingUtils.parseNumber(metricsCursor.getAttrValue("statements")); int methods = (int) ParsingUtils.parseNumber(metricsCursor.getAttrValue("methods")); int conditionals = (int) ParsingUtils.parseNumber(metricsCursor.getAttrValue("conditionals")); int coveredElements = (int) ParsingUtils.parseNumber(metricsCursor.getAttrValue("coveredelements")); int coveredStatements = (int) ParsingUtils.parseNumber(metricsCursor.getAttrValue("coveredstatements")); int coveredMethods = (int) ParsingUtils.parseNumber(metricsCursor.getAttrValue("coveredmethods")); int coveredConditionals = (int) ParsingUtils.parseNumber(metricsCursor.getAttrValue("coveredconditionals")); context.saveMeasure(resource, CoreMetrics.COVERAGE, calculateCoverage(coveredElements, elements)); context.saveMeasure(resource, CoreMetrics.LINE_COVERAGE, calculateCoverage(coveredMethods + coveredStatements, methods + statements)); context.saveMeasure(resource, CoreMetrics.LINES_TO_COVER, (double) (statements + methods)); context.saveMeasure(resource, CoreMetrics.UNCOVERED_LINES, (double) (statements + methods - coveredStatements - coveredMethods)); if (conditionals > 0) { context.saveMeasure(resource, CoreMetrics.BRANCH_COVERAGE, calculateCoverage(coveredConditionals, conditionals)); context.saveMeasure(resource, CoreMetrics.CONDITIONS_TO_COVER, (double) (conditionals)); context.saveMeasure(resource, CoreMetrics.UNCOVERED_CONDITIONS, (double) (conditionals - coveredConditionals)); } } private double calculateCoverage(int coveredElements, int elements) { if (elements > 0) { return scaleValue(100.0 * ((double) coveredElements / (double) elements)); } return 0.0; } private void collectPackageMeasures(SMInputCursor packCursor) throws ParseException, XMLStreamException { while (packCursor.getNext() != null) { JavaPackage pack = new JavaPackage(packCursor.getAttrValue("name")); SMInputCursor packChildrenCursor = packCursor.descendantElementCursor(); packChildrenCursor.setFilter(new SimpleFilter(SMEvent.START_ELEMENT)); SMInputCursor metricsCursor = packChildrenCursor.advance(); analyseMetricsNode(pack, metricsCursor); collectFileMeasures(packChildrenCursor, pack); } } private void collectFileMeasures(SMInputCursor fileCursor, JavaPackage pack) throws ParseException, XMLStreamException { fileCursor.setFilter(SMFilterFactory.getElementOnlyFilter("file")); while (fileCursor.getNext() != null) { if (fileCursor.asEvent().isStartElement()) { String classKey = extractClassName(fileCursor.getAttrValue("name")); if (classKey != null) { SMInputCursor fileChildrenCursor = fileCursor.childCursor(new SimpleFilter(SMEvent.START_ELEMENT)); // cursor should be on the metrics element if (canBeIncludedInFileMetrics(fileChildrenCursor)) { // cursor should be now on the line cursor JavaFile resource = new JavaFile(pack.getKey(), classKey, false); saveHitsData(resource, fileChildrenCursor); } } } } } private void saveHitsData(Resource resource, SMInputCursor lineCursor) throws ParseException, XMLStreamException { fileMeasuresBuilder.reset(); while (lineCursor.getNext() != null) { // skip class elements on format 2_3_2 if (lineCursor.getLocalName().equals("class")) { continue; } final int lineId = Integer.parseInt(lineCursor.getAttrValue("num")); String count = lineCursor.getAttrValue("count"); if (StringUtils.isNotBlank(count)) { fileMeasuresBuilder.setHits(lineId, Integer.parseInt(count)); } else { int trueCount = (int) ParsingUtils.parseNumber(lineCursor.getAttrValue("truecount")); int falseCount = (int) ParsingUtils.parseNumber(lineCursor.getAttrValue("falsecount")); int coveredConditions = 0; if (trueCount > 0) { coveredConditions++; } if (falseCount > 0) { coveredConditions++; } fileMeasuresBuilder.setConditions(lineId, 2, coveredConditions); } } for (Measure measure : fileMeasuresBuilder.createMeasures()) { context.saveMeasure(resource, measure); } } private boolean canBeIncludedInFileMetrics(SMInputCursor metricsCursor) throws ParseException, XMLStreamException { while (metricsCursor.getNext() != null && metricsCursor.getLocalName().equals("class")) { // skip class elements on 1.x xml format } return ParsingUtils.parseNumber(metricsCursor.getAttrValue("elements")) > 0; } protected String extractClassName(String filename) { if (filename != null) { filename = StringUtils.replaceChars(filename, '\\', '/'); filename = StringUtils.substringBeforeLast(filename, ".java"); if (filename.indexOf('/') >= 0) { filename = StringUtils.substringAfterLast(filename, "/"); } } return filename; } }