Java tutorial
/* * Sonar .NET Plugin :: ReSharper * Copyright (C) 2013 John M. Wright * 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 com.wrightfully.sonar.plugins.dotnet.resharper; import org.apache.commons.io.IOUtils; import org.codehaus.staxmate.SMInputFactory; 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.rules.Rule; import org.sonar.api.rules.RuleFinder; import org.sonar.api.rules.RuleQuery; import org.sonar.api.rules.Violation; import org.sonar.api.utils.SonarException; import org.sonar.plugins.dotnet.api.microsoft.MicrosoftWindowsEnvironment; import org.sonar.plugins.dotnet.api.microsoft.VisualStudioProject; import org.sonar.plugins.dotnet.api.microsoft.VisualStudioSolution; import org.sonar.plugins.dotnet.api.utils.StaxParserUtils; import com.wrightfully.sonar.plugins.dotnet.resharper.failingissues.IssueListener; import com.wrightfully.sonar.plugins.dotnet.resharper.failingissues.IssueModel; import javax.xml.stream.XMLStreamException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * Parses the reports generated by a ReSharper analysis. */ public class ReSharperResultParser implements BatchExtension { private static final Logger LOG = LoggerFactory.getLogger(ReSharperResultParser.class); private final VisualStudioSolution vsSolution; private VisualStudioProject vsProject; private Project project; private SensorContext context; private RuleFinder ruleFinder; private String repositoryKey; private Boolean includeAllFiles; private final static String issuesLink = "https://jira.codehaus.org/browse/SONARPLUGINS/component/16153"; private final static String missingIssueTypesRuleKey = "ReSharperInspectCode#Sonar.UnknownIssueType"; private final ReSharperConfiguration configuration; private List<IssueListener> observers = new ArrayList<IssueListener>(); /** * Constructs a @link{ReSharperResultParser}. */ public ReSharperResultParser(MicrosoftWindowsEnvironment env, Project project, SensorContext context, RuleFinder ruleFinder, ReSharperConfiguration configuration) { super(); this.vsSolution = env.getCurrentSolution(); this.configuration = configuration; if (vsSolution == null) { // not a .NET project return; } this.vsProject = vsSolution.getProjectFromSonarProject(project); this.project = project; this.context = context; this.ruleFinder = ruleFinder; String projLanguage = project.getLanguageKey(); repositoryKey = ReSharperConstants.REPOSITORY_KEY + "-" + projLanguage; includeAllFiles = configuration.getBoolean(ReSharperConstants.INCLUDE_ALL_FILES); } /** * Add a listener to parsing events * * @param issueListener instance of class implementing IssueListener */ public void addObserver(IssueListener issueListener) { observers.add(issueListener); } /** * Parses a processed violation file. * * @param file * the file to parse */ public void parse(File file) { SMInputFactory inputFactory = StaxParserUtils.initStax(); LOG.debug("Parsing " + file.getAbsolutePath()); FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream(file); SMHierarchicCursor cursor = inputFactory.rootElementCursor( new InputStreamReader(fileInputStream, project.getFileSystem().getSourceCharset())); SMInputCursor mainCursor = cursor.advance().childElementCursor(); MissingIssueTypeHelper missingTypesHelper = new MissingIssueTypeHelper(); checkInVisitors(); while (mainCursor.getNext() != null) { String nodeName = mainCursor.getQName().getLocalPart(); if (nodeName.equals("Issues")) { parseIssuesBloc(mainCursor, missingTypesHelper); } else if (nodeName.equals("IssueTypes")) { missingTypesHelper.setInputCursor(mainCursor); } } if (missingTypesHelper.hasMissingIssues()) { missingTypesHelper.logMissingIssues(); } cursor.getStreamReader().closeCompletely(); } catch (XMLStreamException e) { throw new SonarException("Error while reading ReSharper result file: " + file.getAbsolutePath(), e); } catch (FileNotFoundException e) { throw new SonarException("Cannot find ReSharper result file: " + file.getAbsolutePath(), e); } finally { IOUtils.closeQuietly(fileInputStream); checkOutVisitors(); } } private class MissingIssueTypeHelper { private final Set<String> _missingIssueTypes; public boolean hasMissingIssues() { return !_missingIssueTypes.isEmpty(); } public MissingIssueTypeHelper() { _missingIssueTypes = new HashSet<String>(); } public void setInputCursor(SMInputCursor issuesTypeCursor) throws XMLStreamException { SMInputCursor _issueTypeCursor = issuesTypeCursor.childElementCursor("IssueType"); LOG.debug("Parsing IssueTypes"); while (_issueTypeCursor.getNext() != null) { String issueTypeId = _issueTypeCursor.getAttrValue("Id"); StringBuilder xml = new StringBuilder("<IssueType "); int attrCount = _issueTypeCursor.getAttrCount(); for (int i = 0; i < attrCount; i++) { String name = _issueTypeCursor.getAttrName(i).getLocalPart(); String value = _issueTypeCursor.getAttrValue(i); xml.append(name + "=\"" + value + "\" "); } xml.append("/>"); String xmlOut = xml.toString(); LOG.debug("Found IssueType " + issueTypeId + " with value " + xmlOut); _issueTypeCache.put(issueTypeId, xmlOut); } } public void addMissingIssueType(String issueTypeName) { _missingIssueTypes.add(issueTypeName); } Map<String, String> _issueTypeCache = new HashMap<String, String>(); public void logMissingIssues() { if (!hasMissingIssues()) { return; } StringBuilder logMessageBuf = new StringBuilder( "The following IssueTypes are not known to the SonarQube ReSharper plugin.\n" + "Add the following text to the 'ReSharper custom rules' property in the Settings UI to add local " + "support for these rules and submit them to " + issuesLink + " so that they can be included in " + "future releases.\n"); for (String missingIssueType : _missingIssueTypes) { if (!_issueTypeCache.containsKey(missingIssueType)) { logMessageBuf.append(" -IssueType not found- "); } else { String messageText = _issueTypeCache.get(missingIssueType); logMessageBuf.append(messageText + "\n"); } } String logMessage = logMessageBuf.toString(); LOG.warn(logMessage); Rule currentRule = ruleFinder.find( RuleQuery.create().withRepositoryKey(repositoryKey).withConfigKey(missingIssueTypesRuleKey)); if (currentRule != null) { Violation violation = Violation.create(currentRule, project); violation.setMessage(logMessage); context.saveViolation(violation); } else { LOG.warn("Could not find rule for " + missingIssueTypesRuleKey); } } } private void parseIssuesBloc(SMInputCursor cursor, MissingIssueTypeHelper missingTypesHelper) throws XMLStreamException { // Cursor on <Issues> SMInputCursor projectsCursor = cursor.childElementCursor("Project"); while (projectsCursor.getNext() != null) { //compare with vsProject to only run the current project's block String projectName = projectsCursor.getAttrValue("Name"); String thisName = vsProject.getName(); if (projectName.equals(thisName)) { parseProjectBloc(projectsCursor, missingTypesHelper); } else { LOG.debug("Skipping project block due to name mismatch. Currently analyzing '" + thisName + "', processing '" + projectName + "'"); } } } private void parseProjectBloc(SMInputCursor projectCursor, MissingIssueTypeHelper missingTypesHelper) throws XMLStreamException { // Cursor in on <Project> SMInputCursor issuesCursor = projectCursor.childElementCursor("Issue"); while (issuesCursor.getNext() != null) { String typeId = issuesCursor.getAttrValue("TypeId"); String configRuleKey = "ReSharperInspectCode#" + typeId; LOG.debug("Searching for rule '" + configRuleKey + "' in repository '" + repositoryKey + "'"); Rule currentRule = ruleFinder .find(RuleQuery.create().withRepositoryKey(repositoryKey).withConfigKey(configRuleKey)); if (currentRule != null) { LOG.debug("Rule found: " + configRuleKey); createViolation(issuesCursor, currentRule); } else { LOG.warn("Could not find the following rule in the ReSharper rule repository: " + configRuleKey); missingTypesHelper.addMissingIssueType(typeId); } } } private void createViolation(SMInputCursor violationsCursor, Rule currentRule) throws XMLStreamException { String relativeFilePath = violationsCursor.getAttrValue("File"); //Paths in the resharper results file are relative to the Solution file LOG.debug("createViolation for relativePath: " + relativeFilePath); File sourceFile = new File(vsSolution.getSolutionDir(), relativeFilePath); final org.sonar.api.resources.File sonarFile = org.sonar.api.resources.File.fromIOFile(sourceFile, project); try { LOG.debug("searching for sourceFile " + sourceFile.getCanonicalFile().getPath() + " - Exists: " + sourceFile.exists()); } catch (Exception ex) { LOG.warn("Exception: " + ex.getMessage()); } if (context.isExcluded(sonarFile)) { LOG.debug("File is marked as excluded, so not reporting violation: {}", sonarFile.getName()); } else if (includeAllFiles || vsProject.contains(sourceFile)) { IssueModel issue = new IssueModel(violationsCursor); greetVisitors(issue); ReSharperViolation violationBuilder = new ReSharperViolation(context, project, vsProject); violationBuilder.createFileOrProjectViolation(violationsCursor, currentRule, sourceFile); } else { LOG.debug("Violation not being saved for unsupported file {}", sourceFile.getName()); } } private void checkInVisitors() { for (IssueListener issueListener : observers) { issueListener.parsingStart(configuration); } } private void greetVisitors(IssueModel issue) { for (IssueListener issueListener : observers) { issueListener.parsedIssue(issue); } } private void checkOutVisitors() { for (IssueListener issueListener : observers) { issueListener.parsingComplete(); } } }