--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.source;
+
+import org.sonar.batch.protocol.output.BatchReport;
+import org.sonar.server.source.db.FileSourceDb;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Maps.newHashMap;
+
+public class DuplicationLineReader implements LineReader {
+
+ private final List<BatchReport.Duplication> duplications;
+ private final Map<BatchReport.Range, Integer> duplicationIdsByRange;
+
+ public DuplicationLineReader(List<BatchReport.Duplication> duplications) {
+ this.duplications = newArrayList(duplications);
+ // Sort duplication to have deterministic results and avoid false variation that would lead to an unnecessary update of the source files
+ // data
+ Collections.sort(this.duplications, new DuplicationComparator());
+
+ this.duplicationIdsByRange = createDuplicationIdsByRange(this.duplications);
+ }
+
+ @Override
+ public void read(FileSourceDb.Line.Builder lineBuilder) {
+ int line = lineBuilder.getLine();
+ List<BatchReport.Range> blocks = findDuplicationBlockMatchingLine(line);
+ for (BatchReport.Range block : blocks) {
+ lineBuilder.addDuplication(duplicationIdsByRange.get(block));
+ }
+ }
+
+ private List<BatchReport.Range> findDuplicationBlockMatchingLine(int line) {
+ List<BatchReport.Range> blocks = newArrayList();
+ for (BatchReport.Duplication duplication : duplications) {
+ if (matchLine(duplication.getOriginPosition(), line)) {
+ blocks.add(duplication.getOriginPosition());
+ }
+ for (BatchReport.Duplicate duplicate : duplication.getDuplicateList()) {
+ if (isDuplicationOnSameFile(duplicate) && matchLine(duplicate.getRange(), line)) {
+ blocks.add(duplicate.getRange());
+ }
+ }
+ }
+ return blocks;
+ }
+
+ private static boolean isDuplicationOnSameFile(BatchReport.Duplicate duplicate) {
+ return !duplicate.hasOtherFileKey() && !duplicate.hasOtherFileRef();
+ }
+
+ private static boolean matchLine(BatchReport.Range range, int line) {
+ return range.getStartLine() <= line && line <= range.getEndLine();
+ }
+
+ private static int length(BatchReport.Range range) {
+ return range.getEndLine() - range.getStartLine() + 1;
+ }
+
+ private Map<BatchReport.Range, Integer> createDuplicationIdsByRange(List<BatchReport.Duplication> duplications) {
+ Map<BatchReport.Range, Integer> map = newHashMap();
+ int blockId = 1;
+ for (BatchReport.Duplication duplication : this.duplications) {
+ map.put(duplication.getOriginPosition(), blockId);
+ blockId++;
+ for (BatchReport.Duplicate duplicate : duplication.getDuplicateList()) {
+ if (isDuplicationOnSameFile(duplicate)) {
+ map.put(duplicate.getRange(), blockId);
+ blockId++;
+ }
+ }
+ }
+ return map;
+ }
+
+ private static class DuplicationComparator implements Comparator<BatchReport.Duplication>, Serializable {
+ @Override
+ public int compare(BatchReport.Duplication d1, BatchReport.Duplication d2) {
+ if (d1.getOriginPosition().getStartLine() == d2.getOriginPosition().getStartLine()) {
+ return Integer.compare(length(d1.getOriginPosition()), length(d2.getOriginPosition()));
+ } else {
+ return Integer.compare(d1.getOriginPosition().getStartLine(), d2.getOriginPosition().getStartLine());
+ }
+ }
+ }
+
+}
import org.sonar.batch.protocol.output.BatchReport;
import org.sonar.server.source.db.FileSourceDb;
+import java.io.Serializable;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
return map;
}
- private static class SymbolsDuplication implements Comparator<BatchReport.Symbols.Symbol> {
+ private static class SymbolsDuplication implements Comparator<BatchReport.Symbols.Symbol>, Serializable {
@Override
public int compare(BatchReport.Symbols.Symbol o1, BatchReport.Symbols.Symbol o2) {
if (o1.getDeclaration().getStartLine() == o2.getDeclaration().getStartLine()) {
BatchReport.Scm scmReport = reportReader.readComponentScm(componentRef);
File highlightingFile = reportReader.readComponentSyntaxHighlighting(componentRef);
List<BatchReport.Symbols.Symbol> symbols = reportReader.readComponentSymbols(componentRef);
+ List<BatchReport.Duplication> duplications = reportReader.readComponentDuplications(componentRef);
if (coverageFile != null) {
ReportIterator<BatchReport.Coverage> coverageReportIterator = new ReportIterator<>(coverageFile, BatchReport.Coverage.PARSER);
reportIterators.add(syntaxHighlightingReportIterator);
lineReaders.add(new HighlightingLineReader(syntaxHighlightingReportIterator));
}
+ if (!duplications.isEmpty()) {
+ lineReaders.add(new DuplicationLineReader(duplications));
+ }
if (!symbols.isEmpty()) {
lineReaders.add(new SymbolsLineReader(newArrayList(symbols)));
}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.source;
+
+import org.junit.Test;
+import org.sonar.batch.protocol.output.BatchReport;
+import org.sonar.server.source.db.FileSourceDb;
+
+import java.util.Collections;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DuplicationLineReaderTest {
+
+ FileSourceDb.Data.Builder sourceData = FileSourceDb.Data.newBuilder();
+ FileSourceDb.Line.Builder line1 = sourceData.addLinesBuilder().setSource("line1").setLine(1);
+ FileSourceDb.Line.Builder line2 = sourceData.addLinesBuilder().setSource("line2").setLine(2);
+ FileSourceDb.Line.Builder line3 = sourceData.addLinesBuilder().setSource("line3").setLine(3);
+ FileSourceDb.Line.Builder line4 = sourceData.addLinesBuilder().setSource("line4").setLine(4);
+
+ @Test
+ public void read_nothing() throws Exception {
+ DuplicationLineReader reader = new DuplicationLineReader(Collections.<BatchReport.Duplication>emptyList());
+
+ reader.read(line1);
+
+ assertThat(line1.getDuplicationList()).isEmpty();
+ }
+
+ @Test
+ public void read_duplication_with_duplicates_on_same_file() throws Exception {
+ DuplicationLineReader reader = new DuplicationLineReader(newArrayList(
+ BatchReport.Duplication.newBuilder()
+ .setOriginPosition(BatchReport.Range.newBuilder()
+ .setStartLine(1)
+ .setEndLine(2)
+ .build())
+ .addDuplicate(BatchReport.Duplicate.newBuilder()
+ .setRange(BatchReport.Range.newBuilder()
+ .setStartLine(3)
+ .setEndLine(4)
+ .build())
+ .build())
+ .build()
+ ));
+
+ reader.read(line1);
+ reader.read(line2);
+ reader.read(line3);
+ reader.read(line4);
+
+ assertThat(line1.getDuplicationList()).containsExactly(1);
+ assertThat(line2.getDuplicationList()).containsExactly(1);
+ assertThat(line3.getDuplicationList()).containsExactly(2);
+ assertThat(line4.getDuplicationList()).containsExactly(2);
+ }
+
+ @Test
+ public void read_duplication_with_duplicates_on_other_file() throws Exception {
+ DuplicationLineReader reader = new DuplicationLineReader(newArrayList(
+ BatchReport.Duplication.newBuilder()
+ .setOriginPosition(BatchReport.Range.newBuilder()
+ .setStartLine(1)
+ .setEndLine(2)
+ .build())
+ .addDuplicate(BatchReport.Duplicate.newBuilder()
+ .setOtherFileRef(2)
+ .setRange(BatchReport.Range.newBuilder()
+ .setStartLine(3)
+ .setEndLine(4)
+ .build())
+ .build())
+ .build()
+ ));
+
+ reader.read(line1);
+ reader.read(line2);
+ reader.read(line3);
+ reader.read(line4);
+
+ assertThat(line1.getDuplicationList()).containsExactly(1);
+ assertThat(line2.getDuplicationList()).containsExactly(1);
+ assertThat(line3.getDuplicationList()).isEmpty();
+ assertThat(line4.getDuplicationList()).isEmpty();
+ }
+
+ @Test
+ public void read_duplication_with_duplicates_on_other_file_from_other_project() throws Exception {
+ DuplicationLineReader reader = new DuplicationLineReader(newArrayList(
+ BatchReport.Duplication.newBuilder()
+ .setOriginPosition(BatchReport.Range.newBuilder()
+ .setStartLine(1)
+ .setEndLine(2)
+ .build())
+ .addDuplicate(BatchReport.Duplicate.newBuilder()
+ .setOtherFileKey("other-component-key-from-another-project")
+ .setRange(BatchReport.Range.newBuilder()
+ .setStartLine(3)
+ .setEndLine(4)
+ .build())
+ .build())
+ .build()
+ ));
+
+ reader.read(line1);
+ reader.read(line2);
+ reader.read(line3);
+ reader.read(line4);
+
+ assertThat(line1.getDuplicationList()).containsExactly(1);
+ assertThat(line2.getDuplicationList()).containsExactly(1);
+ assertThat(line3.getDuplicationList()).isEmpty();
+ assertThat(line4.getDuplicationList()).isEmpty();
+ }
+
+ @Test
+ public void read_many_duplications() throws Exception {
+ DuplicationLineReader reader = new DuplicationLineReader(newArrayList(
+ BatchReport.Duplication.newBuilder()
+ .setOriginPosition(BatchReport.Range.newBuilder()
+ .setStartLine(1)
+ .setEndLine(1)
+ .build())
+ .addDuplicate(BatchReport.Duplicate.newBuilder()
+ .setRange(BatchReport.Range.newBuilder()
+ .setStartLine(2)
+ .setEndLine(2)
+ .build())
+ .build())
+ .build(),
+ BatchReport.Duplication.newBuilder()
+ .setOriginPosition(BatchReport.Range.newBuilder()
+ .setStartLine(1)
+ .setEndLine(2)
+ .build())
+ .addDuplicate(BatchReport.Duplicate.newBuilder()
+ .setRange(BatchReport.Range.newBuilder()
+ .setStartLine(3)
+ .setEndLine(4)
+ .build())
+ .build())
+ .build()
+ ));
+
+ reader.read(line1);
+ reader.read(line2);
+ reader.read(line3);
+ reader.read(line4);
+
+ assertThat(line1.getDuplicationList()).containsExactly(1, 3);
+ assertThat(line2.getDuplicationList()).containsExactly(2, 3);
+ assertThat(line3.getDuplicationList()).containsExactly(4);
+ assertThat(line4.getDuplicationList()).containsExactly(4);
+ }
+
+ @Test
+ public void should_be_sorted_by_line_block() throws Exception {
+ DuplicationLineReader reader = new DuplicationLineReader(newArrayList(
+ BatchReport.Duplication.newBuilder()
+ .setOriginPosition(BatchReport.Range.newBuilder()
+ .setStartLine(2)
+ .setEndLine(2)
+ .build())
+ .addDuplicate(BatchReport.Duplicate.newBuilder()
+ .setRange(BatchReport.Range.newBuilder()
+ .setStartLine(4)
+ .setEndLine(4)
+ .build())
+ .build())
+ .build(),
+ BatchReport.Duplication.newBuilder()
+ .setOriginPosition(BatchReport.Range.newBuilder()
+ .setStartLine(1)
+ .setEndLine(1)
+ .build())
+ .addDuplicate(BatchReport.Duplicate.newBuilder()
+ .setRange(BatchReport.Range.newBuilder()
+ .setStartLine(3)
+ .setEndLine(3)
+ .build())
+ .build())
+ .build()
+ ));
+
+ reader.read(line1);
+ reader.read(line2);
+ reader.read(line3);
+ reader.read(line4);
+
+ assertThat(line1.getDuplicationList()).containsExactly(1);
+ assertThat(line2.getDuplicationList()).containsExactly(3);
+ assertThat(line3.getDuplicationList()).containsExactly(2);
+ assertThat(line4.getDuplicationList()).containsExactly(4);
+ }
+
+ @Test
+ public void should_be_sorted_by_line_length() throws Exception {
+ DuplicationLineReader reader = new DuplicationLineReader(newArrayList(
+ BatchReport.Duplication.newBuilder()
+ .setOriginPosition(BatchReport.Range.newBuilder()
+ .setStartLine(1)
+ .setEndLine(2)
+ .build())
+ .addDuplicate(BatchReport.Duplicate.newBuilder()
+ .setRange(BatchReport.Range.newBuilder()
+ .setStartLine(3)
+ .setEndLine(4)
+ .build())
+ .build())
+ .build(),
+ BatchReport.Duplication.newBuilder()
+ .setOriginPosition(BatchReport.Range.newBuilder()
+ .setStartLine(1)
+ .setEndLine(1)
+ .build())
+ .addDuplicate(BatchReport.Duplicate.newBuilder()
+ .setRange(BatchReport.Range.newBuilder()
+ .setStartLine(4)
+ .setEndLine(4)
+ .build())
+ .build())
+ .build()
+ ));
+
+ reader.read(line1);
+ reader.read(line2);
+ reader.read(line3);
+ reader.read(line4);
+
+ assertThat(line1.getDuplicationList()).containsExactly(1, 3);
+ assertThat(line2.getDuplicationList()).containsExactly(3);
+ assertThat(line3.getDuplicationList()).containsExactly(4);
+ assertThat(line4.getDuplicationList()).containsExactly(2, 4);
+ }
+
+}
.setRef(FILE_REF)
.setType(Constants.ComponentType.FILE)
.setUuid(FILE_UUID)
- // Lines is set to 3 but only 2 lines are read from the file -> the last lines should be added
+ // Lines is set to 3 but only 2 lines are read from the file -> the last lines should be added
.setLines(3)
.build());
BatchReportWriter writer = initBasicReport(1);
writer.writeComponentSyntaxHighlighting(FILE_REF, newArrayList(BatchReport.SyntaxHighlighting.newBuilder()
- .setRange(BatchReport.Range.newBuilder()
- .setStartLine(1).setEndLine(1)
- .setStartOffset(2).setEndOffset(4)
- .build())
- .setType(Constants.HighlightingType.ANNOTATION)
- .build()
- ));
+ .setRange(BatchReport.Range.newBuilder()
+ .setStartLine(1).setEndLine(1)
+ .setStartOffset(2).setEndOffset(4)
+ .build())
+ .setType(Constants.HighlightingType.ANNOTATION)
+ .build()
+ ));
sut.execute(new ComputationContext(new BatchReportReader(reportDir), ComponentTesting.newProjectDto(PROJECT_UUID)));
.setStartLine(1).setEndLine(1).setStartOffset(2).setEndOffset(4)
.build())
.addReference(BatchReport.Range.newBuilder()
- .setStartLine(3).setEndLine(3).setStartOffset(1).setEndOffset(3)
- .build()
+ .setStartLine(3).setEndLine(3).setStartOffset(1).setEndOffset(3)
+ .build()
).build()
- ));
+ ));
sut.execute(new ComputationContext(new BatchReportReader(reportDir), ComponentTesting.newProjectDto(PROJECT_UUID)));
assertThat(data.getLines(2).getSymbols()).isEqualTo("1,3,1");
}
+ @Test
+ public void persist_duplication() throws Exception {
+ BatchReportWriter writer = initBasicReport(1);
+
+ writer.writeComponentDuplications(FILE_REF, newArrayList(
+ BatchReport.Duplication.newBuilder()
+ .setOriginPosition(BatchReport.Range.newBuilder()
+ .setStartLine(1)
+ .setEndLine(2)
+ .build())
+ .addDuplicate(BatchReport.Duplicate.newBuilder()
+ .setRange(BatchReport.Range.newBuilder()
+ .setStartLine(3)
+ .setEndLine(4)
+ .build())
+ .build())
+ .build()
+ ));
+
+ sut.execute(new ComputationContext(new BatchReportReader(reportDir), ComponentTesting.newProjectDto(PROJECT_UUID)));
+
+ assertThat(dbTester.countRowsOfTable("file_sources")).isEqualTo(1);
+ FileSourceDto fileSourceDto = dbClient.fileSourceDao().select(FILE_UUID);
+ FileSourceDb.Data data = FileSourceDto.decodeData(fileSourceDto.getBinaryData());
+
+ assertThat(data.getLinesList()).hasSize(1);
+
+ assertThat(data.getLines(0).getDuplicationList()).hasSize(1);
+ }
+
@Test
public void not_update_sources_when_nothing_has_changed() throws Exception {
// Existing sources