String getMergeBranchUuid();
/**
- * Whether the cross-project duplication tracker must be enabled
+ * Whether the cross-project duplication tracker can be enabled
* or not.
*/
boolean supportsCrossProjectCpd();
*/
package org.sonar.ce.task.projectanalysis.duplication;
-import com.google.common.base.Function;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Ordering;
import java.util.Comparator;
+import java.util.List;
import java.util.Objects;
import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.collect.FluentIterable.from;
import static java.util.Objects.requireNonNull;
@Immutable
public final class Duplication {
- private static final Ordering<Duplicate> DUPLICATE_ORDERING = Ordering.from(DuplicateComparatorByType.INSTANCE)
- .compound(Ordering.natural().onResultOf(DuplicateToFileKey.INSTANCE))
- .compound(Ordering.natural().onResultOf(DuplicateToTextBlock.INSTANCE));
+ private static final Comparator<Duplicate> DUPLICATE_COMPARATOR = DuplicateComparatorByType.INSTANCE
+ .thenComparing(DuplicateToFileKey.INSTANCE).thenComparing(DuplicateToTextBlock.INSTANCE);
private final TextBlock original;
private final SortedSet<Duplicate> duplicates;
/**
- * @throws NullPointerException if {@code original} is {@code null} or {@code duplicates} is {@code null} or {@code duplicates} contains {@code null}
+ * @throws NullPointerException if {@code original} is {@code null} or {@code duplicates} is {@code null} or {@code duplicates} contains {@code null}
* @throws IllegalArgumentException if {@code duplicates} is empty
* @throws IllegalArgumentException if {@code duplicates} contains a {@link InnerDuplicate} with {@code original}
*/
- public Duplication(final TextBlock original, final Iterable<Duplicate> duplicates) {
+ public Duplication(TextBlock original, List<Duplicate> duplicates) {
this.original = requireNonNull(original, "original TextBlock can not be null");
- this.duplicates = from(requireNonNull(duplicates, "duplicates can not be null"))
- .filter(FailOnNullDuplicate.INSTANCE)
- .filter(new EnsureInnerDuplicateIsNotOriginalTextBlock(original))
- .toSortedSet(DUPLICATE_ORDERING);
- checkArgument(!this.duplicates.isEmpty(), "duplicates can not be empty");
+ validateDuplicates(original, duplicates);
+ this.duplicates = new TreeSet<>(DUPLICATE_COMPARATOR);
+ this.duplicates.addAll(duplicates);
+ }
+
+ private static void validateDuplicates(TextBlock original, List<Duplicate> duplicates) {
+ requireNonNull(duplicates, "duplicates can not be null");
+ checkArgument(!duplicates.isEmpty(), "duplicates can not be empty");
+
+ for (Duplicate dup : duplicates) {
+ requireNonNull(dup, "duplicates can not contain null");
+ if (dup instanceof InnerDuplicate) {
+ checkArgument(!original.equals(dup.getTextBlock()), "TextBlock of an InnerDuplicate can not be the original TextBlock");
+ }
+ }
}
/**
* The duplicates of the original, sorted by inner duplicates, then project duplicates, then cross-project duplicates.
* For each category of duplicate, they are sorted by:
* <ul>
- * <li>file key (unless it's an InnerDuplicate)</li>
- * <li>then by TextBlocks by start line and in case of same line, by shortest first</li>
+ * <li>file key (unless it's an InnerDuplicate)</li>
+ * <li>then by TextBlocks by start line and in case of same line, by shortest first</li>
* </ul
* <p>The returned set can not be empty and no inner duplicate can contain the original {@link TextBlock}.</p>
*/
'}';
}
- private enum FailOnNullDuplicate implements Predicate<Duplicate> {
- INSTANCE;
-
- @Override
- public boolean apply(@Nullable Duplicate input) {
- requireNonNull(input, "duplicates can not contain null");
- return true;
- }
- }
-
private enum DuplicateComparatorByType implements Comparator<Duplicate> {
INSTANCE;
}
}
- private static class EnsureInnerDuplicateIsNotOriginalTextBlock implements Predicate<Duplicate> {
- private final TextBlock original;
-
- public EnsureInnerDuplicateIsNotOriginalTextBlock(TextBlock original) {
- this.original = original;
- }
-
- @Override
- public boolean apply(@Nullable Duplicate input) {
- if (input instanceof InnerDuplicate) {
- checkArgument(!original.equals(input.getTextBlock()), "TextBlock of an InnerDuplicate can not be the original TextBlock");
- }
- return true;
- }
- }
-
private enum DuplicateToFileKey implements Function<Duplicate, String> {
INSTANCE;
*/
package org.sonar.ce.task.projectanalysis.duplication;
-import com.google.common.base.Function;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Iterables;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import org.sonar.api.CoreProperties;
import org.sonar.api.config.Configuration;
+import org.sonar.api.utils.System2;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
+import org.sonar.ce.task.log.CeTaskMessages;
import org.sonar.ce.task.projectanalysis.component.Component;
import org.sonar.duplications.block.Block;
import org.sonar.duplications.detector.suffixtree.SuffixTreeCloneDetectionAlgorithm;
import org.sonar.duplications.index.ClonePart;
import org.sonar.duplications.index.PackedMemoryCloneIndex;
-import static com.google.common.collect.FluentIterable.from;
-
/**
* Transform a list of duplication blocks into clone groups, then add these clone groups into the duplication repository.
*/
public class IntegrateCrossProjectDuplications {
private static final Logger LOGGER = Loggers.get(IntegrateCrossProjectDuplications.class);
-
private static final String JAVA_KEY = "java";
+ private static final String DEPRECATED_WARNING = "This analysis uses the deprecated cross-project duplication feature.";
+ private static final String DEPRECATED_WARNING_DASHBOARD = "This project uses the deprecated cross-project duplication feature.";
private static final int MAX_CLONE_GROUP_PER_FILE = 100;
private static final int MAX_CLONE_PART_PER_GROUP = 100;
private Map<String, NumberOfUnitsNotLessThan> numberOfUnitsByLanguage = new HashMap<>();
- public IntegrateCrossProjectDuplications(Configuration config, DuplicationRepository duplicationRepository) {
+ public IntegrateCrossProjectDuplications(Configuration config, DuplicationRepository duplicationRepository, CeTaskMessages ceTaskMessages, System2 system) {
this.config = config;
this.duplicationRepository = duplicationRepository;
if (config.getBoolean(CoreProperties.CPD_CROSS_PROJECT).orElse(false)) {
- LOGGER.warn("This analysis uses the deprecated cross-project duplication feature.");
+ LOGGER.warn(DEPRECATED_WARNING);
+ ceTaskMessages.add(new CeTaskMessages.Message(DEPRECATED_WARNING_DASHBOARD, system.now()));
}
}
populateIndex(duplicationIndex, duplicationBlocks);
List<CloneGroup> duplications = SuffixTreeCloneDetectionAlgorithm.detect(duplicationIndex, originBlocks);
- Iterable<CloneGroup> filtered = from(duplications).filter(getNumberOfUnitsNotLessThan(component.getFileAttributes().getLanguageKey()));
+ Iterable<CloneGroup> filtered = duplications.stream()
+ .filter(getNumberOfUnitsNotLessThan(component.getFileAttributes().getLanguageKey()))
+ .collect(Collectors.toList());
addDuplications(component, filtered);
}
private void addDuplication(Component file, CloneGroup duplication) {
ClonePart originPart = duplication.getOriginPart();
- Iterable<Duplicate> duplicates = convertClonePartsToDuplicates(file, duplication);
- if (!Iterables.isEmpty(duplicates)) {
+ List<Duplicate> duplicates = convertClonePartsToDuplicates(file, duplication);
+ if (!duplicates.isEmpty()) {
duplicationRepository.add(
file,
new Duplication(new TextBlock(originPart.getStartLine(), originPart.getEndLine()), duplicates));
}
}
- private static Iterable<Duplicate> convertClonePartsToDuplicates(final Component file, CloneGroup duplication) {
+ private static List<Duplicate> convertClonePartsToDuplicates(final Component file, CloneGroup duplication) {
final ClonePart originPart = duplication.getOriginPart();
- return from(duplication.getCloneParts())
+ return duplication.getCloneParts().stream()
.filter(new DoesNotMatchSameComponentKey(originPart.getResourceId()))
.filter(new DuplicateLimiter(file, originPart))
- .transform(ClonePartToCrossProjectDuplicate.INSTANCE);
+ .map(ClonePartToCrossProjectDuplicate.INSTANCE)
+ .collect(Collectors.toList());
}
private NumberOfUnitsNotLessThan getNumberOfUnitsNotLessThan(String language) {
private static class NumberOfUnitsNotLessThan implements Predicate<CloneGroup> {
private final int min;
- public NumberOfUnitsNotLessThan(int min) {
+ NumberOfUnitsNotLessThan(int min) {
this.min = min;
}
@Override
- public boolean apply(@Nonnull CloneGroup input) {
+ public boolean test(@Nonnull CloneGroup input) {
return input.getLengthInUnits() >= min;
}
}
}
@Override
- public boolean apply(@Nonnull ClonePart part) {
+ public boolean test(@Nonnull ClonePart part) {
return !part.getResourceId().equals(componentKey);
}
}
private final ClonePart originPart;
private int counter = 0;
- public DuplicateLimiter(Component file, ClonePart originPart) {
+ DuplicateLimiter(Component file, ClonePart originPart) {
this.file = file;
this.originPart = originPart;
}
@Override
- public boolean apply(@Nonnull ClonePart input) {
+ public boolean test(@Nonnull ClonePart input) {
if (counter == MAX_CLONE_PART_PER_GROUP) {
LOGGER.warn("Too many duplication references on file {} for block at line {}. Keeping only the first {} references.",
file.getDbKey(), originPart.getStartLine(), MAX_CLONE_PART_PER_GROUP);
}
- boolean res = counter <= MAX_CLONE_GROUP_PER_FILE;
+ boolean res = counter < MAX_CLONE_GROUP_PER_FILE;
counter++;
return res;
}
*/
package org.sonar.ce.task.projectanalysis.step;
-import com.google.common.base.Function;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.duplications.block.ByteArray;
import org.sonar.scanner.protocol.output.ScannerReport.CpdTextBlock;
-import static com.google.common.collect.FluentIterable.from;
-import static com.google.common.collect.Lists.newArrayList;
-
/**
* 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.
@Override
public void visitFile(Component file) {
- List<CpdTextBlock> cpdTextBlocks;
+ List<CpdTextBlock> cpdTextBlocks = new ArrayList<>();
try (CloseableIterator<CpdTextBlock> blocksIt = reportReader.readCpdTextBlocks(file.getReportAttributes().getRef())) {
- cpdTextBlocks = newArrayList(blocksIt);
- LOGGER.trace("Found {} cpd blocks on file {}", cpdTextBlocks.size(), file.getDbKey());
- if (cpdTextBlocks.isEmpty()) {
- return;
+ while(blocksIt.hasNext()) {
+ cpdTextBlocks.add(blocksIt.next());
}
}
+ LOGGER.trace("Found {} cpd blocks on file {}", cpdTextBlocks.size(), file.getDbKey());
+ if (cpdTextBlocks.isEmpty()) {
+ return;
+ }
- Collection<String> hashes = from(cpdTextBlocks).transform(CpdTextBlockToHash.INSTANCE).toList();
+ Collection<String> hashes = cpdTextBlocks.stream().map(CpdTextBlockToHash.INSTANCE).collect(Collectors.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.getDbKey())).toList();
+ Collection<Block> duplicatedBlocks = dtos.stream().map(DtoToBlock.INSTANCE).collect(Collectors.toList());
+ Collection<Block> originBlocks = cpdTextBlocks.stream().map(new CpdTextBlockToBlock(file.getDbKey())).collect(Collectors.toList());
LOGGER.trace("Found {} duplicated cpd blocks on file {}", duplicatedBlocks.size(), file.getDbKey());
integrateCrossProjectDuplications.computeCpd(file, originBlocks, duplicatedBlocks);
private final String fileKey;
private int indexInFile = 0;
- public CpdTextBlockToBlock(String fileKey) {
+ CpdTextBlockToBlock(String fileKey) {
this.fileKey = fileKey;
}
component,
new Duplication(
original,
- from(Arrays.asList(duplicates)).transform(TextBlockToInnerDuplicate.INSTANCE)));
+ from(Arrays.asList(duplicates)).transform(TextBlockToInnerDuplicate.INSTANCE).toList()));
return this;
}
*/
package org.sonar.ce.task.projectanalysis.duplication;
-import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
expectedException.expect(NullPointerException.class);
expectedException.expectMessage("original TextBlock can not be null");
- new Duplication(null, Collections.emptySet());
+ new Duplication(null, Collections.emptyList());
}
@Test
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("duplicates can not be empty");
- new Duplication(SOME_ORIGINAL_TEXTBLOCK, Collections.emptySet());
+ new Duplication(SOME_ORIGINAL_TEXTBLOCK, Collections.emptyList());
}
@Test
expectedException.expect(NullPointerException.class);
expectedException.expectMessage("duplicates can not contain null");
- new Duplication(SOME_ORIGINAL_TEXTBLOCK, new HashSet<>(Arrays.asList(mock(Duplicate.class), null, mock(Duplicate.class))));
+ new Duplication(SOME_ORIGINAL_TEXTBLOCK, Arrays.asList(mock(Duplicate.class), null, mock(Duplicate.class)));
}
@Test
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("TextBlock of an InnerDuplicate can not be the original TextBlock");
- new Duplication(SOME_ORIGINAL_TEXTBLOCK, new HashSet<>(Arrays.asList(mock(Duplicate.class), new InnerDuplicate(SOME_ORIGINAL_TEXTBLOCK), mock(Duplicate.class))));
+ new Duplication(SOME_ORIGINAL_TEXTBLOCK, Arrays.asList(mock(Duplicate.class), new InnerDuplicate(SOME_ORIGINAL_TEXTBLOCK), mock(Duplicate.class)));
}
@Test
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Unsupported type of Duplicate " + MyDuplicate.class.getName());
- new Duplication(SOME_ORIGINAL_TEXTBLOCK, ImmutableSet.of(new MyDuplicate(), new MyDuplicate()));
+ new Duplication(SOME_ORIGINAL_TEXTBLOCK, Arrays.asList(new MyDuplicate(), new MyDuplicate()));
}
private static final class MyDuplicate implements Duplicate {
@Test
public void getOriginal_returns_original() {
- assertThat(new Duplication(SOME_ORIGINAL_TEXTBLOCK, ImmutableSet.of(mock(Duplicate.class))).getOriginal()).isSameAs(SOME_ORIGINAL_TEXTBLOCK);
+ assertThat(new Duplication(SOME_ORIGINAL_TEXTBLOCK, Arrays.asList(new InnerDuplicate(TEXT_BLOCK_1)))
+ .getOriginal()).isSameAs(SOME_ORIGINAL_TEXTBLOCK);
}
@Test
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.utils.internal.TestSystem2;
import org.sonar.api.utils.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.ce.task.log.CeTaskMessages;
import org.sonar.ce.task.projectanalysis.component.Component;
import org.sonar.ce.task.projectanalysis.component.FileAttributes;
import org.sonar.duplications.block.Block;
import static java.util.Collections.singletonList;
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE;
import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
public class IntegrateCrossProjectDuplicationsTest {
+ private static final String XOO_LANGUAGE = "xoo";
+ private static final String ORIGIN_FILE_KEY = "ORIGIN_FILE_KEY";
+ private static final Component ORIGIN_FILE = builder(FILE, 1)
+ .setKey(ORIGIN_FILE_KEY)
+ .setFileAttributes(new FileAttributes(false, XOO_LANGUAGE, 1))
+ .build();
+ private static final String OTHER_FILE_KEY = "OTHER_FILE_KEY";
@Rule
public LogTester logTester = new LogTester();
@Rule
public DuplicationRepositoryRule duplicationRepository = DuplicationRepositoryRule.create();
- static final String XOO_LANGUAGE = "xoo";
-
- static final String ORIGIN_FILE_KEY = "ORIGIN_FILE_KEY";
- static final Component ORIGIN_FILE = builder(FILE, 1)
- .setKey(ORIGIN_FILE_KEY)
- .setFileAttributes(new FileAttributes(false, XOO_LANGUAGE, 1))
- .build();
-
- static final String OTHER_FILE_KEY = "OTHER_FILE_KEY";
-
- MapSettings settings = new MapSettings();
-
- IntegrateCrossProjectDuplications underTest = new IntegrateCrossProjectDuplications(settings.asConfig(), duplicationRepository);
+ private TestSystem2 system = new TestSystem2();
+ private MapSettings settings = new MapSettings();
+ private CeTaskMessages ceTaskMessages = mock(CeTaskMessages.class);
+ private IntegrateCrossProjectDuplications underTest = new IntegrateCrossProjectDuplications(settings.asConfig(), duplicationRepository, ceTaskMessages, system);
@Test
public void add_duplications_from_two_blocks() {
@Test
public void log_warning_if_this_deprecated_feature_is_enabled() {
settings.setProperty("sonar.cpd.cross_project", "true");
+ system.setNow(1000L);
- new IntegrateCrossProjectDuplications(settings.asConfig(), duplicationRepository);
+ new IntegrateCrossProjectDuplications(settings.asConfig(), duplicationRepository, ceTaskMessages, system);
assertThat(logTester.logs()).containsExactly("This analysis uses the deprecated cross-project duplication feature.");
+ verify(ceTaskMessages).add(new CeTaskMessages.Message("This project uses the deprecated cross-project duplication feature.", 1000L));
}
private static Duplication crossProjectDuplication(TextBlock original, String otherFileKey, TextBlock duplicate) {