]> source.dussan.org Git - sonarqube.git/blob
25ad3067ed799579ded7c1969251e6d0dca9bcf4
[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.duplication;
21
22 import java.util.Collection;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.function.Function;
27 import java.util.function.Predicate;
28 import javax.annotation.Nonnull;
29 import org.sonar.api.CoreProperties;
30 import org.sonar.api.config.Configuration;
31 import org.sonar.api.utils.System2;
32 import org.sonar.api.utils.log.Logger;
33 import org.sonar.api.utils.log.Loggers;
34 import org.sonar.ce.task.log.CeTaskMessages;
35 import org.sonar.ce.task.projectanalysis.component.Component;
36 import org.sonar.duplications.block.Block;
37 import org.sonar.duplications.detector.suffixtree.SuffixTreeCloneDetectionAlgorithm;
38 import org.sonar.duplications.index.CloneGroup;
39 import org.sonar.duplications.index.CloneIndex;
40 import org.sonar.duplications.index.ClonePart;
41 import org.sonar.duplications.index.PackedMemoryCloneIndex;
42
43 /**
44  * Transform a list of duplication blocks into clone groups, then add these clone groups into the duplication repository.
45  */
46 public class IntegrateCrossProjectDuplications {
47
48   private static final Logger LOGGER = Loggers.get(IntegrateCrossProjectDuplications.class);
49   private static final String JAVA_KEY = "java";
50   private static final String DEPRECATED_WARNING = "This analysis uses the deprecated cross-project duplication feature.";
51   private static final String DEPRECATED_WARNING_DASHBOARD = "This project uses the deprecated cross-project duplication feature.";
52
53   private static final int MAX_CLONE_GROUP_PER_FILE = 100;
54   private static final int MAX_CLONE_PART_PER_GROUP = 100;
55
56   private final Configuration config;
57   private final DuplicationRepository duplicationRepository;
58
59   private Map<String, NumberOfUnitsNotLessThan> numberOfUnitsByLanguage = new HashMap<>();
60
61   public IntegrateCrossProjectDuplications(Configuration config, DuplicationRepository duplicationRepository, CeTaskMessages ceTaskMessages, System2 system) {
62     this.config = config;
63     this.duplicationRepository = duplicationRepository;
64     if (config.getBoolean(CoreProperties.CPD_CROSS_PROJECT).orElse(false)) {
65       LOGGER.warn(DEPRECATED_WARNING);
66       ceTaskMessages.add(new CeTaskMessages.Message(DEPRECATED_WARNING_DASHBOARD, system.now()));
67     }
68   }
69
70   public void computeCpd(Component component, Collection<Block> originBlocks, Collection<Block> duplicationBlocks) {
71     CloneIndex duplicationIndex = new PackedMemoryCloneIndex();
72     populateIndex(duplicationIndex, originBlocks);
73     populateIndex(duplicationIndex, duplicationBlocks);
74
75     List<CloneGroup> duplications = SuffixTreeCloneDetectionAlgorithm.detect(duplicationIndex, originBlocks);
76     Iterable<CloneGroup> filtered = duplications.stream()
77       .filter(getNumberOfUnitsNotLessThan(component.getFileAttributes().getLanguageKey()))
78       .toList();
79     addDuplications(component, filtered);
80   }
81
82   private static void populateIndex(CloneIndex duplicationIndex, Collection<Block> duplicationBlocks) {
83     for (Block block : duplicationBlocks) {
84       duplicationIndex.insert(block);
85     }
86   }
87
88   private void addDuplications(Component file, Iterable<CloneGroup> duplications) {
89     int cloneGroupCount = 0;
90     for (CloneGroup duplication : duplications) {
91       cloneGroupCount++;
92       if (cloneGroupCount > MAX_CLONE_GROUP_PER_FILE) {
93         LOGGER.warn("Too many duplication groups on file {}. Keeping only the first {} groups.", file.getKey(), MAX_CLONE_GROUP_PER_FILE);
94         break;
95       }
96       addDuplication(file, duplication);
97     }
98   }
99
100   private void addDuplication(Component file, CloneGroup duplication) {
101     ClonePart originPart = duplication.getOriginPart();
102     List<Duplicate> duplicates = convertClonePartsToDuplicates(file, duplication);
103     if (!duplicates.isEmpty()) {
104       duplicationRepository.add(
105         file,
106         new Duplication(new TextBlock(originPart.getStartLine(), originPart.getEndLine()), duplicates));
107     }
108   }
109
110   private static List<Duplicate> convertClonePartsToDuplicates(final Component file, CloneGroup duplication) {
111     final ClonePart originPart = duplication.getOriginPart();
112     return duplication.getCloneParts().stream()
113       .filter(new DoesNotMatchSameComponentKey(originPart.getResourceId()))
114       .filter(new DuplicateLimiter(file, originPart))
115       .map(ClonePartToCrossProjectDuplicate.INSTANCE)
116       .toList();
117   }
118
119   private NumberOfUnitsNotLessThan getNumberOfUnitsNotLessThan(String language) {
120     NumberOfUnitsNotLessThan numberOfUnitsNotLessThan = numberOfUnitsByLanguage.get(language);
121     if (numberOfUnitsNotLessThan == null) {
122       numberOfUnitsNotLessThan = new NumberOfUnitsNotLessThan(getMinimumTokens(language));
123       numberOfUnitsByLanguage.put(language, numberOfUnitsNotLessThan);
124     }
125     return numberOfUnitsNotLessThan;
126   }
127
128   private int getMinimumTokens(String languageKey) {
129     // The java language is an exception : it doesn't compute tokens but statement, so the settings could not be used.
130     if (languageKey.equalsIgnoreCase(JAVA_KEY)) {
131       return 0;
132     }
133     return config.getInt("sonar.cpd." + languageKey + ".minimumTokens").orElse(100);
134   }
135
136   private static class NumberOfUnitsNotLessThan implements Predicate<CloneGroup> {
137     private final int min;
138
139     NumberOfUnitsNotLessThan(int min) {
140       this.min = min;
141     }
142
143     @Override
144     public boolean test(@Nonnull CloneGroup input) {
145       return input.getLengthInUnits() >= min;
146     }
147   }
148
149   private static class DoesNotMatchSameComponentKey implements Predicate<ClonePart> {
150     private final String componentKey;
151
152     private DoesNotMatchSameComponentKey(String componentKey) {
153       this.componentKey = componentKey;
154     }
155
156     @Override
157     public boolean test(@Nonnull ClonePart part) {
158       return !part.getResourceId().equals(componentKey);
159     }
160   }
161
162   private static class DuplicateLimiter implements Predicate<ClonePart> {
163     private final Component file;
164     private final ClonePart originPart;
165     private int counter = 0;
166
167     DuplicateLimiter(Component file, ClonePart originPart) {
168       this.file = file;
169       this.originPart = originPart;
170     }
171
172     @Override
173     public boolean test(@Nonnull ClonePart input) {
174       if (counter == MAX_CLONE_PART_PER_GROUP) {
175         LOGGER.warn("Too many duplication references on file {} for block at line {}. Keeping only the first {} references.",
176           file.getKey(), originPart.getStartLine(), MAX_CLONE_PART_PER_GROUP);
177       }
178       boolean res = counter < MAX_CLONE_GROUP_PER_FILE;
179       counter++;
180       return res;
181     }
182   }
183
184   private enum ClonePartToCrossProjectDuplicate implements Function<ClonePart, Duplicate> {
185     INSTANCE;
186
187     @Override
188     @Nonnull
189     public Duplicate apply(@Nonnull ClonePart input) {
190       return new CrossProjectDuplicate(
191         input.getResourceId(),
192         new TextBlock(input.getStartLine(), input.getEndLine()));
193     }
194   }
195 }