@@ -30,6 +30,7 @@ import org.sonar.server.computation.component.DbIdsRepositoryImpl; | |||
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; | |||
@@ -137,6 +138,7 @@ public final class ReportComputeEngineContainerPopulator implements ContainerPop | |||
SourceLinesRepositoryImpl.class, | |||
SourceHashRepositoryImpl.class, | |||
ScmInfoRepositoryImpl.class, | |||
DuplicationRepositoryImpl.class, | |||
// issues | |||
RuleRepositoryImpl.class, |
@@ -0,0 +1,55 @@ | |||
/* | |||
* 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(); | |||
} | |||
} |
@@ -0,0 +1,65 @@ | |||
/* | |||
* 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); | |||
} | |||
} |
@@ -0,0 +1,24 @@ | |||
/* | |||
* 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(); | |||
} |
@@ -0,0 +1,180 @@ | |||
/* | |||
* 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()); | |||
} | |||
} | |||
} |
@@ -0,0 +1,91 @@ | |||
/* | |||
* 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); | |||
} |
@@ -0,0 +1,379 @@ | |||
/* | |||
* 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()); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,72 @@ | |||
/* | |||
* 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); | |||
} | |||
} |
@@ -0,0 +1,37 @@ | |||
/* | |||
* 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() + | |||
'}'; | |||
} | |||
} |
@@ -0,0 +1,90 @@ | |||
/* | |||
* 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 + | |||
'}'; | |||
} | |||
} |
@@ -0,0 +1,24 @@ | |||
/* | |||
* 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; |
@@ -0,0 +1,86 @@ | |||
/* | |||
* 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()); | |||
} | |||
} |
@@ -85,6 +85,7 @@ public class ReportComputationSteps implements ComputationSteps { | |||
PersistIssuesStep.class, | |||
PersistProjectLinksStep.class, | |||
PersistEventsStep.class, | |||
LoadDuplicationsFromReportStep.class, | |||
PersistDuplicationsStep.class, | |||
PersistFileSourcesStep.class, | |||
PersistTestsStep.class, |
@@ -0,0 +1,87 @@ | |||
/* | |||
* 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}}"); | |||
} | |||
} |
@@ -0,0 +1,41 @@ | |||
/* | |||
* 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(); | |||
} | |||
} |
@@ -0,0 +1,452 @@ | |||
/* | |||
* 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; | |||
} | |||
} |
@@ -0,0 +1,111 @@ | |||
/* | |||
* 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); | |||
} | |||
} |
@@ -0,0 +1,194 @@ | |||
/* | |||
* 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; | |||
} | |||
} |
@@ -0,0 +1,97 @@ | |||
/* | |||
* 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}}"); | |||
} | |||
} |
@@ -0,0 +1,62 @@ | |||
/* | |||
* 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}}"); | |||
} | |||
} |
@@ -0,0 +1,101 @@ | |||
/* | |||
* 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}"); | |||
} | |||
} |