]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-4682 Start decoupling hash computation from issue matching
authorJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>
Tue, 17 Sep 2013 16:17:18 +0000 (18:17 +0200)
committerJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>
Wed, 18 Sep 2013 10:37:30 +0000 (12:37 +0200)
Create SourceHashHolder to take care of the heavy hash computations

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 [new file with mode: 0644]
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 [new file with mode: 0644]

index 871239ef6d6b1442d2aa736be2dea086e12a36d8..00a6f32fc1e2855feb634d492828dfcde84da6ba 100644 (file)
@@ -20,6 +20,8 @@
 
 package org.sonar.plugins.core.issue;
 
+import org.sonar.plugins.core.issue.tracking.SourceChecksum;
+
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Objects;
 import com.google.common.collect.LinkedHashMultimap;
@@ -27,48 +29,47 @@ import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
 import org.sonar.api.BatchExtension;
-import org.sonar.api.batch.SonarIndex;
 import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.resources.Resource;
 import org.sonar.api.rule.RuleKey;
-import org.sonar.batch.scan.LastSnapshots;
 import org.sonar.core.issue.db.IssueDto;
-import org.sonar.plugins.core.issue.tracking.*;
+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.StringText;
+import org.sonar.plugins.core.issue.tracking.StringTextComparator;
 
 import javax.annotation.Nullable;
 
