3 * Copyright (C) 2009-2019 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.ce.task.projectanalysis.step;
22 import com.google.common.base.Function;
23 import java.util.Collection;
24 import java.util.List;
25 import javax.annotation.Nonnull;
26 import org.sonar.api.utils.log.Logger;
27 import org.sonar.api.utils.log.Loggers;
28 import org.sonar.ce.task.projectanalysis.analysis.Analysis;
29 import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
30 import org.sonar.ce.task.projectanalysis.batch.BatchReportReader;
31 import org.sonar.ce.task.projectanalysis.component.Component;
32 import org.sonar.ce.task.projectanalysis.component.CrawlerDepthLimit;
33 import org.sonar.ce.task.projectanalysis.component.DepthTraversalTypeAwareCrawler;
34 import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
35 import org.sonar.ce.task.projectanalysis.component.TypeAwareVisitorAdapter;
36 import org.sonar.ce.task.projectanalysis.duplication.CrossProjectDuplicationStatusHolder;
37 import org.sonar.ce.task.projectanalysis.duplication.IntegrateCrossProjectDuplications;
38 import org.sonar.ce.task.step.ComputationStep;
39 import org.sonar.core.util.CloseableIterator;
40 import org.sonar.db.DbClient;
41 import org.sonar.db.DbSession;
42 import org.sonar.db.duplication.DuplicationUnitDto;
43 import org.sonar.duplications.block.Block;
44 import org.sonar.duplications.block.ByteArray;
45 import org.sonar.scanner.protocol.output.ScannerReport.CpdTextBlock;
47 import static com.google.common.collect.FluentIterable.from;
48 import static com.google.common.collect.Lists.newArrayList;
51 * Feed the duplications repository from the cross project duplication blocks computed with duplications blocks of the analysis report.
53 * Blocks can be empty if :
54 * - The file is excluded from the analysis using {@link org.sonar.api.CoreProperties#CPD_EXCLUSIONS}
55 * - On Java, if the number of statements of the file is too small, nothing will be sent.
57 public class LoadCrossProjectDuplicationsRepositoryStep implements ComputationStep {
59 private static final Logger LOGGER = Loggers.get(LoadCrossProjectDuplicationsRepositoryStep.class);
61 private final TreeRootHolder treeRootHolder;
62 private final BatchReportReader reportReader;
63 private final AnalysisMetadataHolder analysisMetadataHolder;
64 private final IntegrateCrossProjectDuplications integrateCrossProjectDuplications;
65 private final CrossProjectDuplicationStatusHolder crossProjectDuplicationStatusHolder;
66 private final DbClient dbClient;
68 public LoadCrossProjectDuplicationsRepositoryStep(TreeRootHolder treeRootHolder, BatchReportReader reportReader,
69 AnalysisMetadataHolder analysisMetadataHolder, CrossProjectDuplicationStatusHolder crossProjectDuplicationStatusHolder,
70 IntegrateCrossProjectDuplications integrateCrossProjectDuplications, DbClient dbClient) {
71 this.treeRootHolder = treeRootHolder;
72 this.reportReader = reportReader;
73 this.analysisMetadataHolder = analysisMetadataHolder;
74 this.integrateCrossProjectDuplications = integrateCrossProjectDuplications;
75 this.crossProjectDuplicationStatusHolder = crossProjectDuplicationStatusHolder;
76 this.dbClient = dbClient;
80 public void execute(ComputationStep.Context context) {
81 if (crossProjectDuplicationStatusHolder.isEnabled()) {
82 new DepthTraversalTypeAwareCrawler(new CrossProjectDuplicationVisitor()).visit(treeRootHolder.getRoot());
87 public String getDescription() {
88 return "Compute cross project duplications";
91 private class CrossProjectDuplicationVisitor extends TypeAwareVisitorAdapter {
93 private CrossProjectDuplicationVisitor() {
94 super(CrawlerDepthLimit.FILE, Order.PRE_ORDER);
98 public void visitFile(Component file) {
99 List<CpdTextBlock> cpdTextBlocks;
100 try (CloseableIterator<CpdTextBlock> blocksIt = reportReader.readCpdTextBlocks(file.getReportAttributes().getRef())) {
101 cpdTextBlocks = newArrayList(blocksIt);
102 LOGGER.trace("Found {} cpd blocks on file {}", cpdTextBlocks.size(), file.getDbKey());
103 if (cpdTextBlocks.isEmpty()) {
108 Collection<String> hashes = from(cpdTextBlocks).transform(CpdTextBlockToHash.INSTANCE).toList();
109 List<DuplicationUnitDto> dtos = selectDuplicates(file, hashes);
110 if (dtos.isEmpty()) {
114 Collection<Block> duplicatedBlocks = from(dtos).transform(DtoToBlock.INSTANCE).toList();
115 Collection<Block> originBlocks = from(cpdTextBlocks).transform(new CpdTextBlockToBlock(file.getDbKey())).toList();
116 LOGGER.trace("Found {} duplicated cpd blocks on file {}", duplicatedBlocks.size(), file.getDbKey());
118 integrateCrossProjectDuplications.computeCpd(file, originBlocks, duplicatedBlocks);
121 private List<DuplicationUnitDto> selectDuplicates(Component file, Collection<String> hashes) {
122 try (DbSession dbSession = dbClient.openSession(false)) {
123 Analysis projectAnalysis = analysisMetadataHolder.getBaseAnalysis();
124 String analysisUuid = projectAnalysis == null ? null : projectAnalysis.getUuid();
125 return dbClient.duplicationDao().selectCandidates(dbSession, analysisUuid, file.getFileAttributes().getLanguageKey(), hashes);
130 private enum CpdTextBlockToHash implements Function<CpdTextBlock, String> {
134 public String apply(@Nonnull CpdTextBlock duplicationBlock) {
135 return duplicationBlock.getHash();
139 private enum DtoToBlock implements Function<DuplicationUnitDto, Block> {
143 public Block apply(@Nonnull DuplicationUnitDto dto) {
144 // Note that the dto doesn't contains start/end token indexes
145 return Block.builder()
146 .setResourceId(dto.getComponentKey())
147 .setBlockHash(new ByteArray(dto.getHash()))
148 .setIndexInFile(dto.getIndexInFile())
149 .setLines(dto.getStartLine(), dto.getEndLine())
154 private static class CpdTextBlockToBlock implements Function<CpdTextBlock, Block> {
155 private final String fileKey;
156 private int indexInFile = 0;
158 public CpdTextBlockToBlock(String fileKey) {
159 this.fileKey = fileKey;
163 public Block apply(@Nonnull CpdTextBlock duplicationBlock) {
164 Block block = Block.builder()
165 .setResourceId(fileKey)
166 .setBlockHash(new ByteArray(duplicationBlock.getHash()))
167 .setIndexInFile(indexInFile)
168 .setLines(duplicationBlock.getStartLine(), duplicationBlock.getEndLine())
169 .setUnit(duplicationBlock.getStartTokenIndex(), duplicationBlock.getEndTokenIndex())