2 * SonarQube, open source software quality management tool.
3 * Copyright (C) 2008-2014 SonarSource
4 * mailto:contact AT sonarsource DOT com
6 * SonarQube 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 * SonarQube 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.
21 package org.sonar.server.computation.duplication;
23 import com.google.common.base.Predicate;
24 import java.util.Collection;
25 import java.util.HashMap;
26 import java.util.List;
28 import javax.annotation.Nonnull;
29 import org.sonar.api.config.Settings;
30 import org.sonar.api.utils.log.Logger;
31 import org.sonar.api.utils.log.Loggers;
32 import org.sonar.duplications.block.Block;
33 import org.sonar.duplications.detector.suffixtree.SuffixTreeCloneDetectionAlgorithm;
34 import org.sonar.duplications.index.CloneGroup;
35 import org.sonar.duplications.index.CloneIndex;
36 import org.sonar.duplications.index.ClonePart;
37 import org.sonar.duplications.index.PackedMemoryCloneIndex;
38 import org.sonar.server.computation.component.Component;
40 import static com.google.common.collect.FluentIterable.from;
43 * Transform a list of duplication blocks into clone groups, then add these clone groups into the duplication repository.
45 public class IntegrateCrossProjectDuplications {
47 private static final Logger LOGGER = Loggers.get(IntegrateCrossProjectDuplications.class);
49 private static final String JAVA_KEY = "java";
51 private static final int MAX_CLONE_GROUP_PER_FILE = 100;
52 private static final int MAX_CLONE_PART_PER_GROUP = 100;
54 private final Settings settings;
55 private final DuplicationRepository duplicationRepository;
57 private Map<String, NumberOfUnitsNotLessThan> numberOfUnitsByLanguage = new HashMap<>();
59 public IntegrateCrossProjectDuplications(Settings settings, DuplicationRepository duplicationRepository) {
60 this.settings = settings;
61 this.duplicationRepository = duplicationRepository;
64 public void computeCpd(Component component, Collection<Block> originBlocks, Collection<Block> duplicationBlocks) {
65 CloneIndex duplicationIndex = new PackedMemoryCloneIndex();
66 populateIndex(duplicationIndex, originBlocks);
67 populateIndex(duplicationIndex, duplicationBlocks);
69 List<CloneGroup> duplications = SuffixTreeCloneDetectionAlgorithm.detect(duplicationIndex, originBlocks);
70 Iterable<CloneGroup> filtered = from(duplications).filter(getNumberOfUnitsNotLessThan(component.getFileAttributes().getLanguageKey()));
71 addDuplications(component, filtered);
74 private static void populateIndex(CloneIndex duplicationIndex, Collection<Block> duplicationBlocks) {
75 for (Block block : duplicationBlocks) {
76 duplicationIndex.insert(block);
80 private void addDuplications(Component file, Iterable<CloneGroup> duplications) {
81 int cloneGroupCount = 0;
82 for (CloneGroup duplication : duplications) {
84 if (cloneGroupCount > MAX_CLONE_GROUP_PER_FILE) {
85 LOGGER.warn("Too many duplication groups on file {}. Keeping only the first {} groups.", file.getKey(), MAX_CLONE_GROUP_PER_FILE);
88 addDuplication(file, duplication);
92 private void addDuplication(Component file, CloneGroup duplication) {
93 ClonePart originPart = duplication.getOriginPart();
94 TextBlock originTextBlock = new TextBlock(originPart.getStartLine(), originPart.getEndLine());
95 int clonePartCount = 0;
96 for (ClonePart part : from(duplication.getCloneParts())
97 .filter(new DoesNotMatchSameComponentKey(originPart.getResourceId()))) {
99 if (clonePartCount > MAX_CLONE_PART_PER_GROUP) {
100 LOGGER.warn("Too many duplication references on file {} for block at line {}. Keeping only the first {} references.",
101 file.getKey(), originPart.getStartLine(), MAX_CLONE_PART_PER_GROUP);
104 duplicationRepository.addDuplication(file, originTextBlock, part.getResourceId(),
105 new TextBlock(part.getStartLine(), part.getEndLine()));
109 private NumberOfUnitsNotLessThan getNumberOfUnitsNotLessThan(String language) {
110 NumberOfUnitsNotLessThan numberOfUnitsNotLessThan = numberOfUnitsByLanguage.get(language);
111 if (numberOfUnitsNotLessThan == null) {
112 numberOfUnitsNotLessThan = new NumberOfUnitsNotLessThan(getMinimumTokens(language));
113 numberOfUnitsByLanguage.put(language, numberOfUnitsNotLessThan);
115 return numberOfUnitsNotLessThan;
118 private int getMinimumTokens(String languageKey) {
119 // The java language is an exception : it doesn't compute tokens but statement, so the settings could not be used.
120 if (languageKey.equalsIgnoreCase(JAVA_KEY)) {
123 int minimumTokens = settings.getInt("sonar.cpd." + languageKey + ".minimumTokens");
124 if (minimumTokens == 0) {
127 return minimumTokens;
130 private static class NumberOfUnitsNotLessThan implements Predicate<CloneGroup> {
131 private final int min;
133 public NumberOfUnitsNotLessThan(int min) {
138 public boolean apply(@Nonnull CloneGroup input) {
139 return input.getLengthInUnits() >= min;
143 private static class DoesNotMatchSameComponentKey implements Predicate<ClonePart> {
144 private final String componentKey;
146 private DoesNotMatchSameComponentKey(String componentKey) {
147 this.componentKey = componentKey;
151 public boolean apply(@Nonnull ClonePart part) {
152 return !part.getResourceId().equals(componentKey);