3 * Copyright (C) 2009-2019 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
20 package org.sonar.ce.task.projectanalysis.duplication;
22 import com.google.common.base.Function;
23 import com.google.common.base.Predicate;
24 import com.google.common.collect.Ordering;
25 import java.util.Comparator;
26 import java.util.Objects;
27 import java.util.SortedSet;
28 import javax.annotation.Nonnull;
29 import javax.annotation.Nullable;
30 import javax.annotation.concurrent.Immutable;
32 import static com.google.common.base.Preconditions.checkArgument;
33 import static com.google.common.collect.FluentIterable.from;
34 import static java.util.Objects.requireNonNull;
37 public final class Duplication {
38 private static final Ordering<Duplicate> DUPLICATE_ORDERING = Ordering.from(DuplicateComparatorByType.INSTANCE)
39 .compound(Ordering.natural().onResultOf(DuplicateToFileKey.INSTANCE))
40 .compound(Ordering.natural().onResultOf(DuplicateToTextBlock.INSTANCE));
42 private final TextBlock original;
43 private final SortedSet<Duplicate> duplicates;
46 * @throws NullPointerException if {@code original} is {@code null} or {@code duplicates} is {@code null} or {@code duplicates} contains {@code null}
47 * @throws IllegalArgumentException if {@code duplicates} is empty
48 * @throws IllegalArgumentException if {@code duplicates} contains a {@link InnerDuplicate} with {@code original}
50 public Duplication(final TextBlock original, final Iterable<Duplicate> duplicates) {
51 this.original = requireNonNull(original, "original TextBlock can not be null");
52 this.duplicates = from(requireNonNull(duplicates, "duplicates can not be null"))
53 .filter(FailOnNullDuplicate.INSTANCE)
54 .filter(new EnsureInnerDuplicateIsNotOriginalTextBlock(original))
55 .toSortedSet(DUPLICATE_ORDERING);
56 checkArgument(!this.duplicates.isEmpty(), "duplicates can not be empty");
60 * The duplicated block.
62 public TextBlock getOriginal() {
67 * The duplicates of the original, sorted by inner duplicates, then project duplicates, then cross-project duplicates.
68 * For each category of duplicate, they are sorted by:
70 * <li>file key (unless it's an InnerDuplicate)</li>
71 * <li>then by TextBlocks by start line and in case of same line, by shortest first</li>
73 * <p>The returned set can not be empty and no inner duplicate can contain the original {@link TextBlock}.</p>
75 public SortedSet<Duplicate> getDuplicates() {
76 return this.duplicates;
80 public boolean equals(@Nullable Object o) {
84 if (o == null || getClass() != o.getClass()) {
87 Duplication that = (Duplication) o;
88 return original.equals(that.original) && duplicates.equals(that.duplicates);
92 public int hashCode() {
93 return Objects.hash(original, duplicates);
97 public String toString() {
98 return "Duplication{" +
99 "original=" + original +
100 ", duplicates=" + duplicates +
104 private enum FailOnNullDuplicate implements Predicate<Duplicate> {
108 public boolean apply(@Nullable Duplicate input) {
109 requireNonNull(input, "duplicates can not contain null");
114 private enum DuplicateComparatorByType implements Comparator<Duplicate> {
118 public int compare(Duplicate o1, Duplicate o2) {
119 return toIndexType(o1) - toIndexType(o2);
122 private static int toIndexType(Duplicate duplicate) {
123 if (duplicate instanceof InnerDuplicate) {
126 if (duplicate instanceof InProjectDuplicate) {
129 if (duplicate instanceof CrossProjectDuplicate) {
132 throw new IllegalArgumentException("Unsupported type of Duplicate " + duplicate.getClass().getName());
136 private enum DuplicateToTextBlock implements Function<Duplicate, TextBlock> {
141 public TextBlock apply(@Nonnull Duplicate input) {
142 return input.getTextBlock();
146 private static class EnsureInnerDuplicateIsNotOriginalTextBlock implements Predicate<Duplicate> {
147 private final TextBlock original;
149 public EnsureInnerDuplicateIsNotOriginalTextBlock(TextBlock original) {
150 this.original = original;
154 public boolean apply(@Nullable Duplicate input) {
155 if (input instanceof InnerDuplicate) {
156 checkArgument(!original.equals(input.getTextBlock()), "TextBlock of an InnerDuplicate can not be the original TextBlock");
162 private enum DuplicateToFileKey implements Function<Duplicate, String> {
167 public String apply(@Nonnull Duplicate duplicate) {
168 if (duplicate instanceof InnerDuplicate) {
171 if (duplicate instanceof InProjectDuplicate) {
172 return ((InProjectDuplicate) duplicate).getFile().getDbKey();
174 if (duplicate instanceof CrossProjectDuplicate) {
175 return ((CrossProjectDuplicate) duplicate).getFileKey();
177 throw new IllegalArgumentException("Unsupported type of Duplicate " + duplicate.getClass().getName());