]> source.dussan.org Git - sonarqube.git/blob
db48356be6a32cdac283326a336349ee1c270b64
[sonarqube.git] /
1 /*
2  * SonarQube, open source software quality management tool.
3  * Copyright (C) 2008-2014 SonarSource
4  * mailto:contact AT sonarsource DOT com
5  *
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.
10  *
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.
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.server.computation.duplication;
21
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;
27 import java.util.Map;
28 import java.util.Set;
29 import javax.annotation.Nonnull;
30 import javax.annotation.Nullable;
31 import javax.annotation.concurrent.Immutable;
32 import org.sonar.server.computation.component.Component;
33
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;
38
39 /**
40  * In-memory implementation of {@link DuplicationRepository}.
41  */
42 public class DuplicationRepositoryImpl implements DuplicationRepository {
43   private final Map<String, Duplications> duplicationsByComponentUuid = new HashMap<>();
44
45   @Override
46   public Set<Duplication> getDuplications(Component file) {
47     checkFileComponentArgument(file);
48
49     if (duplicationsByComponentUuid.containsKey(file.getUuid())) {
50       return from(duplicationsByComponentUuid.get(file.getUuid()).getDuplicates())
51           .transform(DuplicatesEntryToDuplication.INSTANCE)
52           .toSet();
53     }
54     return Collections.emptySet();
55   }
56
57   @Override
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");
63
64     Optional<Duplicates> duplicates = getDuplicates(file, original);
65     if (duplicates.isPresent()) {
66       checkDuplicationAlreadyExists(duplicates, file, original, duplicate);
67       checkReverseDuplicationAlreadyExists(file, original, duplicate);
68
69       duplicates.get().add(duplicate);
70     } else {
71       checkReverseDuplicationAlreadyExists(file, original, duplicate);
72       getOrCreate(file, original).add(duplicate);
73     }
74   }
75
76   @Override
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");
83
84     Optional<Duplicates> duplicates = getDuplicates(file, original);
85     if (duplicates.isPresent()) {
86       checkDuplicationAlreadyExists(duplicates, file, original, otherFile, duplicate);
87
88       duplicates.get().add(otherFile, duplicate);
89     } else {
90       getOrCreate(file, original).add(otherFile, duplicate);
91     }
92   }
93
94   @Override
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);
100
101     Optional<Duplicates> duplicates = getDuplicates(file, original);
102     if (duplicates.isPresent()) {
103       checkDuplicationAlreadyExists(duplicates, file, original, otherFileKey, duplicate);
104
105       duplicates.get().add(otherFileKey, duplicate);
106     } else {
107       getOrCreate(file, original).add(otherFileKey, duplicate);
108     }
109   }
110
111   private Optional<Duplicates> getDuplicates(Component file, TextBlock original) {
112     Duplications duplications = duplicationsByComponentUuid.get(file.getUuid());
113     if (duplications == null) {
114       return Optional.absent();
115     }
116     return duplications.get(original);
117   }
118
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());
122   }
123
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());
129     }
130   }
131
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());
135   }
136
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());
140   }
141
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);
147     }
148
149     return duplications.getOrCreate(original);
150   }
151
152   /**
153    * Represents the location of the file of one or more duplicate {@link TextBlock}.
154    */
155   private interface DuplicateLocation {
156
157   }
158
159   private enum InnerDuplicationLocation implements DuplicateLocation {
160     INSTANCE
161   }
162
163   @Immutable
164   private static final class InProjectDuplicationLocation implements DuplicateLocation {
165     private final Component component;
166
167     public InProjectDuplicationLocation(Component component) {
168       this.component = component;
169     }
170
171     public Component getComponent() {
172       return component;
173     }
174
175     @Override
176     public boolean equals(@Nullable Object o) {
177       if (this == o) {
178         return true;
179       }
180       if (o == null || getClass() != o.getClass()) {
181         return false;
182       }
183       InProjectDuplicationLocation that = (InProjectDuplicationLocation) o;
184       return component.equals(that.component);
185     }
186
187     @Override
188     public int hashCode() {
189       return component.hashCode();
190     }
191   }
192
193   @Immutable
194   private static final class CrossProjectDuplicationLocation implements DuplicateLocation {
195     private final String fileKey;
196
197     private CrossProjectDuplicationLocation(String fileKey) {
198       this.fileKey = fileKey;
199     }
200
201     public String getFileKey() {
202       return fileKey;
203     }
204
205     @Override
206     public boolean equals(@Nullable Object o) {
207       if (this == o) {
208         return true;
209       }
210       if (o == null || getClass() != o.getClass()) {
211         return false;
212       }
213       CrossProjectDuplicationLocation that = (CrossProjectDuplicationLocation) o;
214       return fileKey.equals(that.fileKey);
215     }
216
217     @Override
218     public int hashCode() {
219       return fileKey.hashCode();
220     }
221   }
222
223   private static final class Duplications {
224     private final Map<TextBlock, Duplicates> duplicatesByTextBlock = new HashMap<>();
225
226     public Duplicates getOrCreate(TextBlock textBlock) {
227       if (duplicatesByTextBlock.containsKey(textBlock)) {
228         return duplicatesByTextBlock.get(textBlock);
229       }
230       Duplicates res = new Duplicates();
231       duplicatesByTextBlock.put(textBlock, res);
232       return res;
233     }
234
235     public Set<Map.Entry<TextBlock, Duplicates>> getDuplicates() {
236       return duplicatesByTextBlock.entrySet();
237     }
238
239     public Optional<Duplicates> get(TextBlock original) {
240       return Optional.fromNullable(duplicatesByTextBlock.get(original));
241     }
242   }
243
244   private static final class Duplicates {
245     private final Map<DuplicateLocation, Set<TextBlock>> duplicatesByLocation = new HashMap<>();
246
247     public Iterable<DuplicateWithLocation> getDuplicates() {
248       return from(duplicatesByLocation.entrySet())
249           .transformAndConcat(MapEntryToDuplicateWithLocation.INSTANCE);
250     }
251
252     public void add(TextBlock duplicate) {
253       add(InnerDuplicationLocation.INSTANCE, duplicate);
254     }
255
256     public void add(Component otherFile, TextBlock duplicate) {
257       InProjectDuplicationLocation key = new InProjectDuplicationLocation(otherFile);
258       add(key, duplicate);
259     }
260
261     public void add(String otherFileKey, TextBlock duplicate) {
262       CrossProjectDuplicationLocation key = new CrossProjectDuplicationLocation(otherFileKey);
263       add(key, duplicate);
264     }
265
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);
271       }
272       textBlocks.add(duplicate);
273     }
274
275     public boolean hasDuplicate(TextBlock duplicate) {
276       return containsEntry(InnerDuplicationLocation.INSTANCE, duplicate);
277     }
278
279     public boolean hasDuplicate(Component otherFile, TextBlock duplicate) {
280       return containsEntry(new InProjectDuplicationLocation(otherFile), duplicate);
281     }
282
283     public boolean hasDuplicate(String otherFileKey, TextBlock duplicate) {
284       return containsEntry(new CrossProjectDuplicationLocation(otherFileKey), duplicate);
285     }
286
287     private boolean containsEntry(DuplicateLocation duplicateLocation, TextBlock duplicate) {
288       Set<TextBlock> textBlocks = duplicatesByLocation.get(duplicateLocation);
289       return textBlocks != null && textBlocks.contains(duplicate);
290     }
291
292     private enum MapEntryToDuplicateWithLocation implements Function<Map.Entry<DuplicateLocation, Set<TextBlock>>, Iterable<DuplicateWithLocation>> {
293       INSTANCE;
294
295       @Override
296       @Nonnull
297       public Iterable<DuplicateWithLocation> apply(@Nonnull final Map.Entry<DuplicateLocation, Set<TextBlock>> entry) {
298         return from(entry.getValue())
299             .transform(new Function<TextBlock, DuplicateWithLocation>() {
300               @Override
301               @Nonnull
302               public DuplicateWithLocation apply(@Nonnull TextBlock input) {
303                 return new DuplicateWithLocation(entry.getKey(), input);
304               }
305             });
306       }
307     }
308   }
309
310   @Immutable
311   private static final class DuplicateWithLocation {
312     private final DuplicateLocation location;
313     private final TextBlock duplicate;
314
315     private DuplicateWithLocation(DuplicateLocation location, TextBlock duplicate) {
316       this.location = location;
317       this.duplicate = duplicate;
318     }
319
320     public DuplicateLocation getLocation() {
321       return location;
322     }
323
324     public TextBlock getDuplicate() {
325       return duplicate;
326     }
327
328   }
329
330   private static void checkFileComponentArgument(Component file) {
331     checkComponentArgument(file, "file");
332   }
333
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);
337   }
338
339   private static void checkDuplicateTextBlockArgument(TextBlock duplicate) {
340     requireNonNull(duplicate, "duplicate can not be null");
341   }
342
343   private static void checkOriginalTextBlockArgument(TextBlock original) {
344     requireNonNull(original, "original can not be null");
345   }
346
347   private enum DuplicatesEntryToDuplication implements Function<Map.Entry<TextBlock, Duplicates>, Duplication> {
348     INSTANCE;
349
350     @Override
351     @Nonnull
352     public Duplication apply(@Nonnull Map.Entry<TextBlock, Duplicates> entry) {
353       return new Duplication(
354           entry.getKey(),
355           from(entry.getValue().getDuplicates()).transform(DuplicateLocationEntryToDuplicate.INSTANCE)
356       );
357     }
358
359     private enum DuplicateLocationEntryToDuplicate implements Function<DuplicateWithLocation, Duplicate> {
360       INSTANCE;
361
362       @Override
363       @Nonnull
364       public Duplicate apply(@Nonnull DuplicateWithLocation input) {
365         DuplicateLocation duplicateLocation = input.getLocation();
366         if (duplicateLocation instanceof InnerDuplicationLocation) {
367           return new InnerDuplicate(input.getDuplicate());
368         }
369         if (duplicateLocation instanceof InProjectDuplicationLocation) {
370           return new InProjectDuplicate(((InProjectDuplicationLocation) duplicateLocation).getComponent(), input.getDuplicate());
371         }
372         if (duplicateLocation instanceof CrossProjectDuplicationLocation) {
373           return new CrossProjectDuplicate(((CrossProjectDuplicationLocation) duplicateLocation).getFileKey(), input.getDuplicate());
374         }
375         throw new IllegalArgumentException("Unsupported DuplicationLocation type " + duplicateLocation.getClass());
376       }
377     }
378   }
379 }