/* * Sonar C# Plugin :: Gallio * Copyright (C) 2010 Jose Chillan, Alexandre Victoor and 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.csharp.gallio.results.coverage; import static org.sonar.plugins.csharp.gallio.helper.StaxHelper.descendantElements; import static org.sonar.plugins.csharp.gallio.helper.StaxHelper.findAttributeValue; import static org.sonar.plugins.csharp.gallio.helper.StaxHelper.findElementName; import static org.sonar.plugins.csharp.gallio.helper.StaxHelper.findXMLEvent; import static org.sonar.plugins.csharp.gallio.helper.StaxHelper.nextPosition; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import org.codehaus.staxmate.SMInputFactory; import org.codehaus.staxmate.in.SMFilterFactory; import org.codehaus.staxmate.in.SMHierarchicCursor; import org.codehaus.staxmate.in.SMInputCursor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.BatchExtension; import org.sonar.api.batch.SensorContext; import org.sonar.api.resources.Project; import org.sonar.api.utils.SonarException; import org.sonar.plugins.csharp.gallio.results.coverage.model.FileCoverage; import org.sonar.plugins.csharp.gallio.results.coverage.model.ParserResult; import org.sonar.plugins.csharp.gallio.results.coverage.model.ProjectCoverage; import com.google.common.base.Predicate; import com.google.common.collect.Maps; /** * Parses a coverage report using Stax * * @author Maxime SCHNEIDER-DUFEUTRELLE January 26, 2011 */ public class CoverageResultParser implements PointParserCallback, BatchExtension { /** * Generates the logger. */ private static final Logger LOG = LoggerFactory.getLogger(CoverageResultParser.class); private SensorContext context; private Map<Integer, FileCoverage> sourceFilesById; private final Map<String, ProjectCoverage> projectsByAssemblyName; private final List<AbstractParsingStrategy> parsingStrategies; private AbstractParsingStrategy currentStrategy; /** * Constructs a @link{CoverageResultStaxParser}. */ public CoverageResultParser(SensorContext context) { this.context = context; sourceFilesById = new HashMap<Integer, FileCoverage>(); projectsByAssemblyName = new HashMap<String, ProjectCoverage>(); parsingStrategies = new ArrayList<AbstractParsingStrategy>(); parsingStrategies.add(new PartCover23ParsingStrategy()); parsingStrategies.add(new PartCover22ParsingStrategy()); parsingStrategies.add(new PartCover4ParsingStrategy()); parsingStrategies.add(new NCover3ParsingStrategy()); } /** * Parses a file * * @param file * : the file to parse * */ public ParserResult parse(final Project sonarProject, final File file) { try { SMInputFactory inf = new SMInputFactory(XMLInputFactory.newInstance()); SMHierarchicCursor rootCursor = inf.rootElementCursor(file); SMInputCursor root = rootCursor.advance(); LOG.debug("\nrootCursor is at : {}", findElementName(rootCursor)); // First define the version chooseParsingStrategy(root); SMInputCursor rootChildCursor = descendantElements(root); // Then all the indexed files are extracted sourceFilesById = currentStrategy.findFiles(rootChildCursor); if (sourceFilesById.isEmpty()) { // no source, ther is no point to parse further return new ParserResult(Collections.EMPTY_LIST, Collections.EMPTY_LIST); } // filter files according to the exclusion patterns sourceFilesById = Maps.filterValues(sourceFilesById, new Predicate<FileCoverage>() { public boolean apply(FileCoverage input) { return context.isIndexed(org.sonar.api.resources.File.fromIOFile(input.getFile(), sonarProject), false); } }); // We finally process the coverage details fillProjects(rootChildCursor); // We summarize the files for (FileCoverage fileCoverage : sourceFilesById.values()) { fileCoverage.summarize(); } for (ProjectCoverage project : projectsByAssemblyName.values()) { LOG.debug("Summarize project: {}", project); project.summarize(); } } catch (XMLStreamException e) { throw new SonarException("Could not parse the result file", e); } List<ProjectCoverage> projects = new ArrayList<ProjectCoverage>(projectsByAssemblyName.values()); List<FileCoverage> sourceFiles = new ArrayList<FileCoverage>(sourceFilesById.values()); return new ParserResult(projects, sourceFiles); } /** * Processes the details of the coverage * * @param rootChildCursor * cursor positioned to get the method elements */ private void fillProjects(SMInputCursor rootChildCursor) { // Because of a different structure in PartCover 4, we need to get the assemblies first // if the report is from PartCover 4 currentStrategy.saveAssemblyNamesById(rootChildCursor); // Sets the cursor to the tags "Type" for PartCover and "Module" for NCover rootChildCursor.setFilter(SMFilterFactory.getElementOnlyFilter(currentStrategy.getModuleTag())); do { if (findXMLEvent(rootChildCursor) != null) { currentStrategy.saveId(findAttributeValue(rootChildCursor, currentStrategy.getAssemblyReference())); String assemblyName = currentStrategy.findAssemblyName(rootChildCursor); LOG.debug("AssemblyName: {}", assemblyName); currentStrategy.findPoints(assemblyName, rootChildCursor, this); } } while (nextPosition(rootChildCursor) != null); } public void createProjects(String assemblyName, SMInputCursor classElements) { FileCoverage fileCoverage = null; SMInputCursor methodElements = descendantElements(classElements); while (nextPosition(methodElements) != null) { fileCoverage = currentStrategy.parseMethod(methodElements, assemblyName, sourceFilesById); if (fileCoverage != null) { final ProjectCoverage project; if (projectsByAssemblyName.containsKey(assemblyName)) { project = projectsByAssemblyName.get(assemblyName); } else { project = new ProjectCoverage(); project.setAssemblyName(assemblyName); projectsByAssemblyName.put(assemblyName, project); } project.addFile(fileCoverage); } } } /** * This method is necessary due to a modification of the schema between partcover 2.2 and 2.3, for which elements start now with an * uppercase letter. Format is a little bit different with partcover4, and NCover use a different format too. * * @param root * : root cursor */ private void chooseParsingStrategy(SMInputCursor root) { Iterator<AbstractParsingStrategy> strategyIterator = parsingStrategies.iterator(); while (strategyIterator.hasNext()) { AbstractParsingStrategy strategy = (AbstractParsingStrategy) strategyIterator.next(); if (strategy.isCompatible(root)) { this.currentStrategy = strategy; } } if (currentStrategy == null) { LOG.warn("XML coverage format unknown, using default strategy"); this.currentStrategy = parsingStrategies.get(0); } } }