]> source.dussan.org Git - sonarqube.git/blob
1f106ca1bfc2adf6e9a598317a9c9246dd0476b9
[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.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;
29
30 import static com.google.common.base.Preconditions.checkArgument;
31 import static java.util.Objects.requireNonNull;
32
33 @Immutable
34 public final class Duplication {
35   private static final Comparator<Duplicate> DUPLICATE_COMPARATOR = DuplicateComparatorByType.INSTANCE
36     .thenComparing(DuplicateToFileKey.INSTANCE).thenComparing(DuplicateToTextBlock.INSTANCE);
37
38   private final TextBlock original;
39   private final Duplicate[] duplicates;
40
41   /**
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}
45    */
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);
50   }
51
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");
55
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");
60       }
61     }
62   }
63
64   /**
65    * The duplicated block.
66    */
67   public TextBlock getOriginal() {
68     return this.original;
69   }
70
71   /**
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:
74    * <ul>
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>
77    * </ul
78    * <p>The returned set can not be empty and no inner duplicate can contain the original {@link TextBlock}.</p>
79    */
80   public Duplicate[] getDuplicates() {
81     return this.duplicates;
82   }
83
84   @Override
85   public boolean equals(@Nullable Object o) {
86     if (this == o) {
87       return true;
88     }
89     if (o == null || getClass() != o.getClass()) {
90       return false;
91     }
92     Duplication that = (Duplication) o;
93     return original.equals(that.original) && Arrays.equals(duplicates, that.duplicates);
94   }
95
96   @Override
97   public int hashCode() {
98     return Arrays.deepHashCode(new Object[] {original, duplicates});
99   }
100
101   @Override
102   public String toString() {
103     return "Duplication{" +
104       "original=" + original +
105       ", duplicates=" + Arrays.toString(duplicates) +
106       '}';
107   }
108
109   private enum DuplicateComparatorByType implements Comparator<Duplicate> {
110     INSTANCE;
111
112     @Override
113     public int compare(Duplicate o1, Duplicate o2) {
114       return toIndexType(o1) - toIndexType(o2);
115     }
116
117     private static int toIndexType(Duplicate duplicate) {
118       if (duplicate instanceof InnerDuplicate) {
119         return 0;
120       }
121       if (duplicate instanceof InProjectDuplicate) {
122         return 1;
123       }
124       if (duplicate instanceof CrossProjectDuplicate) {
125         return 2;
126       }
127       throw new IllegalArgumentException("Unsupported type of Duplicate " + duplicate.getClass().getName());
128     }
129   }
130
131   private enum DuplicateToTextBlock implements Function<Duplicate, TextBlock> {
132     INSTANCE;
133
134     @Override
135     @Nonnull
136     public TextBlock apply(@Nonnull Duplicate input) {
137       return input.getTextBlock();
138     }
139   }
140
141   private enum DuplicateToFileKey implements Function<Duplicate, String> {
142     INSTANCE;
143
144     @Override
145     @Nonnull
146     public String apply(@Nonnull Duplicate duplicate) {
147       if (duplicate instanceof InnerDuplicate) {
148         return "";
149       }
150       if (duplicate instanceof InProjectDuplicate) {
151         return ((InProjectDuplicate) duplicate).getFile().getKey();
152       }
153       if (duplicate instanceof CrossProjectDuplicate) {
154         return ((CrossProjectDuplicate) duplicate).getFileKey();
155       }
156       throw new IllegalArgumentException("Unsupported type of Duplicate " + duplicate.getClass().getName());
157     }
158   }
159 }