/*
* 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.server.computation.task.projectanalysis.step;
import org.apache.commons.lang.StringEscapeUtils;
import org.sonar.server.computation.task.projectanalysis.component.Component;
import org.sonar.server.computation.task.projectanalysis.component.CrawlerDepthLimit;
import org.sonar.server.computation.task.projectanalysis.component.DepthTraversalTypeAwareCrawler;
import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder;
import org.sonar.server.computation.task.projectanalysis.component.TypeAwareVisitorAdapter;
import org.sonar.server.computation.task.projectanalysis.duplication.CrossProjectDuplicate;
import org.sonar.server.computation.task.projectanalysis.duplication.Duplicate;
import org.sonar.server.computation.task.projectanalysis.duplication.Duplication;
import org.sonar.server.computation.task.projectanalysis.duplication.DuplicationRepository;
import org.sonar.server.computation.task.projectanalysis.duplication.InProjectDuplicate;
import org.sonar.server.computation.task.projectanalysis.duplication.InnerDuplicate;
import org.sonar.server.computation.task.projectanalysis.duplication.TextBlock;
import org.sonar.server.computation.task.projectanalysis.measure.Measure;
import org.sonar.server.computation.task.projectanalysis.measure.MeasureRepository;
import org.sonar.server.computation.task.projectanalysis.metric.Metric;
import org.sonar.server.computation.task.projectanalysis.metric.MetricRepository;
import org.sonar.server.computation.task.step.ComputationStep;
import static com.google.common.collect.Iterables.isEmpty;
import static org.sonar.api.measures.CoreMetrics.DUPLICATIONS_DATA_KEY;
import static org.sonar.server.computation.task.projectanalysis.component.ComponentVisitor.Order.PRE_ORDER;
/**
* Compute duplication data measures on files, based on the {@link DuplicationRepository}
*/
public class DuplicationDataMeasuresStep implements ComputationStep {
private final MeasureRepository measureRepository;
private final TreeRootHolder treeRootHolder;
private final DuplicationRepository duplicationRepository;
private final Metric duplicationDataMetric;
public DuplicationDataMeasuresStep(TreeRootHolder treeRootHolder, MetricRepository metricRepository, MeasureRepository measureRepository,
DuplicationRepository duplicationRepository) {
this.measureRepository = measureRepository;
this.treeRootHolder = treeRootHolder;
this.duplicationRepository = duplicationRepository;
this.duplicationDataMetric = metricRepository.getByKey(DUPLICATIONS_DATA_KEY);
}
@Override
public void execute() {
new DepthTraversalTypeAwareCrawler(new DuplicationVisitor())
.visit(treeRootHolder.getRoot());
}
private class DuplicationVisitor extends TypeAwareVisitorAdapter {
private DuplicationVisitor() {
super(CrawlerDepthLimit.FILE, PRE_ORDER);
}
@Override
public void visitFile(Component file) {
Iterable<Duplication> duplications = duplicationRepository.getDuplications(file);
if (!isEmpty(duplications)) {
computeDuplications(file, duplications);
}
}
private void computeDuplications(Component component, Iterable<Duplication> duplications) {
String duplicationXml = createXmlDuplications(component.getKey(), duplications);
measureRepository.add(
component,
duplicationDataMetric,
Measure.newMeasureBuilder().create(duplicationXml)
);
}
private String createXmlDuplications(String componentKey, Iterable<Duplication> duplications) {
StringBuilder xml = new StringBuilder();
xml.append("<duplications>");
for (Duplication duplication : duplications) {
xml.append("<g>");
appendDuplication(xml, componentKey, duplication.getOriginal());
for (Duplicate duplicate : duplication.getDuplicates()) {
processDuplicationBlock(xml, duplicate, componentKey);
}
xml.append("</g>");
}
xml.append("</duplications>");
return xml.toString();
}
private void processDuplicationBlock(StringBuilder xml, Duplicate duplicate, String componentKey) {
if (duplicate instanceof InnerDuplicate) {
// Duplication is on a the same file
appendDuplication(xml, componentKey, duplicate);
} else if (duplicate instanceof InProjectDuplicate) {
// Duplication is on a different file
appendDuplication(xml, ((InProjectDuplicate) duplicate).getFile().getKey(), duplicate);
} else if (duplicate instanceof CrossProjectDuplicate) {
// componentKey is only set for cross project duplications
String crossProjectComponentKey = ((CrossProjectDuplicate) duplicate).getFileKey();
appendDuplication(xml, crossProjectComponentKey, duplicate);
} else {
throw new IllegalArgumentException("Unsupported type of Duplicate " + duplicate.getClass().getName());
}
}
private void appendDuplication(StringBuilder xml, String componentKey, Duplicate duplicate) {
appendDuplication(xml, componentKey, duplicate.getTextBlock());
}
private void appendDuplication(StringBuilder xml, String componentKey, TextBlock textBlock) {
int length = textBlock.getEnd() - textBlock.getStart() + 1;
xml.append("<b s=\"").append(textBlock.getStart())
.append("\" l=\"").append(length)
.append("\" r=\"").append(StringEscapeUtils.escapeXml(componentKey))
.append("\"/>");
}
}
@Override
public String getDescription() {
return "Compute duplication data measures";
}
}