]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5868 Issue tracking now use hash WS instead of raw sources
authorJulien HENRY <julien.henry@sonarsource.com>
Wed, 26 Nov 2014 14:09:22 +0000 (15:09 +0100)
committerJulien HENRY <julien.henry@sonarsource.com>
Tue, 2 Dec 2014 08:11:06 +0000 (09:11 +0100)
31 files changed:
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTracking.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/SourceHashHolder.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/FileHashes.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/HashedSequence.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/HashedSequenceComparator.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/IssueTrackingBlocksRecognizer.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/RollingFileHashes.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/RollingHashSequence.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/RollingHashSequenceComparator.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/Sequence.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/SequenceComparator.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/SourceChecksum.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/StringText.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/StringTextComparator.java [deleted file]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingTest.java
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/SourceHashHolderTest.java
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/IssueTrackingBlocksRecognizerTest.java
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/RollingFileHashesTest.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/RollingHashSequenceTest.java [deleted file]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/SourceChecksumTest.java [deleted file]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/StringTextComparatorTest.java [deleted file]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/StringTextTest.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/scan/LastLineHashes.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/scan/LastSnapshots.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
sonar-batch/src/test/java/org/sonar/batch/scan/LastLineHashesTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/scan/LastSnapshotsTest.java [deleted file]
sonar-batch/src/test/resources/org/sonar/batch/scan/LastSnapshotsTest/last_snapshot.xml [deleted file]
sonar-batch/src/test/resources/org/sonar/batch/scan/LastSnapshotsTest/no_last_snapshot.xml [deleted file]

index 0e26a7904cce45602f42f460f6db57b6f63fc984..b470b424f84edde32b4a861ee129d92599e41905 100644 (file)
@@ -30,14 +30,8 @@ import org.sonar.api.BatchExtension;
 import org.sonar.api.issue.internal.DefaultIssue;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.core.issue.db.IssueDto;
-import org.sonar.plugins.core.issue.tracking.HashedSequence;
-import org.sonar.plugins.core.issue.tracking.HashedSequenceComparator;
 import org.sonar.plugins.core.issue.tracking.IssueTrackingBlocksRecognizer;
-import org.sonar.plugins.core.issue.tracking.RollingHashSequence;
-import org.sonar.plugins.core.issue.tracking.RollingHashSequenceComparator;
-import org.sonar.plugins.core.issue.tracking.SourceChecksum;
-import org.sonar.plugins.core.issue.tracking.StringText;
-import org.sonar.plugins.core.issue.tracking.StringTextComparator;
+import org.sonar.plugins.core.issue.tracking.RollingFileHashes;
 
 import javax.annotation.Nullable;
 