-import java.util.*;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
 
 public class IssueTracking implements BatchExtension {
 
-  private final LastSnapshots lastSnapshots;
-  private final SonarIndex index;
-
-  public IssueTracking(LastSnapshots lastSnapshots, SonarIndex index) {
-    this.lastSnapshots = lastSnapshots;
-    this.index = index;
-  }
-
-  public IssueTrackingResult track(Resource resource, Collection<IssueDto> dbIssues, Collection<DefaultIssue> newIssues) {
+  public IssueTrackingResult track(SourceHashHolder sourceHashHolder, Collection<IssueDto> dbIssues, Collection<DefaultIssue> newIssues) {
     IssueTrackingResult result = new IssueTrackingResult();
 
-    String source = index.getSource(resource);
-    setChecksumOnNewIssues(newIssues, source);
+    setChecksumOnNewIssues(newIssues, sourceHashHolder);
 
     // Map new issues with old ones
-    mapIssues(newIssues, dbIssues, source, resource, result);
+    mapIssues(newIssues, dbIssues, sourceHashHolder, result);
     return result;
   }
 
-  private void setChecksumOnNewIssues(Collection<DefaultIssue> issues, String source) {
-    List<String> checksums = SourceChecksum.lineChecksumsOfFile(source);
+  private void setChecksumOnNewIssues(Collection<DefaultIssue> issues, SourceHashHolder sourceHashHolder) {
+    List<String> checksums = SourceChecksum.lineChecksumsOfFile(sourceHashHolder.getSource());
     for (DefaultIssue issue : issues) {
       issue.setChecksum(SourceChecksum.getChecksumForLine(checksums, issue.line()));
     }
   }
 
+
   @VisibleForTesting
-  void mapIssues(Collection<DefaultIssue> newIssues, @Nullable Collection<IssueDto> lastIssues, @Nullable String source, @Nullable Resource resource, IssueTrackingResult result) {
+  void mapIssues(Collection<DefaultIssue> newIssues, @Nullable Collection<IssueDto> lastIssues, SourceHashHolder sourceHashHolder, IssueTrackingResult result) {
     boolean hasLastScan = false;
 
     if (lastIssues != null) {
@@ -78,11 +79,8 @@ 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 (source != null && resource != null && hasLastScan) {
-        String referenceSource = lastSnapshots.getSource(resource);
-        if (referenceSource != null) {
-          mapNewissues(referenceSource, newIssues, source, result);
-        }
+      if (sourceHashHolder.hasBothReferenceAndCurrentSource() && hasLastScan) {
+        mapNewissues(sourceHashHolder, newIssues, result);
       }
       mapIssuesOnSameRule(newIssues, result);
     }
@@ -109,20 +107,18 @@ public class IssueTracking implements BatchExtension {
     }
   }
 
-  private void mapNewissues(String referenceSource, Collection<DefaultIssue> newIssues, String source, IssueTrackingResult result) {
-    HashedSequence<StringText> hashedReference = HashedSequence.wrap(new StringText(referenceSource), StringTextComparator.IGNORE_WHITESPACE);
-    HashedSequence<StringText> hashedSource = HashedSequence.wrap(new StringText(source), StringTextComparator.IGNORE_WHITESPACE);
+  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(hashedReference, hashedSource, hashedComparator);
+    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);
 
     Multimap<Integer, DefaultIssue> newIssuesByLines = newIssuesByLines(newIssues, rec, result);
     Multimap<Integer, IssueDto> lastIssuesByLines = lastIssuesByLines(result.unmatched(), rec);
 
-    RollingHashSequence<HashedSequence<StringText>> a = RollingHashSequence.wrap(hashedReference, hashedComparator, 5);
-    RollingHashSequence<HashedSequence<StringText>> b = RollingHashSequence.wrap(hashedSource, hashedComparator, 5);
-    RollingHashSequenceComparator<HashedSequence<StringText>> cmp = new RollingHashSequenceComparator<HashedSequence<StringText>>(hashedComparator);
-
     Map<Integer, HashOccurrence> map = Maps.newHashMap();
 
     for (Integer line : lastIssuesByLines.keySet()) {
index 33efa59f1d6e63056a098a5c381e1a4e57126906..23a618289531a6801d1ba776cb048ec362c9fb8c 100644 (file)
@@ -19,6 +19,8 @@
  */
 package org.sonar.plugins.core.issue;
 
+import org.sonar.api.batch.SonarIndex;
+import org.sonar.batch.scan.LastSnapshots;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
@@ -54,6 +56,8 @@ 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 IssueHandlers handlers;
   private final IssueWorkflow workflow;
   private final IssueUpdater updater;
@@ -63,6 +67,7 @@ public class IssueTrackingDecorator implements Decorator {
   private final RuleFinder ruleFinder;
 
   public IssueTrackingDecorator(IssueCache issueCache, InitialOpenIssuesStack initialOpenIssues, IssueTracking tracking,
+      LastSnapshots lastSnapshots, SonarIndex index,
       IssueHandlers handlers, IssueWorkflow workflow,
       IssueUpdater updater,
       Project project,
@@ -72,6 +77,8 @@ public class IssueTrackingDecorator implements Decorator {
     this.issueCache = issueCache;
     this.initialOpenIssues = initialOpenIssues;
     this.tracking = tracking;
+    this.lastSnapshots = lastSnapshots;
+    this.index = index;
     this.handlers = handlers;
     this.workflow = workflow;
     this.updater = updater;
@@ -104,7 +111,9 @@ 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.selectAndRemove(resource.getEffectiveKey());
 
-    IssueTrackingResult trackingResult = tracking.track(resource, dbOpenIssues, issues);
+    SourceHashHolder sourceHashHolder = new SourceHashHolder(index, lastSnapshots, resource);
+
+    IssueTrackingResult trackingResult = tracking.track(sourceHashHolder, dbOpenIssues, issues);
 
     // unmatched = issues that have been resolved + issues on disabled/removed rules + manual issues
     addUnmatched(trackingResult.unmatched(), issues);
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/SourceHashHolder.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/SourceHashHolder.java
new file mode 100644 (file)
index 0000000..15633db
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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;
+
+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;
+
+
+
+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 HashedSequence<StringText> hashedReference;
+  private HashedSequence<StringText> hashedSource;
+
+  public SourceHashHolder(SonarIndex index, LastSnapshots lastSnapshots, Resource resource) {
+    this.index = index;
+    this.lastSnapshots = lastSnapshots;
+    this.resource = resource;
+  }
+
+  private void initHashes() {
+    hashedReference = HashedSequence.wrap(new StringText(referenceSource), StringTextComparator.IGNORE_WHITESPACE);
+    hashedSource = HashedSequence.wrap(new StringText(source), 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 = index.getSource(resource);
+      sourceInitialized = true;
+    }
+    return source;
+  }
+
+  public String getReferenceSource() {
+    if (! referenceSourceInitialized) {
+      if (resource != null) {
+        referenceSource = lastSnapshots.getSource(resource);
+      }
+      referenceSourceInitialized = true;
+    }
+    return referenceSource;
+  }
+
+  public boolean hasBothReferenceAndCurrentSource() {
+    return getSource() != null && getReferenceSource() != null;
+  }
+
+  private void initHashesIfNull(Object required) {
+    if(required == null) {
+      initHashes();
+    }
+  }
+}
+
index d6940cbb4833f4efc558a2f0a92cb8c7a268c304..8c742eaef33a50d060addbd9a2f0b878c132ccc8 100644 (file)
@@ -19,6 +19,9 @@
  */
 package org.sonar.plugins.core.issue;
 
+import org.sonar.api.batch.SonarIndex;
+
+import org.sonar.batch.scan.LastSnapshots;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
@@ -55,6 +58,8 @@ 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);
   IssueHandlers handlers = mock(IssueHandlers.class);
   IssueWorkflow workflow = mock(IssueWorkflow.class);
   IssueUpdater updater = mock(IssueUpdater.class);
@@ -68,6 +73,8 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
       issueCache,
       initialOpenIssues,
       tracking,
+      lastSnapshots,
+      index,
       handlers,
       workflow,
       updater,
@@ -103,7 +110,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     decorator.doDecorate(file);
 
     // Apply filters, track, apply transitions, notify extensions then update cache
-    verify(tracking).track(eq(file), eq(dbIssues), argThat(new ArgumentMatcher<Collection<DefaultIssue>>() {
+    verify(tracking).track(isA(SourceHashHolder.class), eq(dbIssues), argThat(new ArgumentMatcher<Collection<DefaultIssue>>() {
       @Override
       public boolean matches(Object o) {
         List<DefaultIssue> issues = (List<DefaultIssue>) o;
@@ -126,7 +133,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     IssueTrackingResult trackingResult = new IssueTrackingResult();
     trackingResult.addUnmatched(unmatchedIssue);
 
-    when(tracking.track(eq(file), anyCollection(), anyCollection())).thenReturn(trackingResult);
+    when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
 
     decorator.doDecorate(file);
 
@@ -154,7 +161,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     IssueTrackingResult trackingResult = new IssueTrackingResult();
     trackingResult.addUnmatched(unmatchedIssue);
 
-    when(tracking.track(eq(file), anyCollection(), anyCollection())).thenReturn(trackingResult);
+    when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
 
     decorator.doDecorate(file);
 
@@ -183,7 +190,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     IssueTrackingResult trackingResult = new IssueTrackingResult();
     trackingResult.addUnmatched(unmatchedIssue);
 
-    when(tracking.track(eq(file), anyCollection(), anyCollection())).thenReturn(trackingResult);
+    when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
 
     decorator.doDecorate(file);
 
@@ -212,7 +219,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     IssueTrackingResult trackingResult = new IssueTrackingResult();
     trackingResult.addUnmatched(unmatchedIssue);
 
-    when(tracking.track(eq(file), anyCollection(), anyCollection())).thenReturn(trackingResult);
+    when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
 
     decorator.doDecorate(file);
 
index cd2867c0d2f62a0580353190ef15edebf5ea2db3..3fe4080a746031507d30cc97d455e17abf26639c 100644 (file)
@@ -20,6 +20,8 @@
 
 package org.sonar.plugins.core.issue;
 
+import org.sonar.api.batch.SonarIndex;
+
 import com.google.common.base.Charsets;
 import com.google.common.io.Resources;
 import org.junit.Before;
@@ -44,15 +46,18 @@ public class IssueTrackingTest {
 
   IssueTracking tracking;
   Resource project;
+  SourceHashHolder sourceHashHolder;
+  SonarIndex index;
   LastSnapshots lastSnapshots;
   long violationId = 0;
 
   @Before
   public void before() {
+    index = mock(SonarIndex.class);
     lastSnapshots = mock(LastSnapshots.class);
 
     project = mock(Project.class);
-    tracking = new IssueTracking(lastSnapshots, null);
+    tracking = new IssueTracking();
   }
 
   @Test
@@ -64,13 +69,15 @@ public class IssueTrackingTest {
     DefaultIssue newIssue = newDefaultIssue("message", 10, RuleKey.of("squid", "AvoidCycle"), "checksum1").setKey("200");
 
     IssueTrackingResult result = new IssueTrackingResult();
-    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue1, referenceIssue2), null, null, result);
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue1, referenceIssue2), null, result);
     // same key
     assertThat(result.matching(newIssue)).isSameAs(referenceIssue2);
   }
 
   @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");
 
@@ -78,7 +85,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), null, null, result);
+    tracking.mapIssues(newArrayList(newIssue1, newIssue2), newArrayList(referenceIssue1, referenceIssue2), sourceHashHolder, result);
     assertThat(result.matching(newIssue1)).isSameAs(referenceIssue1);
     assertThat(result.matching(newIssue2)).isSameAs(referenceIssue2);
   }
@@ -88,51 +95,61 @@ 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), null, null, result);
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, 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), null, null, result);
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, 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), null, null, result);
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, 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), null, null, result);
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, 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), null, null, result);
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, result);
     assertThat(result.matching(newIssue)).isSameAs(referenceIssue);
   }
 
