import org.sonar.server.computation.component.ReportTreeRootHolderImpl;
import org.sonar.server.computation.component.SettingsRepositoryImpl;
import org.sonar.server.computation.debt.DebtModelHolderImpl;
+import org.sonar.server.computation.duplication.DuplicationRepositoryImpl;
import org.sonar.server.computation.event.EventRepositoryImpl;
import org.sonar.server.computation.filesystem.ComputationTempFolderProvider;
import org.sonar.server.computation.issue.BaseIssuesLoader;
SourceLinesRepositoryImpl.class,
SourceHashRepositoryImpl.class,
ScmInfoRepositoryImpl.class,
+ DuplicationRepositoryImpl.class,
// issues
RuleRepositoryImpl.class,
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.duplication;
+
+import javax.annotation.Nullable;
+
+import static java.util.Objects.requireNonNull;
+
+abstract class AbstractDuplicate implements Duplicate {
+ private final TextBlock textBlock;
+
+ protected AbstractDuplicate(TextBlock textBlock) {
+ this.textBlock = requireNonNull(textBlock, "textBlock of duplicate can not be null");
+ }
+
+ @Override
+ public TextBlock getTextBlock() {
+ return textBlock;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ AbstractDuplicate that = (AbstractDuplicate) o;
+ return textBlock.equals(that.textBlock);
+ }
+
+ @Override
+ public int hashCode() {
+ return textBlock.hashCode();
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.duplication;
+
+import java.util.Objects;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+import static java.util.Objects.requireNonNull;
+
+@Immutable
+public class CrossProjectDuplicate extends AbstractDuplicate {
+ private final String fileKey;
+
+ public CrossProjectDuplicate(String fileKey, TextBlock textBlock) {
+ super(textBlock);
+ this.fileKey = requireNonNull(fileKey, "fileKey can not be null");
+ }
+
+ public String getFileKey() {
+ return fileKey;
+ }
+
+ @Override
+ public String toString() {
+ return "CrossProjectDuplicate{" +
+ "fileKey='" + fileKey + '\'' +
+ ", textBlock=" + getTextBlock() +
+ '}';
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass() || !super.equals(o)) {
+ return false;
+ }
+ CrossProjectDuplicate that = (CrossProjectDuplicate) o;
+ return fileKey.equals(that.fileKey);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), fileKey);
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.duplication;
+
+public interface Duplicate {
+ TextBlock getTextBlock();
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.duplication;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Ordering;
+import java.util.Comparator;
+import java.util.Objects;
+import java.util.SortedSet;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.FluentIterable.from;
+import static java.util.Objects.requireNonNull;
+
+@Immutable
+public final class Duplication {
+ private static Ordering<Duplicate> DUPLICATE_ORDERING = Ordering.from(DuplicateComparatorByType.INSTANCE)
+ .compound(Ordering.natural().onResultOf(DuplicateToFileKey.INSTANCE))
+ .compound(Ordering.natural().onResultOf(DuplicateToTextBlock.INSTANCE));
+
+ private final TextBlock original;
+ private final SortedSet<Duplicate> duplicates;
+
+ /**
+ * @throws NullPointerException if {@code original} is {@code null} or {@code duplicates} is {@code null} or {@code duplicates} contains {@code null}
+ * @throws IllegalArgumentException if {@code duplicates} is empty
+ * @throws IllegalArgumentException if {@code duplicates} contains a {@link InnerDuplicate} with {@code original}
+ */
+ public Duplication(final TextBlock original, final Iterable<Duplicate> duplicates) {
+ this.original = requireNonNull(original, "original TextBlock can not be null");
+ this.duplicates = from(requireNonNull(duplicates, "duplicates can not be null"))
+ .filter(FailOnNullDuplicate.INSTANCE)
+ .filter(new EnsureInnerDuplicateIsNotOriginalTextBlock(original))
+ .toSortedSet(DUPLICATE_ORDERING);
+ checkArgument(!this.duplicates.isEmpty(), "duplicates can not be empty");
+ }
+
+ /**
+ * The duplicated block.
+ */
+ public TextBlock getOriginal() {
+ return this.original;
+ }
+
+ /**
+ * The duplicates of the original, sorted by inner duplicates, then project duplicates, then cross-project duplicates.
+ * For each category of duplicate, they are sorted by:
+ * <ul>
+ * <li>file key (unless it's an InnerDuplicate)</li>
+ * <li>then by TextBlocks by start line and in case of same line, by shortest first</li>
+ * </ul
+ * <p>The returned set can not be empty and no inner duplicate can contain the original {@link TextBlock}.</p>
+ */
+ public SortedSet<Duplicate> getDuplicates() {
+ return this.duplicates;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Duplication that = (Duplication) o;
+ return original.equals(that.original) && duplicates.equals(that.duplicates);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(original, duplicates);
+ }
+
+ @Override
+ public String toString() {
+ return "Duplication{" +
+ "original=" + original +
+ ", duplicates=" + duplicates +
+ '}';
+ }
+
+ private enum FailOnNullDuplicate implements Predicate<Duplicate> {
+ INSTANCE;
+
+ @Override
+ public boolean apply(@Nullable Duplicate input) {
+ requireNonNull(input, "duplicates can not contain null");
+ return true;
+ }
+ }
+
+ private enum DuplicateComparatorByType implements Comparator<Duplicate> {
+ INSTANCE;
+
+ @Override
+ public int compare(Duplicate o1, Duplicate o2) {
+ return toIndexType(o1) - toIndexType(o2);
+ }
+
+ private static int toIndexType(Duplicate duplicate) {
+ if (duplicate instanceof InnerDuplicate) {
+ return 0;
+ }
+ if (duplicate instanceof InProjectDuplicate) {
+ return 1;
+ }
+ if (duplicate instanceof CrossProjectDuplicate) {
+ return 2;
+ }
+ throw new IllegalArgumentException("Unsupported type of Duplicate " + duplicate.getClass().getName());
+ }
+ }
+
+ private enum DuplicateToTextBlock implements Function<Duplicate, TextBlock> {
+ INSTANCE;
+
+ @Override
+ @Nonnull
+ public TextBlock apply(@Nonnull Duplicate input) {
+ return input.getTextBlock();
+ }
+ }
+
+ private static class EnsureInnerDuplicateIsNotOriginalTextBlock implements Predicate<Duplicate> {
+ private final TextBlock original;
+
+ public EnsureInnerDuplicateIsNotOriginalTextBlock(TextBlock original) {
+ this.original = original;
+ }
+
+ @Override
+ public boolean apply(@Nullable Duplicate input) {
+ if (input instanceof InnerDuplicate) {
+ checkArgument(!original.equals(input.getTextBlock()), "TextBlock of an InnerDuplicate can not be the original TextBlock");
+ }
+ return true;
+ }
+ }
+
+ private enum DuplicateToFileKey implements Function<Duplicate, String> {
+ INSTANCE;
+
+ @Override
+ @Nonnull
+ public String apply(@Nonnull Duplicate duplicate) {
+ if (duplicate instanceof InnerDuplicate) {
+ return "";
+ }
+ if (duplicate instanceof InProjectDuplicate) {
+ return ((InProjectDuplicate) duplicate).getFile().getKey();
+ }
+ if (duplicate instanceof CrossProjectDuplicate) {
+ return ((CrossProjectDuplicate) duplicate).getFileKey();
+ }
+ throw new IllegalArgumentException("Unsupported type of Duplicate " + duplicate.getClass().getName());
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.duplication;
+
+import java.util.Set;
+import org.sonar.server.computation.component.Component;
+
+/**
+ * Repository of code duplications in files of the project.
+ * <p>
+ * It stores:
+ * <ul>
+ * <li>inner duplications (ie. duplications of blocks inside the same file)</li>
+ * <li>project duplications (ie. duplications of blocks between two files of the current project)</li>
+ * <li>cross-project duplications (ie. duplications of blocks of code between a file of the current project and a file of another project)</li>
+ * </ul>
+ * </p>
+ */
+public interface DuplicationRepository {
+
+ /**
+ * Returns the duplications in the specified file {@link Component}, if any.
+ *
+ * @throws NullPointerException if {@code file} is {@code null}
+ * @throws IllegalArgumentException if the type of the {@link Component} argument is not {@link Component.Type#FILE}
+ */
+ Set<Duplication> getDuplications(Component file);
+
+ /**
+ * Adds a project duplication of the specified original {@link TextBlock} in the specified file {@link Component}
+ * which is duplicated by the specified duplicate {@link TextBlock} in the same file.
+ * <p>
+ * This method can be called multiple times with the same {@code file} and {@code original} but a different
+ * {@code duplicate} to add multiple duplications of the same block.
+ * </p>
+ * <p>
+ * It must not, however, be called twice with {@code original} and {@code duplicate} swapped, this would raise
+ * an {@link IllegalArgumentException} as the duplication already exists.
+ * </p>
+ *
+ * @throws NullPointerException if any argument is {@code null}
+ * @throws IllegalArgumentException if the type of the {@link Component} argument is not {@link Component.Type#FILE}
+ * @throws IllegalStateException if {@code original} and {@code duplicate} are the same
+ * @throws IllegalStateException if the specified duplication already exists in the repository
+ */
+ void addDuplication(Component file, TextBlock original, TextBlock duplicate);
+
+ /**
+ * Adds a project duplication of the specified original {@link TextBlock} in the specified file {@link Component} as
+ * duplicated by the duplicate {@link TextBlock} in the other file {@link Component}.
+ * <p>
+ * Note: the reverse duplication relationship between files is not added automatically (which leaves open the
+ * possibility of inconsistent duplication information). This means that it is the responsibility of the repository's
+ * user to call this method again with the {@link Component} arguments and the {@link TextBlock} arguments swapped.
+ * </p>
+ *
+ * @throws NullPointerException if any argument is {@code null}
+ * @throws IllegalArgumentException if the type of any of the {@link Component} arguments is not {@link Component.Type#FILE}
+ * @throws IllegalArgumentException if {@code file} and {@code otherFile} are the same
+ * @throws IllegalStateException if the specified duplication already exists in the repository
+ */
+ void addDuplication(Component file, TextBlock original, Component otherFile, TextBlock duplicate);
+
+ /**
+ * Adds a cross-project duplication of the specified original {@link TextBlock} in the specified file {@link Component},
+ * as duplicated by the specified duplicate {@link TextBlock} in a file of another project identified by its key.
+ *
+ * @throws NullPointerException if any argument is {@code null}
+ * @throws IllegalArgumentException if the type of the {@link Component} argument is not {@link Component.Type#FILE}
+ * @throws IllegalArgumentException if {@code otherFileKey} is the key of a file in the project
+ * @throws IllegalStateException if the specified duplication already exists in the repository
+ */
+ void addDuplication(Component file, TextBlock original, String otherFileKey, TextBlock duplicate);
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.duplication;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import org.sonar.server.computation.component.Component;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.FluentIterable.from;
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * In-memory implementation of {@link DuplicationRepository}.
+ */
+public class DuplicationRepositoryImpl implements DuplicationRepository {
+ private final Map<String, Duplications> duplicationsByComponentUuid = new HashMap<>();
+
+ @Override
+ public Set<Duplication> getDuplications(Component file) {
+ checkFileComponentArgument(file);
+
+ if (duplicationsByComponentUuid.containsKey(file.getUuid())) {
+ return from(duplicationsByComponentUuid.get(file.getUuid()).getDuplicates())
+ .transform(DuplicatesEntryToDuplication.INSTANCE)
+ .toSet();
+ }
+ return Collections.emptySet();
+ }
+
+ @Override
+ public void addDuplication(Component file, TextBlock original, TextBlock duplicate) {
+ checkFileComponentArgument(file);
+ checkOriginalTextBlockArgument(original);
+ checkDuplicateTextBlockArgument(duplicate);
+ checkArgument(!original.equals(duplicate), "original and duplicate TextBlocks can not be the same");
+
+ Optional<Duplicates> duplicates = getDuplicates(file, original);
+ if (duplicates.isPresent()) {
+ checkDuplicationAlreadyExists(duplicates, file, original, duplicate);
+ checkReverseDuplicationAlreadyExists(file, original, duplicate);
+
+ duplicates.get().add(duplicate);
+ } else {
+ checkReverseDuplicationAlreadyExists(file, original, duplicate);
+ getOrCreate(file, original).add(duplicate);
+ }
+ }
+
+ @Override
+ public void addDuplication(Component file, TextBlock original, Component otherFile, TextBlock duplicate) {
+ checkFileComponentArgument(file);
+ checkOriginalTextBlockArgument(original);
+ checkComponentArgument(otherFile, "otherFile");
+ checkDuplicateTextBlockArgument(duplicate);
+ checkArgument(!file.equals(otherFile), "file and otherFile Components can not be the same");
+
+ Optional<Duplicates> duplicates = getDuplicates(file, original);
+ if (duplicates.isPresent()) {
+ checkDuplicationAlreadyExists(duplicates, file, original, otherFile, duplicate);
+
+ duplicates.get().add(otherFile, duplicate);
+ } else {
+ getOrCreate(file, original).add(otherFile, duplicate);
+ }
+ }
+
+ @Override
+ public void addDuplication(Component file, TextBlock original, String otherFileKey, TextBlock duplicate) {
+ checkFileComponentArgument(file);
+ checkOriginalTextBlockArgument(original);
+ requireNonNull(otherFileKey, "otherFileKey can not be null");
+ checkDuplicateTextBlockArgument(duplicate);
+
+ Optional<Duplicates> duplicates = getDuplicates(file, original);
+ if (duplicates.isPresent()) {
+ checkDuplicationAlreadyExists(duplicates, file, original, otherFileKey, duplicate);
+
+ duplicates.get().add(otherFileKey, duplicate);
+ } else {
+ getOrCreate(file, original).add(otherFileKey, duplicate);
+ }
+ }
+
+ private Optional<Duplicates> getDuplicates(Component file, TextBlock original) {
+ Duplications duplications = duplicationsByComponentUuid.get(file.getUuid());
+ if (duplications == null) {
+ return Optional.absent();
+ }
+ return duplications.get(original);
+ }
+
+ private void checkDuplicationAlreadyExists(Optional<Duplicates> duplicates, Component file, TextBlock original, TextBlock duplicate) {
+ checkArgument(!duplicates.get().hasDuplicate(duplicate),
+ "Inner duplicate %s is already associated to original %s in file %s", duplicate, original, file.getKey());
+ }
+
+ private void checkReverseDuplicationAlreadyExists(Component file, TextBlock original, TextBlock duplicate) {
+ Optional<Duplicates> reverseDuplication = getDuplicates(file, duplicate);
+ if (reverseDuplication.isPresent()) {
+ checkArgument(!reverseDuplication.get().hasDuplicate(original),
+ "Inner duplicate %s is already associated to original %s in file %s", duplicate, original, file.getKey());
+ }
+ }
+
+ private static void checkDuplicationAlreadyExists(Optional<Duplicates> duplicates, Component file, TextBlock original, Component otherFile, TextBlock duplicate) {
+ checkArgument(!duplicates.get().hasDuplicate(otherFile, duplicate),
+ "In-project duplicate %s in file %s is already associated to original %s in file %s", duplicate, otherFile.getKey(), original, file.getKey());
+ }
+
+ private static void checkDuplicationAlreadyExists(Optional<Duplicates> duplicates, Component file, TextBlock original, String otherFileKey, TextBlock duplicate) {
+ checkArgument(!duplicates.get().hasDuplicate(otherFileKey, duplicate),
+ "Cross-project duplicate %s in file %s is already associated to original %s in file %s", duplicate, otherFileKey, original, file.getKey());
+ }
+
+ private Duplicates getOrCreate(Component file, TextBlock original) {
+ Duplications duplications = duplicationsByComponentUuid.get(file.getUuid());
+ if (duplications == null) {
+ duplications = new Duplications();
+ duplicationsByComponentUuid.put(file.getUuid(), duplications);
+ }
+
+ return duplications.getOrCreate(original);
+ }
+
+ /**
+ * Represents the location of the file of one or more duplicate {@link TextBlock}.
+ */
+ private interface DuplicateLocation {
+
+ }
+
+ private enum InnerDuplicationLocation implements DuplicateLocation {
+ INSTANCE
+ }
+
+ @Immutable
+ private static final class InProjectDuplicationLocation implements DuplicateLocation {
+ private final Component component;
+
+ public InProjectDuplicationLocation(Component component) {
+ this.component = component;
+ }
+
+ public Component getComponent() {
+ return component;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ InProjectDuplicationLocation that = (InProjectDuplicationLocation) o;
+ return component.equals(that.component);
+ }
+
+ @Override
+ public int hashCode() {
+ return component.hashCode();
+ }
+ }
+
+ @Immutable
+ private static final class CrossProjectDuplicationLocation implements DuplicateLocation {
+ private final String fileKey;
+
+ private CrossProjectDuplicationLocation(String fileKey) {
+ this.fileKey = fileKey;
+ }
+
+ public String getFileKey() {
+ return fileKey;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ CrossProjectDuplicationLocation that = (CrossProjectDuplicationLocation) o;
+ return fileKey.equals(that.fileKey);
+ }
+
+ @Override
+ public int hashCode() {
+ return fileKey.hashCode();
+ }
+ }
+
+ private static final class Duplications {
+ private final Map<TextBlock, Duplicates> duplicatesByTextBlock = new HashMap<>();
+
+ public Duplicates getOrCreate(TextBlock textBlock) {
+ if (duplicatesByTextBlock.containsKey(textBlock)) {
+ return duplicatesByTextBlock.get(textBlock);
+ }
+ Duplicates res = new Duplicates();
+ duplicatesByTextBlock.put(textBlock, res);
+ return res;
+ }
+
+ public Set<Map.Entry<TextBlock, Duplicates>> getDuplicates() {
+ return duplicatesByTextBlock.entrySet();
+ }
+
+ public Optional<Duplicates> get(TextBlock original) {
+ return Optional.fromNullable(duplicatesByTextBlock.get(original));
+ }
+ }
+
+ private static final class Duplicates {
+ private final Map<DuplicateLocation, Set<TextBlock>> duplicatesByLocation = new HashMap<>();
+
+ public Iterable<DuplicateWithLocation> getDuplicates() {
+ return from(duplicatesByLocation.entrySet())
+ .transformAndConcat(MapEntryToDuplicateWithLocation.INSTANCE);
+ }
+
+ public void add(TextBlock duplicate) {
+ add(InnerDuplicationLocation.INSTANCE, duplicate);
+ }
+
+ public void add(Component otherFile, TextBlock duplicate) {
+ InProjectDuplicationLocation key = new InProjectDuplicationLocation(otherFile);
+ add(key, duplicate);
+ }
+
+ public void add(String otherFileKey, TextBlock duplicate) {
+ CrossProjectDuplicationLocation key = new CrossProjectDuplicationLocation(otherFileKey);
+ add(key, duplicate);
+ }
+
+ private void add(DuplicateLocation duplicateLocation, TextBlock duplicate) {
+ Set<TextBlock> textBlocks = duplicatesByLocation.get(duplicateLocation);
+ if (textBlocks == null) {
+ textBlocks = new HashSet<>(1);
+ duplicatesByLocation.put(duplicateLocation, textBlocks);
+ }
+ textBlocks.add(duplicate);
+ }
+
+ public boolean hasDuplicate(TextBlock duplicate) {
+ return containsEntry(InnerDuplicationLocation.INSTANCE, duplicate);
+ }
+
+ public boolean hasDuplicate(Component otherFile, TextBlock duplicate) {
+ return containsEntry(new InProjectDuplicationLocation(otherFile), duplicate);
+ }
+
+ public boolean hasDuplicate(String otherFileKey, TextBlock duplicate) {
+ return containsEntry(new CrossProjectDuplicationLocation(otherFileKey), duplicate);
+ }
+
+ private boolean containsEntry(DuplicateLocation duplicateLocation, TextBlock duplicate) {
+ Set<TextBlock> textBlocks = duplicatesByLocation.get(duplicateLocation);
+ return textBlocks != null && textBlocks.contains(duplicate);
+ }
+
+ private enum MapEntryToDuplicateWithLocation implements Function<Map.Entry<DuplicateLocation, Set<TextBlock>>, Iterable<DuplicateWithLocation>> {
+ INSTANCE;
+
+ @Override
+ @Nonnull
+ public Iterable<DuplicateWithLocation> apply(@Nonnull final Map.Entry<DuplicateLocation, Set<TextBlock>> entry) {
+ return from(entry.getValue())
+ .transform(new Function<TextBlock, DuplicateWithLocation>() {
+ @Override
+ @Nonnull
+ public DuplicateWithLocation apply(@Nonnull TextBlock input) {
+ return new DuplicateWithLocation(entry.getKey(), input);
+ }
+ });
+ }
+ }
+ }
+
+ @Immutable
+ private static final class DuplicateWithLocation {
+ private final DuplicateLocation location;
+ private final TextBlock duplicate;
+
+ private DuplicateWithLocation(DuplicateLocation location, TextBlock duplicate) {
+ this.location = location;
+ this.duplicate = duplicate;
+ }
+
+ public DuplicateLocation getLocation() {
+ return location;
+ }
+
+ public TextBlock getDuplicate() {
+ return duplicate;
+ }
+
+ }
+
+ private static void checkFileComponentArgument(Component file) {
+ checkComponentArgument(file, "file");
+ }
+
+ private static void checkComponentArgument(Component file, String argName) {
+ requireNonNull(file, format("%s can not be null", argName));
+ checkArgument(file.getType() == Component.Type.FILE, "type of %s argument must be FILE", argName);
+ }
+
+ private static void checkDuplicateTextBlockArgument(TextBlock duplicate) {
+ requireNonNull(duplicate, "duplicate can not be null");
+ }
+
+ private static void checkOriginalTextBlockArgument(TextBlock original) {
+ requireNonNull(original, "original can not be null");
+ }
+
+ private enum DuplicatesEntryToDuplication implements Function<Map.Entry<TextBlock, Duplicates>, Duplication> {
+ INSTANCE;
+
+ @Override
+ @Nonnull
+ public Duplication apply(@Nonnull Map.Entry<TextBlock, Duplicates> entry) {
+ return new Duplication(
+ entry.getKey(),
+ from(entry.getValue().getDuplicates()).transform(DuplicateLocationEntryToDuplicate.INSTANCE)
+ );
+ }
+
+ private enum DuplicateLocationEntryToDuplicate implements Function<DuplicateWithLocation, Duplicate> {
+ INSTANCE;
+
+ @Override
+ @Nonnull
+ public Duplicate apply(@Nonnull DuplicateWithLocation input) {
+ DuplicateLocation duplicateLocation = input.getLocation();
+ if (duplicateLocation instanceof InnerDuplicationLocation) {
+ return new InnerDuplicate(input.getDuplicate());
+ }
+ if (duplicateLocation instanceof InProjectDuplicationLocation) {
+ return new InProjectDuplicate(((InProjectDuplicationLocation) duplicateLocation).getComponent(), input.getDuplicate());
+ }
+ if (duplicateLocation instanceof CrossProjectDuplicationLocation) {
+ return new CrossProjectDuplicate(((CrossProjectDuplicationLocation) duplicateLocation).getFileKey(), input.getDuplicate());
+ }
+ throw new IllegalArgumentException("Unsupported DuplicationLocation type " + duplicateLocation.getClass());
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.duplication;
+
+import java.util.Objects;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import org.sonar.server.computation.component.Component;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+@Immutable
+public class InProjectDuplicate extends AbstractDuplicate {
+ private final Component file;
+
+ public InProjectDuplicate(Component file, TextBlock textBlock) {
+ super(textBlock);
+ requireNonNull(file, "file can not be null");
+ checkArgument(file.getType() == Component.Type.FILE, "file must be of type FILE");
+ this.file = file;
+ }
+
+ public Component getFile() {
+ return file;
+ }
+
+ @Override
+ public String toString() {
+ return "InProjectDuplicate{" +
+ "file=" + file +
+ ", textBlock=" + getTextBlock() +
+ '}';
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+ InProjectDuplicate that = (InProjectDuplicate) o;
+ return file.equals(that.file);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), file);
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.duplication;
+
+import javax.annotation.concurrent.Immutable;
+
+@Immutable
+public final class InnerDuplicate extends AbstractDuplicate {
+
+ public InnerDuplicate(TextBlock textBlock) {
+ super(textBlock);
+ }
+
+ @Override
+ public String toString() {
+ return "InnerDuplicate{" +
+ "textBlock=" + getTextBlock() +
+ '}';
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.duplication;
+
+import java.util.Objects;
+import javax.annotation.Nullable;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * A block of text in some file represented by its first line index (1-based) and its last line index (included).
+ * <p>
+ * This class defines a natural ordering which sorts {@link TextBlock} by lowest start line first and then, in case of
+ * same start line, by smallest size (ie. lowest end line).
+ * </p>
+ */
+public final class TextBlock implements Comparable<TextBlock> {
+ private final int start;
+ private final int end;
+
+ /**
+ * @throws IllegalArgumentException if {@code start} is 0 or less
+ * @throws IllegalStateException if {@code end} is less than {@code start}
+ */
+ public TextBlock(int start, int end) {
+ checkArgument(start > 0, "First line index must be >= 1");
+ checkArgument(end >= start, "Last line index must be >= first line index");
+ this.start = start;
+ this.end = end;
+ }
+
+ public int getStart() {
+ return start;
+ }
+
+ public int getEnd() {
+ return end;
+ }
+
+ @Override
+ public int compareTo(TextBlock other) {
+ int res = start - other.start;
+ if (res == 0) {
+ return end - other.end;
+ }
+ return res;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TextBlock textBlock = (TextBlock) o;
+ return start == textBlock.start && end == textBlock.end;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(start, end);
+ }
+
+ @Override
+ public String toString() {
+ return "TextBlock{" +
+ "start=" + start +
+ ", end=" + end +
+ '}';
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.server.computation.duplication;
+
+import javax.annotation.ParametersAreNonnullByDefault;
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.step;
+
+import org.sonar.batch.protocol.output.BatchReport;
+import org.sonar.core.util.CloseableIterator;
+import org.sonar.server.computation.batch.BatchReportReader;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.CrawlerDepthLimit;
+import org.sonar.server.computation.component.DepthTraversalTypeAwareCrawler;
+import org.sonar.server.computation.component.ReportTreeRootHolder;
+import org.sonar.server.computation.component.TypeAwareVisitorAdapter;
+import org.sonar.server.computation.duplication.DuplicationRepository;
+import org.sonar.server.computation.duplication.TextBlock;
+
+import static org.sonar.server.computation.component.ComponentVisitor.Order.POST_ORDER;
+
+/**
+ * Loads duplication information from the report and loads them into the {@link DuplicationRepository}.
+ */
+public class LoadDuplicationsFromReportStep implements ComputationStep {
+ private final ReportTreeRootHolder treeRootHolder;
+ private final BatchReportReader batchReportReader;
+ private final DuplicationRepository duplicationRepository;
+
+ public LoadDuplicationsFromReportStep(ReportTreeRootHolder treeRootHolder, BatchReportReader batchReportReader, DuplicationRepository duplicationRepository) {
+ this.treeRootHolder = treeRootHolder;
+ this.batchReportReader = batchReportReader;
+ this.duplicationRepository = duplicationRepository;
+ }
+
+ @Override
+ public String getDescription() {
+ return "Load inner and project duplications";
+ }
+
+ @Override
+ public void execute() {
+ new DepthTraversalTypeAwareCrawler(
+ new TypeAwareVisitorAdapter(CrawlerDepthLimit.FILE, POST_ORDER) {
+ @Override
+ public void visitFile(Component file) {
+ CloseableIterator<BatchReport.Duplication> duplications = batchReportReader.readComponentDuplications(file.getReportAttributes().getRef());
+ try {
+ while (duplications.hasNext()) {
+ loadDuplications(file, duplications.next());
+ }
+ } finally {
+ duplications.close();
+ }
+ }
+ }).visit(treeRootHolder.getRoot());
+ }
+
+ private void loadDuplications(Component file, BatchReport.Duplication duplication) {
+ TextBlock original = convert(duplication.getOriginPosition());
+ for (BatchReport.Duplicate duplicate : duplication.getDuplicateList()) {
+ if (duplicate.hasOtherFileRef()) {
+ duplicationRepository.addDuplication(file, original, treeRootHolder.getComponentByRef(duplicate.getOtherFileRef()), convert(duplicate.getRange()));
+ } else {
+ duplicationRepository.addDuplication(file, original, convert(duplicate.getRange()));
+ }
+ }
+ }
+
+ private static TextBlock convert(BatchReport.TextRange textRange) {
+ return new TextBlock(textRange.getStartLine(), textRange.getEndLine());
+ }
+}
PersistIssuesStep.class,
PersistProjectLinksStep.class,
PersistEventsStep.class,
+ LoadDuplicationsFromReportStep.class,
PersistDuplicationsStep.class,
PersistFileSourcesStep.class,
PersistTestsStep.class,
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.duplication;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class CrossProjectDuplicateTest {
+ private static final String FILE_KEY_1 = "file key 1";
+ private static final String FILE_KEY_2 = "file key 2";
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void constructors_throws_NPE_if_fileKey_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("fileKey can not be null");
+
+ new CrossProjectDuplicate(null, new TextBlock(1, 1));
+ }
+
+ @Test
+ public void constructors_throws_NPE_if_textBlock_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("textBlock of duplicate can not be null");
+
+ new CrossProjectDuplicate(FILE_KEY_1, null);
+ }
+
+ @Test
+ public void getTextBlock_returns_TextBlock_constructor_argument() {
+ TextBlock textBlock = new TextBlock(2, 3);
+
+ assertThat(new CrossProjectDuplicate(FILE_KEY_1, textBlock).getTextBlock()).isSameAs(textBlock);
+ }
+
+ @Test
+ public void getFileKey_returns_constructor_argument() {
+ assertThat(new CrossProjectDuplicate(FILE_KEY_1, new TextBlock(2, 3)).getFileKey()).isEqualTo(FILE_KEY_1);
+ }
+
+ @Test
+ public void equals_compares_on_file_and_TextBlock() {
+ TextBlock textBlock1 = new TextBlock(1, 2);
+
+ assertThat(new CrossProjectDuplicate(FILE_KEY_1, textBlock1)).isEqualTo(new CrossProjectDuplicate(FILE_KEY_1, new TextBlock(1, 2)));
+
+ assertThat(new CrossProjectDuplicate(FILE_KEY_1, textBlock1)).isNotEqualTo(new CrossProjectDuplicate(FILE_KEY_1, new TextBlock(1, 1)));
+ assertThat(new CrossProjectDuplicate(FILE_KEY_1, textBlock1)).isNotEqualTo(new CrossProjectDuplicate(FILE_KEY_2, textBlock1));
+ }
+
+ @Test
+ public void hashcode_depends_on_file_and_TextBlock() {
+ TextBlock textBlock = new TextBlock(1, 2);
+ assertThat(new CrossProjectDuplicate(FILE_KEY_1, textBlock).hashCode()).isEqualTo(new CrossProjectDuplicate(FILE_KEY_1, textBlock).hashCode());
+
+ assertThat(new CrossProjectDuplicate(FILE_KEY_1, textBlock).hashCode()).isNotEqualTo(new CrossProjectDuplicate(FILE_KEY_2, textBlock).hashCode());
+ assertThat(new CrossProjectDuplicate(FILE_KEY_1, textBlock).hashCode()).isNotEqualTo(new CrossProjectDuplicate(FILE_KEY_2, new TextBlock(1, 1)).hashCode());
+ }
+
+ @Test
+ public void verify_toString() {
+ assertThat(new CrossProjectDuplicate(FILE_KEY_1, new TextBlock(1, 2)).toString()).isEqualTo("CrossProjectDuplicate{fileKey='file key 1', textBlock=TextBlock{start=1, end=2}}");
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.duplication;
+
+import org.junit.Test;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.ReportComponent;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DuplicateTest {
+ @Test
+ public void duplicate_implementations_are_not_equals_to_each_other_even_if_TextBlock_is_the_same() {
+ TextBlock textBlock = new TextBlock(1, 2);
+
+ InnerDuplicate innerDuplicate = new InnerDuplicate(textBlock);
+ InProjectDuplicate inProjectDuplicate = new InProjectDuplicate(ReportComponent.builder(Component.Type.FILE, 1).build(), textBlock);
+ CrossProjectDuplicate crossProjectDuplicate = new CrossProjectDuplicate("file key", textBlock);
+
+ assertThat(innerDuplicate.equals(inProjectDuplicate)).isFalse();
+ assertThat(innerDuplicate.equals(crossProjectDuplicate)).isFalse();
+ assertThat(inProjectDuplicate.equals(crossProjectDuplicate)).isFalse();
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.duplication;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Arrays;
+import java.util.Set;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.ReportComponent;
+import org.sonar.server.util.WrapInSingleElementArray;
+
+import static com.google.common.base.Predicates.equalTo;
+import static com.google.common.base.Predicates.not;
+import static com.google.common.collect.FluentIterable.from;
+import static java.lang.String.format;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(DataProviderRunner.class)
+public class DuplicationRepositoryImplTest {
+
+ private static final Component COMPONENT_1 = ReportComponent.builder(Component.Type.FILE, 1).build();
+ private static final Component COMPONENT_2 = ReportComponent.builder(Component.Type.FILE, 2).build();
+ private static final Component COMPONENT_3 = ReportComponent.builder(Component.Type.FILE, 3).build();
+ private static final TextBlock ORIGINAL_TEXTBLOCK = new TextBlock(1, 2);
+ private static final TextBlock COPY_OF_ORIGINAL_TEXTBLOCK = new TextBlock(1, 2);
+ private static final TextBlock DUPLICATE_TEXTBLOCK_1 = new TextBlock(15, 15);
+ private static final TextBlock DUPLICATE_TEXTBLOCK_2 = new TextBlock(15, 16);
+ private static final String FILE_KEY_1 = "1";
+ private static final String FILE_KEY_2 = "2";
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private DuplicationRepository underTest = new DuplicationRepositoryImpl();
+
+ @Test
+ public void getDuplications_throws_NPE_if_Component_argument_is_null() {
+ expectFileArgumentNPE();
+
+ underTest.getDuplications(null);
+ }
+
+ @Test
+ @UseDataProvider("allComponentTypesButFile")
+ public void getDuplications_throws_IAE_if_Component_type_is_not_FILE(Component.Type type) {
+ expectFileTypeIAE();
+
+ Component component = mockComponentGetType(type);
+
+ underTest.getDuplications(component);
+ }
+
+ @Test
+ public void getDuplications_returns_empty_set_when_repository_is_empty() {
+ assertNoDuplication(COMPONENT_1);
+ }
+
+ @Test
+ public void addDuplication_inner_throws_NPE_if_file_argument_is_null() {
+ expectFileArgumentNPE();
+
+ underTest.addDuplication(null, ORIGINAL_TEXTBLOCK, DUPLICATE_TEXTBLOCK_1);
+ }
+
+ @Test
+ public void addDuplication_inner_throws_NPE_if_original_argument_is_null() {
+ expectOriginalArgumentNPE();
+
+ underTest.addDuplication(COMPONENT_1, null, DUPLICATE_TEXTBLOCK_1);
+ }
+
+ @Test
+ public void addDuplication_inner_throws_NPE_if_duplicate_argument_is_null() {
+ expectDuplicateArgumentNPE();
+
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, null);
+ }
+
+ @Test
+ @UseDataProvider("allComponentTypesButFile")
+ public void addDuplication_inner_throws_IAE_if_file_type_is_not_FILE(Component.Type type) {
+ expectFileTypeIAE();
+
+ Component component = mockComponentGetType(type);
+
+ underTest.addDuplication(component, ORIGINAL_TEXTBLOCK, DUPLICATE_TEXTBLOCK_1);
+ }
+
+ @Test
+ public void addDuplication_inner_throws_IAE_if_original_and_duplicate_are_equal() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("original and duplicate TextBlocks can not be the same");
+
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, COPY_OF_ORIGINAL_TEXTBLOCK);
+ }
+
+ @Test
+ public void addDuplication_inner_throws_IAE_if_duplication_already_exists() {
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, DUPLICATE_TEXTBLOCK_1);
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage(format(
+ "Inner duplicate %s is already associated to original %s in file %s",
+ DUPLICATE_TEXTBLOCK_1, ORIGINAL_TEXTBLOCK, COMPONENT_1.getKey()));
+
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, DUPLICATE_TEXTBLOCK_1);
+ }
+
+ @Test
+ public void addDuplication_inner_throws_IAE_if_reverse_duplication_already_exists() {
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, DUPLICATE_TEXTBLOCK_1);
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage(format(
+ "Inner duplicate %s is already associated to original %s in file %s",
+ ORIGINAL_TEXTBLOCK, DUPLICATE_TEXTBLOCK_1, COMPONENT_1.getKey()));
+
+ underTest.addDuplication(COMPONENT_1, DUPLICATE_TEXTBLOCK_1, ORIGINAL_TEXTBLOCK);
+ }
+
+ @Test
+ public void addDuplication_inner_throws_IAE_if_reverse_duplication_already_exists_and_duplicate_has_duplicates_of_its_own() {
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, DUPLICATE_TEXTBLOCK_1);
+ underTest.addDuplication(COMPONENT_1, DUPLICATE_TEXTBLOCK_1, DUPLICATE_TEXTBLOCK_2);
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage(format(
+ "Inner duplicate %s is already associated to original %s in file %s",
+ ORIGINAL_TEXTBLOCK, DUPLICATE_TEXTBLOCK_1, COMPONENT_1.getKey()));
+
+ underTest.addDuplication(COMPONENT_1, DUPLICATE_TEXTBLOCK_1, ORIGINAL_TEXTBLOCK);
+ }
+
+ @Test
+ public void addDuplication_inner_is_returned_by_getDuplications() {
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, DUPLICATE_TEXTBLOCK_1);
+
+ Set<Duplication> duplications = underTest.getDuplications(COMPONENT_1);
+ assertThat(duplications).hasSize(1);
+ assertDuplication(
+ duplications.iterator().next(),
+ ORIGINAL_TEXTBLOCK,
+ new InnerDuplicate(DUPLICATE_TEXTBLOCK_1));
+
+ assertNoDuplication(COMPONENT_2);
+ }
+
+ @Test
+ public void addDuplication_inner_called_multiple_times_populate_a_single_Duplication() {
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, DUPLICATE_TEXTBLOCK_2);
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, DUPLICATE_TEXTBLOCK_1);
+
+ Set<Duplication> duplications = underTest.getDuplications(COMPONENT_1);
+ assertThat(duplications).hasSize(1);
+ assertDuplication(
+ duplications.iterator().next(),
+ ORIGINAL_TEXTBLOCK,
+ new InnerDuplicate(DUPLICATE_TEXTBLOCK_1), new InnerDuplicate(DUPLICATE_TEXTBLOCK_2));
+
+ assertNoDuplication(COMPONENT_2);
+ }
+
+ @Test
+ public void addDuplication_inProject_throws_NPE_if_file_argument_is_null() {
+ expectFileArgumentNPE();
+
+ underTest.addDuplication(null, ORIGINAL_TEXTBLOCK, COMPONENT_2, DUPLICATE_TEXTBLOCK_1);
+ }
+
+ @Test
+ public void addDuplication_inProject_throws_NPE_if_original_argument_is_null() {
+ expectOriginalArgumentNPE();
+
+ underTest.addDuplication(COMPONENT_1, null, COMPONENT_2, DUPLICATE_TEXTBLOCK_1);
+ }
+
+ @Test
+ public void addDuplication_inProject_throws_NPE_if_otherFile_argument_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("otherFile can not be null");
+
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, (Component) null, DUPLICATE_TEXTBLOCK_1);
+ }
+
+ @Test
+ @UseDataProvider("allComponentTypesButFile")
+ public void addDuplication_inProject_throws_NPE_if_otherFile_type_is_not_FILE(Component.Type type) {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("type of otherFile argument must be FILE");
+
+ Component component = mockComponentGetType(type);
+
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, component, DUPLICATE_TEXTBLOCK_1);
+ }
+
+ @Test
+ public void addDuplication_inProject_throws_NPE_if_duplicate_argument_is_null() {
+ expectDuplicateArgumentNPE();
+
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, COMPONENT_2, null);
+ }
+
+ @Test
+ @UseDataProvider("allComponentTypesButFile")
+ public void addDuplication_inProject_throws_NPE_if_file_type_is_not_FILE(Component.Type type) {
+ expectFileTypeIAE();
+
+ Component component = mockComponentGetType(type);
+
+ underTest.addDuplication(component, ORIGINAL_TEXTBLOCK, COMPONENT_2, DUPLICATE_TEXTBLOCK_1);
+ }
+
+ @Test
+ public void addDuplication_inProject_throws_NPE_if_file_and_otherFile_are_the_same() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("file and otherFile Components can not be the same");
+
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, COMPONENT_1, DUPLICATE_TEXTBLOCK_1);
+ }
+
+ @Test
+ public void addDuplication_inProject_throws_IAE_if_duplication_already_exists() {
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, COMPONENT_2, DUPLICATE_TEXTBLOCK_1);
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage(format(
+ "In-project duplicate %s in file %s is already associated to original %s in file %s",
+ DUPLICATE_TEXTBLOCK_1, COMPONENT_2.getKey(), ORIGINAL_TEXTBLOCK, COMPONENT_1.getKey()));
+
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, COMPONENT_2, DUPLICATE_TEXTBLOCK_1);
+ }
+
+ @Test
+ public void addDuplication_inProject_is_returned_by_getDuplications() {
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, COMPONENT_2, DUPLICATE_TEXTBLOCK_1);
+
+ Set<Duplication> duplications = underTest.getDuplications(COMPONENT_1);
+ assertThat(duplications).hasSize(1);
+ assertDuplication(
+ duplications.iterator().next(),
+ ORIGINAL_TEXTBLOCK,
+ new InProjectDuplicate(COMPONENT_2, DUPLICATE_TEXTBLOCK_1));
+
+ assertNoDuplication(COMPONENT_2);
+ }
+
+ @Test
+ public void addDuplication_inProject_called_multiple_times_populate_a_single_Duplication() {
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, COMPONENT_2, DUPLICATE_TEXTBLOCK_2);
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, COMPONENT_2, DUPLICATE_TEXTBLOCK_1);
+
+ Set<Duplication> duplications = underTest.getDuplications(COMPONENT_1);
+ assertThat(duplications).hasSize(1);
+ assertDuplication(
+ duplications.iterator().next(),
+ ORIGINAL_TEXTBLOCK,
+ new InProjectDuplicate(COMPONENT_2, DUPLICATE_TEXTBLOCK_1), new InProjectDuplicate(COMPONENT_2, DUPLICATE_TEXTBLOCK_2));
+
+ assertNoDuplication(COMPONENT_2);
+ }
+
+ @Test
+ public void addDuplication_inProject_called_multiple_times_with_different_components_populate_a_single_Duplication() {
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, COMPONENT_2, DUPLICATE_TEXTBLOCK_2);
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, COMPONENT_3, DUPLICATE_TEXTBLOCK_2);
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, COMPONENT_3, DUPLICATE_TEXTBLOCK_1);
+
+ Set<Duplication> duplications = underTest.getDuplications(COMPONENT_1);
+ assertThat(duplications).hasSize(1);
+ assertDuplication(
+ duplications.iterator().next(),
+ ORIGINAL_TEXTBLOCK,
+ new InProjectDuplicate(COMPONENT_2, DUPLICATE_TEXTBLOCK_2), new InProjectDuplicate(COMPONENT_3, DUPLICATE_TEXTBLOCK_1), new InProjectDuplicate(COMPONENT_3, DUPLICATE_TEXTBLOCK_2));
+
+ assertNoDuplication(COMPONENT_2);
+ assertNoDuplication(COMPONENT_3);
+ }
+
+ @Test
+ public void addDuplication_crossProject_throws_NPE_if_file_argument_is_null() {
+ expectFileArgumentNPE();
+
+ underTest.addDuplication(null, ORIGINAL_TEXTBLOCK, FILE_KEY_1, DUPLICATE_TEXTBLOCK_1);
+ }
+
+ @Test
+ public void addDuplication_crossProject_throws_NPE_if_original_argument_is_null() {
+ expectOriginalArgumentNPE();
+
+ underTest.addDuplication(COMPONENT_1, null, FILE_KEY_1, DUPLICATE_TEXTBLOCK_1);
+ }
+
+ @Test
+ public void addDuplication_crossProject_throws_NPE_if_otherFileKey_argument_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("otherFileKey can not be null");
+
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, (String) null, DUPLICATE_TEXTBLOCK_1);
+ }
+
+ @Test
+ public void addDuplication_crossProject_throws_NPE_if_duplicate_argument_is_null() {
+ expectDuplicateArgumentNPE();
+
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_KEY_1, null);
+ }
+
+ @Test
+ @Ignore
+ public void addDuplication_crossProject_throws_IAE_if_otherFileKey_is_key_of_Component_in_the_project() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("type of file argument must be FILE");
+
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, COMPONENT_2.getKey(), DUPLICATE_TEXTBLOCK_1);
+ }
+
+ @Test
+ @UseDataProvider("allComponentTypesButFile")
+ public void addDuplication_crossProject_throws_NPE_if_file_type_is_not_FILE(Component.Type type) {
+ expectFileTypeIAE();
+
+ Component component = mockComponentGetType(type);
+
+ underTest.addDuplication(component, ORIGINAL_TEXTBLOCK, FILE_KEY_1, DUPLICATE_TEXTBLOCK_1);
+ }
+
+ @Test
+ public void addDuplication_crossProject_throws_IAE_if_duplication_already_exists() {
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_KEY_1, DUPLICATE_TEXTBLOCK_1);
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage(format(
+ "Cross-project duplicate %s in file %s is already associated to original %s in file %s",
+ DUPLICATE_TEXTBLOCK_1, FILE_KEY_1, ORIGINAL_TEXTBLOCK, COMPONENT_1.getKey()));
+
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_KEY_1, DUPLICATE_TEXTBLOCK_1);
+ }
+
+ @Test
+ public void addDuplication_crossProject_is_returned_by_getDuplications() {
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_KEY_1, DUPLICATE_TEXTBLOCK_1);
+
+ Set<Duplication> duplications = underTest.getDuplications(COMPONENT_1);
+ assertThat(duplications).hasSize(1);
+ assertDuplication(
+ duplications.iterator().next(),
+ ORIGINAL_TEXTBLOCK,
+ new CrossProjectDuplicate(FILE_KEY_1, DUPLICATE_TEXTBLOCK_1));
+
+ assertNoDuplication(COMPONENT_2);
+ }
+
+ @Test
+ public void addDuplication_crossProject_called_multiple_times_populate_a_single_Duplication() {
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_KEY_1, DUPLICATE_TEXTBLOCK_2);
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_KEY_1, DUPLICATE_TEXTBLOCK_1);
+
+ Set<Duplication> duplications = underTest.getDuplications(COMPONENT_1);
+ assertThat(duplications).hasSize(1);
+ assertDuplication(
+ duplications.iterator().next(),
+ ORIGINAL_TEXTBLOCK,
+ new CrossProjectDuplicate(FILE_KEY_1, DUPLICATE_TEXTBLOCK_1), new CrossProjectDuplicate(FILE_KEY_1, DUPLICATE_TEXTBLOCK_2));
+
+ assertNoDuplication(COMPONENT_2);
+ }
+
+ @Test
+ public void addDuplication_crossProject_called_multiple_times_with_different_fileKeys_populate_a_single_Duplication() {
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_KEY_1, DUPLICATE_TEXTBLOCK_2);
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_KEY_2, DUPLICATE_TEXTBLOCK_2);
+ underTest.addDuplication(COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_KEY_2, DUPLICATE_TEXTBLOCK_1);
+
+ Set<Duplication> duplications = underTest.getDuplications(COMPONENT_1);
+ assertThat(duplications).hasSize(1);
+ assertDuplication(
+ duplications.iterator().next(),
+ ORIGINAL_TEXTBLOCK,
+ new CrossProjectDuplicate(FILE_KEY_1, DUPLICATE_TEXTBLOCK_2), new CrossProjectDuplicate(FILE_KEY_2, DUPLICATE_TEXTBLOCK_1), new CrossProjectDuplicate(FILE_KEY_2, DUPLICATE_TEXTBLOCK_2));
+
+ assertNoDuplication(COMPONENT_2);
+ }
+
+ @DataProvider
+ public static Object[][] allComponentTypesButFile() {
+ return from(Arrays.asList(Component.Type.values()))
+ .filter(not(equalTo(Component.Type.FILE)))
+ .transform(WrapInSingleElementArray.INSTANCE)
+ .toArray(Object[].class);
+ }
+
+ private static void assertDuplication(Duplication duplication, TextBlock original, Duplicate... duplicates) {
+ assertThat(duplication.getOriginal()).isEqualTo(original);
+ assertThat(duplication.getDuplicates()).containsExactly(duplicates);
+ }
+
+ private void assertNoDuplication(Component component) {
+ assertThat(underTest.getDuplications(component)).isEmpty();
+ }
+
+ private void expectFileArgumentNPE() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("file can not be null");
+ }
+
+ private void expectOriginalArgumentNPE() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("original can not be null");
+ }
+
+ private void expectDuplicateArgumentNPE() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("duplicate can not be null");
+ }
+
+ private void expectFileTypeIAE() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("type of file argument must be FILE");
+ }
+
+ private Component mockComponentGetType(Component.Type type) {
+ Component component = mock(Component.class);
+ when(component.getType()).thenReturn(type);
+ return component;
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.duplication;
+
+import java.util.Set;
+import org.junit.rules.ExternalResource;
+import org.sonar.server.computation.batch.TreeRootHolderRule;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.ComponentProvider;
+import org.sonar.server.computation.component.NoComponentProvider;
+import org.sonar.server.computation.component.TreeComponentProvider;
+import org.sonar.server.computation.component.TreeRootHolderComponentProvider;
+
+public class DuplicationRepositoryRule extends ExternalResource implements DuplicationRepository {
+ private final ComponentProvider componentProvider;
+ private DuplicationRepositoryImpl delegate;
+
+ private DuplicationRepositoryRule(ComponentProvider componentProvider) {
+ this.componentProvider = componentProvider;
+ }
+
+ public static DuplicationRepositoryRule standalone() {
+ return new DuplicationRepositoryRule(NoComponentProvider.INSTANCE);
+ }
+
+ public static DuplicationRepositoryRule create(TreeRootHolderRule treeRootHolder) {
+ return new DuplicationRepositoryRule(new TreeRootHolderComponentProvider(treeRootHolder));
+ }
+
+ public static DuplicationRepositoryRule create(Component root) {
+ return new DuplicationRepositoryRule(new TreeComponentProvider(root));
+ }
+
+ @Override
+ protected void before() throws Throwable {
+ this.delegate = new DuplicationRepositoryImpl();
+ }
+
+ @Override
+ protected void after() {
+ this.componentProvider.reset();
+ this.delegate = null;
+ }
+
+ public Set<Duplication> getDuplications(int fileRef) {
+ componentProvider.ensureInitialized();
+
+ return delegate.getDuplications(componentProvider.getByRef(fileRef));
+ }
+
+ public DuplicationRepositoryRule addDuplication(int fileRef, TextBlock original, TextBlock duplicate) {
+ componentProvider.ensureInitialized();
+
+ delegate.addDuplication(componentProvider.getByRef(fileRef), original, duplicate);
+
+ return this;
+ }
+
+ public DuplicationRepositoryRule addDuplication(int fileRef, TextBlock original, int otherFileRef, TextBlock duplicate) {
+ componentProvider.ensureInitialized();
+
+ delegate.addDuplication(componentProvider.getByRef(fileRef), original, componentProvider.getByRef(otherFileRef), duplicate);
+
+ return this;
+ }
+
+ public DuplicationRepositoryRule addDuplication(int fileRef, TextBlock original, String otherFileKey, TextBlock duplicate) {
+ componentProvider.ensureInitialized();
+
+ delegate.addDuplication(componentProvider.getByRef(fileRef), original, otherFileKey, duplicate);
+
+ return this;
+ }
+
+ @Override
+ public Set<Duplication> getDuplications(Component file) {
+ return delegate.getDuplications(file);
+ }
+
+ @Override
+ public void addDuplication(Component file, TextBlock original, TextBlock duplicate) {
+ delegate.addDuplication(file, original, duplicate);
+ }
+
+ @Override
+ public void addDuplication(Component file, TextBlock original, Component otherFile, TextBlock duplicate) {
+ delegate.addDuplication(file, original, otherFile, duplicate);
+ }
+
+ @Override
+ public void addDuplication(Component file, TextBlock original, String otherFileKey, TextBlock duplicate) {
+ delegate.addDuplication(file, original, otherFileKey, duplicate);
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.duplication;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.ReportComponent;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class DuplicationTest {
+ private static final TextBlock SOME_ORIGINAL_TEXTBLOCK = new TextBlock(1, 2);
+ private static final TextBlock TEXT_BLOCK_1 = new TextBlock(2, 2);
+ private static final TextBlock TEXT_BLOCK_2 = new TextBlock(2, 3);
+ private static final ReportComponent FILE_COMPONENT_1 = ReportComponent.builder(Component.Type.FILE, 1).build();
+ private static final ReportComponent FILE_COMPONENT_2 = ReportComponent.builder(Component.Type.FILE, 2).build();
+ private static final String FILE_KEY_1 = "1";
+ private static final String FILE_KEY_2 = "2";
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void constructor_throws_NPE_if_original_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("original TextBlock can not be null");
+
+ new Duplication(null, Collections.<Duplicate>emptySet());
+ }
+
+ @Test
+ public void constructor_throws_NPE_if_duplicates_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("duplicates can not be null");
+
+ new Duplication(SOME_ORIGINAL_TEXTBLOCK, null);
+ }
+
+ @Test
+ public void constructor_throws_IAE_if_duplicates_is_empty() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("duplicates can not be empty");
+
+ new Duplication(SOME_ORIGINAL_TEXTBLOCK, Collections.<Duplicate>emptySet());
+ }
+
+ @Test
+ public void constructor_throws_NPE_if_duplicates_contains_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("duplicates can not contain null");
+
+ new Duplication(SOME_ORIGINAL_TEXTBLOCK, new HashSet<>(Arrays.asList(mock(Duplicate.class), null, mock(Duplicate.class))));
+ }
+
+ @Test
+ public void constructor_throws_IAE_if_duplicates_contains_InnerDuplicate_of_original() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("TextBlock of an InnerDuplicate can not be the original TextBlock");
+
+ new Duplication(SOME_ORIGINAL_TEXTBLOCK, new HashSet<>(Arrays.asList(mock(Duplicate.class), new InnerDuplicate(SOME_ORIGINAL_TEXTBLOCK), mock(Duplicate.class))));
+ }
+
+ @Test
+ public void constructor_throws_IAE_when_attempting_to_sort_Duplicate_of_unkown_type() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Unsupported type of Duplicate " + MyDuplicate.class.getName());
+
+ new Duplication(SOME_ORIGINAL_TEXTBLOCK, ImmutableSet.<Duplicate>of(new MyDuplicate(), new MyDuplicate()));
+ }
+
+ private static final class MyDuplicate implements Duplicate {
+
+ @Override
+ public TextBlock getTextBlock() {
+ throw new UnsupportedOperationException("getTextBlock not implemented");
+ }
+ }
+
+ @Test
+ public void getOriginal_returns_original() {
+ assertThat(new Duplication(SOME_ORIGINAL_TEXTBLOCK, ImmutableSet.of(mock(Duplicate.class))).getOriginal()).isSameAs(SOME_ORIGINAL_TEXTBLOCK);
+ }
+
+ @Test
+ public void getDuplicates_sorts_duplicates_by_Inner_then_InProject_then_CrossProject() {
+ CrossProjectDuplicate crossProjectDuplicate = new CrossProjectDuplicate("some key", TEXT_BLOCK_1);
+ InProjectDuplicate inProjectDuplicate = new InProjectDuplicate(FILE_COMPONENT_1, TEXT_BLOCK_1);
+ InnerDuplicate innerDuplicate = new InnerDuplicate(TEXT_BLOCK_1);
+
+ Duplication duplication = new Duplication(
+ SOME_ORIGINAL_TEXTBLOCK,
+ shuffledList(crossProjectDuplicate, inProjectDuplicate, innerDuplicate));
+
+ assertThat(duplication.getDuplicates()).containsExactly(innerDuplicate, inProjectDuplicate, crossProjectDuplicate);
+ }
+
+ @Test
+ public void getDuplicates_sorts_duplicates_of_InnerDuplicate_by_TextBlock() {
+ InnerDuplicate innerDuplicate1 = new InnerDuplicate(TEXT_BLOCK_2);
+ InnerDuplicate innerDuplicate2 = new InnerDuplicate(new TextBlock(3, 3));
+ InnerDuplicate innerDuplicate3 = new InnerDuplicate(new TextBlock(3, 4));
+ InnerDuplicate innerDuplicate4 = new InnerDuplicate(new TextBlock(4, 4));
+
+ assertGetDuplicatesSorting(innerDuplicate1, innerDuplicate2, innerDuplicate3, innerDuplicate4);
+ }
+
+ @Test
+ public void getDuplicates_sorts_duplicates_of_InProjectDuplicate_by_component_then_TextBlock() {
+ InProjectDuplicate innerDuplicate1 = new InProjectDuplicate(FILE_COMPONENT_1, TEXT_BLOCK_1);
+ InProjectDuplicate innerDuplicate2 = new InProjectDuplicate(FILE_COMPONENT_1, TEXT_BLOCK_2);
+ InProjectDuplicate innerDuplicate3 = new InProjectDuplicate(FILE_COMPONENT_2, TEXT_BLOCK_1);
+ InProjectDuplicate innerDuplicate4 = new InProjectDuplicate(FILE_COMPONENT_2, TEXT_BLOCK_2);
+
+ assertGetDuplicatesSorting(innerDuplicate1, innerDuplicate2, innerDuplicate3, innerDuplicate4);
+ }
+
+ @Test
+ public void getDuplicates_sorts_duplicates_of_CrossProjectDuplicate_by_fileKey_then_TextBlock() {
+ CrossProjectDuplicate innerDuplicate1 = new CrossProjectDuplicate(FILE_KEY_1, TEXT_BLOCK_1);
+ CrossProjectDuplicate innerDuplicate2 = new CrossProjectDuplicate(FILE_KEY_1, TEXT_BLOCK_2);
+ CrossProjectDuplicate innerDuplicate3 = new CrossProjectDuplicate(FILE_KEY_2, TEXT_BLOCK_1);
+ CrossProjectDuplicate innerDuplicate4 = new CrossProjectDuplicate(FILE_KEY_2, TEXT_BLOCK_2);
+
+ assertGetDuplicatesSorting(innerDuplicate1, innerDuplicate2, innerDuplicate3, innerDuplicate4);
+ }
+
+ @Test
+ public void equals_compares_on_original_and_duplicates() {
+ Duplication duplication = new Duplication(SOME_ORIGINAL_TEXTBLOCK, Arrays.<Duplicate>asList(new InnerDuplicate(TEXT_BLOCK_1)));
+
+ assertThat(duplication).isEqualTo(duplication);
+ assertThat(duplication).isEqualTo(new Duplication(SOME_ORIGINAL_TEXTBLOCK, Arrays.<Duplicate>asList(new InnerDuplicate(TEXT_BLOCK_1))));
+ assertThat(duplication).isNotEqualTo(new Duplication(SOME_ORIGINAL_TEXTBLOCK, Arrays.<Duplicate>asList(new InnerDuplicate(TEXT_BLOCK_2))));
+ assertThat(duplication).isNotEqualTo(new Duplication(TEXT_BLOCK_1, Arrays.<Duplicate>asList(new InnerDuplicate(SOME_ORIGINAL_TEXTBLOCK))));
+ }
+
+ @Test
+ public void hashcode_is_based_on_original_only() {
+ Duplication duplication = new Duplication(SOME_ORIGINAL_TEXTBLOCK, Arrays.<Duplicate>asList(new InnerDuplicate(TEXT_BLOCK_1)));
+
+ assertThat(duplication.hashCode()).isEqualTo(new Duplication(SOME_ORIGINAL_TEXTBLOCK, Arrays.<Duplicate>asList(new InnerDuplicate(TEXT_BLOCK_1))).hashCode());
+ assertThat(duplication.hashCode()).isNotEqualTo(new Duplication(SOME_ORIGINAL_TEXTBLOCK, Arrays.<Duplicate>asList(new InnerDuplicate(TEXT_BLOCK_2))).hashCode());
+ assertThat(duplication.hashCode()).isNotEqualTo(new Duplication(TEXT_BLOCK_1, Arrays.<Duplicate>asList(new InnerDuplicate(SOME_ORIGINAL_TEXTBLOCK))).hashCode());
+ }
+
+ @Test
+ public void verify_toString() {
+ Duplication duplication = new Duplication(
+ SOME_ORIGINAL_TEXTBLOCK,
+ Arrays.<Duplicate>asList(new InnerDuplicate(TEXT_BLOCK_1)));
+
+ assertThat(duplication.toString())
+ .isEqualTo("Duplication{original=TextBlock{start=1, end=2}, duplicates=[InnerDuplicate{textBlock=TextBlock{start=2, end=2}}]}");
+ }
+
+ @SafeVarargs
+ private final <T extends Duplicate> void assertGetDuplicatesSorting(T... expected) {
+ Duplication duplication = new Duplication(SOME_ORIGINAL_TEXTBLOCK, shuffledList(expected));
+
+ assertThat(duplication.getDuplicates()).containsExactly(expected);
+ }
+
+ private static List<Duplicate> shuffledList(Duplicate... duplicates) {
+ List<Duplicate> res = new ArrayList<>(Arrays.asList(duplicates));
+ Collections.shuffle(res);
+ return res;
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.duplication;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.ReportComponent;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class InProjectDuplicateTest {
+ private static final Component FILE_1 = ReportComponent.builder(Component.Type.FILE, 1).build();
+ private static final Component FILE_2 = ReportComponent.builder(Component.Type.FILE, 2).build();
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void constructors_throws_NPE_if_file_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("file can not be null");
+
+ new InProjectDuplicate(null, new TextBlock(1, 1));
+ }
+
+ @Test
+ public void constructors_throws_NPE_if_textBlock_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("textBlock of duplicate can not be null");
+
+ new InProjectDuplicate(FILE_1, null);
+ }
+
+ @Test
+ public void constructors_throws_IAE_if_type_of_file_argument_is_not_FILE() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("file must be of type FILE");
+
+ new InProjectDuplicate(ReportComponent.builder(Component.Type.PROJECT, 1).build(), new TextBlock(1, 1));
+ }
+
+ @Test
+ public void getTextBlock_returns_TextBlock_constructor_argument() {
+ TextBlock textBlock = new TextBlock(2, 3);
+
+ assertThat(new InProjectDuplicate(FILE_1, textBlock).getTextBlock()).isSameAs(textBlock);
+ }
+
+ @Test
+ public void getFile_returns_Component_constructor_argument() {
+ assertThat(new InProjectDuplicate(FILE_1, new TextBlock(2, 3)).getFile()).isSameAs(FILE_1);
+ }
+
+ @Test
+ public void equals_compares_on_file_and_TextBlock() {
+ TextBlock textBlock1 = new TextBlock(1, 2);
+
+ assertThat(new InProjectDuplicate(FILE_1, textBlock1)).isEqualTo(new InProjectDuplicate(FILE_1, new TextBlock(1, 2)));
+
+ assertThat(new InProjectDuplicate(FILE_1, textBlock1)).isNotEqualTo(new InProjectDuplicate(FILE_1, new TextBlock(1, 1)));
+ assertThat(new InProjectDuplicate(FILE_1, textBlock1)).isNotEqualTo(new InProjectDuplicate(FILE_2, textBlock1));
+ }
+
+ @Test
+ public void hashcode_depends_on_file_and_TextBlock() {
+ TextBlock textBlock = new TextBlock(1, 2);
+ assertThat(new InProjectDuplicate(FILE_1, textBlock).hashCode()).isEqualTo(new InProjectDuplicate(FILE_1, textBlock).hashCode());
+
+ assertThat(new InProjectDuplicate(FILE_1, textBlock).hashCode()).isNotEqualTo(new InProjectDuplicate(FILE_2, textBlock).hashCode());
+ assertThat(new InProjectDuplicate(FILE_1, textBlock).hashCode()).isNotEqualTo(new InProjectDuplicate(FILE_2, new TextBlock(1, 1)).hashCode());
+ }
+
+ @Test
+ public void verify_toString() {
+ assertThat(new InProjectDuplicate(FILE_1, new TextBlock(1, 2)).toString()).isEqualTo("InProjectDuplicate{file=ReportComponent{ref=1, key='key_1', type=FILE}, textBlock=TextBlock{start=1, end=2}}");
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.duplication;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class InnerDuplicateTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void constructors_throws_NPE_if_textBlock_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("textBlock of duplicate can not be null");
+
+ new InnerDuplicate(null);
+ }
+
+ @Test
+ public void getTextBlock_returns_TextBlock_constructor_argument() {
+ TextBlock textBlock = new TextBlock(2, 3);
+ assertThat(new InnerDuplicate(textBlock).getTextBlock()).isSameAs(textBlock);
+ }
+
+ @Test
+ public void equals_compares_on_TextBlock() {
+ assertThat(new InnerDuplicate(new TextBlock(1, 2))).isEqualTo(new InnerDuplicate(new TextBlock(1, 2)));
+ assertThat(new InnerDuplicate(new TextBlock(1, 2))).isNotEqualTo(new InnerDuplicate(new TextBlock(1, 1)));
+ }
+
+ @Test
+ public void hashcode_is_TextBlock_hashcode() {
+ TextBlock textBlock = new TextBlock(1, 2);
+ assertThat(new InnerDuplicate(textBlock).hashCode()).isEqualTo(textBlock.hashCode());
+ }
+
+ @Test
+ public void verify_toString() {
+ assertThat(new InnerDuplicate(new TextBlock(1, 2)).toString()).isEqualTo("InnerDuplicate{textBlock=TextBlock{start=1, end=2}}");
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.duplication;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class TextBlockTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void constructor_throws_IAE_if_start_is_0() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("First line index must be >= 1");
+
+ new TextBlock(0, 2);
+ }
+
+ @Test
+ public void constructor_throws_IAE_if_end_is_less_than_start() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Last line index must be >= first line index");
+
+ new TextBlock(1, 0);
+ }
+
+ @Test
+ public void getStart_returns_constructor_argument() {
+ TextBlock textBlock = new TextBlock(15, 300);
+
+ assertThat(textBlock.getStart()).isEqualTo(15);
+ }
+
+ @Test
+ public void getEnd_returns_constructor_argument() {
+ TextBlock textBlock = new TextBlock(15, 300);
+
+ assertThat(textBlock.getEnd()).isEqualTo(300);
+ }
+
+ @Test
+ public void equals_compares_on_start_and_end() {
+ assertThat(new TextBlock(15, 15)).isEqualTo(new TextBlock(15, 15));
+ assertThat(new TextBlock(15, 300)).isEqualTo(new TextBlock(15, 300));
+ assertThat(new TextBlock(15, 300)).isNotEqualTo(new TextBlock(15, 15));
+ }
+
+ @Test
+ public void hashcode_is_based__on_start_and_end() {
+ assertThat(new TextBlock(15, 15).hashCode()).isEqualTo(new TextBlock(15, 15).hashCode());
+ assertThat(new TextBlock(15, 300).hashCode()).isEqualTo(new TextBlock(15, 300).hashCode());
+ assertThat(new TextBlock(15, 300).hashCode()).isNotEqualTo(new TextBlock(15, 15).hashCode());
+ }
+
+ @Test
+ public void TextBlock_defines_natural_order_by_start_then_end() {
+ TextBlock textBlock1 = new TextBlock(1, 1);
+ TextBlock textBlock2 = new TextBlock(1, 2);
+ TextBlock textBlock3 = new TextBlock(2, 3);
+ TextBlock textBlock4 = new TextBlock(2, 4);
+ TextBlock textBlock5 = new TextBlock(5, 5);
+
+ List<TextBlock> shuffledList = new ArrayList<>(Arrays.asList(textBlock1, textBlock2, textBlock3, textBlock4, textBlock5));
+ Collections.shuffle(shuffledList, new Random());
+
+ Collections.sort(shuffledList);
+ assertThat(shuffledList).containsExactly(textBlock1, textBlock2, textBlock3, textBlock4, textBlock5);
+ }
+
+ @Test
+ public void verify_toString() {
+ assertThat(new TextBlock(13, 400).toString()).isEqualTo("TextBlock{start=13, end=400}");
+
+ }
+}