/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* 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 02110-1301, USA.
*/
package org.sonar.xoo.rule;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Table;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.sonar.api.batch.fs.FilePredicates;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.InputFile.Type;
import org.sonar.api.batch.fs.TextPointer;
import org.sonar.api.batch.sensor.Sensor;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.SensorDescriptor;
import org.sonar.api.batch.sensor.issue.NewIssue;
import org.sonar.api.batch.sensor.issue.NewIssueLocation;
import org.sonar.api.rule.RuleKey;
import org.sonar.xoo.Xoo;
public class MultilineIssuesSensor implements Sensor {
public static final String RULE_KEY = "MultilineIssue";
private static final Pattern START_ISSUE_PATTERN = Pattern.compile("\\{xoo-start-issue:([0-9]+)\\}");
private static final Pattern END_ISSUE_PATTERN = Pattern.compile("\\{xoo-end-issue:([0-9]+)\\}");
private static final Pattern START_FLOW_PATTERN = Pattern.compile("\\{xoo-start-flow:([0-9]+):([0-9]+):([0-9]+)\\}");
private static final Pattern END_FLOW_PATTERN = Pattern.compile("\\{xoo-end-flow:([0-9]+):([0-9]+):([0-9]+)\\}");
@Override
public void describe(SensorDescriptor descriptor) {
descriptor
.name("Multiline Issues")
.onlyOnLanguages(Xoo.KEY)
.createIssuesForRuleRepositories(XooRulesDefinition.XOO_REPOSITORY);
}
@Override
public void execute(SensorContext context) {
FileSystem fs = context.fileSystem();
FilePredicates p = fs.predicates();
for (InputFile file : fs.inputFiles(p.and(p.hasLanguages(Xoo.KEY), p.hasType(Type.MAIN)))) {
createIssues(file, context);
}
}
private static void createIssues(InputFile file, SensorContext context) {
Map<Integer, TextPointer> startIssuesPositions = Maps.newHashMap();
Map<Integer, TextPointer> endIssuesPositions = Maps.newHashMap();
Map<Integer, Table<Integer, Integer, TextPointer>> startFlowsPositions = Maps.newHashMap();
Map<Integer, Table<Integer, Integer, TextPointer>> endFlowsPositions = Maps.newHashMap();
parseIssues(file, context, startIssuesPositions, endIssuesPositions);
parseFlows(file, context, startFlowsPositions, endFlowsPositions);
createIssues(file, context, startIssuesPositions, endIssuesPositions, startFlowsPositions, endFlowsPositions);
}
private static void parseFlows(InputFile file, SensorContext context, Map<Integer, Table<Integer, Integer, TextPointer>> startFlowsPositions,
Map<Integer, Table<Integer, Integer, TextPointer>> endFlowsPositions) {
int currentLine = 0;
try {
for (String lineStr : Files.readAllLines(file.path(), file.charset())) {
currentLine++;
Matcher m = START_FLOW_PATTERN.matcher(lineStr);
while (m.find()) {
Integer issueId = Integer.parseInt(m.group(1));
Integer issueFlowId = Integer.parseInt(m.group(2));
Integer issueFlowNum = Integer.parseInt(m.group(3));
TextPointer newPointer = file.newPointer(currentLine, m.end());
if (!startFlowsPositions.containsKey(issueId)) {
startFlowsPositions.put(issueId, HashBasedTable.<Integer, Integer, TextPointer>create());
}
startFlowsPositions.get(issueId).row(issueFlowId).put(issueFlowNum, newPointer);
}
m = END_FLOW_PATTERN.matcher(lineStr);
while (m.find()) {
Integer issueId = Integer.parseInt(m.group(1));
Integer issueFlowId = Integer.parseInt(m.group(2));
Integer issueFlowNum = Integer.parseInt(m.group(3));
TextPointer newPointer = file.newPointer(currentLine, m.start());
if (!endFlowsPositions.containsKey(issueId)) {
endFlowsPositions.put(issueId, HashBasedTable.<Integer, Integer, TextPointer>create());
}
endFlowsPositions.get(issueId).row(issueFlowId).put(issueFlowNum, newPointer);
}
}
} catch (IOException e) {
throw new IllegalStateException("Unable to read file", e);
}
}
private static void createIssues(InputFile file, SensorContext context, Map<Integer, TextPointer> startPositions,
Map<Integer, TextPointer> endPositions, Map<Integer, Table<Integer, Integer, TextPointer>> startFlowsPositions,
Map<Integer, Table<Integer, Integer, TextPointer>> endFlowsPositions) {
RuleKey ruleKey = RuleKey.of(XooRulesDefinition.XOO_REPOSITORY, RULE_KEY);
for (Map.Entry<Integer, TextPointer> entry : startPositions.entrySet()) {
NewIssue newIssue = context.newIssue().forRule(ruleKey);
Integer issueId = entry.getKey();
NewIssueLocation primaryLocation = newIssue.newLocation()
.on(file)
.at(file.newRange(entry.getValue(), endPositions.get(issueId)));
newIssue.at(primaryLocation.message("Primary location"));
if (startFlowsPositions.containsKey(issueId)) {
Table<Integer, Integer, TextPointer> flows = startFlowsPositions.get(issueId);
for (Map.Entry<Integer, Map<Integer, TextPointer>> flowEntry : flows.rowMap().entrySet()) {
Integer flowId = flowEntry.getKey();
List<NewIssueLocation> flowLocations = Lists.newArrayList();
List<Integer> flowNums = Lists.newArrayList(flowEntry.getValue().keySet());
Collections.sort(flowNums);
for (Integer flowNum : flowNums) {
TextPointer start = flowEntry.getValue().get(flowNum);
TextPointer end = endFlowsPositions.get(issueId).row(flowId).get(flowNum);
NewIssueLocation newLocation = newIssue.newLocation()
.on(file)
.at(file.newRange(start, end))
.message("Flow step #" + flowNum);
flowLocations.add(newLocation);
}
if (flowLocations.size() == 1) {
newIssue.addLocation(flowLocations.get(0));
} else {
newIssue.addFlow(flowLocations);
}
}
}
newIssue.save();
}
}
private static void parseIssues(InputFile file, SensorContext context, Map<Integer, TextPointer> startPositions,
Map<Integer, TextPointer> endPositions) {
int currentLine = 0;
try {
for (String lineStr : Files.readAllLines(file.path(), file.charset())) {
currentLine++;
Matcher m = START_ISSUE_PATTERN.matcher(lineStr);
while (m.find()) {
Integer issueId = Integer.parseInt(m.group(1));
TextPointer newPointer = file.newPointer(currentLine, m.end());
startPositions.put(issueId, newPointer);
}
m = END_ISSUE_PATTERN.matcher(lineStr);
while (m.find()) {
Integer issueId = Integer.parseInt(m.group(1));
TextPointer newPointer = file.newPointer(currentLine, m.start());
endPositions.put(issueId, newPointer);
}
}
} catch (IOException e) {
throw new IllegalStateException("Unable to read file", e);
}
}
}