]> source.dussan.org Git - sonarqube.git/blob
f2b856547a0d2cf5f53e4a48eda9ad5509bac6e6
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2023 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
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.
19  */
20 package org.sonar.ce.task.projectanalysis.step;
21
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.List;
25 import java.util.function.Function;
26 import java.util.stream.Collectors;
27 import javax.annotation.Nonnull;
28 import org.sonar.api.utils.log.Logger;
29 import org.sonar.api.utils.log.Loggers;
30 import org.sonar.ce.task.projectanalysis.analysis.Analysis;
31 import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
32 import org.sonar.ce.task.projectanalysis.batch.BatchReportReader;
33 import org.sonar.ce.task.projectanalysis.component.Component;
34 import org.sonar.ce.task.projectanalysis.component.CrawlerDepthLimit;
35 import org.sonar.ce.task.projectanalysis.component.DepthTraversalTypeAwareCrawler;
36 import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
37 import org.sonar.ce.task.projectanalysis.component.TypeAwareVisitorAdapter;
38 import org.sonar.ce.task.projectanalysis.duplication.CrossProjectDuplicationStatusHolder;
39 import org.sonar.ce.task.projectanalysis.duplication.IntegrateCrossProjectDuplications;
40 import org.sonar.ce.task.step.ComputationStep;
41 import org.sonar.core.util.CloseableIterator;
42 import org.sonar.db.DbClient;
43 import org.sonar.db.DbSession;
44 import org.sonar.db.duplication.DuplicationUnitDto;
45 import org.sonar.duplications.block.Block;
46 import org.sonar.duplications.block.ByteArray;
47 import org.sonar.scanner.protocol.output.ScannerReport.CpdTextBlock;
48
49 /**
50  * Feed the duplications repository from the cross project duplication blocks computed with duplications blocks of the analysis report.
51  * Blocks can be empty if :
52  * - The file is excluded from the analysis using {@link org.sonar.api.CoreProperties#CPD_EXCLUSIONS}
53  * - On Java, if the number of statements of the file is too small, nothing will be sent.
54  */
55 public class LoadCrossProjectDuplicationsRepositoryStep implements ComputationStep {
56
57   private static final Logger LOGGER = Loggers.get(LoadCrossProjectDuplicationsRepositoryStep.class);
58
59   private final TreeRootHolder treeRootHolder;
60   private final BatchReportReader reportReader;
61   private final AnalysisMetadataHolder analysisMetadataHolder;
62   private final IntegrateCrossProjectDuplications integrateCrossProjectDuplications;
63   private final CrossProjectDuplicationStatusHolder crossProjectDuplicationStatusHolder;
64   private final DbClient dbClient;
65
66   public LoadCrossProjectDuplicationsRepositoryStep(TreeRootHolder treeRootHolder, BatchReportReader reportReader,
67     AnalysisMetadataHolder analysisMetadataHolder, CrossProjectDuplicationStatusHolder crossProjectDuplicationStatusHolder,
68     IntegrateCrossProjectDuplications integrateCrossProjectDuplications, DbClient dbClient) {
69     this.treeRootHolder = treeRootHolder;
70     this.reportReader = reportReader;
71     this.analysisMetadataHolder = analysisMetadataHolder;
72     this.integrateCrossProjectDuplications = integrateCrossProjectDuplications;
73     this.crossProjectDuplicationStatusHolder = crossProjectDuplicationStatusHolder;
74     this.dbClient = dbClient;
75   }
76
77   @Override
78   public void execute(ComputationStep.Context context) {
79     if (crossProjectDuplicationStatusHolder.isEnabled()) {
80       new DepthTraversalTypeAwareCrawler(new CrossProjectDuplicationVisitor()).visit(treeRootHolder.getRoot());
81     }
82   }
83
84   @Override
85   public String getDescription() {
86     return "Compute cross project duplications";
87   }
88
89   private class CrossProjectDuplicationVisitor extends TypeAwareVisitorAdapter {
90
91     private CrossProjectDuplicationVisitor() {
92       super(CrawlerDepthLimit.FILE, Order.PRE_ORDER);
93     }
94
95     @Override
96     public void visitFile(Component file) {
97       List<CpdTextBlock> cpdTextBlocks = new ArrayList<>();
98       try (CloseableIterator<CpdTextBlock> blocksIt = reportReader.readCpdTextBlocks(file.getReportAttributes().getRef())) {
99         while(blocksIt.hasNext()) {
100           cpdTextBlocks.add(blocksIt.next());
101         }
102       }
103       LOGGER.trace("Found {} cpd blocks on file {}", cpdTextBlocks.size(), file.getKey());
104       if (cpdTextBlocks.isEmpty()) {
105         return;
106       }
107
108       Collection<String> hashes = cpdTextBlocks.stream().map(CpdTextBlockToHash.INSTANCE).collect(Collectors.toList());
109       List<DuplicationUnitDto> dtos = selectDuplicates(file, hashes);
110       if (dtos.isEmpty()) {
111         return;
112       }
113
114       Collection<Block> duplicatedBlocks = dtos.stream().map(DtoToBlock.INSTANCE).collect(Collectors.toList());
115       Collection<Block> originBlocks = cpdTextBlocks.stream().map(new CpdTextBlockToBlock(file.getKey())).collect(Collectors.toList());
116       LOGGER.trace("Found {} duplicated cpd blocks on file {}", duplicatedBlocks.size(), file.getKey());
117
118       integrateCrossProjectDuplications.computeCpd(file, originBlocks, duplicatedBlocks);
119     }
120
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);
126       }
127     }
128   }
129
130   private enum CpdTextBlockToHash implements Function<CpdTextBlock, String> {
131     INSTANCE;
132
133     @Override
134     public String apply(@Nonnull CpdTextBlock duplicationBlock) {
135       return duplicationBlock.getHash();
136     }
137   }
138
139   private enum DtoToBlock implements Function<DuplicationUnitDto, Block> {
140     INSTANCE;
141
142     @Override
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())
150         .build();
151     }
152   }
153
154   private static class CpdTextBlockToBlock implements Function<CpdTextBlock, Block> {
155     private final String fileKey;
156     private int indexInFile = 0;
157
158     CpdTextBlockToBlock(String fileKey) {
159       this.fileKey = fileKey;
160     }
161
162     @Override
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())
170         .build();
171       indexInFile++;
172       return block;
173     }
174   }
175
176 }