/*
* 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 com.google.common.base.Function;
import java.util.Collection;
import java.util.List;
import javax.annotation.Nonnull;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.duplication.DuplicationUnitDto;
import org.sonar.duplications.block.Block;
import org.sonar.duplications.block.ByteArray;
import org.sonar.scanner.protocol.output.ScannerReport.CpdTextBlock;
import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.server.computation.task.projectanalysis.batch.BatchReportReader;
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.CrossProjectDuplicationStatusHolder;
import org.sonar.server.computation.task.projectanalysis.duplication.IntegrateCrossProjectDuplications;
import org.sonar.server.computation.task.projectanalysis.analysis.Analysis;
import org.sonar.server.computation.task.step.ComputationStep;
import static com.google.common.collect.FluentIterable.from;
import static com.google.common.collect.Lists.newArrayList;
import static org.sonar.server.computation.task.projectanalysis.component.ComponentVisitor.Order.PRE_ORDER;
/**
* Feed the duplications repository from the cross project duplication blocks computed with duplications blocks of the analysis report.
*
* Blocks can be empty if :
* - The file is excluded from the analysis using {@link org.sonar.api.CoreProperties#CPD_EXCLUSIONS}
* - On Java, if the number of statements of the file is too small, nothing will be sent.
*/
public class LoadCrossProjectDuplicationsRepositoryStep implements ComputationStep {
private static final Logger LOGGER = Loggers.get(LoadCrossProjectDuplicationsRepositoryStep.class);
private final TreeRootHolder treeRootHolder;
private final BatchReportReader reportReader;
private final AnalysisMetadataHolder analysisMetadataHolder;
private final IntegrateCrossProjectDuplications integrateCrossProjectDuplications;
private final CrossProjectDuplicationStatusHolder crossProjectDuplicationStatusHolder;
private final DbClient dbClient;
public LoadCrossProjectDuplicationsRepositoryStep(TreeRootHolder treeRootHolder, BatchReportReader reportReader,
AnalysisMetadataHolder analysisMetadataHolder, CrossProjectDuplicationStatusHolder crossProjectDuplicationStatusHolder,
IntegrateCrossProjectDuplications integrateCrossProjectDuplications, DbClient dbClient) {
this.treeRootHolder = treeRootHolder;
this.reportReader = reportReader;
this.analysisMetadataHolder = analysisMetadataHolder;
this.integrateCrossProjectDuplications = integrateCrossProjectDuplications;
this.crossProjectDuplicationStatusHolder = crossProjectDuplicationStatusHolder;
this.dbClient = dbClient;
}
@Override
public void execute() {
if (crossProjectDuplicationStatusHolder.isEnabled()) {
new DepthTraversalTypeAwareCrawler(new CrossProjectDuplicationVisitor()).visit(treeRootHolder.getRoot());
}
}
@Override
public String getDescription() {
return "Compute cross project duplications";
}
private class CrossProjectDuplicationVisitor extends TypeAwareVisitorAdapter {
private CrossProjectDuplicationVisitor() {
super(CrawlerDepthLimit.FILE, PRE_ORDER);
}
@Override
public void visitFile(Component file) {
List<CpdTextBlock> cpdTextBlocks = newArrayList(reportReader.readCpdTextBlocks(file.getReportAttributes().getRef()));
LOGGER.trace("Found {} cpd blocks on file {}", cpdTextBlocks.size(), file.getKey());
if (cpdTextBlocks.isEmpty()) {
return;
}
Collection<String> hashes = from(cpdTextBlocks).transform(CpdTextBlockToHash.INSTANCE).toList();
List<DuplicationUnitDto> dtos = selectDuplicates(file, hashes);
if (dtos.isEmpty()) {
return;
}
Collection<Block> duplicatedBlocks = from(dtos).transform(DtoToBlock.INSTANCE).toList();
Collection<Block> originBlocks = from(cpdTextBlocks).transform(new CpdTextBlockToBlock(file.getKey())).toList();
LOGGER.trace("Found {} duplicated cpd blocks on file {}", duplicatedBlocks.size(), file.getKey());
integrateCrossProjectDuplications.computeCpd(file, originBlocks, duplicatedBlocks);
}
private List<DuplicationUnitDto> selectDuplicates(Component file, Collection<String> hashes) {
try (DbSession dbSession = dbClient.openSession(false)) {
Analysis projectAnalysis = analysisMetadataHolder.getBaseAnalysis();
String analysisUuid = projectAnalysis == null ? null : projectAnalysis.getUuid();
return dbClient.duplicationDao().selectCandidates(dbSession, analysisUuid, file.getFileAttributes().getLanguageKey(), hashes);
}
}
}
private enum CpdTextBlockToHash implements Function<CpdTextBlock, String> {
INSTANCE;
@Override
public String apply(@Nonnull CpdTextBlock duplicationBlock) {
return duplicationBlock.getHash();
}
}
private enum DtoToBlock implements Function<DuplicationUnitDto, Block> {
INSTANCE;
@Override
public Block apply(@Nonnull DuplicationUnitDto dto) {
// Note that the dto doesn't contains start/end token indexes
return Block.builder()
.setResourceId(dto.getComponentKey())
.setBlockHash(new ByteArray(dto.getHash()))
.setIndexInFile(dto.getIndexInFile())
.setLines(dto.getStartLine(), dto.getEndLine())
.build();
}
}
private static class CpdTextBlockToBlock implements Function<CpdTextBlock, Block> {
private final String fileKey;
private int indexInFile = 0;
public CpdTextBlockToBlock(String fileKey) {
this.fileKey = fileKey;
}
@Override
public Block apply(@Nonnull CpdTextBlock duplicationBlock) {
Block block = Block.builder()
.setResourceId(fileKey)
.setBlockHash(new ByteArray(duplicationBlock.getHash()))
.setIndexInFile(indexInFile)
.setLines(duplicationBlock.getStartLine(), duplicationBlock.getEndLine())
.setUnit(duplicationBlock.getStartTokenIndex(), duplicationBlock.getEndTokenIndex())
.build();
indexInFile++;
return block;
}
}
}