Browse Source

SONAR-6990 add DuplicationRepository and LoadDuplicationsFromReportStep

tags/5.3-RC1
Sébastien Lesaint 8 years ago
parent
commit
6ec797b491
21 changed files with 2251 additions and 0 deletions
  1. 2
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/container/ReportComputeEngineContainerPopulator.java
  2. 55
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/duplication/AbstractDuplicate.java
  3. 65
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/duplication/CrossProjectDuplicate.java
  4. 24
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/duplication/Duplicate.java
  5. 180
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/duplication/Duplication.java
  6. 91
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/duplication/DuplicationRepository.java
  7. 379
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/duplication/DuplicationRepositoryImpl.java
  8. 72
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/duplication/InProjectDuplicate.java
  9. 37
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/duplication/InnerDuplicate.java
  10. 90
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/duplication/TextBlock.java
  11. 24
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/duplication/package-info.java
  12. 86
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/step/LoadDuplicationsFromReportStep.java
  13. 1
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/step/ReportComputationSteps.java
  14. 87
    0
      server/sonar-server/src/test/java/org/sonar/server/computation/duplication/CrossProjectDuplicateTest.java
  15. 41
    0
      server/sonar-server/src/test/java/org/sonar/server/computation/duplication/DuplicateTest.java
  16. 452
    0
      server/sonar-server/src/test/java/org/sonar/server/computation/duplication/DuplicationRepositoryImplTest.java
  17. 111
    0
      server/sonar-server/src/test/java/org/sonar/server/computation/duplication/DuplicationRepositoryRule.java
  18. 194
    0
      server/sonar-server/src/test/java/org/sonar/server/computation/duplication/DuplicationTest.java
  19. 97
    0
      server/sonar-server/src/test/java/org/sonar/server/computation/duplication/InProjectDuplicateTest.java
  20. 62
    0
      server/sonar-server/src/test/java/org/sonar/server/computation/duplication/InnerDuplicateTest.java
  21. 101
    0
      server/sonar-server/src/test/java/org/sonar/server/computation/duplication/TextBlockTest.java

+ 2
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/container/ReportComputeEngineContainerPopulator.java View File

@@ -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,

+ 55
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/duplication/AbstractDuplicate.java View File

@@ -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();
}

}

+ 65
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/duplication/CrossProjectDuplicate.java View File

@@ -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);
}
}

+ 24
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/duplication/Duplicate.java View File

@@ -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();
}

+ 180
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/duplication/Duplication.java View File

@@ -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());
}
}
}

+ 91
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/duplication/DuplicationRepository.java View File

@@ -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);
}

+ 379
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/duplication/DuplicationRepositoryImpl.java View File

@@ -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());
}
}
}
}

+ 72
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/duplication/InProjectDuplicate.java View File

@@ -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);
}
}

+ 37
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/duplication/InnerDuplicate.java View 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() +
'}';
}
}

+ 90
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/duplication/TextBlock.java View File

@@ -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 +
'}';
}
}

+ 24
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/duplication/package-info.java View File

@@ -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;

+ 86
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/step/LoadDuplicationsFromReportStep.java View File

@@ -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());
}
}

+ 1
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/step/ReportComputationSteps.java View File

@@ -85,6 +85,7 @@ public class ReportComputationSteps implements ComputationSteps {
PersistIssuesStep.class,
PersistProjectLinksStep.class,
PersistEventsStep.class,
LoadDuplicationsFromReportStep.class,
PersistDuplicationsStep.class,
PersistFileSourcesStep.class,
PersistTestsStep.class,

+ 87
- 0
server/sonar-server/src/test/java/org/sonar/server/computation/duplication/CrossProjectDuplicateTest.java View File

@@ -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}}");
}

}

+ 41
- 0
server/sonar-server/src/test/java/org/sonar/server/computation/duplication/DuplicateTest.java View File

@@ -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();
}
}

+ 452
- 0
server/sonar-server/src/test/java/org/sonar/server/computation/duplication/DuplicationRepositoryImplTest.java View File

@@ -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;
}
}

+ 111
- 0
server/sonar-server/src/test/java/org/sonar/server/computation/duplication/DuplicationRepositoryRule.java View File

@@ -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);
}
}

+ 194
- 0
server/sonar-server/src/test/java/org/sonar/server/computation/duplication/DuplicationTest.java View File

@@ -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;
}
}

+ 97
- 0
server/sonar-server/src/test/java/org/sonar/server/computation/duplication/InProjectDuplicateTest.java View File

@@ -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}}");
}

}

+ 62
- 0
server/sonar-server/src/test/java/org/sonar/server/computation/duplication/InnerDuplicateTest.java View File

@@ -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}}");
}
}

+ 101
- 0
server/sonar-server/src/test/java/org/sonar/server/computation/duplication/TextBlockTest.java View File

@@ -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}");

}
}

Loading…
Cancel
Save