@@ -49,10 +43,15 @@ import java.util.Map;
 
 public class IssueTracking implements BatchExtension {
 
-  public IssueTrackingResult track(SourceHashHolder sourceHashHolder, Collection<IssueDto> dbIssues, Collection<DefaultIssue> newIssues) {
+  /**
+   * @param sourceHashHolder Null when working on resource that is not a file (directory/project)
+   */
+  public IssueTrackingResult track(@Nullable SourceHashHolder sourceHashHolder, Collection<IssueDto> dbIssues, Collection<DefaultIssue> newIssues) {
     IssueTrackingResult result = new IssueTrackingResult();
 
-    setChecksumOnNewIssues(newIssues, sourceHashHolder);
+    if (sourceHashHolder != null) {
+      setChecksumOnNewIssues(newIssues, sourceHashHolder);
+    }
 
     // Map new issues with old ones
     mapIssues(newIssues, dbIssues, sourceHashHolder, result);
@@ -63,9 +62,10 @@ public class IssueTracking implements BatchExtension {
     if (issues.isEmpty()) {
       return;
     }
-    List<String> checksums = SourceChecksum.lineChecksumsOfFile(sourceHashHolder.getSource());
     for (DefaultIssue issue : issues) {
-      issue.setChecksum(SourceChecksum.getChecksumForLine(checksums, issue.line()));
+      if (issue.line() != null) {
+        issue.setChecksum(sourceHashHolder.getHashedSource().getHash(issue.line()));
+      }
     }
   }
 
@@ -80,7 +80,7 @@ public class IssueTracking implements BatchExtension {
 
     // If each new issue matches an old one we can stop the matching mechanism
     if (result.matched().size() != newIssues.size()) {
-      if (sourceHashHolder.hasBothReferenceAndCurrentSource() && hasLastScan) {
+      if (sourceHashHolder != null && sourceHashHolder.getHashedReference() != null && hasLastScan) {
         mapNewissues(sourceHashHolder, newIssues, result);
       }
       mapIssuesOnSameRule(newIssues, result);
@@ -110,12 +110,10 @@ public class IssueTracking implements BatchExtension {
 
   private void mapNewissues(SourceHashHolder sourceHashHolder, Collection<DefaultIssue> newIssues, IssueTrackingResult result) {
 
-    HashedSequenceComparator<StringText> hashedComparator = new HashedSequenceComparator<StringText>(StringTextComparator.IGNORE_WHITESPACE);
-    IssueTrackingBlocksRecognizer rec = new IssueTrackingBlocksRecognizer(sourceHashHolder.getHashedReference(), sourceHashHolder.getHashedSource(), hashedComparator);
+    IssueTrackingBlocksRecognizer rec = new IssueTrackingBlocksRecognizer(sourceHashHolder.getHashedReference(), sourceHashHolder.getHashedSource());
 
-    RollingHashSequence<HashedSequence<StringText>> a = RollingHashSequence.wrap(sourceHashHolder.getHashedReference(), hashedComparator, 5);
-    RollingHashSequence<HashedSequence<StringText>> b = RollingHashSequence.wrap(sourceHashHolder.getHashedSource(), hashedComparator, 5);
-    RollingHashSequenceComparator<HashedSequence<StringText>> cmp = new RollingHashSequenceComparator<HashedSequence<StringText>>(hashedComparator);
+    RollingFileHashes a = RollingFileHashes.create(sourceHashHolder.getHashedReference(), 5);
+    RollingFileHashes b = RollingFileHashes.create(sourceHashHolder.getHashedSource(), 5);
 
     Multimap<Integer, DefaultIssue> newIssuesByLines = newIssuesByLines(newIssues, rec, result);
     Multimap<Integer, IssueDto> lastIssuesByLines = lastIssuesByLines(result.unmatched(), rec);
@@ -123,7 +121,7 @@ public class IssueTracking implements BatchExtension {
     Map<Integer, HashOccurrence> map = Maps.newHashMap();
 
     for (Integer line : lastIssuesByLines.keySet()) {
-      int hash = cmp.hash(a, line - 1);
+      int hash = a.getHash(line);
       HashOccurrence hashOccurrence = map.get(hash);
       if (hashOccurrence == null) {
         // first occurrence in A
@@ -137,7 +135,7 @@ public class IssueTracking implements BatchExtension {
     }
 
     for (Integer line : newIssuesByLines.keySet()) {
-      int hash = cmp.hash(b, line - 1);
+      int hash = b.getHash(line);
       HashOccurrence hashOccurrence = map.get(hash);
       if (hashOccurrence != null) {
         hashOccurrence.lineB = line;
@@ -159,7 +157,7 @@ public class IssueTracking implements BatchExtension {
       List<LinePair> possibleLinePairs = Lists.newArrayList();
       for (Integer oldLine : lastIssuesByLines.keySet()) {
         for (Integer newLine : newIssuesByLines.keySet()) {
-          int weight = rec.computeLengthOfMaximalBlock(oldLine - 1, newLine - 1);
+          int weight = rec.computeLengthOfMaximalBlock(oldLine, newLine);
           possibleLinePairs.add(new LinePair(oldLine, newLine, weight));
         }
       }
index 37276d00f8e0314f0a5064cd5b2dbc8373c7a3c0..7c39348de1fa5e5de8fe8ebb419d19b50e4bfe0f 100644 (file)
@@ -30,13 +30,15 @@ import org.sonar.api.batch.DecoratorBarriers;
 import org.sonar.api.batch.DecoratorContext;
 import org.sonar.api.batch.DependedUpon;
 import org.sonar.api.batch.DependsUpon;
-import org.sonar.api.batch.SonarIndex;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
 import org.sonar.api.component.ResourcePerspectives;
 import org.sonar.api.issue.Issuable;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.issue.internal.DefaultIssue;
 import org.sonar.api.issue.internal.IssueChangeContext;
 import org.sonar.api.profiles.RulesProfile;
+import org.sonar.api.resources.File;
 import org.sonar.api.resources.Project;
 import org.sonar.api.resources.Resource;
 import org.sonar.api.resources.ResourceUtils;
@@ -46,7 +48,8 @@ import org.sonar.api.rules.RuleFinder;
 import org.sonar.api.utils.Duration;
 import org.sonar.api.utils.KeyValueFormat;
 import org.sonar.batch.issue.IssueCache;
-import org.sonar.batch.scan.LastSnapshots;
+import org.sonar.batch.scan.LastLineHashes;
+import org.sonar.batch.scan.filesystem.InputPathCache;
 import org.sonar.core.issue.IssueUpdater;
 import org.sonar.core.issue.db.IssueChangeDto;
 import org.sonar.core.issue.db.IssueDto;
@@ -63,8 +66,7 @@ public class IssueTrackingDecorator implements Decorator {
   private final IssueCache issueCache;
   private final InitialOpenIssuesStack initialOpenIssues;
   private final IssueTracking tracking;
-  private final LastSnapshots lastSnapshots;
-  private final SonarIndex index;
+  private final LastLineHashes lastLineHashes;
   private final IssueHandlers handlers;
   private final IssueWorkflow workflow;
   private final IssueUpdater updater;
@@ -72,23 +74,26 @@ public class IssueTrackingDecorator implements Decorator {
   private final ResourcePerspectives perspectives;
   private final RulesProfile rulesProfile;
   private final RuleFinder ruleFinder;
+  private final InputPathCache inputPathCache;
+  private final Project project;
 
   public IssueTrackingDecorator(IssueCache issueCache, InitialOpenIssuesStack initialOpenIssues, IssueTracking tracking,
-    LastSnapshots lastSnapshots, SonarIndex index,
+    LastLineHashes lastLineHashes,
     IssueHandlers handlers, IssueWorkflow workflow,
     IssueUpdater updater,
     Project project,
     ResourcePerspectives perspectives,
     RulesProfile rulesProfile,
-    RuleFinder ruleFinder) {
+    RuleFinder ruleFinder, InputPathCache inputPathCache) {
     this.issueCache = issueCache;
     this.initialOpenIssues = initialOpenIssues;
     this.tracking = tracking;
-    this.lastSnapshots = lastSnapshots;
-    this.index = index;
+    this.lastLineHashes = lastLineHashes;
     this.handlers = handlers;
     this.workflow = workflow;
     this.updater = updater;
+    this.project = project;
+    this.inputPathCache = inputPathCache;
     this.changeContext = IssueChangeContext.createScan(project.getAnalysisDate());
     this.perspectives = perspectives;
     this.rulesProfile = rulesProfile;
@@ -120,7 +125,12 @@ public class IssueTrackingDecorator implements Decorator {
     // all the issues that are not closed in db before starting this module scan, including manual issues
     Collection<IssueDto> dbOpenIssues = initialOpenIssues.selectAndRemoveIssues(resource.getEffectiveKey());
 
-    SourceHashHolder sourceHashHolder = new SourceHashHolder(index, lastSnapshots, resource);
+    SourceHashHolder sourceHashHolder = null;
+    if (ResourceUtils.isFile(resource)) {
+      File sonarFile = (File) resource;
+      InputFile file = inputPathCache.getFile(project.getEffectiveKey(), sonarFile.getPath());
+      sourceHashHolder = new SourceHashHolder((DefaultInputFile) file, lastLineHashes);
+    }
 
     IssueTrackingResult trackingResult = tracking.track(sourceHashHolder, dbOpenIssues, issues);
 
index c10586fda0583edb946f7cf3d71bcffab9601f58..ffb8be6b4efc788f70cdba911472015326fde399 100644 (file)
  */
 package org.sonar.plugins.core.issue;
 
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.batch.SonarIndex;
-import org.sonar.api.resources.Resource;
-import org.sonar.batch.scan.LastSnapshots;
-import org.sonar.plugins.core.issue.tracking.HashedSequence;
-import org.sonar.plugins.core.issue.tracking.StringText;
-import org.sonar.plugins.core.issue.tracking.StringTextComparator;
+import org.sonar.api.batch.fs.InputFile.Status;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.batch.scan.LastLineHashes;
+import org.sonar.plugins.core.issue.tracking.FileHashes;
+
+import javax.annotation.CheckForNull;
 
 import java.util.Collection;
 
 public class SourceHashHolder {
 
-  private final SonarIndex index;
-  private final LastSnapshots lastSnapshots;
-  private final Resource resource;
-
-  private String source;
-  private boolean sourceInitialized;
-  private String referenceSource;
-  private boolean referenceSourceInitialized;
+  private final LastLineHashes lastSnapshots;
 
-  private HashedSequence<StringText> hashedReference;
-  private HashedSequence<StringText> hashedSource;
+  private FileHashes hashedReference;
+  private FileHashes hashedSource;
+  private DefaultInputFile inputFile;
 
-  public SourceHashHolder(SonarIndex index, LastSnapshots lastSnapshots, Resource resource) {
-    this.index = index;
+  public SourceHashHolder(DefaultInputFile inputFile, LastLineHashes lastSnapshots) {
+    this.inputFile = inputFile;
     this.lastSnapshots = lastSnapshots;
-    this.resource = resource;
   }
 
   private void initHashes() {
-    hashedReference = HashedSequence.wrap(new StringText(getReferenceSource()), StringTextComparator.IGNORE_WHITESPACE);
-    hashedSource = HashedSequence.wrap(new StringText(getSource()), StringTextComparator.IGNORE_WHITESPACE);
-  }
-
-  public HashedSequence<StringText> getHashedReference() {
-    initHashesIfNull(hashedReference);
-    return hashedReference;
-  }
-
-  public HashedSequence<StringText> getHashedSource() {
-    initHashesIfNull(hashedSource);
-    return hashedSource;
-  }
-
-  public String getSource() {
-    if (!sourceInitialized) {
-      source = StringUtils.defaultString(index.getSource(resource), "");
-      sourceInitialized = true;
-    }
-    return source;
-  }
-
-  public String getReferenceSource() {
-    if (!referenceSourceInitialized) {
-      if (resource != null) {
-        referenceSource = lastSnapshots.getSource(resource);
+    if (hashedSource == null) {
+      hashedSource = FileHashes.create(inputFile.lineHashes());
+      Status status = inputFile.status();
+      if (status == Status.ADDED) {
+        hashedReference = null;
+      } else if (status == Status.SAME) {
+        hashedReference = hashedSource;
+      } else {
+        String[] lineHashes = lastSnapshots.getLineHashes(inputFile.key());
+        hashedReference = lineHashes != null ? FileHashes.create(lineHashes) : null;
       }
-      referenceSourceInitialized = true;
     }
-    return referenceSource;
   }
 
-  public boolean hasBothReferenceAndCurrentSource() {
-    return getSource() != null && getReferenceSource() != null;
+  @CheckForNull
+  public FileHashes getHashedReference() {
+    initHashes();
+    return hashedReference;
   }
 
-  private void initHashesIfNull(Object required) {
-    if (required == null) {
-      initHashes();
-    }
+  public FileHashes getHashedSource() {
+    initHashes();
+    return hashedSource;
   }
 
   public Collection<Integer> getNewLinesMatching(Integer originLine) {
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/FileHashes.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/FileHashes.java
new file mode 100644 (file)
index 0000000..d45ac2d
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * 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.plugins.core.issue.tracking;
+
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.lang.ObjectUtils;
+
+import java.util.Collection;
+
+/**
+ * Wraps a {@link Sequence} to assign hash codes to elements.
+ */
+public final class FileHashes {
+
+  private final String[] hashes;
+  private final Multimap<String, Integer> linesByHash;
+
+  private FileHashes(String[] hashes, Multimap<String, Integer> linesByHash) {
+    this.hashes = hashes;
+    this.linesByHash = linesByHash;
+  }
+
+  public static FileHashes create(String[] hashes) {
+    int size = hashes.length;
+    Multimap<String, Integer> linesByHash = LinkedHashMultimap.create();
+    for (int i = 0; i < size; i++) {
+      // indices in array are shifted one line before
+      linesByHash.put(hashes[i], i + 1);
+    }
+    return new FileHashes(hashes, linesByHash);
+  }
+
+  public static FileHashes create(byte[][] hashes) {
+    int size = hashes.length;
+    Multimap<String, Integer> linesByHash = LinkedHashMultimap.create();
+    String[] hexHashes = new String[size];
+    for (int i = 0; i < size; i++) {
+      String hash = hashes[i] != null ? Hex.encodeHexString(hashes[i]) : "";
+      hexHashes[i] = hash;
+      // indices in array are shifted one line before
+      linesByHash.put(hash, i + 1);
+    }
+    return new FileHashes(hexHashes, linesByHash);
+  }
+
+  public int length() {
+    return hashes.length;
+  }
+
+  public Collection<Integer> getLinesForHash(String hash) {
+    return linesByHash.get(hash);
+  }
+
+  public String getHash(int line) {
+    // indices in array are shifted one line before
+    return (String) ObjectUtils.defaultIfNull(hashes[line - 1], "");
+  }
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/HashedSequence.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/HashedSequence.java
deleted file mode 100644 (file)
index abfc23f..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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.plugins.core.issue.tracking;
-
-import com.google.common.collect.LinkedHashMultimap;
-import com.google.common.collect.Multimap;
-
-import java.util.Collection;
-
-/**
- * Wraps a {@link Sequence} to assign hash codes to elements.
- */
-public final class HashedSequence<S extends Sequence> implements Sequence {
-
-  final S base;
-  final int[] hashes;
-  final Multimap<Integer, Integer> linesByHash;
-
-  private HashedSequence(S base, int[] hashes, Multimap<Integer, Integer> linesByHash) {
-    this.base = base;
-    this.hashes = hashes;
-    this.linesByHash = linesByHash;
-  }
-
-  public static <S extends Sequence> HashedSequence<S> wrap(S base, SequenceComparator<S> cmp) {
-    int size = base.length();
-    int[] hashes = new int[size];
-    Multimap<Integer, Integer> linesByHash = LinkedHashMultimap.create();
-    for (int i = 0; i < size; i++) {
-      hashes[i] = cmp.hash(base, i);
-      // indices in array are shifted one line before
-      linesByHash.put(hashes[i], i + 1);
-    }
-    return new HashedSequence<S>(base, hashes, linesByHash);
-  }
-
-  @Override
-  public int length() {
-    return base.length();
-  }
-
-  public Collection<Integer> getLinesForHash(Integer hash) {
-    return linesByHash.get(hash);
-  }
-
-  public Integer getHash(Integer line) {
-    // indices in array are shifted one line before
-    return hashes[line - 1];
-  }
-}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/HashedSequenceComparator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/HashedSequenceComparator.java
deleted file mode 100644 (file)
index 093cf96..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.plugins.core.issue.tracking;
-
-/**
- * Wrap another {@link SequenceComparator} for use with {@link HashedSequence}.
- */
-public class HashedSequenceComparator<S extends Sequence> implements SequenceComparator<HashedSequence<S>> {
-
-  private final SequenceComparator<? super S> cmp;
-
-  public HashedSequenceComparator(SequenceComparator<? super S> cmp) {
-    this.cmp = cmp;
-  }
-
-  @Override
-  public boolean equals(HashedSequence<S> a, int ai, HashedSequence<S> b, int bi) {
-    if (a.hashes[ai] == b.hashes[bi]) {
-      return cmp.equals(a.base, ai, b.base, bi);
-    }
-    return false;
-  }
-
-  @Override
-  public int hash(HashedSequence<S> seq, int i) {
-    return seq.hashes[i];
-  }
-
-}
index 15d0bc1539890368e0af57297bd70c1a5abc8d83..11611ad952dec6d00b84dd57653d87aac4ad5f79 100644 (file)
  */
 package org.sonar.plugins.core.issue.tracking;
 
-import com.google.common.annotations.VisibleForTesting;
-import org.sonar.plugins.core.issue.tracking.HashedSequence;
-import org.sonar.plugins.core.issue.tracking.HashedSequenceComparator;
-import org.sonar.plugins.core.issue.tracking.StringText;
-import org.sonar.plugins.core.issue.tracking.StringTextComparator;
-
 import javax.annotation.Nullable;
 
 public class IssueTrackingBlocksRecognizer {
 
-  private final HashedSequence<StringText> a;
-  private final HashedSequence<StringText> b;
-  private final HashedSequenceComparator<StringText> cmp;
-
-  @VisibleForTesting
-  public IssueTrackingBlocksRecognizer(String referenceSource, String source) {
-    this.a = HashedSequence.wrap(new StringText(referenceSource), StringTextComparator.IGNORE_WHITESPACE);
-    this.b = HashedSequence.wrap(new StringText(source), StringTextComparator.IGNORE_WHITESPACE);
-    this.cmp = new HashedSequenceComparator<StringText>(StringTextComparator.IGNORE_WHITESPACE);
-  }
+  private final FileHashes a;
+  private final FileHashes b;
 
-  public IssueTrackingBlocksRecognizer(HashedSequence<StringText> a, HashedSequence<StringText> b, HashedSequenceComparator<StringText> cmp) {
+  public IssueTrackingBlocksRecognizer(FileHashes a, FileHashes b) {
     this.a = a;
     this.b = b;
-    this.cmp = cmp;
   }
 
   public boolean isValidLineInReference(@Nullable Integer line) {
@@ -55,24 +40,24 @@ public class IssueTrackingBlocksRecognizer {
   }
 
   /**
-   * @param startA number of line from first version of text (numbering starts from 0)
-   * @param startB number of line from second version of text (numbering starts from 0)
+   * @param startA number of line from first version of text (numbering starts from 1)
+   * @param startB number of line from second version of text (numbering starts from 1)
    */
   public int computeLengthOfMaximalBlock(int startA, int startB) {
-    if (!cmp.equals(a, startA, b, startB)) {
+    if (!a.getHash(startA).equals(b.getHash(startB))) {
       return 0;
     }
     int length = 0;
     int ai = startA;
     int bi = startB;
-    while (ai < a.length() && bi < b.length() && cmp.equals(a, ai, b, bi)) {
+    while (ai <= a.length() && bi <= b.length() && a.getHash(ai).equals(b.getHash(bi))) {
       ai++;
       bi++;
       length++;
     }
     ai = startA;
     bi = startB;
-    while (ai >= 0 && bi >= 0 && cmp.equals(a, ai, b, bi)) {
+    while (ai > 0 && bi > 0 && a.getHash(ai).equals(b.getHash(bi))) {
       ai--;
       bi--;
       length++;
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/RollingFileHashes.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/RollingFileHashes.java
new file mode 100644 (file)
index 0000000..313507a
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * 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.plugins.core.issue.tracking;
+
+/**
+ * Compute hashes of block around each line
+ */
+public class RollingFileHashes {
+
+  final int[] rollingHashes;
+
+  public static RollingFileHashes create(FileHashes hashes, int halfBlockSize) {
+    int size = hashes.length();
+    int[] rollingHashes = new int[size];
+
+    RollingHashCalculator hashCalulator = new RollingHashCalculator(halfBlockSize * 2 + 1);
+    for (int i = 1; i <= Math.min(size, halfBlockSize + 1); i++) {
+      hashCalulator.add(hashes.getHash(i).hashCode());
+    }
+    for (int i = 1; i <= size; i++) {
+      rollingHashes[i - 1] = hashCalulator.getHash();
+      if (i - halfBlockSize > 0) {
+        hashCalulator.remove(hashes.getHash(i - halfBlockSize).hashCode());
+      }
+      if (i + 1 + halfBlockSize <= size) {
+        hashCalulator.add(hashes.getHash(i + 1 + halfBlockSize).hashCode());
+      } else {
+        hashCalulator.add(0);
+      }
+    }
+
+    return new RollingFileHashes(rollingHashes);
+  }
+
+  public int getHash(int line) {
+    return rollingHashes[line - 1];
+  }
+
+  private RollingFileHashes(int[] hashes) {
+    this.rollingHashes = hashes;
+  }
+
+  private static class RollingHashCalculator {
+
+    private static final int PRIME_BASE = 31;
+
+    private final int power;
+    private int hash;
+
+    public RollingHashCalculator(int size) {
+      int pow = 1;
+      for (int i = 0; i < size - 1; i++) {
+        pow = pow * PRIME_BASE;
+      }
+      this.power = pow;
+    }
+
+    public void add(int value) {
+      hash = hash * PRIME_BASE + value;
+    }
+
+    public void remove(int value) {
+      hash = hash - power * value;
+    }
+
+    public int getHash() {
+      return hash;
+    }
+
+  }
+
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/RollingHashSequence.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/RollingHashSequence.java
deleted file mode 100644 (file)
index cb3d7ac..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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.plugins.core.issue.tracking;
-
-/**
- * Wraps a {@link Sequence} to assign hash codes to elements.
- */
-public class RollingHashSequence<S extends Sequence> implements Sequence {
-
-  final S base;
-  final int[] hashes;
-
-  public static <S extends Sequence> RollingHashSequence<S> wrap(S base, SequenceComparator<S> cmp, int lines) {
-    int size = base.length();
-    int[] hashes = new int[size];
-
-    RollingHashCalculator hashCalulator = new RollingHashCalculator(lines * 2 + 1);
-    for (int i = 0; i <= Math.min(size - 1, lines); i++) {
-      hashCalulator.add(cmp.hash(base, i));
-    }
-    for (int i = 0; i < size; i++) {
-      hashes[i] = hashCalulator.getHash();
-      if (i - lines >= 0) {
-        hashCalulator.remove(cmp.hash(base, i - lines));
-      }
-      if (i + lines + 1 < size) {
-        hashCalulator.add(cmp.hash(base, i + lines + 1));
-      } else {
-        hashCalulator.add(0);
-      }
-    }
-
-    return new RollingHashSequence<S>(base, hashes);
-  }
-
-  private RollingHashSequence(S base, int[] hashes) {
-    this.base = base;
-    this.hashes = hashes;
-  }
-
-  @Override
-  public int length() {
-    return base.length();
-  }
-
-  private static class RollingHashCalculator {
-
-    private static final int PRIME_BASE = 31;
-
-    private final int power;
-    private int hash;
-
-    public RollingHashCalculator(int size) {
-      int pow = 1;
-      for (int i = 0; i < size - 1; i++) {
-        pow = pow * PRIME_BASE;
-      }
-      this.power = pow;
-    }
-
-    public void add(int value) {
-      hash = hash * PRIME_BASE + value;
-    }
-
-    public void remove(int value) {
-      hash = hash - power * value;
-    }
-
-    public int getHash() {
-      return hash;
-    }
-
-  }
-
-}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/RollingHashSequenceComparator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/RollingHashSequenceComparator.java
deleted file mode 100644 (file)
index b3663b4..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.plugins.core.issue.tracking;
-
-/**
- * Wrap another {@link SequenceComparator} for use with {@link RollingHashSequence}.
- */
-public class RollingHashSequenceComparator<S extends Sequence> implements SequenceComparator<RollingHashSequence<S>> {
-
-  private final SequenceComparator<? super S> cmp;
-
-  public RollingHashSequenceComparator(SequenceComparator<? super S> cmp) {
-    this.cmp = cmp;
-  }
-
-  @Override
-  public boolean equals(RollingHashSequence<S> a, int ai, RollingHashSequence<S> b, int bi) {
-    if (a.hashes[ai] == b.hashes[bi]) {
-      return cmp.equals(a.base, ai, b.base, bi);
-    }
-    return false;
-  }
-
-  @Override
-  public int hash(RollingHashSequence<S> seq, int i) {
-    return seq.hashes[i];
-  }
-
-}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/Sequence.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/Sequence.java
deleted file mode 100644 (file)
index e4f2b12..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.plugins.core.issue.tracking;
-
-/**
- * Arbitrary sequence of elements.
- */
-public interface Sequence {
-
-  /**
-   * @return total number of items in the sequence
-   */
-  int length();
-
-}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/SequenceComparator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/SequenceComparator.java
deleted file mode 100644 (file)
index b2a8605..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.plugins.core.issue.tracking;
-
-/**
- * Equivalence function for a {@link Sequence}.
- */
-public interface SequenceComparator<S extends Sequence> {
-
-  /**
-   * Compare two items to determine if they are equivalent.
-   */
-  boolean equals(S a, int ai, S b, int bi);
-
-  /**
-   * Get a hash value for an item in a sequence.
-   *
-   * If two items are equal according to this comparator's
-   * {@link #equals(Sequence, int, Sequence, int)} method,
-   * then this hash method must produce the same integer result for both items.
-   * However not required to have different hash values for different items.
-   */
-  int hash(S seq, int i);
-
-}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/SourceChecksum.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/SourceChecksum.java
deleted file mode 100644 (file)
index 21c1bb2..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.plugins.core.issue.tracking;
-
-import com.google.common.collect.Lists;
-import org.apache.commons.codec.digest.DigestUtils;
-import org.apache.commons.lang.StringUtils;
-
-import javax.annotation.Nullable;
-import java.util.List;
-
-public final class SourceChecksum {
-
-  private static final String SPACE_CHARS = "\t\n\r ";
-
-  private SourceChecksum() {
-    // only static methods
-  }
-
-  /**
-   * @param line line number (first line has number 1)
-   * @return checksum or null if checksum not exists for line
-   */
-  public static String getChecksumForLine(List<String> checksums, @Nullable Integer line) {
-    if (line == null || line < 1 || line > checksums.size()) {
-      return null;
-    }
-    return checksums.get(line - 1);
-  }
-
-  public static List<String> lineChecksumsOfFile(String file) {
-    List<String> result = Lists.newArrayList();
-    if (file != null) {
-      String[] lines = file.split("\r?\n|\r", -1);
-      for (String line : lines) {
-        result.add(lineChecksum(line));
-      }
-    }
-    return result;
-  }
-
-  public static String lineChecksum(String line) {
-    String reducedLine = StringUtils.replaceChars(line, SPACE_CHARS, "");
-    return DigestUtils.md5Hex(reducedLine);
-  }
-
-}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/StringText.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/StringText.java
deleted file mode 100644 (file)
index 50ce2d4..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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.plugins.core.issue.tracking;
-
-import com.google.common.collect.Lists;
-
-import java.util.List;
-
-/**
- * Text is a {@link Sequence} of lines.
- */
-public class StringText implements Sequence {
-
-  final String content;
-
-  /**
-   * Map of line number to starting position within {@link #content}.
-   */
-  final List<Integer> lines;
-
-  public StringText(String str) {
-    this.content = str;
-    this.lines = lineMap(content, 0, content.length());
-  }
-
-  @Override
-  public int length() {
-    return lines.size() - 2;
-  }
-
-  private static List<Integer> lineMap(String buf, int ptr, int end) {
-    List<Integer> lines = Lists.newArrayList();
-    lines.add(Integer.MIN_VALUE);
-    for (; ptr < end; ptr = nextLF(buf, ptr)) {
-      lines.add(ptr);
-    }
-    lines.add(end);
-    return lines;
-  }
-
-  private static int nextLF(String b, int ptr) {
-    return next(b, ptr, '\n');
-  }
-
-  private static int next(final String b, int ptr, final char chrA) {
-    final int sz = b.length();
-    while (ptr < sz) {
-      if (b.charAt(ptr++) == chrA) {
-        return ptr;
-      }
-    }
-    return ptr;
-  }
-
-}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/StringTextComparator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/StringTextComparator.java
deleted file mode 100644 (file)
index e7f6390..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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.plugins.core.issue.tracking;
-
-/**
- * Equivalence function for {@link StringText}.
- */
-public abstract class StringTextComparator implements SequenceComparator<StringText> {
-
-  /**
-   * Ignores all whitespace.
-   */
-  public static final StringTextComparator IGNORE_WHITESPACE = new StringTextComparator() {
-
-    @Override
-    public boolean equals(StringText a, int ai, StringText b, int bi) {
-      ai++;
-      bi++;
-      int as = a.lines.get(ai);
-      int bs = b.lines.get(bi);
-      int ae = a.lines.get(ai + 1);
-      int be = b.lines.get(bi + 1);
-      ae = trimTrailingWhitespace(a.content, as, ae);
-      be = trimTrailingWhitespace(b.content, bs, be);
-      while ((as < ae) && (bs < be)) {
-        char ac = a.content.charAt(as);
-        char bc = b.content.charAt(bs);
-        while ((as < ae - 1) && (Character.isWhitespace(ac))) {
-          as++;
-          ac = a.content.charAt(as);
-        }
-        while ((bs < be - 1) && (Character.isWhitespace(bc))) {
-          bs++;
-          bc = b.content.charAt(bs);
-        }
-        if (ac != bc) {
-          return false;
-        }
-        as++;
-        bs++;
-      }
-      return (as == ae) && (bs == be);
-    }
-
-    @Override
-    protected int hashRegion(String content, int start, int end) {
-      int hash = 5381;
-      for (; start < end; start++) {
-        char c = content.charAt(start);
-        if (!Character.isWhitespace(c)) {
-          hash = ((hash << 5) + hash) + (c & 0xff);
-        }
-      }
-      return hash;
-    }
-
-  };
-
-  @Override
-  public int hash(StringText seq, int line) {
-    final int begin = seq.lines.get(line + 1);
-    final int end = seq.lines.get(line + 2);
-    return hashRegion(seq.content, begin, end);
-  }
-
-  protected abstract int hashRegion(String content, int start, int end);
-
-  public static int trimTrailingWhitespace(String content, int start, int end) {
-    end--;
-    while (start <= end && Character.isWhitespace(content.charAt(end))) {
-      end--;
-    }
-    return end + 1;
-  }
-
-}
index 7d4bc08c27abe94432f0f5fc5109c05f5ba83313..844b2a8dfd82ef4423e6776a224322e103a0c2a8 100644 (file)
  */
 package org.sonar.plugins.core.issue;
 
+import org.apache.commons.codec.digest.DigestUtils;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatcher;
 import org.sonar.api.batch.DecoratorContext;
-import org.sonar.api.batch.SonarIndex;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
 import org.sonar.api.component.ResourcePerspectives;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.issue.internal.DefaultIssue;
@@ -38,7 +39,8 @@ import org.sonar.api.rules.Rule;
 import org.sonar.api.rules.RuleFinder;
 import org.sonar.api.utils.Duration;
 import org.sonar.batch.issue.IssueCache;
-import org.sonar.batch.scan.LastSnapshots;
+import org.sonar.batch.scan.LastLineHashes;
+import org.sonar.batch.scan.filesystem.InputPathCache;
 import org.sonar.core.issue.IssueUpdater;
 import org.sonar.core.issue.db.IssueChangeDto;
 import org.sonar.core.issue.db.IssueDto;
@@ -55,11 +57,17 @@ import static com.google.common.collect.Lists.newArrayList;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyCollection;
+import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.argThat;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.isA;
-import static org.mockito.Mockito.*;
-import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.RETURNS_MOCKS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
 public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
 
@@ -67,14 +75,14 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
   IssueCache issueCache = mock(IssueCache.class, RETURNS_MOCKS);
   InitialOpenIssuesStack initialOpenIssues = mock(InitialOpenIssuesStack.class);
   IssueTracking tracking = mock(IssueTracking.class, RETURNS_MOCKS);
-  LastSnapshots lastSnapshots = mock(LastSnapshots.class);
-  SonarIndex index = mock(SonarIndex.class);
+  LastLineHashes lastSnapshots = mock(LastLineHashes.class);
   IssueHandlers handlers = mock(IssueHandlers.class);
   IssueWorkflow workflow = mock(IssueWorkflow.class);
   IssueUpdater updater = mock(IssueUpdater.class);
   ResourcePerspectives perspectives = mock(ResourcePerspectives.class);
   RulesProfile profile = mock(RulesProfile.class);
   RuleFinder ruleFinder = mock(RuleFinder.class);
+  InputPathCache inputPathCache = mock(InputPathCache.class);
 
   @Before
   public void init() {
@@ -83,14 +91,14 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
       initialOpenIssues,
       tracking,
       lastSnapshots,
-      index,
       handlers,
       workflow,
       updater,
-      mock(Project.class),
+      new Project("foo"),
       perspectives,
       profile,
-      ruleFinder);
+      ruleFinder,
+      inputPathCache);
   }
 
   @Test
@@ -160,8 +168,6 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
 
   @Test
   public void manual_issues_should_be_moved_if_matching_line_found() throws Exception {
-    Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
-
     // INPUT : one issue existing during previous scan
     IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(6).setStatus("OPEN").setRuleKey("manual", "Performance");
     when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
@@ -183,8 +189,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
       + "   void method3();\n"
       + "   void method4();\n"
       + "}";
-    when(index.getSource(file)).thenReturn(newSource);
-    when(lastSnapshots.getSource(file)).thenReturn(originalSource);
+    Resource file = mockHashes(originalSource, newSource);
 
     when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
 
@@ -204,9 +209,19 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     assertThat(issue.isOnDisabledRule()).isFalse();
   }
 
+  private Resource mockHashes(String originalSource, String newSource) {
+    DefaultInputFile inputFile = mock(DefaultInputFile.class);
+    byte[][] hashes = computeHashes(newSource);
+    when(inputFile.lineHashes()).thenReturn(hashes);
+    when(inputFile.key()).thenReturn("foo:Action.java");
+    when(inputPathCache.getFile("foo", "Action.java")).thenReturn(inputFile);
+    when(lastSnapshots.getLineHashes("foo:Action.java")).thenReturn(computeHexHashes(originalSource));
+    Resource file = File.create("Action.java");
+    return file;
+  }
+
   @Test
   public void manual_issues_should_be_untouched_if_already_closed() throws Exception {
-    Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
 
     // INPUT : one issue existing during previous scan
     IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("CLOSED").setRuleKey("manual", "Performance");
@@ -216,8 +231,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     trackingResult.addUnmatched(unmatchedIssue);
 
     String originalSource = "public interface Action {}";
-    when(index.getSource(file)).thenReturn(originalSource);
-    when(lastSnapshots.getSource(file)).thenReturn(originalSource);
+    Resource file = mockHashes(originalSource, originalSource);
 
     when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
 
@@ -240,7 +254,6 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
 
   @Test
   public void manual_issues_should_be_untouched_if_line_is_null() throws Exception {
-    Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
 
     // INPUT : one issue existing during previous scan
     IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(null).setStatus("OPEN").setRuleKey("manual", "Performance");
@@ -250,8 +263,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     trackingResult.addUnmatched(unmatchedIssue);
 
     String originalSource = "public interface Action {}";
-    when(index.getSource(file)).thenReturn(originalSource);
-    when(lastSnapshots.getSource(file)).thenReturn(originalSource);
+    Resource file = mockHashes(originalSource, originalSource);
 
     when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
 
@@ -275,7 +287,6 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
   @Test
   public void manual_issues_should_be_kept_if_matching_line_not_found() throws Exception {
     // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
-    Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
 
     // INPUT : one issue existing during previous scan
     final int issueOnLine = 6;
@@ -299,8 +310,8 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
       + "   void method4();\n"
       + "   void method6();\n" // Poof, no method5 anymore
       + "}";
-    when(index.getSource(file)).thenReturn(newSource);
-    when(lastSnapshots.getSource(file)).thenReturn(originalSource);
+
+    Resource file = mockHashes(originalSource, newSource);
 
     when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
 
@@ -323,7 +334,6 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
   @Test
   public void manual_issues_should_be_kept_if_multiple_matching_lines_found() throws Exception {
     // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
-    Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
 
     // INPUT : one issue existing during previous scan
     final int issueOnLine = 3;
@@ -347,8 +357,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
       + "     notify();\n"
       + "   }\n"
       + "}";
-    when(index.getSource(file)).thenReturn(newSource);
-    when(lastSnapshots.getSource(file)).thenReturn(originalSource);
+    Resource file = mockHashes(originalSource, newSource);
 
     when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
 
@@ -368,11 +377,9 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     assertThat(issue.isOnDisabledRule()).isFalse();
   }
 
-
   @Test
   public void manual_issues_should_be_closed_if_manual_rule_is_removed() throws Exception {
     // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
-    Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
 
     // INPUT : one issue existing during previous scan
     IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("OPEN").setRuleKey("manual", "Performance");
@@ -382,8 +389,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     trackingResult.addUnmatched(unmatchedIssue);
 
     String source = "public interface Action {}";
-    when(index.getSource(file)).thenReturn(source);
-    when(lastSnapshots.getSource(file)).thenReturn(source);
+    Resource file = mockHashes(source, source);
 
     when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
 
@@ -405,7 +411,6 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
   @Test
   public void manual_issues_should_be_closed_if_manual_rule_is_not_found() throws Exception {
     // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
-    Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
 
     // INPUT : one issue existing during previous scan
     IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("OPEN").setRuleKey("manual", "Performance");
@@ -415,8 +420,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     trackingResult.addUnmatched(unmatchedIssue);
 
     String source = "public interface Action {}";
-    when(index.getSource(file)).thenReturn(source);
-    when(lastSnapshots.getSource(file)).thenReturn(source);
+    Resource file = mockHashes(source, source);
 
     when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
 
@@ -438,7 +442,6 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
   @Test
   public void manual_issues_should_be_closed_if_new_source_is_shorter() throws Exception {
     // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
-    Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
 
     // INPUT : one issue existing during previous scan
     IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(6).setStatus("OPEN").setRuleKey("manual", "Performance");
@@ -458,8 +461,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
       + "   void method1();\n"
       + "   void method2();\n"
       + "}";
-    when(index.getSource(file)).thenReturn(newSource);
-    when(lastSnapshots.getSource(file)).thenReturn(originalSource);
+    Resource file = mockHashes(originalSource, newSource);
 
     when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
 
@@ -556,4 +558,22 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     assertThat(issue.changes()).hasSize(1);
   }
 
+  private byte[][] computeHashes(String source) {
+    String[] lines = source.split("\n");
+    byte[][] hashes = new byte[lines.length][];
+    for (int i = 0; i < lines.length; i++) {
+      hashes[i] = DigestUtils.md5(lines[i].replaceAll("[\t ]", ""));
+    }
+    return hashes;
+  }
+
+  private String[] computeHexHashes(String source) {
+    String[] lines = source.split("\n");
+    String[] hashes = new String[lines.length];
+    for (int i = 0; i < lines.length; i++) {
+      hashes[i] = DigestUtils.md5Hex(lines[i].replaceAll("[\t ]", ""));
+    }
+    return hashes;
+  }
+
 }
index 4b14f77c5b57c07bfeb3750fa5fdc8d156e47dd4..bef105859862847611548f2aaf347d34f9b490f6 100644 (file)
@@ -22,15 +22,16 @@ package org.sonar.plugins.core.issue;
 
 import com.google.common.base.Charsets;
 import com.google.common.io.Resources;
+import org.apache.commons.codec.digest.DigestUtils;
 import org.junit.Before;
 import org.junit.Test;
-import org.sonar.api.batch.SonarIndex;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.issue.internal.DefaultIssue;
 import org.sonar.api.resources.Project;
 import org.sonar.api.resources.Resource;
 import org.sonar.api.rule.RuleKey;
-import org.sonar.batch.scan.LastSnapshots;
+import org.sonar.batch.scan.LastLineHashes;
 import org.sonar.core.issue.db.IssueDto;
 
 import java.io.IOException;
@@ -48,14 +49,12 @@ public class IssueTrackingTest {
   IssueTracking tracking;
   Resource project;
   SourceHashHolder sourceHashHolder;
-  SonarIndex index;
-  LastSnapshots lastSnapshots;
+  LastLineHashes lastSnapshots;
   long violationId = 0;
 
   @Before
   public void before() {
-    index = mock(SonarIndex.class);
-    lastSnapshots = mock(LastSnapshots.class);
+    lastSnapshots = mock(LastLineHashes.class);
 
     project = mock(Project.class);
     tracking = new IssueTracking();
@@ -77,8 +76,6 @@ public class IssueTrackingTest {
 
   @Test
   public void checksum_should_have_greater_priority_than_line() {
-    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, null);
-
     IssueDto referenceIssue1 = newReferenceIssue("message", 1, "squid", "AvoidCycle", "checksum1");
     IssueDto referenceIssue2 = newReferenceIssue("message", 3, "squid", "AvoidCycle", "checksum2");
 
@@ -86,7 +83,7 @@ public class IssueTrackingTest {
     DefaultIssue newIssue2 = newDefaultIssue("message", 5, RuleKey.of("squid", "AvoidCycle"), "checksum2");
 
     IssueTrackingResult result = new IssueTrackingResult();
-    tracking.mapIssues(newArrayList(newIssue1, newIssue2), newArrayList(referenceIssue1, referenceIssue2), sourceHashHolder, result);
+    tracking.mapIssues(newArrayList(newIssue1, newIssue2), newArrayList(referenceIssue1, referenceIssue2), null, result);
     assertThat(result.matching(newIssue1)).isSameAs(referenceIssue1);
     assertThat(result.matching(newIssue2)).isSameAs(referenceIssue2);
   }
@@ -96,61 +93,51 @@ public class IssueTrackingTest {
    */
   @Test
   public void same_rule_and_null_line_and_checksum_but_different_messages() {
-    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, null);
-
     DefaultIssue newIssue = newDefaultIssue("new message", null, RuleKey.of("squid", "AvoidCycle"), "checksum1");
     IssueDto referenceIssue = newReferenceIssue("old message", null, "squid", "AvoidCycle", "checksum1");
 
     IssueTrackingResult result = new IssueTrackingResult();
-    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, result);
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result);
     assertThat(result.matching(newIssue)).isSameAs(referenceIssue);
   }
 
   @Test
   public void same_rule_and_line_and_checksum_but_different_messages() {
-    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, null);
-
     DefaultIssue newIssue = newDefaultIssue("new message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum1");
     IssueDto referenceIssue = newReferenceIssue("old message", 1, "squid", "AvoidCycle", "checksum1");
 
     IssueTrackingResult result = new IssueTrackingResult();
-    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, result);
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result);
     assertThat(result.matching(newIssue)).isSameAs(referenceIssue);
   }
 
   @Test
   public void same_rule_and_line_message() {
-    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, null);
-
     DefaultIssue newIssue = newDefaultIssue("message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum1");
     IssueDto referenceIssue = newReferenceIssue("message", 1, "squid", "AvoidCycle", "checksum2");
 
     IssueTrackingResult result = new IssueTrackingResult();
-    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, result);
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result);
     assertThat(result.matching(newIssue)).isSameAs(referenceIssue);
   }
 
   @Test
   public void should_ignore_reference_measure_without_checksum() {
-    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, null);
-
     DefaultIssue newIssue = newDefaultIssue("message", 1, RuleKey.of("squid", "AvoidCycle"), null);
     IssueDto referenceIssue = newReferenceIssue("message", 1, "squid", "NullDeref", null);
 
     IssueTrackingResult result = new IssueTrackingResult();
-    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, result);
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result);
     assertThat(result.matching(newIssue)).isNull();
   }
 
   @Test
   public void same_rule_and_message_and_checksum_but_different_line() {
-    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, null);
-
     DefaultIssue newIssue = newDefaultIssue("message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum1");
     IssueDto referenceIssue = newReferenceIssue("message", 2, "squid", "AvoidCycle", "checksum1");
 
     IssueTrackingResult result = new IssueTrackingResult();
-    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, result);
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result);
     assertThat(result.matching(newIssue)).isSameAs(referenceIssue);
   }
 
@@ -159,80 +146,66 @@ public class IssueTrackingTest {
    */
   @Test
   public void same_checksum_and_rule_but_different_line_and_different_message() {
-    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, null);
-
     DefaultIssue newIssue = newDefaultIssue("new message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum1");
     IssueDto referenceIssue = newReferenceIssue("old message", 2, "squid", "AvoidCycle", "checksum1");
 
     IssueTrackingResult result = new IssueTrackingResult();
-    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, result);
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result);
     assertThat(result.matching(newIssue)).isSameAs(referenceIssue);
   }
 
   @Test
   public void should_create_new_issue_when_same_rule_same_message_but_different_line_and_checksum() {
-    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, null);
-
     DefaultIssue newIssue = newDefaultIssue("message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum1");
     IssueDto referenceIssue = newReferenceIssue("message", 2, "squid", "AvoidCycle", "checksum2");
 
     IssueTrackingResult result = new IssueTrackingResult();
-    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, result);
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result);
     assertThat(result.matching(newIssue)).isNull();
   }
 
   @Test
   public void should_not_track_issue_if_different_rule() {
-    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, null);
-
     DefaultIssue newIssue = newDefaultIssue("message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum1");
     IssueDto referenceIssue = newReferenceIssue("message", 1, "squid", "NullDeref", "checksum1");
 
     IssueTrackingResult result = new IssueTrackingResult();
-    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, result);
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result);
     assertThat(result.matching(newIssue)).isNull();
   }
 
   @Test
   public void should_compare_issues_with_database_format() {
-    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, null);
-
     // issue messages are trimmed and can be abbreviated when persisted in database.
     // Comparing issue messages must use the same format.
     DefaultIssue newIssue = newDefaultIssue("      message    ", 1, RuleKey.of("squid", "AvoidCycle"), "checksum1");
     IssueDto referenceIssue = newReferenceIssue("message", 1, "squid", "AvoidCycle", "checksum2");
 
     IssueTrackingResult result = new IssueTrackingResult();
-    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, result);
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result);
     assertThat(result.matching(newIssue)).isSameAs(referenceIssue);
   }
 
   @Test
   public void past_issue_not_associated_with_line_should_not_cause_npe() throws Exception {
-    when(lastSnapshots.getSource(project)).thenReturn(load("example2-v1"));
-    when(index.getSource(project)).thenReturn(load("example2-v2"));
-    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, project);
+    initLastHashes("example2-v1", "example2-v2");
 
     DefaultIssue newIssue = newDefaultIssue("Indentation", 9, RuleKey.of("squid", "AvoidCycle"), "foo");
     IssueDto referenceIssue = newReferenceIssue("2 branches need to be covered", null, "squid", "AvoidCycle", null);
 
-    IssueTrackingResult result = new IssueTrackingResult();
-    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, result);
+    IssueTrackingResult result = tracking.track(sourceHashHolder, newArrayList(referenceIssue), newArrayList(newIssue));
 
     assertThat(result.matched()).isEmpty();
   }
 
   @Test
   public void new_issue_not_associated_with_line_should_not_cause_npe() throws Exception {
-    when(lastSnapshots.getSource(project)).thenReturn(load("example2-v1"));
-    when(index.getSource(project)).thenReturn(load("example2-v2"));
-    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, project);
+    initLastHashes("example2-v1", "example2-v2");
 
     DefaultIssue newIssue = newDefaultIssue("1 branch need to be covered", null, RuleKey.of("squid", "AvoidCycle"), "foo");
     IssueDto referenceIssue = newReferenceIssue("Indentationd", 7, "squid", "AvoidCycle", null);
 
-    IssueTrackingResult result = new IssueTrackingResult();
-    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, result);
+    IssueTrackingResult result = tracking.track(sourceHashHolder, newArrayList(referenceIssue), newArrayList(newIssue));
 
     assertThat(result.matched()).isEmpty();
   }
@@ -242,15 +215,12 @@ public class IssueTrackingTest {
    */
   @Test
   public void issue_not_associated_with_line() throws Exception {
-    when(lastSnapshots.getSource(project)).thenReturn(load("example2-v1"));
-    when(index.getSource(project)).thenReturn(load("example2-v2"));
-    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, project);
+    initLastHashes("example2-v1", "example2-v2");
 
     DefaultIssue newIssue = newDefaultIssue("1 branch need to be covered", null, RuleKey.of("squid", "AvoidCycle"), null);
     IssueDto referenceIssue = newReferenceIssue("2 branches need to be covered", null, "squid", "AvoidCycle", null);
 
-    IssueTrackingResult result = new IssueTrackingResult();
-    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, result);
+    IssueTrackingResult result = tracking.track(sourceHashHolder, newArrayList(referenceIssue), newArrayList(newIssue));
 
     assertThat(result.matching(newIssue)).isEqualTo(referenceIssue);
   }
@@ -260,9 +230,7 @@ public class IssueTrackingTest {
    */
   @Test
   public void should_track_issues_based_on_blocks_recognition_on_example1() throws Exception {
-    when(lastSnapshots.getSource(project)).thenReturn(load("example1-v1"));
-    when(index.getSource(project)).thenReturn(load("example1-v2"));
-    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, project);
+    initLastHashes("example1-v1", "example1-v2");
 
     IssueDto referenceIssue1 = newReferenceIssue("Indentation", 7, "squid", "AvoidCycle", null);
     IssueDto referenceIssue2 = newReferenceIssue("Indentation", 11, "squid", "AvoidCycle", null);
@@ -272,8 +240,7 @@ public class IssueTrackingTest {
     DefaultIssue newIssue3 = newDefaultIssue("Indentation", 17, RuleKey.of("squid", "AvoidCycle"), null);
     DefaultIssue newIssue4 = newDefaultIssue("Indentation", 21, RuleKey.of("squid", "AvoidCycle"), null);
 
-    IssueTrackingResult result = new IssueTrackingResult();
-    tracking.mapIssues(Arrays.asList(newIssue1, newIssue2, newIssue3, newIssue4), Arrays.asList(referenceIssue1, referenceIssue2), sourceHashHolder, result);
+    IssueTrackingResult result = tracking.track(sourceHashHolder, Arrays.asList(referenceIssue1, referenceIssue2), Arrays.asList(newIssue1, newIssue2, newIssue3, newIssue4));
 
     assertThat(result.matching(newIssue1)).isNull();
     assertThat(result.matching(newIssue2)).isNull();
@@ -286,9 +253,7 @@ public class IssueTrackingTest {
    */
   @Test
   public void should_track_issues_based_on_blocks_recognition_on_example2() throws Exception {
-    when(lastSnapshots.getSource(project)).thenReturn(load("example2-v1"));
-    when(index.getSource(project)).thenReturn(load("example2-v2"));
-    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, project);
+    initLastHashes("example2-v1", "example2-v2");
 
     IssueDto referenceIssue1 = newReferenceIssue("SystemPrintln", 5, "squid", "AvoidCycle", null);
 
@@ -309,9 +274,7 @@ public class IssueTrackingTest {
 
   @Test
   public void should_track_issues_based_on_blocks_recognition_on_example3() throws Exception {
-    when(lastSnapshots.getSource(project)).thenReturn(load("example3-v1"));
-    when(index.getSource(project)).thenReturn(load("example3-v2"));
-    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, project);
+    initLastHashes("example3-v1", "example3-v2");
 
     IssueDto referenceIssue1 = newReferenceIssue("Avoid unused local variables such as 'j'.", 6, "squid", "AvoidCycle", "63c11570fc0a76434156be5f8138fa03");
     IssueDto referenceIssue2 = newReferenceIssue("Avoid unused private methods such as 'myMethod()'.", 13, "squid", "NullDeref", "ef23288705d1ef1e512448ace287586e");
@@ -376,4 +339,31 @@ public class IssueTrackingTest {
     referenceIssue.setStatus(Issue.STATUS_OPEN);
     return referenceIssue;
   }
+
+  private void initLastHashes(String reference, String newSource) throws IOException {
+    DefaultInputFile inputFile = mock(DefaultInputFile.class);
+    byte[][] hashes = computeHashes(load(newSource));
+    when(inputFile.lineHashes()).thenReturn(hashes);
+    when(inputFile.key()).thenReturn("foo:Action.java");
+    when(lastSnapshots.getLineHashes("foo:Action.java")).thenReturn(computeHexHashes(load(reference)));
+    sourceHashHolder = new SourceHashHolder(inputFile, lastSnapshots);
+  }
+
+  private byte[][] computeHashes(String source) {
+    String[] lines = source.split("\n");
+    byte[][] hashes = new byte[lines.length][];
+    for (int i = 0; i < lines.length; i++) {
+      hashes[i] = DigestUtils.md5(lines[i].replaceAll("[\t ]", ""));
+    }
+    return hashes;
+  }
+
+  private String[] computeHexHashes(String source) {
+    String[] lines = source.split("\n");
+    String[] hashes = new String[lines.length];
+    for (int i = 0; i < lines.length; i++) {
+      hashes[i] = DigestUtils.md5Hex(lines[i].replaceAll("[\t ]", ""));
+    }
+    return hashes;
+  }
 }
index 5687bc449f1c6242e614ed3c3a3d044fd6a7025c..5c9c9c9a5cebdf58552da250a5aa9cb25c8d5745 100644 (file)
  */
 package org.sonar.plugins.core.issue;
 
-import static org.mockito.Mockito.verify;
-
-import org.mockito.Mockito;
 import org.junit.Before;
 import org.junit.Test;
-import org.sonar.api.batch.SonarIndex;
-import org.sonar.api.resources.Resource;
-import org.sonar.batch.scan.LastSnapshots;
+import org.mockito.Mockito;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.batch.scan.LastLineHashes;
+
+import static org.apache.commons.codec.digest.DigestUtils.md5;
+import static org.apache.commons.codec.digest.DigestUtils.md5Hex;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 public class SourceHashHolderTest {
 
   SourceHashHolder sourceHashHolder;
 
-  SonarIndex index;
-  LastSnapshots lastSnapshots;
-  Resource resource;
+  LastLineHashes lastSnapshots;
+  DefaultInputFile file;
 
   @Before
   public void setUp() {
-    index = mock(SonarIndex.class);
-    lastSnapshots = mock(LastSnapshots.class);
-    resource = mock(Resource.class);
+    lastSnapshots = mock(LastLineHashes.class);
+    file = mock(DefaultInputFile.class);
 
-    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, resource);
+    sourceHashHolder = new SourceHashHolder(file, lastSnapshots);
   }
 
   @Test
-  public void should_lazy_load_source() {
+  public void should_lazy_load_line_hashes() {
     final String source = "source";
-    when(index.getSource(resource)).thenReturn(source);
+    when(file.lineHashes()).thenReturn(new byte[][] {md5(source), null});
 
-    assertThat(sourceHashHolder.getSource()).isEqualTo(source);
-    verify(index).getSource(resource);
+    assertThat(sourceHashHolder.getHashedSource().getHash(1)).isEqualTo(md5Hex(source));
+    assertThat(sourceHashHolder.getHashedSource().getHash(2)).isEqualTo("");
+    verify(file).lineHashes();
+    verify(file).key();
+    verify(file).status();
 
-    assertThat(sourceHashHolder.getSource()).isEqualTo(source);
-    Mockito.verifyNoMoreInteractions(index);
+    assertThat(sourceHashHolder.getHashedSource().getHash(1)).isEqualTo(md5Hex(source));
+    Mockito.verifyNoMoreInteractions(file);
   }
 
   @Test
-  public void should_lazy_load_reference_source() {
+  public void should_lazy_load_reference_hashes_when_status_changed() {
     final String source = "source";
-    when(lastSnapshots.getSource(resource)).thenReturn(source);
+    String key = "foo:src/Foo.java";
+    when(file.lineHashes()).thenReturn(new byte[][] {md5(source)});
+    when(file.key()).thenReturn(key);
+    when(file.status()).thenReturn(InputFile.Status.CHANGED);
+    when(lastSnapshots.getLineHashes(key)).thenReturn(new String[] {md5Hex(source)});
+
+    assertThat(sourceHashHolder.getHashedReference().getHash(1)).isEqualTo(md5Hex(source));
+    verify(lastSnapshots).getLineHashes(key);
 
-    assertThat(sourceHashHolder.getReferenceSource()).isEqualTo(source);
-    verify(lastSnapshots).getSource(resource);
+    assertThat(sourceHashHolder.getHashedReference().getHash(1)).isEqualTo(md5Hex(source));
+    Mockito.verifyNoMoreInteractions(lastSnapshots);
+  }
 
-    assertThat(sourceHashHolder.getReferenceSource()).isEqualTo(source);
+  @Test
+  public void should_not_load_reference_hashes_when_status_same() {
+    final String source = "source";
+    String key = "foo:src/Foo.java";
+    when(file.lineHashes()).thenReturn(new byte[][] {md5(source)});
+    when(file.key()).thenReturn(key);
+    when(file.status()).thenReturn(InputFile.Status.SAME);
+
+    assertThat(sourceHashHolder.getHashedReference().getHash(1)).isEqualTo(md5Hex(source));
+    assertThat(sourceHashHolder.getHashedReference().getHash(1)).isEqualTo(md5Hex(source));
     Mockito.verifyNoMoreInteractions(lastSnapshots);
   }
 
   @Test
-  public void should_have_null_reference_source_for_null_resource() {
-    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, null);
+  public void no_reference_hashes_when_status_added() {
+    final String source = "source";
+    String key = "foo:src/Foo.java";
+    when(file.lineHashes()).thenReturn(new byte[][] {md5(source)});
+    when(file.key()).thenReturn(key);
+    when(file.status()).thenReturn(InputFile.Status.ADDED);
 
-    assertThat(sourceHashHolder.getReferenceSource()).isNull();
+    assertThat(sourceHashHolder.getHashedReference()).isNull();
     Mockito.verifyNoMoreInteractions(lastSnapshots);
   }
+
 }
index 871402fe7d4e9112bcfb5bdd635a0cce99c099fc..d48126b7d0c4b3d5444e7d0263c2407630bb0bf5 100644 (file)
@@ -20,7 +20,6 @@
 package org.sonar.plugins.core.issue.tracking;
 
 import org.junit.Test;
-import org.sonar.plugins.core.issue.tracking.IssueTrackingBlocksRecognizer;
 
 import static org.fest.assertions.Assertions.assertThat;
 
@@ -28,23 +27,23 @@ public class IssueTrackingBlocksRecognizerTest {
 
   @Test
   public void test() {
-    assertThat(compute(t("abcde"), t("abcde"), 3, 3)).isEqualTo(5);
-    assertThat(compute(t("abcde"), t("abcd"), 3, 3)).isEqualTo(4);
-    assertThat(compute(t("bcde"), t("abcde"), 3, 3)).isEqualTo(0);
-    assertThat(compute(t("bcde"), t("abcde"), 2, 3)).isEqualTo(4);
+    assertThat(compute(t("abcde"), t("abcde"), 4, 4)).isEqualTo(5);
+    assertThat(compute(t("abcde"), t("abcd"), 4, 4)).isEqualTo(4);
+    assertThat(compute(t("bcde"), t("abcde"), 4, 4)).isEqualTo(0);
+    assertThat(compute(t("bcde"), t("abcde"), 3, 4)).isEqualTo(4);
   }
 
-  private static int compute(String a, String b, int ai, int bi) {
+  private static int compute(FileHashes a, FileHashes b, int ai, int bi) {
     IssueTrackingBlocksRecognizer rec = new IssueTrackingBlocksRecognizer(a, b);
     return rec.computeLengthOfMaximalBlock(ai, bi);
   }
 
-  private static String t(String text) {
-    StringBuilder sb = new StringBuilder();
+  private static FileHashes t(String text) {
+    String[] array = new String[text.length()];
     for (int i = 0; i < text.length(); i++) {
-      sb.append(text.charAt(i)).append('\n');
+      array[i] = "" + text.charAt(i);
     }
-    return sb.toString();
+    return FileHashes.create(array);
   }
 
 }
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/RollingFileHashesTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/RollingFileHashesTest.java
new file mode 100644 (file)
index 0000000..db1050c
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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.plugins.core.issue.tracking;
+
+import org.junit.Test;
+
+import static org.apache.commons.codec.digest.DigestUtils.md5Hex;
+import static org.fest.assertions.Assertions.assertThat;
+
+public class RollingFileHashesTest {
+
+  @Test
+  public void test_equals() {
+    RollingFileHashes a = RollingFileHashes.create(FileHashes.create(new String[] {md5Hex("line0"), md5Hex("line1"), md5Hex("line2")}), 1);
+    RollingFileHashes b = RollingFileHashes.create(FileHashes.create(new String[] {md5Hex("line0"), md5Hex("line1"), md5Hex("line2"), md5Hex("line3")}), 1);
+
+    assertThat(a.getHash(1) == b.getHash(1)).isTrue();
+    assertThat(a.getHash(2) == b.getHash(2)).isTrue();
+    assertThat(a.getHash(3) == b.getHash(3)).isFalse();
+
+    RollingFileHashes c = RollingFileHashes.create(FileHashes.create(new String[] {md5Hex("line-1"), md5Hex("line0"), md5Hex("line1"), md5Hex("line2"), md5Hex("line3")}), 1);
+    assertThat(a.getHash(1) == c.getHash(2)).isFalse();
+    assertThat(a.getHash(2) == c.getHash(3)).isTrue();
+  }
+
+}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/RollingHashSequenceTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/RollingHashSequenceTest.java
deleted file mode 100644 (file)
index 6a4b69b..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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.plugins.core.issue.tracking;
-
-import org.junit.Test;
-import org.sonar.plugins.core.issue.tracking.RollingHashSequence;
-import org.sonar.plugins.core.issue.tracking.RollingHashSequenceComparator;
-import org.sonar.plugins.core.issue.tracking.StringText;
-import org.sonar.plugins.core.issue.tracking.StringTextComparator;
-
-import static org.fest.assertions.Assertions.assertThat;
-
-public class RollingHashSequenceTest {
-
-  @Test
-  public void test_hash() {
-    StringText seq = new StringText("line0 \n line1 \n line2");
-    StringTextComparator cmp = StringTextComparator.IGNORE_WHITESPACE;
-    RollingHashSequence<StringText> seq2 = RollingHashSequence.wrap(seq, cmp, 1);
-    RollingHashSequenceComparator<StringText> cmp2 = new RollingHashSequenceComparator<StringText>(cmp);
-
-    assertThat(seq2.length()).isEqualTo(3);
-    assertThat(cmp2.hash(seq2, 0)).isEqualTo(cmp.hash(seq, 0) * 31 + cmp.hash(seq, 1));
-    assertThat(cmp2.hash(seq2, 1)).isEqualTo((cmp.hash(seq, 0) * 31 + cmp.hash(seq, 1)) * 31 + cmp.hash(seq, 2));
-    assertThat(cmp2.hash(seq2, 2)).isEqualTo((cmp.hash(seq, 1) * 31 + cmp.hash(seq, 2)) * 31);
-  }
-
-  @Test
-  public void test_equals() {
-    StringTextComparator baseCmp = StringTextComparator.IGNORE_WHITESPACE;
-    RollingHashSequence<StringText> a = RollingHashSequence.wrap(new StringText("line0 \n line1 \n line2"), baseCmp, 1);
-    RollingHashSequence<StringText> b = RollingHashSequence.wrap(new StringText("line0 \n line1 \n line2 \n line3"), baseCmp, 1);
-    RollingHashSequenceComparator<StringText> cmp = new RollingHashSequenceComparator<StringText>(baseCmp);
-
-    assertThat(cmp.equals(a, 0, b, 0)).isTrue();
-    assertThat(cmp.equals(a, 1, b, 1)).isTrue();
-    assertThat(cmp.equals(a, 2, b, 2)).isFalse();
-  }
-
-}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/SourceChecksumTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/SourceChecksumTest.java
deleted file mode 100644 (file)
index e81b277..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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.plugins.core.issue.tracking;
-
-import org.junit.Test;
-import org.sonar.plugins.core.issue.tracking.SourceChecksum;
-
-import java.util.List;
-
-import static org.fest.assertions.Assertions.assertThat;
-
-public class SourceChecksumTest {
-  @Test
-  public void shouldGetChecksumForLine() {
-    List<String> checksums = SourceChecksum.lineChecksumsOfFile("line");
-    assertThat(SourceChecksum.getChecksumForLine(checksums, null)).isNull();
-    assertThat(SourceChecksum.getChecksumForLine(checksums, 0)).isNull();
-    assertThat(SourceChecksum.getChecksumForLine(checksums, 1)).isNotNull();
-    assertThat(SourceChecksum.getChecksumForLine(checksums, 2)).isNull();
-  }
-
-  /**
-   * See http://jira.codehaus.org/browse/SONAR-2358
-   */
-  @Test
-  public void shouldGenerateCorrectChecksums() {
-    List<String> encoding = SourceChecksum.lineChecksumsOfFile("Привет Мир");
-    assertThat(encoding).hasSize(1);
-    assertThat(encoding.get(0)).isEqualTo("5ba3a45e1299ede07f56e5531351be52");
-  }
-
-  @Test
-  public void shouldSplitLinesAndIgnoreSpaces() {
-    List<String> crlf = SourceChecksum.lineChecksumsOfFile("Hello\r\nWorld");
-    List<String> lf = SourceChecksum.lineChecksumsOfFile("Hello\nWorld");
-    List<String> cr = SourceChecksum.lineChecksumsOfFile("Hello\rWorld");
-    assertThat(crlf).hasSize(2);
-    assertThat(crlf.get(0)).isNotEqualTo(crlf.get(1));
-    assertThat(lf).isEqualTo(crlf);
-    assertThat(cr).isEqualTo(crlf);
-
-    assertThat(SourceChecksum.lineChecksum("\tvoid  method()  {\n")).isEqualTo(SourceChecksum.lineChecksum("  void method() {"));
-  }
-}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/StringTextComparatorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/StringTextComparatorTest.java
deleted file mode 100644 (file)
index f0a1d02..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.plugins.core.issue.tracking;
-
-import org.junit.Test;
-import org.sonar.plugins.core.issue.tracking.StringText;
-import org.sonar.plugins.core.issue.tracking.StringTextComparator;
-
-import static org.fest.assertions.Assertions.assertThat;
-
-
-public class StringTextComparatorTest {
-
-  @Test
-  public void testEquals() {
-    StringTextComparator cmp = StringTextComparator.IGNORE_WHITESPACE;
-
-    StringText a = new StringText("abc\nabc\na bc");
-    StringText b = new StringText("abc\nabc d\nab c");
-
-    assertThat(cmp.equals(a, 0, b, 0)).as("abc == abc").isTrue();
-    assertThat(cmp.equals(a, 1, b, 1)).as("abc != abc d").isFalse();
-    assertThat(cmp.equals(a, 2, b, 2)).as("a bc == ab c").isTrue();
-    assertThat(cmp.hash(a, 0)).isEqualTo(cmp.hash(b, 0));
-    assertThat(cmp.hash(a, 2)).isEqualTo(cmp.hash(b, 2));
-  }
-
-}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/StringTextTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/StringTextTest.java
deleted file mode 100644 (file)
index 765ee05..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.plugins.core.issue.tracking;
-
-import org.junit.Test;
-import org.sonar.plugins.core.issue.tracking.StringText;
-
-import static org.fest.assertions.Assertions.assertThat;
-
-public class StringTextTest {
-
-  @Test
-  public void testEmpty() {
-    StringText r = new StringText("");
-    assertThat(r.length()).isEqualTo(0);
-  }
-
-  @Test
-  public void testTwoLines() {
-    StringText r = new StringText("a\nb");
-    assertThat(r.length()).isEqualTo(2);
-  }
-
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/LastLineHashes.java b/sonar-batch/src/main/java/org/sonar/batch/scan/LastLineHashes.java
new file mode 100644 (file)
index 0000000..ed0e3ad
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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.batch.scan;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterators;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.BatchComponent;
+import org.sonar.api.utils.TimeProfiler;
+import org.sonar.batch.bootstrap.ServerClient;
+
+public class LastLineHashes implements BatchComponent {
+
+  private static final Logger LOG = LoggerFactory.getLogger(LastLineHashes.class);
+
+  private final ServerClient server;
+
+  public LastLineHashes(ServerClient server) {
+    this.server = server;
+  }
+
+  public String[] getLineHashes(String fileKey) {
+    String hashesFromWs = loadHashesFromWs(fileKey);
+    return hashesFromWs != null ? Iterators.toArray(Splitter.on('\n').split(hashesFromWs).iterator(), String.class) : null;
+  }
+
+  private String loadHashesFromWs(String fileKey) {
+    TimeProfiler profiler = new TimeProfiler(LOG).start("Load previous line hashes of: " + fileKey).setLevelToDebug();
+    try {
+      return server.request("/api/sources/hash?key=" + ServerClient.encodeForUrl(fileKey));
+    } finally {
+      profiler.stop();
+    }
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/LastSnapshots.java b/sonar-batch/src/main/java/org/sonar/batch/scan/LastSnapshots.java
deleted file mode 100644 (file)
index 8a5d2bf..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.batch.scan;
-
-import org.apache.commons.lang.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.BatchComponent;
-import org.sonar.api.resources.Resource;
-import org.sonar.api.resources.ResourceUtils;
-import org.sonar.api.utils.HttpDownloader;
-import org.sonar.api.utils.TimeProfiler;
-import org.sonar.batch.bootstrap.AnalysisMode;
-import org.sonar.batch.bootstrap.ServerClient;
-import org.sonar.core.source.db.SnapshotSourceDao;
-
-import javax.annotation.CheckForNull;
-
-public class LastSnapshots implements BatchComponent {
-
-  private static final Logger LOG = LoggerFactory.getLogger(LastSnapshots.class);
-
-  private final AnalysisMode analysisMode;
-  private final ServerClient server;
-  private final SnapshotSourceDao sourceDao;
-
-  public LastSnapshots(AnalysisMode analysisMode, SnapshotSourceDao dao, ServerClient server) {
-    this.analysisMode = analysisMode;
-    this.sourceDao = dao;
-    this.server = server;
-  }
-
-  public String getSource(Resource resource) {
-    String source = null;
-    if (ResourceUtils.isFile(resource)) {
-      if (analysisMode.isPreview()) {
-        source = loadSourceFromWs(resource);
-      } else {
-        source = loadSourceFromDb(resource);
-      }
-    }
-    return StringUtils.defaultString(source, "");
-  }
-
-  private String loadSourceFromWs(Resource resource) {
-    TimeProfiler profiler = new TimeProfiler(LOG).start("Load previous source code of: " + resource.getEffectiveKey()).setLevelToDebug();
-    try {
-      return server
-        .request("/api/sources/raw?key=" + ServerClient.encodeForUrl(resource.getEffectiveKey()), "GET", false, analysisMode.getPreviewReadTimeoutSec() * 1000);
-    } catch (HttpDownloader.HttpException he) {
-      if (he.getResponseCode() == 404) {
-        return "";
-      }
-      throw he;
-    } finally {
-      profiler.stop();
-    }
-  }
-
-  @CheckForNull
-  private String loadSourceFromDb(Resource resource) {
-    return sourceDao.selectSnapshotSourceByComponentKey(resource.getEffectiveKey());
-  }
-}
index e1a1467a95f447c947019faa2ae1968d1b6a616a..b22723814e0a845496cdcb68439b369d55818eb8 100644 (file)
@@ -149,7 +149,7 @@ public class ProjectScanContainer extends ComponentContainer {
       ResourceKeyMigration.class,
       DefaultFileLinesContextFactory.class,
       ProjectLock.class,
-      LastSnapshots.class,
+      LastLineHashes.class,
       Caches.class,
       SnapshotCache.class,
       ResourceCache.class,
diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/LastLineHashesTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/LastLineHashesTest.java
new file mode 100644 (file)
index 0000000..7490781
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * 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.batch.scan;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.HttpDownloader;
+import org.sonar.batch.bootstrap.ServerClient;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class LastLineHashesTest {
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  @Before
+  public void before() {
+  }
+
+  @Test
+  public void should_download_source_from_ws_if_preview_mode() {
+    ServerClient server = mock(ServerClient.class);
+    when(server.request(anyString())).thenReturn("ae12\n\n43fb");
+
+    LastLineHashes lastSnapshots = new LastLineHashes(server);
+
+    String[] hashes = lastSnapshots.getLineHashes("myproject:org/foo/Bar.c");
+    assertThat(hashes).containsOnly("ae12", "", "43fb");
+    verify(server).request("/api/sources/hash?key=myproject%3Aorg%2Ffoo%2FBar.c");
+  }
+
+  @Test
+  public void should_download_source_with_space_from_ws_if_preview_mode() {
+    ServerClient server = mock(ServerClient.class);
+    when(server.request(anyString())).thenReturn("ae12\n\n43fb");
+
+    LastLineHashes lastSnapshots = new LastLineHashes(server);
+
+    String[] hashes = lastSnapshots.getLineHashes("myproject:org/foo/Foo Bar.c");
+    assertThat(hashes).containsOnly("ae12", "", "43fb");
+    verify(server).request("/api/sources/hash?key=myproject%3Aorg%2Ffoo%2FFoo+Bar.c");
+  }
+
+  @Test
+  public void should_fail_to_download_source_from_ws() throws URISyntaxException {
+    ServerClient server = mock(ServerClient.class);
+    when(server.request(anyString())).thenThrow(new HttpDownloader.HttpException(new URI(""), 500));
+
+    LastLineHashes lastSnapshots = new LastLineHashes(server);
+
+    thrown.expect(HttpDownloader.HttpException.class);
+    lastSnapshots.getLineHashes("foo");
+  }
+
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/LastSnapshotsTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/LastSnapshotsTest.java
deleted file mode 100644 (file)
index 719ae5c..0000000
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * 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.batch.scan;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.api.resources.File;
-import org.sonar.api.resources.Project;
-import org.sonar.api.utils.HttpDownloader;
-import org.sonar.batch.bootstrap.AnalysisMode;
-import org.sonar.batch.bootstrap.ServerClient;
-import org.sonar.core.persistence.TestDatabase;
-import org.sonar.core.source.db.SnapshotSourceDao;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-public class LastSnapshotsTest {
-
-  @Rule
-  public ExpectedException thrown = ExpectedException.none();
-
-  @Rule
-  public TestDatabase db = new TestDatabase();
-
-  private AnalysisMode mode;
-
-  @Before
-  public void before() {
-    mode = mock(AnalysisMode.class);
-    when(mode.getPreviewReadTimeoutSec()).thenReturn(30);
-  }
-
-  @Test
-  public void should_get_source_of_last_snapshot() {
-    db.prepareDbUnit(getClass(), "last_snapshot.xml");
-
-    ServerClient server = mock(ServerClient.class);
-    LastSnapshots lastSnapshots = new LastSnapshots(mode, new SnapshotSourceDao(db.myBatis()), server);
-
-    assertThat(lastSnapshots.getSource(newFile())).isEqualTo("this is bar");
-    verifyZeroInteractions(server);
-  }
-
-  @Test
-  public void should_return_empty_source_if_no_last_snapshot() {
-    db.prepareDbUnit(getClass(), "no_last_snapshot.xml");
-    ServerClient server = mock(ServerClient.class);
-
-    LastSnapshots lastSnapshots = new LastSnapshots(mode, new SnapshotSourceDao(db.myBatis()), server);
-
-    assertThat(lastSnapshots.getSource(newFile())).isEqualTo("");
-    verifyZeroInteractions(server);
-  }
-
-  @Test
-  public void should_download_source_from_ws_if_preview_mode() {
-    db.prepareDbUnit(getClass(), "last_snapshot.xml");
-    ServerClient server = mock(ServerClient.class);
-    when(server.request(anyString(), eq("GET"), eq(false), eq(30 * 1000))).thenReturn("downloaded source of Bar.c");
-
-    when(mode.isPreview()).thenReturn(true);
-    LastSnapshots lastSnapshots = new LastSnapshots(mode, new SnapshotSourceDao(db.myBatis()), server);
-
-    String source = lastSnapshots.getSource(newFile());
-    assertThat(source).isEqualTo("downloaded source of Bar.c");
-    verify(server).request("/api/sources/raw?key=myproject%3Aorg%2Ffoo%2FBar.c", "GET", false, 30 * 1000);
-  }
-
-  @Test
-  public void should_download_source_with_space_from_ws_if_preview_mode() {
-    db.prepareDbUnit(getClass(), "last_snapshot.xml");
-    ServerClient server = mock(ServerClient.class);
-    when(server.request(anyString(), eq("GET"), eq(false), eq(30 * 1000))).thenReturn("downloaded source of Foo Bar.c");
-
-    when(mode.isPreview()).thenReturn(true);
-    LastSnapshots lastSnapshots = new LastSnapshots(mode, new SnapshotSourceDao(db.myBatis()), server);
-
-    String source = lastSnapshots.getSource(newFile());
-    assertThat(source).isEqualTo("downloaded source of Foo Bar.c");
-    verify(server).request("/api/sources/raw?key=myproject%3Aorg%2Ffoo%2FBar.c", "GET", false, 30 * 1000);
-  }
-
-  @Test
-  public void should_fail_to_download_source_from_ws() throws URISyntaxException {
-    db.prepareDbUnit(getClass(), "last_snapshot.xml");
-    ServerClient server = mock(ServerClient.class);
-    when(server.request(anyString(), eq("GET"), eq(false), eq(30 * 1000))).thenThrow(new HttpDownloader.HttpException(new URI(""), 500));
-
-    when(mode.isPreview()).thenReturn(true);
-    LastSnapshots lastSnapshots = new LastSnapshots(mode, new SnapshotSourceDao(db.myBatis()), server);
-
-    thrown.expect(HttpDownloader.HttpException.class);
-    lastSnapshots.getSource(newFile());
-  }
-
-  @Test
-  public void should_return_empty_source_if_preview_mode_and_no_last_snapshot() throws URISyntaxException {
-    db.prepareDbUnit(getClass(), "last_snapshot.xml");
-    ServerClient server = mock(ServerClient.class);
-    when(server.request(anyString(), eq("GET"), eq(false), eq(30 * 1000))).thenThrow(new HttpDownloader.HttpException(new URI(""), 404));
-
-    when(mode.isPreview()).thenReturn(true);
-    LastSnapshots lastSnapshots = new LastSnapshots(mode, new SnapshotSourceDao(db.myBatis()), server);
-
-    String source = lastSnapshots.getSource(newFileWithSpace());
-    assertThat(source).isEqualTo("");
-    verify(server).request("/api/sources/raw?key=myproject%3Aorg%2Ffoo%2FFoo+Bar.c", "GET", false, 30 * 1000);
-  }
-
-  @Test
-  public void should_not_load_source_of_non_files() throws URISyntaxException {
-    db.prepareDbUnit(getClass(), "last_snapshot.xml");
-    ServerClient server = mock(ServerClient.class);
-
-    LastSnapshots lastSnapshots = new LastSnapshots(mode, new SnapshotSourceDao(db.myBatis()), server);
-
-    String source = lastSnapshots.getSource(new Project("my-project"));
-    assertThat(source).isEqualTo("");
-  }
-
-  private File newFile() {
-    File file = new File("org/foo", "Bar.c");
-    file.setEffectiveKey("myproject:org/foo/Bar.c");
-    return file;
-  }
-
-  private File newFileWithSpace() {
-    File file = new File("org/foo", "Foo Bar.c");
-    file.setEffectiveKey("myproject:org/foo/Foo Bar.c");
-    return file;
-  }
-}
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/scan/LastSnapshotsTest/last_snapshot.xml b/sonar-batch/src/test/resources/org/sonar/batch/scan/LastSnapshotsTest/last_snapshot.xml
deleted file mode 100644 (file)
index 8cc02f5..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-<dataset>
-  <projects id="100" kee="myproject:org/foo/Bar.c" enabled="[true]" scope="FIL" qualifier="FIL" language="c"/>
-  <snapshots id="1000" project_id="100" status="P" islast="[false]" purge_status="[null]"/>
-  <snapshots id="1100" project_id="100" status="P" islast="[true]" purge_status="[null]"/>
-  <snapshot_sources ID="10000" SNAPSHOT_ID="1100" DATA="this is bar"/>
-</dataset>
\ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/scan/LastSnapshotsTest/no_last_snapshot.xml b/sonar-batch/src/test/resources/org/sonar/batch/scan/LastSnapshotsTest/no_last_snapshot.xml
deleted file mode 100644 (file)
index 84d67a0..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-<dataset>
-
-</dataset>
\ No newline at end of file