@@ -141,56 +158,65 @@ 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), null, null, result);
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, 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), null, null, result);
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, 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), null, null, result);
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, 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), null, null, result);
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, 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"));
-    String source = load("example2-v2");
+    when(index.getSource(project)).thenReturn(load("example2-v2"));
+    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, project);
 
     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), source, project, result);
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, result);
 
     assertThat(result.matched()).isEmpty();
   }
@@ -198,13 +224,14 @@ public class IssueTrackingTest {
   @Test
   public void new_issue_not_associated_with_line_should_not_cause_npe() throws Exception {
     when(lastSnapshots.getSource(project)).thenReturn(load("example2-v1"));
-    String source = load("example2-v2");
+    when(index.getSource(project)).thenReturn(load("example2-v2"));
+    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, project);
 
     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), source, project, result);
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, result);
 
     assertThat(result.matched()).isEmpty();
   }
@@ -215,13 +242,14 @@ public class IssueTrackingTest {
   @Test
   public void issue_not_associated_with_line() throws Exception {
     when(lastSnapshots.getSource(project)).thenReturn(load("example2-v1"));
-    String source = load("example2-v2");
+    when(index.getSource(project)).thenReturn(load("example2-v2"));
+    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, project);
 
     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), source, project, result);
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, result);
 
     assertThat(result.matching(newIssue)).isEqualTo(referenceIssue);
   }
