3 * Copyright (C) 2009-2023 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 java.util.Arrays;
23 import java.util.Comparator;
24 import java.util.List;
25 import java.util.function.Function;
26 import javax.annotation.Nonnull;
27 import javax.annotation.Nullable;
28 import javax.annotation.concurrent.Immutable;
30 import static com.google.common.base.Preconditions.checkArgument;
31 import static java.util.Objects.requireNonNull;
34 public final class Duplication {
35 private static final Comparator<Duplicate> DUPLICATE_COMPARATOR = DuplicateComparatorByType.INSTANCE
36 .thenComparing(DuplicateToFileKey.INSTANCE).thenComparing(DuplicateToTextBlock.INSTANCE);
38 private final TextBlock original;
39 private final Duplicate[] duplicates;
42 * @throws NullPointerException if {@code original} is {@code null} or {@code duplicates} is {@code null} or {@code duplicates} contains {@code null}
43 * @throws IllegalArgumentException if {@code duplicates} is empty
44 * @throws IllegalArgumentException if {@code duplicates} contains a {@link InnerDuplicate} with {@code original}
46 public Duplication(TextBlock original, List<Duplicate> duplicates) {
47 this.original = requireNonNull(original, "original TextBlock can not be null");
48 validateDuplicates(original, duplicates);
49 this.duplicates = duplicates.stream().sorted(DUPLICATE_COMPARATOR).distinct().toArray(Duplicate[]::new);
52 private static void validateDuplicates(TextBlock original, List<Duplicate> duplicates) {
53 requireNonNull(duplicates, "duplicates can not be null");
54 checkArgument(!duplicates.isEmpty(), "duplicates can not be empty");
56 for (Duplicate dup : duplicates) {
57 requireNonNull(dup, "duplicates can not contain null");
58 if (dup instanceof InnerDuplicate) {
59 checkArgument(!original.equals(dup.getTextBlock()), "TextBlock of an InnerDuplicate can not be the original TextBlock");
65 * The duplicated block.
67 public TextBlock getOriginal() {
72 * The duplicates of the original, sorted by inner duplicates, then project duplicates, then cross-project duplicates.
73 * For each category of duplicate, they are sorted by:
75 * <li>file key (unless it's an InnerDuplicate)</li>
76 * <li>then by TextBlocks by start line and in case of same line, by shortest first</li>
78 * <p>The returned set can not be empty and no inner duplicate can contain the original {@link TextBlock}.</p>
80 public Duplicate[] getDuplicates() {
81 return this.duplicates;
85 public boolean equals(@Nullable Object o) {
89 if (o == null || getClass() != o.getClass()) {
92 Duplication that = (Duplication) o;
93 return original.equals(that.original) && Arrays.equals(duplicates, that.duplicates);
97 public int hashCode() {
98 return Arrays.deepHashCode(new Object[] {original, duplicates});
102 public String toString() {
103 return "Duplication{" +
104 "original=" + original +
105 ", duplicates=" + Arrays.toString(duplicates) +
109 private enum DuplicateComparatorByType implements Comparator<Duplicate> {
113 public int compare(Duplicate o1, Duplicate o2) {
114 return toIndexType(o1) - toIndexType(o2);
117 private static int toIndexType(Duplicate duplicate) {
118 if (duplicate instanceof InnerDuplicate) {
121 if (duplicate instanceof InProjectDuplicate) {
124 if (duplicate instanceof CrossProjectDuplicate) {
127 throw new IllegalArgumentException("Unsupported type of Duplicate " + duplicate.getClass().getName());
131 private enum DuplicateToTextBlock implements Function<Duplicate, TextBlock> {
136 public TextBlock apply(@Nonnull Duplicate input) {
137 return input.getTextBlock();
141 private enum DuplicateToFileKey implements Function<Duplicate, String> {
146 public String apply(@Nonnull Duplicate duplicate) {
147 if (duplicate instanceof InnerDuplicate) {
150 if (duplicate instanceof InProjectDuplicate) {
151 return ((InProjectDuplicate) duplicate).getFile().getKey();
153 if (duplicate instanceof CrossProjectDuplicate) {
154 return ((CrossProjectDuplicate) duplicate).getFileKey();
156 throw new IllegalArgumentException("Unsupported type of Duplicate " + duplicate.getClass().getName());