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.
20 package org.sonar.server.computation.duplication;
22 import com.google.common.base.Function;
23 import com.google.common.base.Optional;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.HashSet;
29 import javax.annotation.Nonnull;
30 import javax.annotation.Nullable;
31 import javax.annotation.concurrent.Immutable;
32 import org.sonar.server.computation.component.Component;
34 import static com.google.common.base.Preconditions.checkArgument;
35 import static com.google.common.collect.FluentIterable.from;
36 import static java.lang.String.format;
37 import static java.util.Objects.requireNonNull;
40 * In-memory implementation of {@link DuplicationRepository}.
42 public class DuplicationRepositoryImpl implements DuplicationRepository {
43 private final Map<String, Duplications> duplicationsByComponentUuid = new HashMap<>();
46 public Set<Duplication> getDuplications(Component file) {
47 checkFileComponentArgument(file);
49 if (duplicationsByComponentUuid.containsKey(file.getUuid())) {
50 return from(duplicationsByComponentUuid.get(file.getUuid()).getDuplicates())
51 .transform(DuplicatesEntryToDuplication.INSTANCE)
54 return Collections.emptySet();
58 public void addDuplication(Component file, TextBlock original, TextBlock duplicate) {
59 checkFileComponentArgument(file);
60 checkOriginalTextBlockArgument(original);
61 checkDuplicateTextBlockArgument(duplicate);
62 checkArgument(!original.equals(duplicate), "original and duplicate TextBlocks can not be the same");
64 Optional<Duplicates> duplicates = getDuplicates(file, original);
65 if (duplicates.isPresent()) {
66 checkDuplicationAlreadyExists(duplicates, file, original, duplicate);
67 checkReverseDuplicationAlreadyExists(file, original, duplicate);
69 duplicates.get().add(duplicate);
71 checkReverseDuplicationAlreadyExists(file, original, duplicate);
72 getOrCreate(file, original).add(duplicate);
77 public void addDuplication(Component file, TextBlock original, Component otherFile, TextBlock duplicate) {
78 checkFileComponentArgument(file);
79 checkOriginalTextBlockArgument(original);
80 checkComponentArgument(otherFile, "otherFile");
81 checkDuplicateTextBlockArgument(duplicate);
82 checkArgument(!file.equals(otherFile), "file and otherFile Components can not be the same");
84 Optional<Duplicates> duplicates = getDuplicates(file, original);
85 if (duplicates.isPresent()) {
86 checkDuplicationAlreadyExists(duplicates, file, original, otherFile, duplicate);
88 duplicates.get().add(otherFile, duplicate);
90 getOrCreate(file, original).add(otherFile, duplicate);
95 public void addDuplication(Component file, TextBlock original, String otherFileKey, TextBlock duplicate) {
96 checkFileComponentArgument(file);
97 checkOriginalTextBlockArgument(original);
98 requireNonNull(otherFileKey, "otherFileKey can not be null");
99 checkDuplicateTextBlockArgument(duplicate);
101 Optional<Duplicates> duplicates = getDuplicates(file, original);
102 if (duplicates.isPresent()) {
103 checkDuplicationAlreadyExists(duplicates, file, original, otherFileKey, duplicate);
105 duplicates.get().add(otherFileKey, duplicate);
107 getOrCreate(file, original).add(otherFileKey, duplicate);
111 private Optional<Duplicates> getDuplicates(Component file, TextBlock original) {
112 Duplications duplications = duplicationsByComponentUuid.get(file.getUuid());
113 if (duplications == null) {
114 return Optional.absent();
116 return duplications.get(original);
119 private void checkDuplicationAlreadyExists(Optional<Duplicates> duplicates, Component file, TextBlock original, TextBlock duplicate) {
120 checkArgument(!duplicates.get().hasDuplicate(duplicate),
121 "Inner duplicate %s is already associated to original %s in file %s", duplicate, original, file.getKey());
124 private void checkReverseDuplicationAlreadyExists(Component file, TextBlock original, TextBlock duplicate) {
125 Optional<Duplicates> reverseDuplication = getDuplicates(file, duplicate);
126 if (reverseDuplication.isPresent()) {
127 checkArgument(!reverseDuplication.get().hasDuplicate(original),
128 "Inner duplicate %s is already associated to original %s in file %s", duplicate, original, file.getKey());
132 private static void checkDuplicationAlreadyExists(Optional<Duplicates> duplicates, Component file, TextBlock original, Component otherFile, TextBlock duplicate) {
133 checkArgument(!duplicates.get().hasDuplicate(otherFile, duplicate),
134 "In-project duplicate %s in file %s is already associated to original %s in file %s", duplicate, otherFile.getKey(), original, file.getKey());
137 private static void checkDuplicationAlreadyExists(Optional<Duplicates> duplicates, Component file, TextBlock original, String otherFileKey, TextBlock duplicate) {
138 checkArgument(!duplicates.get().hasDuplicate(otherFileKey, duplicate),
139 "Cross-project duplicate %s in file %s is already associated to original %s in file %s", duplicate, otherFileKey, original, file.getKey());
142 private Duplicates getOrCreate(Component file, TextBlock original) {
143 Duplications duplications = duplicationsByComponentUuid.get(file.getUuid());
144 if (duplications == null) {
145 duplications = new Duplications();
146 duplicationsByComponentUuid.put(file.getUuid(), duplications);
149 return duplications.getOrCreate(original);
153 * Represents the location of the file of one or more duplicate {@link TextBlock}.
155 private interface DuplicateLocation {
159 private enum InnerDuplicationLocation implements DuplicateLocation {
164 private static final class InProjectDuplicationLocation implements DuplicateLocation {
165 private final Component component;
167 public InProjectDuplicationLocation(Component component) {
168 this.component = component;
171 public Component getComponent() {
176 public boolean equals(@Nullable Object o) {
180 if (o == null || getClass() != o.getClass()) {
183 InProjectDuplicationLocation that = (InProjectDuplicationLocation) o;
184 return component.equals(that.component);
188 public int hashCode() {
189 return component.hashCode();
194 private static final class CrossProjectDuplicationLocation implements DuplicateLocation {
195 private final String fileKey;
197 private CrossProjectDuplicationLocation(String fileKey) {
198 this.fileKey = fileKey;
201 public String getFileKey() {
206 public boolean equals(@Nullable Object o) {
210 if (o == null || getClass() != o.getClass()) {
213 CrossProjectDuplicationLocation that = (CrossProjectDuplicationLocation) o;
214 return fileKey.equals(that.fileKey);
218 public int hashCode() {
219 return fileKey.hashCode();
223 private static final class Duplications {
224 private final Map<TextBlock, Duplicates> duplicatesByTextBlock = new HashMap<>();
226 public Duplicates getOrCreate(TextBlock textBlock) {
227 if (duplicatesByTextBlock.containsKey(textBlock)) {
228 return duplicatesByTextBlock.get(textBlock);
230 Duplicates res = new Duplicates();
231 duplicatesByTextBlock.put(textBlock, res);
235 public Set<Map.Entry<TextBlock, Duplicates>> getDuplicates() {
236 return duplicatesByTextBlock.entrySet();
239 public Optional<Duplicates> get(TextBlock original) {
240 return Optional.fromNullable(duplicatesByTextBlock.get(original));
244 private static final class Duplicates {
245 private final Map<DuplicateLocation, Set<TextBlock>> duplicatesByLocation = new HashMap<>();
247 public Iterable<DuplicateWithLocation> getDuplicates() {
248 return from(duplicatesByLocation.entrySet())
249 .transformAndConcat(MapEntryToDuplicateWithLocation.INSTANCE);
252 public void add(TextBlock duplicate) {
253 add(InnerDuplicationLocation.INSTANCE, duplicate);
256 public void add(Component otherFile, TextBlock duplicate) {
257 InProjectDuplicationLocation key = new InProjectDuplicationLocation(otherFile);
261 public void add(String otherFileKey, TextBlock duplicate) {
262 CrossProjectDuplicationLocation key = new CrossProjectDuplicationLocation(otherFileKey);
266 private void add(DuplicateLocation duplicateLocation, TextBlock duplicate) {
267 Set<TextBlock> textBlocks = duplicatesByLocation.get(duplicateLocation);
268 if (textBlocks == null) {
269 textBlocks = new HashSet<>(1);
270 duplicatesByLocation.put(duplicateLocation, textBlocks);
272 textBlocks.add(duplicate);
275 public boolean hasDuplicate(TextBlock duplicate) {
276 return containsEntry(InnerDuplicationLocation.INSTANCE, duplicate);
279 public boolean hasDuplicate(Component otherFile, TextBlock duplicate) {
280 return containsEntry(new InProjectDuplicationLocation(otherFile), duplicate);
283 public boolean hasDuplicate(String otherFileKey, TextBlock duplicate) {
284 return containsEntry(new CrossProjectDuplicationLocation(otherFileKey), duplicate);
287 private boolean containsEntry(DuplicateLocation duplicateLocation, TextBlock duplicate) {
288 Set<TextBlock> textBlocks = duplicatesByLocation.get(duplicateLocation);
289 return textBlocks != null && textBlocks.contains(duplicate);
292 private enum MapEntryToDuplicateWithLocation implements Function<Map.Entry<DuplicateLocation, Set<TextBlock>>, Iterable<DuplicateWithLocation>> {
297 public Iterable<DuplicateWithLocation> apply(@Nonnull final Map.Entry<DuplicateLocation, Set<TextBlock>> entry) {
298 return from(entry.getValue())
299 .transform(new Function<TextBlock, DuplicateWithLocation>() {
302 public DuplicateWithLocation apply(@Nonnull TextBlock input) {
303 return new DuplicateWithLocation(entry.getKey(), input);
311 private static final class DuplicateWithLocation {
312 private final DuplicateLocation location;
313 private final TextBlock duplicate;
315 private DuplicateWithLocation(DuplicateLocation location, TextBlock duplicate) {
316 this.location = location;
317 this.duplicate = duplicate;
320 public DuplicateLocation getLocation() {
324 public TextBlock getDuplicate() {
330 private static void checkFileComponentArgument(Component file) {
331 checkComponentArgument(file, "file");
334 private static void checkComponentArgument(Component file, String argName) {
335 requireNonNull(file, format("%s can not be null", argName));
336 checkArgument(file.getType() == Component.Type.FILE, "type of %s argument must be FILE", argName);
339 private static void checkDuplicateTextBlockArgument(TextBlock duplicate) {
340 requireNonNull(duplicate, "duplicate can not be null");
343 private static void checkOriginalTextBlockArgument(TextBlock original) {
344 requireNonNull(original, "original can not be null");
347 private enum DuplicatesEntryToDuplication implements Function<Map.Entry<TextBlock, Duplicates>, Duplication> {
352 public Duplication apply(@Nonnull Map.Entry<TextBlock, Duplicates> entry) {
353 return new Duplication(
355 from(entry.getValue().getDuplicates()).transform(DuplicateLocationEntryToDuplicate.INSTANCE)
359 private enum DuplicateLocationEntryToDuplicate implements Function<DuplicateWithLocation, Duplicate> {
364 public Duplicate apply(@Nonnull DuplicateWithLocation input) {
365 DuplicateLocation duplicateLocation = input.getLocation();
366 if (duplicateLocation instanceof InnerDuplicationLocation) {
367 return new InnerDuplicate(input.getDuplicate());
369 if (duplicateLocation instanceof InProjectDuplicationLocation) {
370 return new InProjectDuplicate(((InProjectDuplicationLocation) duplicateLocation).getComponent(), input.getDuplicate());
372 if (duplicateLocation instanceof CrossProjectDuplicationLocation) {
373 return new CrossProjectDuplicate(((CrossProjectDuplicationLocation) duplicateLocation).getFileKey(), input.getDuplicate());
375 throw new IllegalArgumentException("Unsupported DuplicationLocation type " + duplicateLocation.getClass());