@@ -232,7 +260,8 @@ 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"));
-    String source = load("example1-v2");
+    when(index.getSource(project)).thenReturn(load("example1-v2"));
+    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, project);
 
     IssueDto referenceIssue1 = newReferenceIssue("Indentation", 7, "squid", "AvoidCycle", null);
     IssueDto referenceIssue2 = newReferenceIssue("Indentation", 11, "squid", "AvoidCycle", null);
@@ -243,7 +272,7 @@ public class IssueTrackingTest {
     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), source, project, result);
+    tracking.mapIssues(Arrays.asList(newIssue1, newIssue2, newIssue3, newIssue4), Arrays.asList(referenceIssue1, referenceIssue2), sourceHashHolder, result);
 
     assertThat(result.matching(newIssue1)).isNull();
     assertThat(result.matching(newIssue2)).isNull();
@@ -257,7 +286,8 @@ 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"));
-    String source = load("example2-v2");
+    when(index.getSource(project)).thenReturn(load("example2-v2"));
+    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, project);
 
     IssueDto referenceIssue1 = newReferenceIssue("SystemPrintln", 5, "squid", "AvoidCycle", null);
 
@@ -269,7 +299,7 @@ public class IssueTrackingTest {
     tracking.mapIssues(
       Arrays.asList(newIssue1, newIssue2, newIssue3),
       Arrays.asList(referenceIssue1),
-      source, project, result);
+      sourceHashHolder, result);
 
     assertThat(result.matching(newIssue1)).isNull();
     assertThat(result.matching(newIssue2)).isSameAs(referenceIssue1);
@@ -279,7 +309,8 @@ 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"));
-    String source = load("example3-v2");
+    when(index.getSource(project)).thenReturn(load("example3-v2"));
+    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, project);
 
     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");
@@ -300,7 +331,7 @@ public class IssueTrackingTest {
     tracking.mapIssues(
       Arrays.asList(newIssue1, newIssue2, newIssue3, newIssue4, newIssue5),
       Arrays.asList(referenceIssue1, referenceIssue2, referenceIssue3),
-      source, project, result);
+      sourceHashHolder, result);
 
     assertThat(result.matching(newIssue1)).isNull();
     assertThat(result.matching(newIssue2)).isSameAs(referenceIssue2);
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/SourceHashHolderTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/SourceHashHolderTest.java
new file mode 100644 (file)
index 0000000..f8fac61
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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;
+
+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 static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class SourceHashHolderTest {
+
+  SourceHashHolder sourceHashHolder;
+
+  SonarIndex index;
+  LastSnapshots lastSnapshots;
+  Resource resource;
+
+  @Before
+  public void setUp() {
+    index = mock(SonarIndex.class);
+    lastSnapshots = mock(LastSnapshots.class);
+    resource = mock(Resource.class);
+
+    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, resource);
+  }
+
+  @Test
+  public void should_lazy_load_source() {
+    final String source = "source";
+    when(index.getSource(resource)).thenReturn(source);
+
+    assertThat(sourceHashHolder.getSource()).isEqualTo(source);
+    verify(index).getSource(resource);
+
+    assertThat(sourceHashHolder.getSource()).isEqualTo(source);
+    Mockito.verifyNoMoreInteractions(index);
+  }
+
+  @Test
+  public void should_lazy_load_reference_source() {
+    final String source = "source";
+    when(lastSnapshots.getSource(resource)).thenReturn(source);
+
+    assertThat(sourceHashHolder.getReferenceSource()).isEqualTo(source);
+    verify(lastSnapshots).getSource(resource);
+
+    assertThat(sourceHashHolder.getReferenceSource()).isEqualTo(source);
+    Mockito.verifyNoMoreInteractions(lastSnapshots);
+  }
+
+  @Test
+  public void should_have_null_reference_source_for_null_resource() {
+    sourceHashHolder = new SourceHashHolder(index, lastSnapshots, null);
+
+    assertThat(sourceHashHolder.getReferenceSource()).isNull();
+    Mockito.verifyNoMoreInteractions(lastSnapshots);
+  }
+}