]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6012 Local issue tracking
authorJulien HENRY <julien.henry@sonarsource.com>
Wed, 21 Jan 2015 10:22:44 +0000 (11:22 +0100)
committerJulien HENRY <julien.henry@sonarsource.com>
Fri, 23 Jan 2015 08:59:47 +0000 (09:59 +0100)
112 files changed:
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/InitialOpenIssuesSensor.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/InitialOpenIssuesStack.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueHandlers.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTracking.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingResult.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/SourceHashHolder.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/FileHashes.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/IssueTrackingBlocksRecognizer.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/RollingFileHashes.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/package-info.java [deleted file]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/InitialOpenIssuesSensorTest.java [deleted file]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/InitialOpenIssuesStackTest.java [deleted file]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueHandlersTest.java [deleted file]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java [deleted file]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingTest.java [deleted file]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/SourceHashHolderTest.java [deleted file]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/IssueTrackingBlocksRecognizerTest.java [deleted file]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/RollingFileHashesTest.java [deleted file]
plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/IssueTrackingTest/example1-v1.txt [deleted file]
plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/IssueTrackingTest/example1-v2.txt [deleted file]
plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/IssueTrackingTest/example2-v1.txt [deleted file]
plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/IssueTrackingTest/example2-v2.txt [deleted file]
plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/IssueTrackingTest/example3-v1.txt [deleted file]
plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/IssueTrackingTest/example3-v2.txt [deleted file]
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/CreateIssueByInternalKeySensor.java
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssuePerLineSensor.java
server/sonar-server/src/main/java/org/sonar/server/batch/ProjectRepositoryAction.java
server/sonar-server/src/main/java/org/sonar/server/batch/ProjectRepositoryLoader.java
server/sonar-server/src/test/java/org/sonar/server/batch/ProjectRepositoryActionTest.java
server/sonar-server/src/test/java/org/sonar/server/batch/ProjectRepositoryLoaderMediumTest.java
sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/ProjectReferentials.java [deleted file]
sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/ProjectRepository.java [new file with mode: 0644]
sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/issues/PreviousIssue.java
sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/issues/PreviousIssueHelper.java
sonar-batch-protocol/src/test/java/org/sonar/batch/protocol/input/ProjectReferentialsTest.java [deleted file]
sonar-batch-protocol/src/test/java/org/sonar/batch/protocol/input/ProjectRepositoryTest.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapContainer.java
sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java
sonar-batch/src/main/java/org/sonar/batch/index/ResourceCache.java
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/FileHashes.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/InitialOpenIssuesSensor.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/InitialOpenIssuesStack.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/IssueHandlers.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/IssueTracking.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/IssueTrackingBlocksRecognizer.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/IssueTrackingDecorator.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/IssueTrackingResult.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/LocalIssueTracking.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/PreviousIssue.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/PreviousIssueFromDb.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/PreviousIssueFromWs.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/PreviousIssueRepository.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/RollingFileHashes.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/SourceHashHolder.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/issue/tracking/package-info.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java
sonar-batch/src/main/java/org/sonar/batch/phases/PreviewPhaseExecutor.java
sonar-batch/src/main/java/org/sonar/batch/referential/DefaultGlobalReferentialsLoader.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/referential/DefaultProjectReferentialsLoader.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/referential/GlobalReferentialsLoader.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/referential/GlobalReferentialsProvider.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/referential/ProjectReferentialsLoader.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/referential/ProjectReferentialsProvider.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/referential/package-info.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/repository/DefaultGlobalReferentialsLoader.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/repository/DefaultPreviousIssuesLoader.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/repository/DefaultProjectReferentialsLoader.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/repository/GlobalReferentialsLoader.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/repository/GlobalReferentialsProvider.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/repository/PreviousIssuesLoader.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/repository/ProjectRepositoriesLoader.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/repository/ProjectRepositoriesProvider.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/repository/package-info.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/rule/ActiveRulesProvider.java
sonar-batch/src/main/java/org/sonar/batch/rule/ModuleQProfiles.java
sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java
sonar-batch/src/main/java/org/sonar/batch/scan/ModuleSettings.java
sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
sonar-batch/src/main/java/org/sonar/batch/scan/ProjectSettings.java
sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/StatusDetection.java
sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/StatusDetectionFactory.java
sonar-batch/src/main/java/org/sonar/batch/scm/ScmSensor.java
sonar-batch/src/test/java/org/sonar/batch/issue/tracking/InitialOpenIssuesSensorTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/issue/tracking/InitialOpenIssuesStackTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/issue/tracking/IssueHandlersTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/issue/tracking/IssueTrackingBlocksRecognizerTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/issue/tracking/IssueTrackingDecoratorTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/issue/tracking/IssueTrackingTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/issue/tracking/RollingFileHashesTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/issue/tracking/SourceHashHolderTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesMediumTest.java
sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/ReportsMediumTest.java
sonar-batch/src/test/java/org/sonar/batch/referential/DefaultProjectReferentialsLoaderTest.java [deleted file]
sonar-batch/src/test/java/org/sonar/batch/repository/DefaultProjectReferentialsLoaderTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/scan/ModuleSettingsTest.java
sonar-batch/src/test/java/org/sonar/batch/scan/ProjectScanContainerTest.java
sonar-batch/src/test/java/org/sonar/batch/scan/ProjectSettingsTest.java
sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/FileMetadataTest.java
sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/StatusDetectionFactoryTest.java
sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/StatusDetectionTest.java
sonar-batch/src/test/resources/mediumtest/xoo/sample/xources/hello/HelloJava.xoo.measures
sonar-batch/src/test/resources/mediumtest/xoo/sample/xources/hello/helloscala.xoo.measures
sonar-batch/src/test/resources/org/sonar/batch/issue/tracking/IssueTrackingTest/example1-v1.txt [new file with mode: 0644]
sonar-batch/src/test/resources/org/sonar/batch/issue/tracking/IssueTrackingTest/example1-v2.txt [new file with mode: 0644]
sonar-batch/src/test/resources/org/sonar/batch/issue/tracking/IssueTrackingTest/example2-v1.txt [new file with mode: 0644]
sonar-batch/src/test/resources/org/sonar/batch/issue/tracking/IssueTrackingTest/example2-v2.txt [new file with mode: 0644]
sonar-batch/src/test/resources/org/sonar/batch/issue/tracking/IssueTrackingTest/example3-v1.txt [new file with mode: 0644]
sonar-batch/src/test/resources/org/sonar/batch/issue/tracking/IssueTrackingTest/example3-v2.txt [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/FileSystem.java

index 9040b071b1abdd49724c9e30fe2624b3325fdd1e..2187ca1cf85a58a7332633e5f9ab2f430ae9f177 100644 (file)
@@ -34,11 +34,6 @@ import org.sonar.plugins.core.dashboards.ProjectIssuesDashboard;
 import org.sonar.plugins.core.dashboards.ProjectTimeMachineDashboard;
 import org.sonar.plugins.core.issue.CountFalsePositivesDecorator;
 import org.sonar.plugins.core.issue.CountUnresolvedIssuesDecorator;
-import org.sonar.plugins.core.issue.InitialOpenIssuesSensor;
-import org.sonar.plugins.core.issue.InitialOpenIssuesStack;
-import org.sonar.plugins.core.issue.IssueHandlers;
-import org.sonar.plugins.core.issue.IssueTracking;
-import org.sonar.plugins.core.issue.IssueTrackingDecorator;
 import org.sonar.plugins.core.measurefilters.MyFavouritesFilter;
 import org.sonar.plugins.core.measurefilters.ProjectFilter;
 import org.sonar.plugins.core.notifications.alerts.NewAlerts;
@@ -325,13 +320,8 @@ public final class CorePlugin extends SonarPlugin {
       DistributionAreaChart.class,
 
       // issues
-      IssueTrackingDecorator.class,
-      IssueTracking.class,
-      IssueHandlers.class,
       CountUnresolvedIssuesDecorator.class,
       CountFalsePositivesDecorator.class,
-      InitialOpenIssuesSensor.class,
-      InitialOpenIssuesStack.class,
       HotspotMostViolatedRulesWidget.class,
       MyUnresolvedIssuesWidget.class,
       FalsePositiveIssuesWidget.class,
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/InitialOpenIssuesSensor.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/InitialOpenIssuesSensor.java
deleted file mode 100644 (file)
index 17f2275..0000000
+++ /dev/null
@@ -1,84 +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;
-
-import org.apache.commons.lang.time.DateUtils;
-import org.apache.ibatis.session.ResultContext;
-import org.apache.ibatis.session.ResultHandler;
-import org.sonar.api.batch.Sensor;
-import org.sonar.api.batch.SensorContext;
-import org.sonar.api.resources.Project;
-import org.sonar.core.issue.db.IssueChangeDao;
-import org.sonar.core.issue.db.IssueChangeDto;
-import org.sonar.core.issue.db.IssueDao;
-import org.sonar.core.issue.db.IssueDto;
-
-import java.util.Calendar;
-import java.util.Date;
-
-/**
- * Load all the issues referenced during the previous scan.
- */
-public class InitialOpenIssuesSensor implements Sensor {
-
-  private final InitialOpenIssuesStack initialOpenIssuesStack;
-  private final IssueDao issueDao;
-  private final IssueChangeDao issueChangeDao;
-
-  public InitialOpenIssuesSensor(InitialOpenIssuesStack initialOpenIssuesStack, IssueDao issueDao, IssueChangeDao issueChangeDao) {
-    this.initialOpenIssuesStack = initialOpenIssuesStack;
-    this.issueDao = issueDao;
-    this.issueChangeDao = issueChangeDao;
-  }
-
-  @Override
-  public boolean shouldExecuteOnProject(Project project) {
-    return true;
-  }
-
-  @Override
-  public void analyse(Project project, SensorContext context) {
-    // Adding one second is a hack for resolving conflicts with concurrent user
-    // changes during issue persistence
-    final Date now = DateUtils.addSeconds(DateUtils.truncate(new Date(), Calendar.MILLISECOND), 1);
-
-    issueDao.selectNonClosedIssuesByModule(project.getId(), new ResultHandler() {
-      @Override
-      public void handleResult(ResultContext rc) {
-        IssueDto dto = (IssueDto) rc.getResultObject();
-        dto.setSelectedAt(now.getTime());
-        initialOpenIssuesStack.addIssue(dto);
-      }
-    });
-
-    issueChangeDao.selectChangelogOnNonClosedIssuesByModuleAndType(project.getId(), new ResultHandler() {
-      @Override
-      public void handleResult(ResultContext rc) {
-        IssueChangeDto dto = (IssueChangeDto) rc.getResultObject();
-        initialOpenIssuesStack.addChangelog(dto);
-      }
-    });
-  }
-
-  @Override
-  public String toString() {
-    return getClass().getSimpleName();
-  }
-}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/InitialOpenIssuesStack.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/InitialOpenIssuesStack.java
deleted file mode 100644 (file)
index 1d6f359..0000000
+++ /dev/null
@@ -1,85 +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;
-
-import java.util.Collections;
-import org.sonar.api.BatchExtension;
-import org.sonar.api.batch.InstantiationStrategy;
-import org.sonar.batch.index.Cache;
-import org.sonar.batch.index.Caches;
-import org.sonar.core.issue.db.IssueChangeDto;
-import org.sonar.core.issue.db.IssueDto;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static com.google.common.collect.Lists.newArrayList;
-
-@InstantiationStrategy(InstantiationStrategy.PER_BATCH)
-public class InitialOpenIssuesStack implements BatchExtension {
-
-  private final Cache<IssueDto> issuesCache;
-  private final Cache<ArrayList<IssueChangeDto>> issuesChangelogCache;
-
-  public InitialOpenIssuesStack(Caches caches) {
-    issuesCache = caches.createCache("last-open-issues");
-    issuesChangelogCache = caches.createCache("issues-changelog");
-  }
-
-  public InitialOpenIssuesStack addIssue(IssueDto issueDto) {
-    issuesCache.put(issueDto.getComponentKey(), issueDto.getKee(), issueDto);
-    return this;
-  }
-
-  public List<IssueDto> selectAndRemoveIssues(String componentKey) {
-    Iterable<IssueDto> issues = issuesCache.values(componentKey);
-    List<IssueDto> result = newArrayList();
-    for (IssueDto issue : issues) {
-      result.add(issue);
-    }
-    issuesCache.clear(componentKey);
-    return result;
-  }
-
-  public Iterable<IssueDto> selectAllIssues() {
-    return issuesCache.values();
-  }
-
-  public InitialOpenIssuesStack addChangelog(IssueChangeDto issueChangeDto) {
-    List<IssueChangeDto> changeDtos = issuesChangelogCache.get(issueChangeDto.getIssueKey());
-    if (changeDtos == null) {
-      changeDtos = newArrayList();
-    }
-    changeDtos.add(issueChangeDto);
-    issuesChangelogCache.put(issueChangeDto.getIssueKey(), newArrayList(changeDtos));
-    return this;
-  }
-
-  public List<IssueChangeDto> selectChangelog(String issueKey) {
-    List<IssueChangeDto> changeDtos = issuesChangelogCache.get(issueKey);
-    return changeDtos != null ? changeDtos : Collections.<IssueChangeDto>emptyList();
-  }
-
-  public void clear() {
-    issuesCache.clear();
-    issuesChangelogCache.clear();
-  }
-}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueHandlers.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueHandlers.java
deleted file mode 100644 (file)
index d416b57..0000000
+++ /dev/null
@@ -1,140 +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;
-
-import org.sonar.api.BatchExtension;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.issue.IssueHandler;
-import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.issue.internal.IssueChangeContext;
-import org.sonar.api.user.User;
-import org.sonar.core.issue.IssueUpdater;
-import org.sonar.core.user.DefaultUser;
-
-import javax.annotation.Nullable;
-
-public class IssueHandlers implements BatchExtension {
-  private final IssueHandler[] handlers;
-  private final DefaultContext context;
-
-  public IssueHandlers(IssueUpdater updater, IssueHandler[] handlers) {
-    this.handlers = handlers;
-    this.context = new DefaultContext(updater);
-  }
-
-  public IssueHandlers(IssueUpdater updater) {
-    this(updater, new IssueHandler[0]);
-  }
-
-  public void execute(DefaultIssue issue, IssueChangeContext changeContext) {
-    context.reset(issue, changeContext);
-    for (IssueHandler handler : handlers) {
-      handler.onIssue(context);
-    }
-  }
-
-  static class DefaultContext implements IssueHandler.Context {
-    private final IssueUpdater updater;
-    private DefaultIssue issue;
-    private IssueChangeContext changeContext;
-
-    private DefaultContext(IssueUpdater updater) {
-      this.updater = updater;
-    }
-
-    private void reset(DefaultIssue i, IssueChangeContext changeContext) {
-      this.issue = i;
-      this.changeContext = changeContext;
-    }
-
-    @Override
-    public Issue issue() {
-      return issue;
-    }
-
-    @Override
-    public boolean isNew() {
-      return issue.isNew();
-    }
-
-    @Override
-    public boolean isEndOfLife() {
-      return issue.isEndOfLife();
-    }
-
-    @Override
-    public IssueHandler.Context setLine(@Nullable Integer line) {
-      updater.setLine(issue, line);
-      return this;
-    }
-
-    @Override
-    public IssueHandler.Context setMessage(@Nullable String s) {
-      updater.setMessage(issue, s, changeContext);
-      return this;
-    }
-
-    @Override
-    public IssueHandler.Context setSeverity(String severity) {
-      updater.setSeverity(issue, severity, changeContext);
-      return this;
-    }
-
-    @Override
-    public IssueHandler.Context setAuthorLogin(@Nullable String login) {
-      updater.setAuthorLogin(issue, login, changeContext);
-      return this;
-    }
-
-    @Override
-    public IssueHandler.Context setEffortToFix(@Nullable Double d) {
-      updater.setEffortToFix(issue, d, changeContext);
-      return this;
-    }
-
-    @Override
-    public IssueHandler.Context setAttribute(String key, @Nullable String value) {
-      throw new UnsupportedOperationException("TODO");
-    }
-
-    @Override
-    public IssueHandler.Context assign(@Nullable String assignee) {
-      User user = null;
-      if(assignee != null) {
-        user = new DefaultUser().setLogin(assignee).setName(assignee);
-      }
-      updater.assign(issue, user, changeContext);
-      return this;
-    }
-
-    @Override
-    public IssueHandler.Context assign(@Nullable User user) {
-      updater.assign(issue, user, changeContext);
-      return this;
-    }
-
-    @Override
-    public IssueHandler.Context addComment(String text) {
-      updater.addComment(issue, text, changeContext);
-      return this;
-    }
-  }
-
-}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTracking.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTracking.java
deleted file mode 100644 (file)
index da4059e..0000000
+++ /dev/null
@@ -1,340 +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;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Objects;
-import com.google.common.collect.LinkedHashMultimap;
-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.issue.internal.DefaultIssue;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.core.issue.db.IssueDto;
-import org.sonar.plugins.core.issue.tracking.FileHashes;
-import org.sonar.plugins.core.issue.tracking.IssueTrackingBlocksRecognizer;
-import org.sonar.plugins.core.issue.tracking.RollingFileHashes;
-
-import javax.annotation.Nullable;
-
-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 {
-
-  /**
-   * @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();
-
-    if (sourceHashHolder != null) {
-      setChecksumOnNewIssues(newIssues, sourceHashHolder);
-    }
-
-    // Map new issues with old ones
-    mapIssues(newIssues, dbIssues, sourceHashHolder, result);
-    return result;
-  }
-
-  private void setChecksumOnNewIssues(Collection<DefaultIssue> issues, SourceHashHolder sourceHashHolder) {
-    if (issues.isEmpty()) {
-      return;
-    }
-    for (DefaultIssue issue : issues) {
-      Integer line = issue.line();
-      if (line != null) {
-        issue.setChecksum(sourceHashHolder.getHashedSource().getHash(line));
-      }
-    }
-  }
-
-  @VisibleForTesting
-  void mapIssues(Collection<DefaultIssue> newIssues, @Nullable Collection<IssueDto> lastIssues, @Nullable SourceHashHolder sourceHashHolder, IssueTrackingResult result) {
-    boolean hasLastScan = false;
-
-    if (lastIssues != null) {
-      hasLastScan = true;
-      mapLastIssues(newIssues, lastIssues, result);
-    }
-
-    // If each new issue matches an old one we can stop the matching mechanism
-    if (result.matched().size() != newIssues.size()) {
-      if (sourceHashHolder != null && hasLastScan) {
-        FileHashes hashedReference = sourceHashHolder.getHashedReference();
-        if (hashedReference != null) {
-          mapNewissues(hashedReference, sourceHashHolder.getHashedSource(), newIssues, result);
-        }
-      }
-      mapIssuesOnSameRule(newIssues, result);
-    }
-  }
-
-  private void mapLastIssues(Collection<DefaultIssue> newIssues, Collection<IssueDto> lastIssues, IssueTrackingResult result) {
-    for (IssueDto lastIssue : lastIssues) {
-      result.addUnmatched(lastIssue);
-    }
-
-    // Match the key of the issue. (For manual issues)
-    for (DefaultIssue newIssue : newIssues) {
-      mapIssue(newIssue, result.unmatchedByKeyForRule(newIssue.ruleKey()).get(newIssue.key()), result);
-    }
-
-    // Try first to match issues on same rule with same line and with same checksum (but not necessarily with same message)
-    for (DefaultIssue newIssue : newIssues) {
-      if (isNotAlreadyMapped(newIssue, result)) {
-        mapIssue(
-          newIssue,
-          findLastIssueWithSameLineAndChecksum(newIssue, result),
-          result);
-      }
-    }
-  }
-
-  private void mapNewissues(FileHashes hashedReference, FileHashes hashedSource, Collection<DefaultIssue> newIssues, IssueTrackingResult result) {
-
-    IssueTrackingBlocksRecognizer rec = new IssueTrackingBlocksRecognizer(hashedReference, hashedSource);
-
-    RollingFileHashes a = RollingFileHashes.create(hashedReference, 5);
-    RollingFileHashes b = RollingFileHashes.create(hashedSource, 5);
-
-    Multimap<Integer, DefaultIssue> newIssuesByLines = newIssuesByLines(newIssues, rec, result);
-    Multimap<Integer, IssueDto> lastIssuesByLines = lastIssuesByLines(result.unmatched(), rec);
-
-    Map<Integer, HashOccurrence> map = Maps.newHashMap();
-
-    for (Integer line : lastIssuesByLines.keySet()) {
-      int hash = a.getHash(line);
-      HashOccurrence hashOccurrence = map.get(hash);
-      if (hashOccurrence == null) {
-        // first occurrence in A
-        hashOccurrence = new HashOccurrence();
-        hashOccurrence.lineA = line;
-        hashOccurrence.countA = 1;
-        map.put(hash, hashOccurrence);
-      } else {
-        hashOccurrence.countA++;
-      }
-    }
-
-    for (Integer line : newIssuesByLines.keySet()) {
-      int hash = b.getHash(line);
-      HashOccurrence hashOccurrence = map.get(hash);
-      if (hashOccurrence != null) {
-        hashOccurrence.lineB = line;
-        hashOccurrence.countB++;
-      }
-    }
-
-    for (HashOccurrence hashOccurrence : map.values()) {
-      if (hashOccurrence.countA == 1 && hashOccurrence.countB == 1) {
-        // Guaranteed that lineA has been moved to lineB, so we can map all issues on lineA to all issues on lineB
-        map(newIssuesByLines.get(hashOccurrence.lineB), lastIssuesByLines.get(hashOccurrence.lineA), result);
-        lastIssuesByLines.removeAll(hashOccurrence.lineA);
-        newIssuesByLines.removeAll(hashOccurrence.lineB);
-      }
-    }
-
-    // Check if remaining number of lines exceeds threshold
-    if (lastIssuesByLines.keySet().size() * newIssuesByLines.keySet().size() < 250000) {
-      List<LinePair> possibleLinePairs = Lists.newArrayList();
-      for (Integer oldLine : lastIssuesByLines.keySet()) {
-        for (Integer newLine : newIssuesByLines.keySet()) {
-          int weight = rec.computeLengthOfMaximalBlock(oldLine, newLine);
-          possibleLinePairs.add(new LinePair(oldLine, newLine, weight));
-        }
-      }
-      Collections.sort(possibleLinePairs, LINE_PAIR_COMPARATOR);
-      for (LinePair linePair : possibleLinePairs) {
-        // High probability that lineA has been moved to lineB, so we can map all Issues on lineA to all Issues on lineB
-        map(newIssuesByLines.get(linePair.lineB), lastIssuesByLines.get(linePair.lineA), result);
-      }
-    }
-  }
-
-  private void mapIssuesOnSameRule(Collection<DefaultIssue> newIssues, IssueTrackingResult result) {
-    // Try then to match issues on same rule with same message and with same checksum
-    for (DefaultIssue newIssue : newIssues) {
-      if (isNotAlreadyMapped(newIssue, result)) {
-        mapIssue(
-          newIssue,
-          findLastIssueWithSameChecksumAndMessage(newIssue, result.unmatchedByKeyForRule(newIssue.ruleKey()).values()),
-          result);
-      }
-    }
-
-    // Try then to match issues on same rule with same line and with same message
-    for (DefaultIssue newIssue : newIssues) {
-      if (isNotAlreadyMapped(newIssue, result)) {
-        mapIssue(
-          newIssue,
-          findLastIssueWithSameLineAndMessage(newIssue, result.unmatchedByKeyForRule(newIssue.ruleKey()).values()),
-          result);
-      }
-    }
-
-    // Last check: match issue if same rule and same checksum but different line and different message
-    // See SONAR-2812
-    for (DefaultIssue newIssue : newIssues) {
-      if (isNotAlreadyMapped(newIssue, result)) {
-        mapIssue(
-          newIssue,
-          findLastIssueWithSameChecksum(newIssue, result.unmatchedByKeyForRule(newIssue.ruleKey()).values()),
-          result);
-      }
-    }
-  }
-
-  private void map(Collection<DefaultIssue> newIssues, Collection<IssueDto> lastIssues, IssueTrackingResult result) {
-    for (DefaultIssue newIssue : newIssues) {
-      if (isNotAlreadyMapped(newIssue, result)) {
-        for (IssueDto pastIssue : lastIssues) {
-          if (isNotAlreadyMapped(pastIssue, result) && Objects.equal(newIssue.ruleKey(), RuleKey.of(pastIssue.getRuleRepo(), pastIssue.getRule()))) {
-            mapIssue(newIssue, pastIssue, result);
-            break;
-          }
-        }
-      }
-    }
-  }
-
-  private Multimap<Integer, DefaultIssue> newIssuesByLines(Collection<DefaultIssue> newIssues, IssueTrackingBlocksRecognizer rec, IssueTrackingResult result) {
-    Multimap<Integer, DefaultIssue> newIssuesByLines = LinkedHashMultimap.create();
-    for (DefaultIssue newIssue : newIssues) {
-      if (isNotAlreadyMapped(newIssue, result) && rec.isValidLineInSource(newIssue.line())) {
-        newIssuesByLines.put(newIssue.line(), newIssue);
-      }
-    }
-    return newIssuesByLines;
-  }
-
-  private Multimap<Integer, IssueDto> lastIssuesByLines(Collection<IssueDto> lastIssues, IssueTrackingBlocksRecognizer rec) {
-    Multimap<Integer, IssueDto> lastIssuesByLines = LinkedHashMultimap.create();
-    for (IssueDto pastIssue : lastIssues) {
-      if (rec.isValidLineInReference(pastIssue.getLine())) {
-        lastIssuesByLines.put(pastIssue.getLine(), pastIssue);
-      }
-    }
-    return lastIssuesByLines;
-  }
-
-  private IssueDto findLastIssueWithSameChecksum(DefaultIssue newIssue, Collection<IssueDto> lastIssues) {
-    for (IssueDto pastIssue : lastIssues) {
-      if (isSameChecksum(newIssue, pastIssue)) {
-        return pastIssue;
-      }
-    }
-    return null;
-  }
-
-  private IssueDto findLastIssueWithSameLineAndMessage(DefaultIssue newIssue, Collection<IssueDto> lastIssues) {
-    for (IssueDto pastIssue : lastIssues) {
-      if (isSameLine(newIssue, pastIssue) && isSameMessage(newIssue, pastIssue)) {
-        return pastIssue;
-      }
-    }
-    return null;
-  }
-
-  private IssueDto findLastIssueWithSameChecksumAndMessage(DefaultIssue newIssue, Collection<IssueDto> lastIssues) {
-    for (IssueDto pastIssue : lastIssues) {
-      if (isSameChecksum(newIssue, pastIssue) && isSameMessage(newIssue, pastIssue)) {
-        return pastIssue;
-      }
-    }
-    return null;
-  }
-
-  private IssueDto findLastIssueWithSameLineAndChecksum(DefaultIssue newIssue, IssueTrackingResult result) {
-    Collection<IssueDto> sameRuleAndSameLineAndSameChecksum = result.unmatchedForRuleAndForLineAndForChecksum(newIssue.ruleKey(), newIssue.line(), newIssue.checksum());
-    if (!sameRuleAndSameLineAndSameChecksum.isEmpty()) {
-      return sameRuleAndSameLineAndSameChecksum.iterator().next();
-    }
-    return null;
-  }
-
-  private boolean isNotAlreadyMapped(IssueDto pastIssue, IssueTrackingResult result) {
-    return result.unmatched().contains(pastIssue);
-  }
-
-  private boolean isNotAlreadyMapped(DefaultIssue newIssue, IssueTrackingResult result) {
-    return !result.isMatched(newIssue);
-  }
-
-  private boolean isSameChecksum(DefaultIssue newIssue, IssueDto pastIssue) {
-    return Objects.equal(pastIssue.getChecksum(), newIssue.checksum());
-  }
-
-  private boolean isSameLine(DefaultIssue newIssue, IssueDto pastIssue) {
-    return Objects.equal(pastIssue.getLine(), newIssue.line());
-  }
-
-  private boolean isSameMessage(DefaultIssue newIssue, IssueDto pastIssue) {
-    return Objects.equal(newIssue.message(), pastIssue.getMessage());
-  }
-
-  private void mapIssue(DefaultIssue issue, @Nullable IssueDto ref, IssueTrackingResult result) {
-    if (ref != null) {
-      result.setMatch(issue, ref);
-    }
-  }
-
-  @Override
-  public String toString() {
-    return getClass().getSimpleName();
-  }
-
-  private static class LinePair {
-    int lineA;
-    int lineB;
-    int weight;
-
-    public LinePair(int lineA, int lineB, int weight) {
-      this.lineA = lineA;
-      this.lineB = lineB;
-      this.weight = weight;
-    }
-  }
-
-  private static class HashOccurrence {
-    int lineA;
-    int lineB;
-    int countA;
-    int countB;
-  }
-
-  private static final Comparator<LinePair> LINE_PAIR_COMPARATOR = new Comparator<LinePair>() {
-    @Override
-    public int compare(LinePair o1, LinePair o2) {
-      int weightDiff = o2.weight - o1.weight;
-      if (weightDiff != 0) {
-        return weightDiff;
-      } else {
-        return Math.abs(o1.lineA - o1.lineB) - Math.abs(o2.lineA - o2.lineB);
-      }
-    }
-  };
-
-}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java
deleted file mode 100644 (file)
index 3d2bfc4..0000000
+++ /dev/null
@@ -1,277 +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;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
-import org.apache.commons.lang.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.batch.Decorator;
-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.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;
-import org.sonar.api.rules.ActiveRule;
-import org.sonar.api.rules.Rule;
-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.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;
-import org.sonar.core.issue.workflow.IssueWorkflow;
-
-import java.util.Collection;
-
-@DependsUpon(DecoratorBarriers.ISSUES_ADDED)
-@DependedUpon(DecoratorBarriers.ISSUES_TRACKED)
-public class IssueTrackingDecorator implements Decorator {
-
-  private static final Logger LOG = LoggerFactory.getLogger(IssueTrackingDecorator.class);
-
-  private final IssueCache issueCache;
-  private final InitialOpenIssuesStack initialOpenIssues;
-  private final IssueTracking tracking;
-  private final LastLineHashes lastLineHashes;
-  private final IssueHandlers handlers;
-  private final IssueWorkflow workflow;
-  private final IssueUpdater updater;
-  private final IssueChangeContext changeContext;
-  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,
-    LastLineHashes lastLineHashes,
-    IssueHandlers handlers, IssueWorkflow workflow,
-    IssueUpdater updater,
-    Project project,
-    ResourcePerspectives perspectives,
-    RulesProfile rulesProfile,
-    RuleFinder ruleFinder, InputPathCache inputPathCache) {
-    this.issueCache = issueCache;
-    this.initialOpenIssues = initialOpenIssues;
-    this.tracking = tracking;
-    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;
-    this.ruleFinder = ruleFinder;
-  }
-
-  @Override
-  public boolean shouldExecuteOnProject(Project project) {
-    return true;
-  }
-
-  @Override
-  public void decorate(Resource resource, DecoratorContext context) {
-    Issuable issuable = perspectives.as(Issuable.class, resource);
-    if (issuable != null) {
-      doDecorate(resource);
-    }
-  }
-
-  @VisibleForTesting
-  void doDecorate(Resource resource) {
-    Collection<DefaultIssue> issues = Lists.newArrayList();
-    for (Issue issue : issueCache.byComponent(resource.getEffectiveKey())) {
-      issues.add((DefaultIssue) issue);
-    }
-    issueCache.clear(resource.getEffectiveKey());
-    // issues = all the issues created by rule engines during this module scan and not excluded by filters
-
-    // 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 = null;
-    if (ResourceUtils.isFile(resource)) {
-      File sonarFile = (File) resource;
-      InputFile file = inputPathCache.getFile(project.getEffectiveKey(), sonarFile.getPath());
-      if (file == null) {
-        throw new IllegalStateException("Resource " + resource + " was not found in InputPath cache");
-      }
-      sourceHashHolder = new SourceHashHolder((DefaultInputFile) file, lastLineHashes);
-    }
-
-    IssueTrackingResult trackingResult = tracking.track(sourceHashHolder, dbOpenIssues, issues);
-
-    // unmatched = issues that have been resolved + issues on disabled/removed rules + manual issues
-    addUnmatched(trackingResult.unmatched(), sourceHashHolder, issues);
-
-    mergeMatched(trackingResult);
-
-    if (ResourceUtils.isProject(resource)) {
-      // issues that relate to deleted components
-      addIssuesOnDeletedComponents(issues);
-    }
-
-    for (DefaultIssue issue : issues) {
-      workflow.doAutomaticTransition(issue, changeContext);
-      handlers.execute(issue, changeContext);
-      issueCache.put(issue);
-    }
-  }
-
-  @VisibleForTesting
-  protected void mergeMatched(IssueTrackingResult result) {
-    for (DefaultIssue issue : result.matched()) {
-      IssueDto ref = result.matching(issue);
-
-      // invariant fields
-      issue.setKey(ref.getKee());
-      issue.setCreationDate(ref.getIssueCreationDate());
-      issue.setUpdateDate(ref.getIssueUpdateDate());
-      issue.setCloseDate(ref.getIssueCloseDate());
-
-      // non-persisted fields
-      issue.setNew(false);
-      issue.setEndOfLife(false);
-      issue.setOnDisabledRule(false);
-      issue.setSelectedAt(ref.getSelectedAt());
-
-      // fields to update with old values
-      issue.setActionPlanKey(ref.getActionPlanKey());
-      issue.setResolution(ref.getResolution());
-      issue.setStatus(ref.getStatus());
-      issue.setAssignee(ref.getAssignee());
-      issue.setAuthorLogin(ref.getAuthorLogin());
-      issue.setTags(ref.getTags());
-
-      if (ref.getIssueAttributes() != null) {
-        issue.setAttributes(KeyValueFormat.parse(ref.getIssueAttributes()));
-      }
-
-      // populate existing changelog
-      Collection<IssueChangeDto> issueChangeDtos = initialOpenIssues.selectChangelog(issue.key());
-      for (IssueChangeDto issueChangeDto : issueChangeDtos) {
-        issue.addChange(issueChangeDto.toFieldDiffs());
-      }
-
-      // fields to update with current values
-      if (ref.isManualSeverity()) {
-        issue.setManualSeverity(true);
-        issue.setSeverity(ref.getSeverity());
-      } else {
-        updater.setPastSeverity(issue, ref.getSeverity(), changeContext);
-      }
-      updater.setPastLine(issue, ref.getLine());
-      updater.setPastMessage(issue, ref.getMessage(), changeContext);
-      updater.setPastEffortToFix(issue, ref.getEffortToFix(), changeContext);
-      Long debtInMinutes = ref.getDebt();
-      Duration previousTechnicalDebt = debtInMinutes != null ? Duration.create(debtInMinutes) : null;
-      updater.setPastTechnicalDebt(issue, previousTechnicalDebt, changeContext);
-      updater.setPastProject(issue, ref.getProjectKey(), changeContext);
-    }
-  }
-
-  private void addUnmatched(Collection<IssueDto> unmatchedIssues, SourceHashHolder sourceHashHolder, Collection<DefaultIssue> issues) {
-    for (IssueDto unmatchedDto : unmatchedIssues) {
-      DefaultIssue unmatched = unmatchedDto.toDefaultIssue();
-      if (StringUtils.isNotBlank(unmatchedDto.getReporter()) && !Issue.STATUS_CLOSED.equals(unmatchedDto.getStatus())) {
-        relocateManualIssue(unmatched, unmatchedDto, sourceHashHolder);
-      }
-      updateUnmatchedIssue(unmatched, false /* manual issues can be kept open */);
-      issues.add(unmatched);
-    }
-  }
-
-  private void addIssuesOnDeletedComponents(Collection<DefaultIssue> issues) {
-    for (IssueDto deadDto : initialOpenIssues.selectAllIssues()) {
-      DefaultIssue dead = deadDto.toDefaultIssue();
-      updateUnmatchedIssue(dead, true);
-      issues.add(dead);
-    }
-    initialOpenIssues.clear();
-  }
-
-  private void updateUnmatchedIssue(DefaultIssue issue, boolean forceEndOfLife) {
-    issue.setNew(false);
-
-    boolean manualIssue = !Strings.isNullOrEmpty(issue.reporter());
-    Rule rule = ruleFinder.findByKey(issue.ruleKey());
-    if (manualIssue) {
-      // Manual rules are not declared in Quality profiles, so no need to check ActiveRule
-      boolean isRemovedRule = rule == null || Rule.STATUS_REMOVED.equals(rule.getStatus());
-      issue.setEndOfLife(forceEndOfLife || isRemovedRule);
-      issue.setOnDisabledRule(isRemovedRule);
-    } else {
-      ActiveRule activeRule = rulesProfile.getActiveRule(issue.ruleKey().repository(), issue.ruleKey().rule());
-      issue.setEndOfLife(true);
-      issue.setOnDisabledRule(activeRule == null || rule == null || Rule.STATUS_REMOVED.equals(rule.getStatus()));
-    }
-  }
-
-  private void relocateManualIssue(DefaultIssue newIssue, IssueDto oldIssue, SourceHashHolder sourceHashHolder) {
-    LOG.debug("Trying to relocate manual issue {}", oldIssue.getKee());
-
-    Integer previousLine = oldIssue.getLine();
-    if (previousLine == null) {
-      LOG.debug("Cannot relocate issue at resource level");
-      return;
-    }
-
-    Collection<Integer> newLinesWithSameHash = sourceHashHolder.getNewLinesMatching(previousLine);
-    LOG.debug("Found the following lines with same hash: {}", newLinesWithSameHash);
-    if (newLinesWithSameHash.isEmpty()) {
-      if (previousLine > sourceHashHolder.getHashedSource().length()) {
-        LOG.debug("Old issue line {} is out of new source, closing and removing line number", previousLine);
-        newIssue.setLine(null);
-        updater.setStatus(newIssue, Issue.STATUS_CLOSED, changeContext);
-        updater.setResolution(newIssue, Issue.RESOLUTION_REMOVED, changeContext);
-        updater.setPastLine(newIssue, previousLine);
-        updater.setPastMessage(newIssue, oldIssue.getMessage(), changeContext);
-        updater.setPastEffortToFix(newIssue, oldIssue.getEffortToFix(), changeContext);
-      }
-    } else if (newLinesWithSameHash.size() == 1) {
-      Integer newLine = newLinesWithSameHash.iterator().next();
-      LOG.debug("Relocating issue to line {}", newLine);
-
-      newIssue.setLine(newLine);
-      updater.setPastLine(newIssue, previousLine);
-      updater.setPastMessage(newIssue, oldIssue.getMessage(), changeContext);
-      updater.setPastEffortToFix(newIssue, oldIssue.getEffortToFix(), changeContext);
-    }
-  }
-}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingResult.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingResult.java
deleted file mode 100644 (file)
index f63f94f..0000000
+++ /dev/null
@@ -1,113 +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;
-
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Multimap;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.core.issue.db.IssueDto;
-
-import javax.annotation.Nullable;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-class IssueTrackingResult {
-  private final Map<String, IssueDto> unmatchedByKey = new HashMap<String, IssueDto>();
-  private final Map<RuleKey, Map<String, IssueDto>> unmatchedByRuleAndKey = new HashMap<RuleKey, Map<String, IssueDto>>();
-  private final Map<RuleKey, Map<Integer, Multimap<String, IssueDto>>> unmatchedByRuleAndLineAndChecksum =
-    new HashMap<RuleKey, Map<Integer, Multimap<String, IssueDto>>>();
-  private final Map<DefaultIssue, IssueDto> matched = Maps.newIdentityHashMap();
-
-  Collection<IssueDto> unmatched() {
-    return unmatchedByKey.values();
-  }
-
-  Map<String, IssueDto> unmatchedByKeyForRule(RuleKey ruleKey) {
-    return unmatchedByRuleAndKey.containsKey(ruleKey) ? unmatchedByRuleAndKey.get(ruleKey) : Collections.<String, IssueDto>emptyMap();
-  }
-
-  Collection<IssueDto> unmatchedForRuleAndForLineAndForChecksum(RuleKey ruleKey, @Nullable Integer line, @Nullable String checksum) {
-    if (!unmatchedByRuleAndLineAndChecksum.containsKey(ruleKey)) {
-      return Collections.emptyList();
-    }
-    Map<Integer, Multimap<String, IssueDto>> unmatchedForRule = unmatchedByRuleAndLineAndChecksum.get(ruleKey);
-    Integer lineNotNull = line != null ? line : 0;
-    if (!unmatchedForRule.containsKey(lineNotNull)) {
-      return Collections.emptyList();
-    }
-    Multimap<String, IssueDto> unmatchedForRuleAndLine = unmatchedForRule.get(lineNotNull);
-    String checksumNotNull = StringUtils.defaultString(checksum, "");
-    if (!unmatchedForRuleAndLine.containsKey(checksumNotNull)) {
-      return Collections.emptyList();
-    }
-    return unmatchedForRuleAndLine.get(checksumNotNull);
-  }
-
-  Collection<DefaultIssue> matched() {
-    return matched.keySet();
-  }
-
-  boolean isMatched(DefaultIssue issue) {
-    return matched.containsKey(issue);
-  }
-
-  IssueDto matching(DefaultIssue issue) {
-    return matched.get(issue);
-  }
-
-  void addUnmatched(IssueDto i) {
-    unmatchedByKey.put(i.getKee(), i);
-    RuleKey ruleKey = RuleKey.of(i.getRuleRepo(), i.getRule());
-    if (!unmatchedByRuleAndKey.containsKey(ruleKey)) {
-      unmatchedByRuleAndKey.put(ruleKey, new HashMap<String, IssueDto>());
-      unmatchedByRuleAndLineAndChecksum.put(ruleKey, new HashMap<Integer, Multimap<String, IssueDto>>());
-    }
-    unmatchedByRuleAndKey.get(ruleKey).put(i.getKee(), i);
-    Map<Integer, Multimap<String, IssueDto>> unmatchedForRule = unmatchedByRuleAndLineAndChecksum.get(ruleKey);
-    Integer lineNotNull = lineNotNull(i);
-    if (!unmatchedForRule.containsKey(lineNotNull)) {
-      unmatchedForRule.put(lineNotNull, HashMultimap.<String, IssueDto>create());
-    }
-    Multimap<String, IssueDto> unmatchedForRuleAndLine = unmatchedForRule.get(lineNotNull);
-    String checksumNotNull = StringUtils.defaultString(i.getChecksum(), "");
-    unmatchedForRuleAndLine.put(checksumNotNull, i);
-  }
-
-  private Integer lineNotNull(IssueDto i) {
-    Integer line = i.getLine();
-    return line != null ? line : 0;
-  }
-
-  void setMatch(DefaultIssue issue, IssueDto matching) {
-    matched.put(issue, matching);
-    RuleKey ruleKey = RuleKey.of(matching.getRuleRepo(), matching.getRule());
-    unmatchedByRuleAndKey.get(ruleKey).remove(matching.getKee());
-    unmatchedByKey.remove(matching.getKee());
-    Integer lineNotNull = lineNotNull(matching);
-    String checksumNotNull = StringUtils.defaultString(matching.getChecksum(), "");
-    unmatchedByRuleAndLineAndChecksum.get(ruleKey).get(lineNotNull).get(checksumNotNull).remove(matching);
-  }
-}
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
deleted file mode 100644 (file)
index 612968e..0000000
+++ /dev/null
@@ -1,79 +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;
-
-import com.google.common.collect.ImmutableSet;
-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 LastLineHashes lastSnapshots;
-
-  private FileHashes hashedReference;
-  private FileHashes hashedSource;
-  private DefaultInputFile inputFile;
-
-  public SourceHashHolder(DefaultInputFile inputFile, LastLineHashes lastSnapshots) {
-    this.inputFile = inputFile;
-    this.lastSnapshots = lastSnapshots;
-  }
-
-  private void initHashes() {
-    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;
-      }
-    }
-  }
-
-  @CheckForNull
-  public FileHashes getHashedReference() {
-    initHashes();
-    return hashedReference;
-  }
-
-  public FileHashes getHashedSource() {
-    initHashes();
-    return hashedSource;
-  }
-
-  public Collection<Integer> getNewLinesMatching(Integer originLine) {
-    FileHashes reference = getHashedReference();
-    if (reference == null) {
-      return ImmutableSet.of();
-    } else {
-      return getHashedSource().getLinesForHash(reference.getHash(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
deleted file mode 100644 (file)
index d45ac2d..0000000
+++ /dev/null
@@ -1,77 +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 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/IssueTrackingBlocksRecognizer.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/IssueTrackingBlocksRecognizer.java
deleted file mode 100644 (file)
index 11611ad..0000000
+++ /dev/null
@@ -1,69 +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 javax.annotation.Nullable;
-
-public class IssueTrackingBlocksRecognizer {
-
-  private final FileHashes a;
-  private final FileHashes b;
-
-  public IssueTrackingBlocksRecognizer(FileHashes a, FileHashes b) {
-    this.a = a;
-    this.b = b;
-  }
-
-  public boolean isValidLineInReference(@Nullable Integer line) {
-    return (line != null) && (0 <= line - 1) && (line - 1 < a.length());
-  }
-
-  public boolean isValidLineInSource(@Nullable Integer line) {
-    return (line != null) && (0 <= line - 1) && (line - 1 < b.length());
-  }
-
-  /**
-   * @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 (!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() && a.getHash(ai).equals(b.getHash(bi))) {
-      ai++;
-      bi++;
-      length++;
-    }
-    ai = startA;
-    bi = startB;
-    while (ai > 0 && bi > 0 && a.getHash(ai).equals(b.getHash(bi))) {
-      ai--;
-      bi--;
-      length++;
-    }
-    // Note that position (startA, startB) was counted twice
-    return length - 1;
-  }
-
-}
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
deleted file mode 100644 (file)
index 313507a..0000000
+++ /dev/null
@@ -1,89 +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;
-
-/**
- * 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/package-info.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/package-info.java
deleted file mode 100644 (file)
index 4a3c698..0000000
+++ /dev/null
@@ -1,23 +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.
- */
-
-@javax.annotation.ParametersAreNonnullByDefault
-package org.sonar.plugins.core.issue.tracking;
-
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/InitialOpenIssuesSensorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/InitialOpenIssuesSensorTest.java
deleted file mode 100644 (file)
index e1c7033..0000000
+++ /dev/null
@@ -1,65 +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;
-
-import org.apache.ibatis.session.ResultHandler;
-import org.junit.Test;
-import org.sonar.api.resources.Project;
-import org.sonar.core.issue.db.IssueChangeDao;
-import org.sonar.core.issue.db.IssueDao;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-public class InitialOpenIssuesSensorTest {
-
-  InitialOpenIssuesStack stack = mock(InitialOpenIssuesStack.class);
-  IssueDao issueDao = mock(IssueDao.class);
-  IssueChangeDao issueChangeDao = mock(IssueChangeDao.class);
-
-  InitialOpenIssuesSensor sensor = new InitialOpenIssuesSensor(stack, issueDao, issueChangeDao);
-
-  @Test
-  public void should_select_module_open_issues() {
-    Project project = new Project("key");
-    project.setId(1);
-    sensor.analyse(project, null);
-
-    verify(issueDao).selectNonClosedIssuesByModule(eq(1), any(ResultHandler.class));
-  }
-
-  @Test
-  public void should_select_module_open_issues_changelog() {
-    Project project = new Project("key");
-    project.setId(1);
-    sensor.analyse(project, null);
-
-    verify(issueChangeDao).selectChangelogOnNonClosedIssuesByModuleAndType(eq(1), any(ResultHandler.class));
-  }
-
-  @Test
-  public void test_toString() throws Exception {
-    assertThat(sensor.toString()).isEqualTo("InitialOpenIssuesSensor");
-
-  }
-}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/InitialOpenIssuesStackTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/InitialOpenIssuesStackTest.java
deleted file mode 100644 (file)
index ed7782e..0000000
+++ /dev/null
@@ -1,142 +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;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.api.CoreProperties;
-import org.sonar.batch.bootstrap.BootstrapProperties;
-import org.sonar.batch.bootstrap.TempFolderProvider;
-import org.sonar.batch.index.Caches;
-import org.sonar.core.issue.db.IssueChangeDto;
-import org.sonar.core.issue.db.IssueDto;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class InitialOpenIssuesStackTest {
-
-  @ClassRule
-  public static TemporaryFolder temp = new TemporaryFolder();
-
-  public static Caches createCacheOnTemp(TemporaryFolder temp) {
-    BootstrapProperties bootstrapSettings = new BootstrapProperties(Collections.<String, String>emptyMap());
-    try {
-      bootstrapSettings.properties().put(CoreProperties.WORKING_DIRECTORY, temp.newFolder().getAbsolutePath());
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
-    return new Caches(new TempFolderProvider().provide(bootstrapSettings));
-  }
-
-  InitialOpenIssuesStack stack;
-  Caches caches;
-
-  @Before
-  public void before() throws Exception {
-    caches = createCacheOnTemp(temp);
-    caches.start();
-    stack = new InitialOpenIssuesStack(caches);
-  }
-
-  @After
-  public void after() {
-    caches.stop();
-  }
-
-  @Test
-  public void get_and_remove_issues() {
-    IssueDto issueDto = new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-1");
-    stack.addIssue(issueDto);
-
-    List<IssueDto> issueDtos = stack.selectAndRemoveIssues("org.struts.Action");
-    assertThat(issueDtos).hasSize(1);
-    assertThat(issueDtos.get(0).getKee()).isEqualTo("ISSUE-1");
-
-    assertThat(stack.selectAllIssues()).isEmpty();
-  }
-
-  @Test
-  public void get_and_remove_with_many_issues_on_same_resource() {
-    stack.addIssue(new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-1"));
-    stack.addIssue(new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-2"));
-
-    List<IssueDto> issueDtos = stack.selectAndRemoveIssues("org.struts.Action");
-    assertThat(issueDtos).hasSize(2);
-
-    assertThat(stack.selectAllIssues()).isEmpty();
-  }
-
-  @Test
-  public void get_and_remove_do_nothing_if_resource_not_found() {
-    stack.addIssue(new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-1"));
-
-    List<IssueDto> issueDtos = stack.selectAndRemoveIssues("Other");
-    assertThat(issueDtos).hasSize(0);
-
-    assertThat(stack.selectAllIssues()).hasSize(1);
-  }
-
-  @Test
-  public void select_changelog() {
-    stack.addChangelog(new IssueChangeDto().setKey("CHANGE-1").setIssueKey("ISSUE-1"));
-    stack.addChangelog(new IssueChangeDto().setKey("CHANGE-2").setIssueKey("ISSUE-1"));
-
-    List<IssueChangeDto> issueChangeDtos = stack.selectChangelog("ISSUE-1");
-    assertThat(issueChangeDtos).hasSize(2);
-    assertThat(issueChangeDtos.get(0).getKey()).isEqualTo("CHANGE-1");
-    assertThat(issueChangeDtos.get(1).getKey()).isEqualTo("CHANGE-2");
-  }
-
-  @Test
-  public void return_empty_changelog() {
-    assertThat(stack.selectChangelog("ISSUE-1")).isEmpty();
-  }
-
-  @Test
-  public void clear_issues() {
-    stack.addIssue(new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-1"));
-
-    assertThat(stack.selectAllIssues()).hasSize(1);
-
-    // issues are not removed
-    assertThat(stack.selectAllIssues()).hasSize(1);
-
-    stack.clear();
-    assertThat(stack.selectAllIssues()).isEmpty();
-  }
-
-  @Test
-  public void clear_issues_changelog() {
-    stack.addChangelog(new IssueChangeDto().setKey("CHANGE-1").setIssueKey("ISSUE-1"));
-
-    assertThat(stack.selectChangelog("ISSUE-1")).hasSize(1);
-
-    stack.clear();
-    assertThat(stack.selectChangelog("ISSUE-1")).isEmpty();
-  }
-}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueHandlersTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueHandlersTest.java
deleted file mode 100644 (file)
index b1f7459..0000000
+++ /dev/null
@@ -1,60 +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;
-
-import org.junit.Test;
-import org.mockito.ArgumentMatcher;
-import org.sonar.api.issue.IssueHandler;
-import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.issue.internal.IssueChangeContext;
-import org.sonar.core.issue.IssueUpdater;
-
-import java.util.Date;
-
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Mockito.*;
-
-public class IssueHandlersTest {
-  @Test
-  public void should_execute_handlers() throws Exception {
-    IssueHandler h1 = mock(IssueHandler.class);
-    IssueHandler h2 = mock(IssueHandler.class);
-    IssueUpdater updater = mock(IssueUpdater.class);
-
-    IssueHandlers handlers = new IssueHandlers(updater, new IssueHandler[]{h1, h2});
-    final DefaultIssue issue = new DefaultIssue();
-    handlers.execute(issue, IssueChangeContext.createScan(new Date()));
-
-    verify(h1).onIssue(argThat(new ArgumentMatcher<IssueHandler.Context>() {
-      @Override
-      public boolean matches(Object o) {
-        return ((IssueHandler.Context) o).issue() == issue;
-      }
-    }));
-  }
-
-  @Test
-  public void test_no_handlers() {
-    IssueUpdater updater = mock(IssueUpdater.class);
-    IssueHandlers handlers = new IssueHandlers(updater);
-    handlers.execute(new DefaultIssue(), IssueChangeContext.createScan(new Date()));
-    verifyZeroInteractions(updater);
-  }
-}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java
deleted file mode 100644 (file)
index 337f16b..0000000
+++ /dev/null
@@ -1,574 +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;
-
-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.fs.internal.DefaultInputFile;
-import org.sonar.api.component.ResourcePerspectives;
-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.rule.RuleKey;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.utils.Duration;
-import org.sonar.api.utils.System2;
-import org.sonar.batch.issue.IssueCache;
-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;
-import org.sonar.core.issue.workflow.IssueWorkflow;
-import org.sonar.java.api.JavaClass;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static org.assertj.core.api.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.*;
-
-public class IssueTrackingDecoratorTest {
-
-  IssueTrackingDecorator decorator;
-  IssueCache issueCache = mock(IssueCache.class, RETURNS_MOCKS);
-  InitialOpenIssuesStack initialOpenIssues = mock(InitialOpenIssuesStack.class);
-  IssueTracking tracking = mock(IssueTracking.class, RETURNS_MOCKS);
-  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() {
-    decorator = new IssueTrackingDecorator(
-      issueCache,
-      initialOpenIssues,
-      tracking,
-      lastSnapshots,
-      handlers,
-      workflow,
-      updater,
-      new Project("foo"),
-      perspectives,
-      profile,
-      ruleFinder,
-      inputPathCache);
-  }
-
-  @Test
-  public void should_execute_on_project() {
-    Project project = mock(Project.class);
-    assertThat(decorator.shouldExecuteOnProject(project)).isTrue();
-  }
-
-  @Test
-  public void should_not_be_executed_on_classes_not_methods() throws Exception {
-    DecoratorContext context = mock(DecoratorContext.class);
-    decorator.decorate(JavaClass.create("org.foo.Bar"), context);
-    verifyZeroInteractions(context, issueCache, tracking, handlers, workflow);
-  }
-
-  @Test
-  public void should_process_open_issues() throws Exception {
-    Resource file = File.create("Action.java").setEffectiveKey("struts:Action.java").setId(123);
-    final DefaultIssue issue = new DefaultIssue();
-
-    // INPUT : one issue, no open issues during previous scan, no filtering
-    when(issueCache.byComponent("struts:Action.java")).thenReturn(Arrays.asList(issue));
-    List<IssueDto> dbIssues = Collections.emptyList();
-    when(initialOpenIssues.selectAndRemoveIssues("struts:Action.java")).thenReturn(dbIssues);
-    when(inputPathCache.getFile("foo", "Action.java")).thenReturn(mock(DefaultInputFile.class));
-    decorator.doDecorate(file);
-
-    // Apply filters, track, apply transitions, notify extensions then update cache
-    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;
-        return issues.size() == 1 && issues.get(0) == issue;
-      }
-    }));
-    verify(workflow).doAutomaticTransition(eq(issue), any(IssueChangeContext.class));
-    verify(handlers).execute(eq(issue), any(IssueChangeContext.class));
-    verify(issueCache).put(issue);
-  }
-
-  @Test
-  public void should_register_unmatched_issues_as_end_of_life() throws Exception {
-    // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
-    Resource file = File.create("Action.java").setEffectiveKey("struts:Action.java").setId(123);
-
-    // INPUT : one issue existing during previous scan
-    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle");
-
-    IssueTrackingResult trackingResult = new IssueTrackingResult();
-    trackingResult.addUnmatched(unmatchedIssue);
-
-    when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
-    when(inputPathCache.getFile("foo", "Action.java")).thenReturn(mock(DefaultInputFile.class));
-
-    decorator.doDecorate(file);
-
-    verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
-    verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
-
-    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
-    verify(issueCache).put(argument.capture());
-
-    DefaultIssue issue = argument.getValue();
-    assertThat(issue.key()).isEqualTo("ABCDE");
-    assertThat(issue.isNew()).isFalse();
-    assertThat(issue.isEndOfLife()).isTrue();
-  }
-
-  @Test
-  public void manual_issues_should_be_moved_if_matching_line_found() throws Exception {
-    // 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"));
-
-    IssueTrackingResult trackingResult = new IssueTrackingResult();
-    trackingResult.addUnmatched(unmatchedIssue);
-
-    String originalSource = "public interface Action {\n"
-      + "   void method1();\n"
-      + "   void method2();\n"
-      + "   void method3();\n"
-      + "   void method4();\n"
-      + "   void method5();\n" // Original issue here
-      + "}";
-    String newSource = "public interface Action {\n"
-      + "   void method5();\n" // New issue here
-      + "   void method1();\n"
-      + "   void method2();\n"
-      + "   void method3();\n"
-      + "   void method4();\n"
-      + "}";
-    Resource file = mockHashes(originalSource, newSource);
-
-    when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
-
-    decorator.doDecorate(file);
-
-    verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
-    verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
-
-    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
-    verify(issueCache).put(argument.capture());
-
-    DefaultIssue issue = argument.getValue();
-    assertThat(issue.line()).isEqualTo(2);
-    assertThat(issue.key()).isEqualTo("ABCDE");
-    assertThat(issue.isNew()).isFalse();
-    assertThat(issue.isEndOfLife()).isFalse();
-    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 {
-
-    // INPUT : one issue existing during previous scan
-    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("CLOSED").setRuleKey("manual", "Performance");
-    when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
-
-    IssueTrackingResult trackingResult = new IssueTrackingResult();
-    trackingResult.addUnmatched(unmatchedIssue);
-
-    String originalSource = "public interface Action {}";
-    Resource file = mockHashes(originalSource, originalSource);
-
-    when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
-
-    decorator.doDecorate(file);
-
-    verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
-    verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
-
-    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
-    verify(issueCache).put(argument.capture());
-
-    DefaultIssue issue = argument.getValue();
-    assertThat(issue.line()).isEqualTo(1);
-    assertThat(issue.key()).isEqualTo("ABCDE");
-    assertThat(issue.isNew()).isFalse();
-    assertThat(issue.isEndOfLife()).isFalse();
-    assertThat(issue.isOnDisabledRule()).isFalse();
-    assertThat(issue.status()).isEqualTo("CLOSED");
-  }
-
-  @Test
-  public void manual_issues_should_be_untouched_if_line_is_null() throws Exception {
-
-    // INPUT : one issue existing during previous scan
-    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(null).setStatus("OPEN").setRuleKey("manual", "Performance");
-    when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
-
-    IssueTrackingResult trackingResult = new IssueTrackingResult();
-    trackingResult.addUnmatched(unmatchedIssue);
-
-    String originalSource = "public interface Action {}";
-    Resource file = mockHashes(originalSource, originalSource);
-
-    when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
-
-    decorator.doDecorate(file);
-
-    verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
-    verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
-
-    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
-    verify(issueCache).put(argument.capture());
-
-    DefaultIssue issue = argument.getValue();
-    assertThat(issue.line()).isEqualTo(null);
-    assertThat(issue.key()).isEqualTo("ABCDE");
-    assertThat(issue.isNew()).isFalse();
-    assertThat(issue.isEndOfLife()).isFalse();
-    assertThat(issue.isOnDisabledRule()).isFalse();
-    assertThat(issue.status()).isEqualTo("OPEN");
-  }
-
-  @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
-
-    // INPUT : one issue existing during previous scan
-    final int issueOnLine = 6;
-    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(issueOnLine).setStatus("OPEN").setRuleKey("manual", "Performance");
-    when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
-
-    IssueTrackingResult trackingResult = new IssueTrackingResult();
-    trackingResult.addUnmatched(unmatchedIssue);
-
-    String originalSource = "public interface Action {\n"
-      + "   void method1();\n"
-      + "   void method2();\n"
-      + "   void method3();\n"
-      + "   void method4();\n"
-      + "   void method5();\n" // Original issue here
-      + "}";
-    String newSource = "public interface Action {\n"
-      + "   void method1();\n"
-      + "   void method2();\n"
-      + "   void method3();\n"
-      + "   void method4();\n"
-      + "   void method6();\n" // Poof, no method5 anymore
-      + "}";
-
-    Resource file = mockHashes(originalSource, newSource);
-
-    when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
-
-    decorator.doDecorate(file);
-
-    verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
-    verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
-
-    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
-    verify(issueCache).put(argument.capture());
-
-    DefaultIssue issue = argument.getValue();
-    assertThat(issue.line()).isEqualTo(issueOnLine);
-    assertThat(issue.key()).isEqualTo("ABCDE");
-    assertThat(issue.isNew()).isFalse();
-    assertThat(issue.isEndOfLife()).isFalse();
-    assertThat(issue.isOnDisabledRule()).isFalse();
-  }
-
-  @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
-
-    // INPUT : one issue existing during previous scan
-    final int issueOnLine = 3;
-    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(issueOnLine).setStatus("OPEN").setRuleKey("manual", "Performance");
-    when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
-
-    IssueTrackingResult trackingResult = new IssueTrackingResult();
-    trackingResult.addUnmatched(unmatchedIssue);
-
-    String originalSource = "public class Action {\n"
-      + "   void method1() {\n"
-      + "     notify();\n" // initial issue
-      + "   }\n"
-      + "}";
-    String newSource = "public class Action {\n"
-      + "   \n"
-      + "   void method1() {\n" // new issue will appear here
-      + "     notify();\n"
-      + "   }\n"
-      + "   void method2() {\n"
-      + "     notify();\n"
-      + "   }\n"
-      + "}";
-    Resource file = mockHashes(originalSource, newSource);
-
-    when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
-
-    decorator.doDecorate(file);
-
-    verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
-    verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
-
-    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
-    verify(issueCache).put(argument.capture());
-
-    DefaultIssue issue = argument.getValue();
-    assertThat(issue.line()).isEqualTo(issueOnLine);
-    assertThat(issue.key()).isEqualTo("ABCDE");
-    assertThat(issue.isNew()).isFalse();
-    assertThat(issue.isEndOfLife()).isFalse();
-    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
-
-    // INPUT : one issue existing during previous scan
-    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("OPEN").setRuleKey("manual", "Performance");
-    when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance").setStatus(Rule.STATUS_REMOVED));
-
-    IssueTrackingResult trackingResult = new IssueTrackingResult();
-    trackingResult.addUnmatched(unmatchedIssue);
-
-    String source = "public interface Action {}";
-    Resource file = mockHashes(source, source);
-
-    when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
-
-    decorator.doDecorate(file);
-
-    verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
-    verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
-
-    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
-    verify(issueCache).put(argument.capture());
-
-    DefaultIssue issue = argument.getValue();
-    assertThat(issue.key()).isEqualTo("ABCDE");
-    assertThat(issue.isNew()).isFalse();
-    assertThat(issue.isEndOfLife()).isTrue();
-    assertThat(issue.isOnDisabledRule()).isTrue();
-  }
-
-  @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
-
-    // INPUT : one issue existing during previous scan
-    IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("OPEN").setRuleKey("manual", "Performance");
-    when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(null);
-
-    IssueTrackingResult trackingResult = new IssueTrackingResult();
-    trackingResult.addUnmatched(unmatchedIssue);
-
-    String source = "public interface Action {}";
-    Resource file = mockHashes(source, source);
-
-    when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
-
-    decorator.doDecorate(file);
-
-    verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
-    verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
-
-    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
-    verify(issueCache).put(argument.capture());
-
-    DefaultIssue issue = argument.getValue();
-    assertThat(issue.key()).isEqualTo("ABCDE");
-    assertThat(issue.isNew()).isFalse();
-    assertThat(issue.isEndOfLife()).isTrue();
-    assertThat(issue.isOnDisabledRule()).isTrue();
-  }
-
-  @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
-
-    // 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(null);
-
-    IssueTrackingResult trackingResult = new IssueTrackingResult();
-    trackingResult.addUnmatched(unmatchedIssue);
-
-    String originalSource = "public interface Action {\n"
-      + "   void method1();\n"
-      + "   void method2();\n"
-      + "   void method3();\n"
-      + "   void method4();\n"
-      + "   void method5();\n"
-      + "}";
-    String newSource = "public interface Action {\n"
-      + "   void method1();\n"
-      + "   void method2();\n"
-      + "}";
-    Resource file = mockHashes(originalSource, newSource);
-
-    when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
-
-    decorator.doDecorate(file);
-
-    verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
-    verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
-
-    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
-    verify(issueCache).put(argument.capture());
-
-    DefaultIssue issue = argument.getValue();
-    verify(updater).setResolution(eq(issue), eq(Issue.RESOLUTION_REMOVED), any(IssueChangeContext.class));
-    verify(updater).setStatus(eq(issue), eq(Issue.STATUS_CLOSED), any(IssueChangeContext.class));
-
-    assertThat(issue.key()).isEqualTo("ABCDE");
-    assertThat(issue.isNew()).isFalse();
-    assertThat(issue.isEndOfLife()).isTrue();
-    assertThat(issue.isOnDisabledRule()).isTrue();
-  }
-
-  @Test
-  public void should_register_issues_on_deleted_components() throws Exception {
-    Project project = new Project("struts");
-    DefaultIssue openIssue = new DefaultIssue();
-    when(issueCache.byComponent("struts")).thenReturn(Arrays.asList(openIssue));
-    IssueDto deadIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle");
-    when(initialOpenIssues.selectAllIssues()).thenReturn(Arrays.asList(deadIssue));
-
-    decorator.doDecorate(project);
-
-    // the dead issue must be closed -> apply automatic transition, notify handlers and add to cache
-    verify(workflow, times(2)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
-    verify(handlers, times(2)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
-    verify(issueCache, times(2)).put(any(DefaultIssue.class));
-
-    verify(issueCache).put(argThat(new ArgumentMatcher<DefaultIssue>() {
-      @Override
-      public boolean matches(Object o) {
-        DefaultIssue dead = (DefaultIssue) o;
-        return "ABCDE".equals(dead.key()) && !dead.isNew() && dead.isEndOfLife();
-      }
-    }));
-  }
-
-  @Test
-  public void merge_matched_issue() throws Exception {
-    IssueDto previousIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle")
-      .setLine(10).setSeverity("MAJOR").setMessage("Message").setEffortToFix(1.5).setDebt(1L).setProjectKey("sample");
-    DefaultIssue issue = new DefaultIssue();
-
-    IssueTrackingResult trackingResult = mock(IssueTrackingResult.class);
-    when(trackingResult.matched()).thenReturn(newArrayList(issue));
-    when(trackingResult.matching(eq(issue))).thenReturn(previousIssue);
-    decorator.mergeMatched(trackingResult);
-
-    verify(updater).setPastSeverity(eq(issue), eq("MAJOR"), any(IssueChangeContext.class));
-    verify(updater).setPastLine(eq(issue), eq(10));
-    verify(updater).setPastMessage(eq(issue), eq("Message"), any(IssueChangeContext.class));
-    verify(updater).setPastEffortToFix(eq(issue), eq(1.5), any(IssueChangeContext.class));
-    verify(updater).setPastTechnicalDebt(eq(issue), eq(Duration.create(1L)), any(IssueChangeContext.class));
-    verify(updater).setPastProject(eq(issue), eq("sample"), any(IssueChangeContext.class));
-  }
-
-  @Test
-  public void merge_matched_issue_on_manual_severity() throws Exception {
-    IssueDto previousIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle")
-      .setLine(10).setManualSeverity(true).setSeverity("MAJOR").setMessage("Message").setEffortToFix(1.5).setDebt(1L);
-    DefaultIssue issue = new DefaultIssue();
-
-    IssueTrackingResult trackingResult = mock(IssueTrackingResult.class);
-    when(trackingResult.matched()).thenReturn(newArrayList(issue));
-    when(trackingResult.matching(eq(issue))).thenReturn(previousIssue);
-    decorator.mergeMatched(trackingResult);
-
-    assertThat(issue.manualSeverity()).isTrue();
-    assertThat(issue.severity()).isEqualTo("MAJOR");
-    verify(updater, never()).setPastSeverity(eq(issue), anyString(), any(IssueChangeContext.class));
-  }
-
-  @Test
-  public void merge_issue_changelog_with_previous_changelog() throws Exception {
-    when(initialOpenIssues.selectChangelog("ABCDE")).thenReturn(newArrayList(new IssueChangeDto().setIssueKey("ABCD").setCreatedAt(System2.INSTANCE.now())));
-
-    IssueDto previousIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle")
-      .setLine(10).setMessage("Message").setEffortToFix(1.5).setDebt(1L).setCreatedAt(System2.INSTANCE.now());
-    DefaultIssue issue = new DefaultIssue();
-
-    IssueTrackingResult trackingResult = mock(IssueTrackingResult.class);
-    when(trackingResult.matched()).thenReturn(newArrayList(issue));
-    when(trackingResult.matching(eq(issue))).thenReturn(previousIssue);
-    decorator.mergeMatched(trackingResult);
-
-    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;
-  }
-
-}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingTest.java
deleted file mode 100644 (file)
index 7fb8b6a..0000000
+++ /dev/null
@@ -1,369 +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;
-
-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.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.LastLineHashes;
-import org.sonar.core.issue.db.IssueDto;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collections;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-public class IssueTrackingTest {
-
-  IssueTracking tracking;
-  Resource project;
-  SourceHashHolder sourceHashHolder;
-  LastLineHashes lastSnapshots;
-  long violationId = 0;
-
-  @Before
-  public void before() {
-    lastSnapshots = mock(LastLineHashes.class);
-
-    project = mock(Project.class);
-    tracking = new IssueTracking();
-  }
-
-  @Test
-  public void key_should_be_the_prioritary_field_to_check() {
-    IssueDto referenceIssue1 = newReferenceIssue("message", 10, "squid", "AvoidCycle", "checksum1").setKee("100");
-    IssueDto referenceIssue2 = newReferenceIssue("message", 10, "squid", "AvoidCycle", "checksum2").setKee("200");
-
-    // exactly the fields of referenceIssue1 but not the same key
-    DefaultIssue newIssue = newDefaultIssue("message", 10, RuleKey.of("squid", "AvoidCycle"), "checksum1").setKey("200");
-
-    IssueTrackingResult result = new IssueTrackingResult();
-    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() {
-    IssueDto referenceIssue1 = newReferenceIssue("message", 1, "squid", "AvoidCycle", "checksum1");
-    IssueDto referenceIssue2 = newReferenceIssue("message", 3, "squid", "AvoidCycle", "checksum2");
-
-    DefaultIssue newIssue1 = newDefaultIssue("message", 3, RuleKey.of("squid", "AvoidCycle"), "checksum1");
-    DefaultIssue newIssue2 = newDefaultIssue("message", 5, RuleKey.of("squid", "AvoidCycle"), "checksum2");
-
-    IssueTrackingResult result = new IssueTrackingResult();
-    tracking.mapIssues(newArrayList(newIssue1, newIssue2), newArrayList(referenceIssue1, referenceIssue2), null, result);
-    assertThat(result.matching(newIssue1)).isSameAs(referenceIssue1);
-    assertThat(result.matching(newIssue2)).isSameAs(referenceIssue2);
-  }
-
-  /**
-   * SONAR-2928
-   */
-  @Test
-  public void same_rule_and_null_line_and_checksum_but_different_messages() {
-    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, result);
-    assertThat(result.matching(newIssue)).isSameAs(referenceIssue);
-  }
-
-  @Test
-  public void same_rule_and_line_and_checksum_but_different_messages() {
-    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, result);
-    assertThat(result.matching(newIssue)).isSameAs(referenceIssue);
-  }
-
-  @Test
-  public void same_rule_and_line_message() {
-    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, result);
-    assertThat(result.matching(newIssue)).isSameAs(referenceIssue);
-  }
-
-  @Test
-  public void should_ignore_reference_measure_without_checksum() {
-    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, result);
-    assertThat(result.matching(newIssue)).isNull();
-  }
-
-  @Test
-  public void same_rule_and_message_and_checksum_but_different_line() {
-    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, result);
-    assertThat(result.matching(newIssue)).isSameAs(referenceIssue);
-  }
-
-  /**
-   * SONAR-2812
-   */
-  @Test
-  public void same_checksum_and_rule_but_different_line_and_different_message() {
-    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, result);
-    assertThat(result.matching(newIssue)).isSameAs(referenceIssue);
-  }
-
-  @Test
-  public void should_create_new_issue_when_same_rule_same_message_but_different_line_and_checksum() {
-    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, result);
-    assertThat(result.matching(newIssue)).isNull();
-  }
-
-  @Test
-  public void should_not_track_issue_if_different_rule() {
-    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, result);
-    assertThat(result.matching(newIssue)).isNull();
-  }
-
-  @Test
-  public void should_compare_issues_with_database_format() {
-    // 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, result);
-    assertThat(result.matching(newIssue)).isSameAs(referenceIssue);
-  }
-
-  @Test
-  public void past_issue_not_associated_with_line_should_not_cause_npe() throws Exception {
-    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 = 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 {
-    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 = tracking.track(sourceHashHolder, newArrayList(referenceIssue), newArrayList(newIssue));
-
-    assertThat(result.matched()).isEmpty();
-  }
-
-  /**
-   * SONAR-2928
-   */
-  @Test
-  public void issue_not_associated_with_line() throws Exception {
-    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 = tracking.track(sourceHashHolder, newArrayList(referenceIssue), newArrayList(newIssue));
-
-    assertThat(result.matching(newIssue)).isEqualTo(referenceIssue);
-  }
-
-  /**
-   * SONAR-3072
-   */
-  @Test
-  public void should_track_issues_based_on_blocks_recognition_on_example1() throws Exception {
-    initLastHashes("example1-v1", "example1-v2");
-
-    IssueDto referenceIssue1 = newReferenceIssue("Indentation", 7, "squid", "AvoidCycle", null);
-    IssueDto referenceIssue2 = newReferenceIssue("Indentation", 11, "squid", "AvoidCycle", null);
-
-    DefaultIssue newIssue1 = newDefaultIssue("Indentation", 9, RuleKey.of("squid", "AvoidCycle"), null);
-    DefaultIssue newIssue2 = newDefaultIssue("Indentation", 13, RuleKey.of("squid", "AvoidCycle"), null);
-    DefaultIssue newIssue3 = newDefaultIssue("Indentation", 17, RuleKey.of("squid", "AvoidCycle"), null);
-    DefaultIssue newIssue4 = newDefaultIssue("Indentation", 21, RuleKey.of("squid", "AvoidCycle"), null);
-
-    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();
-    assertThat(result.matching(newIssue3)).isSameAs(referenceIssue1);
-    assertThat(result.matching(newIssue4)).isSameAs(referenceIssue2);
-  }
-
-  /**
-   * SONAR-3072
-   */
-  @Test
-  public void should_track_issues_based_on_blocks_recognition_on_example2() throws Exception {
-    initLastHashes("example2-v1", "example2-v2");
-
-    IssueDto referenceIssue1 = newReferenceIssue("SystemPrintln", 5, "squid", "AvoidCycle", null);
-
-    DefaultIssue newIssue1 = newDefaultIssue("SystemPrintln", 6, RuleKey.of("squid", "AvoidCycle"), null);
-    DefaultIssue newIssue2 = newDefaultIssue("SystemPrintln", 10, RuleKey.of("squid", "AvoidCycle"), null);
-    DefaultIssue newIssue3 = newDefaultIssue("SystemPrintln", 14, RuleKey.of("squid", "AvoidCycle"), null);
-
-    IssueTrackingResult result = new IssueTrackingResult();
-    tracking.mapIssues(
-      Arrays.asList(newIssue1, newIssue2, newIssue3),
-      Arrays.asList(referenceIssue1),
-      sourceHashHolder, result);
-
-    assertThat(result.matching(newIssue1)).isNull();
-    assertThat(result.matching(newIssue2)).isSameAs(referenceIssue1);
-    assertThat(result.matching(newIssue3)).isNull();
-  }
-
-  @Test
-  public void should_track_issues_based_on_blocks_recognition_on_example3() throws Exception {
-    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");
-    IssueDto referenceIssue3 = newReferenceIssue("Method 'avoidUtilityClass' is not designed for extension - needs to be abstract, final or empty.", 9, "pmd",
-      "UnusedLocalVariable", "ed5cdd046fda82727d6fedd1d8e3a310");
-
-    // New issue
-    DefaultIssue newIssue1 = newDefaultIssue("Avoid unused local variables such as 'msg'.", 18, RuleKey.of("squid", "AvoidCycle"), "a24254126be2bf1a9b9a8db43f633733");
-    // Same as referenceIssue2
-    DefaultIssue newIssue2 = newDefaultIssue("Avoid unused private methods such as 'myMethod()'.", 13, RuleKey.of("squid", "NullDeref"), "ef23288705d1ef1e512448ace287586e");
-    // Same as referenceIssue3
-    DefaultIssue newIssue3 = newDefaultIssue("Method 'avoidUtilityClass' is not designed for extension - needs to be abstract, final or empty.", 9,
-      RuleKey.of("pmd", "UnusedLocalVariable"), "ed5cdd046fda82727d6fedd1d8e3a310");
-    // New issue
-    DefaultIssue newIssue4 = newDefaultIssue("Method 'newViolation' is not designed for extension - needs to be abstract, final or empty.", 17,
-      RuleKey.of("pmd", "UnusedLocalVariable"), "7d58ac9040c27e4ca2f11a0269e251e2");
-    // Same as referenceIssue1
-    DefaultIssue newIssue5 = newDefaultIssue("Avoid unused local variables such as 'j'.", 6, RuleKey.of("squid", "AvoidCycle"), "4432a2675ec3e1620daefe38386b51ef");
-
-    IssueTrackingResult result = new IssueTrackingResult();
-    tracking.mapIssues(
-      Arrays.asList(newIssue1, newIssue2, newIssue3, newIssue4, newIssue5),
-      Arrays.asList(referenceIssue1, referenceIssue2, referenceIssue3),
-      sourceHashHolder, result);
-
-    assertThat(result.matching(newIssue1)).isNull();
-    assertThat(result.matching(newIssue2)).isSameAs(referenceIssue2);
-    assertThat(result.matching(newIssue3)).isSameAs(referenceIssue3);
-    assertThat(result.matching(newIssue4)).isNull();
-    assertThat(result.matching(newIssue5)).isSameAs(referenceIssue1);
-  }
-
-  @Test
-  public void dont_load_checksum_if_no_new_issue() throws Exception {
-    sourceHashHolder = mock(SourceHashHolder.class);
-
-    IssueDto referenceIssue = newReferenceIssue("2 branches need to be covered", null, "squid", "AvoidCycle", null);
-
-    tracking.track(sourceHashHolder, newArrayList(referenceIssue), Collections.<DefaultIssue>emptyList());
-
-    verifyZeroInteractions(lastSnapshots, sourceHashHolder);
-  }
-
-  private static String load(String name) throws IOException {
-    return Resources.toString(IssueTrackingTest.class.getResource("IssueTrackingTest/" + name + ".txt"), Charsets.UTF_8);
-  }
-
-  private DefaultIssue newDefaultIssue(String message, Integer line, RuleKey ruleKey, String checksum) {
-    return new DefaultIssue().setMessage(message).setLine(line).setRuleKey(ruleKey).setChecksum(checksum).setStatus(Issue.STATUS_OPEN);
-  }
-
-  private IssueDto newReferenceIssue(String message, Integer lineId, String ruleRepo, String ruleKey, String lineChecksum) {
-    IssueDto referenceIssue = new IssueDto();
-    Long id = violationId++;
-    referenceIssue.setId(id);
-    referenceIssue.setKee(Long.toString(id));
-    referenceIssue.setLine(lineId);
-    referenceIssue.setMessage(message);
-    referenceIssue.setRuleKey(ruleRepo, ruleKey);
-    referenceIssue.setChecksum(lineChecksum);
-    referenceIssue.setResolution(null);
-    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;
-  }
-}
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
deleted file mode 100644 (file)
index c9942c8..0000000
+++ /dev/null
@@ -1,107 +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;
-
-import org.junit.Before;
-import org.junit.Test;
-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.assertj.core.api.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;
-
-  LastLineHashes lastSnapshots;
-  DefaultInputFile file;
-
-  @Before
-  public void setUp() {
-    lastSnapshots = mock(LastLineHashes.class);
-    file = mock(DefaultInputFile.class);
-
-    sourceHashHolder = new SourceHashHolder(file, lastSnapshots);
-  }
-
-  @Test
-  public void should_lazy_load_line_hashes() {
-    final String source = "source";
-    when(file.lineHashes()).thenReturn(new byte[][] {md5(source), null});
-
-    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.getHashedSource().getHash(1)).isEqualTo(md5Hex(source));
-    Mockito.verifyNoMoreInteractions(file);
-  }
-
-  @Test
-  public void should_lazy_load_reference_hashes_when_status_changed() {
-    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.CHANGED);
-    when(lastSnapshots.getLineHashes(key)).thenReturn(new String[] {md5Hex(source)});
-
-    assertThat(sourceHashHolder.getHashedReference().getHash(1)).isEqualTo(md5Hex(source));
-    verify(lastSnapshots).getLineHashes(key);
-
-    assertThat(sourceHashHolder.getHashedReference().getHash(1)).isEqualTo(md5Hex(source));
-    Mockito.verifyNoMoreInteractions(lastSnapshots);
-  }
-
-  @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 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.getHashedReference()).isNull();
-    Mockito.verifyNoMoreInteractions(lastSnapshots);
-  }
-
-}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/IssueTrackingBlocksRecognizerTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/IssueTrackingBlocksRecognizerTest.java
deleted file mode 100644 (file)
index 55d8468..0000000
+++ /dev/null
@@ -1,49 +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 static org.assertj.core.api.Assertions.assertThat;
-
-public class IssueTrackingBlocksRecognizerTest {
-
-  @Test
-  public void test() {
-    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(FileHashes a, FileHashes b, int ai, int bi) {
-    IssueTrackingBlocksRecognizer rec = new IssueTrackingBlocksRecognizer(a, b);
-    return rec.computeLengthOfMaximalBlock(ai, bi);
-  }
-
-  private static FileHashes t(String text) {
-    String[] array = new String[text.length()];
-    for (int i = 0; i < text.length(); i++) {
-      array[i] = "" + text.charAt(i);
-    }
-    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
deleted file mode 100644 (file)
index 50c3d0e..0000000
+++ /dev/null
@@ -1,43 +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 static org.apache.commons.codec.digest.DigestUtils.md5Hex;
-import static org.assertj.core.api.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/resources/org/sonar/plugins/core/issue/IssueTrackingTest/example1-v1.txt b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/IssueTrackingTest/example1-v1.txt
deleted file mode 100644 (file)
index 1920333..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-package example1;
-
-public class Toto {
-
-    public void doSomething() {
-        // doSomething
-        }
-
-    public void doSomethingElse() {
-        // doSomethingElse
-        }
-}
diff --git a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/IssueTrackingTest/example1-v2.txt b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/IssueTrackingTest/example1-v2.txt
deleted file mode 100644 (file)
index 2315324..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-package example1;
-
-public class Toto {
-
-    public Toto(){}
-
-    public void doSomethingNew() {
-        // doSomethingNew
-        }
-
-    public void doSomethingElseNew() {
-        // doSomethingElseNew
-        }
-
-    public void doSomething() {
-        // doSomething
-        }
-
-    public void doSomethingElse() {
-        // doSomethingElse
-        }
-}
diff --git a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/IssueTrackingTest/example2-v1.txt b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/IssueTrackingTest/example2-v1.txt
deleted file mode 100644 (file)
index a920afe..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-package example2;
-
-public class Toto {
-  void method1() {
-    System.out.println("toto");
-  }
-}
diff --git a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/IssueTrackingTest/example2-v2.txt b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/IssueTrackingTest/example2-v2.txt
deleted file mode 100644 (file)
index c5c8250..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-package example2;
-
-public class Toto {
-
-  void method2() {
-    System.out.println("toto");
-  }
-
-  void method1() {
-    System.out.println("toto");
-  }
-
-  void method3() {
-    System.out.println("toto");
-  }
-}
diff --git a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/IssueTrackingTest/example3-v1.txt b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/IssueTrackingTest/example3-v1.txt
deleted file mode 100644 (file)
index facdcbc..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-package sample;
-
-public class Sample {
-       
-       public Sample(int i) {
-               int j = i+1; // violation: unused local variable
-       }
-
-       public boolean avoidUtilityClass() {
-               return true;
-       }
-
-       private String myMethod() { // violation : unused private method
-               return "hello";
-       }
-}
diff --git a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/IssueTrackingTest/example3-v2.txt b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/IssueTrackingTest/example3-v2.txt
deleted file mode 100644 (file)
index 91db843..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-package sample;
-
-public class Sample {
-
-       public Sample(int i) {
-               int j = i+1; // still the same violation: unused local variable
-       }
-       
-       public boolean avoidUtilityClass() {
-               return true;
-       }
-       
-       private String myMethod() { // violation "unused private method" is fixed because it's called in newViolation
-               return "hello";
-       }
-
-  public void newViolation() {
-    String msg = myMethod(); // new violation : msg is an unused variable
-  }
-}
index 886104c14a01b911f9c41ffb322bd5dac9ece81b..01973adef9741f8abede28d563d2d702705330d0 100644 (file)
  */
 package org.sonar.xoo.rule;
 
+import org.sonar.api.batch.fs.FilePredicates;
+import org.sonar.api.batch.fs.FileSystem;
 import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.InputFile.Type;
 import org.sonar.api.batch.rule.ActiveRule;
 import org.sonar.api.batch.sensor.Sensor;
 import org.sonar.api.batch.sensor.SensorContext;
@@ -40,7 +43,9 @@ public class CreateIssueByInternalKeySensor implements Sensor {
 
   @Override
   public void execute(SensorContext context) {
-    for (InputFile file : context.fileSystem().inputFiles(context.fileSystem().predicates().hasLanguages(Xoo.KEY))) {
+    FileSystem fs = context.fileSystem();
+    FilePredicates p = fs.predicates();
+    for (InputFile file : fs.inputFiles(p.and(p.hasLanguages(Xoo.KEY), p.hasType(Type.MAIN)))) {
       createIssues(file, context);
     }
   }
index 3e0b9f48ee50f5c1b122115e6b42d382b5f94ad8..7e79e2857f6e3bfc8bad4f4732e18c31d4f1c7a5 100644 (file)
  */
 package org.sonar.xoo.rule;
 
+import org.sonar.api.batch.fs.FilePredicates;
+import org.sonar.api.batch.fs.FileSystem;
 import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.InputFile.Type;
 import org.sonar.api.batch.sensor.Sensor;
 import org.sonar.api.batch.sensor.SensorContext;
 import org.sonar.api.batch.sensor.SensorDescriptor;
@@ -43,7 +46,9 @@ public class OneIssuePerLineSensor implements Sensor {
 
   @Override
   public void execute(SensorContext context) {
-    for (InputFile file : context.fileSystem().inputFiles(context.fileSystem().predicates().hasLanguages(Xoo.KEY))) {
+    FileSystem fs = context.fileSystem();
+    FilePredicates p = fs.predicates();
+    for (InputFile file : fs.inputFiles(p.and(p.hasLanguages(Xoo.KEY), p.hasType(Type.MAIN)))) {
       createIssues(file, context);
     }
   }
index 9f87aa9ad1f4678b9f535b019d412cd1ef9c336a..a6a40efbc4522445c553d905bcd4e11957aed606 100644 (file)
@@ -25,7 +25,7 @@ import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.RequestHandler;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
 import org.sonar.server.plugins.MimeTypes;
 
 public class ProjectRepositoryAction implements RequestHandler {
@@ -67,7 +67,7 @@ public class ProjectRepositoryAction implements RequestHandler {
 
   @Override
   public void handle(Request request, Response response) throws Exception {
-    ProjectReferentials ref = projectReferentialsLoader.load(ProjectRepositoryQuery.create()
+    ProjectRepository ref = projectReferentialsLoader.load(ProjectRepositoryQuery.create()
       .setModuleKey(request.mandatoryParam(PARAM_KEY))
       .setProfileName(request.param(PARAM_PROFILE))
       .setPreview(request.mandatoryParamAsBoolean(PARAM_PREVIEW)));
index 90d0151b79c593bc2108991f05ad42842e6401e6..deea4177e3354f7691bf560a9316a15ccb9e6051 100644 (file)
@@ -28,7 +28,7 @@ import org.sonar.api.resources.Language;
 import org.sonar.api.resources.Languages;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.batch.protocol.input.FileData;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
 import org.sonar.core.UtcDateUtils;
 import org.sonar.core.component.ComponentDto;
 import org.sonar.core.component.FilePathWithHashDto;
@@ -77,13 +77,13 @@ public class ProjectRepositoryLoader implements ServerComponent {
     this.languages = languages;
   }
 
-  public ProjectReferentials load(ProjectRepositoryQuery query) {
+  public ProjectRepository load(ProjectRepositoryQuery query) {
     boolean hasScanPerm = UserSession.get().hasGlobalPermission(GlobalPermissions.SCAN_EXECUTION);
     checkPermission(query.isPreview());
 
     DbSession session = dbClient.openSession(false);
     try {
-      ProjectReferentials ref = new ProjectReferentials();
+      ProjectRepository ref = new ProjectRepository();
       String projectKey = query.getModuleKey();
       ComponentDto module = dbClient.componentDao().getNullableByKey(session, query.getModuleKey());
       // Current project/module can be null when analysing a new project
@@ -145,7 +145,7 @@ public class ProjectRepositoryLoader implements ServerComponent {
     }
   }
 
-  private void addSettingsToChildrenModules(ProjectReferentials ref, String moduleKey, Map<String, String> parentProperties, TreeModuleSettings treeModuleSettings,
+  private void addSettingsToChildrenModules(ProjectRepository ref, String moduleKey, Map<String, String> parentProperties, TreeModuleSettings treeModuleSettings,
     boolean hasScanPerm, DbSession session) {
     Map<String, String> currentParentProperties = newHashMap();
     currentParentProperties.putAll(parentProperties);
@@ -158,7 +158,7 @@ public class ProjectRepositoryLoader implements ServerComponent {
     }
   }
 
-  private void addSettings(ProjectReferentials ref, String module, Map<String, String> properties) {
+  private void addSettings(ProjectRepository ref, String module, Map<String, String> properties) {
     if (!properties.isEmpty()) {
       ref.addSettings(module, properties);
     }
@@ -180,7 +180,7 @@ public class ProjectRepositoryLoader implements ServerComponent {
     return !key.contains(".secured") || hasScanPerm;
   }
 
-  private void addProfiles(ProjectReferentials ref, @Nullable String projectKey, @Nullable String profileName, DbSession session) {
+  private void addProfiles(ProjectRepository ref, @Nullable String projectKey, @Nullable String profileName, DbSession session) {
     for (Language language : languages.all()) {
       String languageKey = language.getKey();
       QualityProfileDto qualityProfileDto = getProfile(languageKey, projectKey, profileName, session);
@@ -212,7 +212,7 @@ public class ProjectRepositoryLoader implements ServerComponent {
     }
   }
 
-  private void addActiveRules(ProjectReferentials ref) {
+  private void addActiveRules(ProjectRepository ref) {
     for (org.sonar.batch.protocol.input.QProfile qProfile : ref.qProfiles()) {
       for (ActiveRule activeRule : qProfileLoader.findActiveRulesByProfile(qProfile.key())) {
         Rule rule = ruleService.getNonNullByKey(activeRule.key().ruleKey());
@@ -231,7 +231,7 @@ public class ProjectRepositoryLoader implements ServerComponent {
     }
   }
 
-  private void addManualRules(ProjectReferentials ref) {
+  private void addManualRules(ProjectRepository ref) {
     Result<Rule> ruleSearchResult = ruleService.search(new RuleQuery().setRepositories(newArrayList(RuleKey.MANUAL_REPOSITORY_KEY)), new QueryContext().setScroll(true)
       .setFieldsToReturn(newArrayList(RuleNormalizer.RuleField.KEY.field(), RuleNormalizer.RuleField.NAME.field())));
     Iterator<Rule> rules = ruleSearchResult.scroll();
@@ -245,7 +245,7 @@ public class ProjectRepositoryLoader implements ServerComponent {
     }
   }
 
-  private void addFileData(DbSession session, ProjectReferentials ref, List<ComponentDto> moduleChildren, String moduleKey) {
+  private void addFileData(DbSession session, ProjectRepository ref, List<ComponentDto> moduleChildren, String moduleKey) {
     Map<String, String> moduleKeysByUuid = newHashMap();
     for (ComponentDto module : moduleChildren) {
       moduleKeysByUuid.put(module.uuid(), module.key());
index 4dd4ae8e46f6f2866cdf7ca027e4efba2a146ea5..97085c32ad9414db0e609376146c22352a104ddf 100644 (file)
@@ -26,7 +26,7 @@ import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
 import org.sonar.server.ws.WsTester;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -51,7 +51,7 @@ public class ProjectRepositoryActionTest {
   public void project_referentials() throws Exception {
     String projectKey = "org.codehaus.sonar:sonar";
 
-    ProjectReferentials projectReferentials = mock(ProjectReferentials.class);
+    ProjectRepository projectReferentials = mock(ProjectRepository.class);
     when(projectReferentials.toJson()).thenReturn("{\"settingsByModule\": {}}");
 
     ArgumentCaptor<ProjectRepositoryQuery> queryArgumentCaptor = ArgumentCaptor.forClass(ProjectRepositoryQuery.class);
index 5726333c222851ef9129edbb130651a67664a259..0b3ed071d82591561a0d856f19dc3c8088f2ab0f 100644 (file)
@@ -31,7 +31,7 @@ import org.sonar.api.server.rule.RuleParamType;
 import org.sonar.api.utils.DateUtils;
 import org.sonar.batch.protocol.input.ActiveRule;
 import org.sonar.batch.protocol.input.FileData;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
 import org.sonar.batch.protocol.input.QProfile;
 import org.sonar.core.component.ComponentDto;
 import org.sonar.core.permission.GlobalPermissions;
@@ -99,7 +99,7 @@ public class ProjectRepositoryLoaderMediumTest {
       dbSession);
     dbSession.commit();
 
-    ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+    ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
     Map<String, String> projectSettings = ref.settings(project.key());
     assertThat(projectSettings).isEqualTo(ImmutableMap.of(
       "sonar.jira.project.key", "SONAR",
@@ -124,7 +124,7 @@ public class ProjectRepositoryLoaderMediumTest {
       dbSession);
     dbSession.commit();
 
-    ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()).setPreview(true));
+    ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()).setPreview(true));
     Map<String, String> projectSettings = ref.settings(project.key());
     assertThat(projectSettings).isEqualTo(ImmutableMap.of(
       "sonar.jira.project.key", "SONAR"
@@ -156,7 +156,7 @@ public class ProjectRepositoryLoaderMediumTest {
 
     dbSession.commit();
 
-    ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+    ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
     assertThat(ref.settings(project.key())).isEqualTo(ImmutableMap.of(
       "sonar.jira.project.key", "SONAR",
       "sonar.jira.login.secured", "john"
@@ -189,7 +189,7 @@ public class ProjectRepositoryLoaderMediumTest {
 
     dbSession.commit();
 
-    ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+    ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
     assertThat(ref.settings(project.key())).isEqualTo(ImmutableMap.of(
       "sonar.jira.project.key", "SONAR",
       "sonar.jira.login.secured", "john"
@@ -232,7 +232,7 @@ public class ProjectRepositoryLoaderMediumTest {
 
     dbSession.commit();
 
-    ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+    ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
     assertThat(ref.settings(project.key())).isEqualTo(ImmutableMap.of(
       "sonar.jira.project.key", "SONAR",
       "sonar.jira.login.secured", "john"
@@ -278,7 +278,7 @@ public class ProjectRepositoryLoaderMediumTest {
 
     dbSession.commit();
 
-    ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+    ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
     assertThat(ref.settings(project.key())).isEqualTo(ImmutableMap.of(
       "sonar.jira.project.key", "SONAR",
       "sonar.jira.login.secured", "john"
@@ -294,6 +294,28 @@ public class ProjectRepositoryLoaderMediumTest {
       ));
   }
 
+  @Test
+  public void return_provisioned_project_settings() throws Exception {
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
+
+    // No snapshot attached on the project -> provisioned project
+    ComponentDto project = ComponentTesting.newProjectDto();
+    tester.get(DbClient.class).componentDao().insert(dbSession, project);
+    addDefaultProfile();
+
+    // Project properties
+    tester.get(DbClient.class).propertiesDao().setProperty(new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR").setResourceId(project.getId()), dbSession);
+    tester.get(DbClient.class).propertiesDao().setProperty(new PropertyDto().setKey("sonar.jira.login.secured").setValue("john").setResourceId(project.getId()), dbSession);
+
+    dbSession.commit();
+
+    ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+    assertThat(ref.settings(project.key())).isEqualTo(ImmutableMap.of(
+      "sonar.jira.project.key", "SONAR",
+      "sonar.jira.login.secured", "john"
+      ));
+  }
+
   @Test
   public void return_sub_module_settings() throws Exception {
     MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
@@ -317,7 +339,7 @@ public class ProjectRepositoryLoaderMediumTest {
 
     dbSession.commit();
 
-    ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(subModule.key()));
+    ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(subModule.key()));
     assertThat(ref.settings(project.key())).isEmpty();
     assertThat(ref.settings(module.key())).isEmpty();
     assertThat(ref.settings(subModule.key())).isEqualTo(ImmutableMap.of(
@@ -352,7 +374,7 @@ public class ProjectRepositoryLoaderMediumTest {
 
     dbSession.commit();
 
-    ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(subModule.key()));
+    ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(subModule.key()));
     assertThat(ref.settings(project.key())).isEmpty();
     assertThat(ref.settings(module.key())).isEmpty();
     assertThat(ref.settings(subModule.key())).isEqualTo(ImmutableMap.of(
@@ -385,7 +407,7 @@ public class ProjectRepositoryLoaderMediumTest {
 
     dbSession.commit();
 
-    ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(subModule.key()));
+    ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(subModule.key()));
     assertThat(ref.settings(project.key())).isEmpty();
     assertThat(ref.settings(module.key())).isEmpty();
     assertThat(ref.settings(subModule.key())).isEqualTo(ImmutableMap.of(
@@ -419,7 +441,7 @@ public class ProjectRepositoryLoaderMediumTest {
 
     dbSession.commit();
 
-    ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(subModule.key()));
+    ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(subModule.key()));
     assertThat(ref.settings(project.key())).isEmpty();
     assertThat(ref.settings(module.key())).isEmpty();
     assertThat(ref.settings(subModule.key())).isEqualTo(ImmutableMap.of(
@@ -444,7 +466,7 @@ public class ProjectRepositoryLoaderMediumTest {
 
     dbSession.commit();
 
-    ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+    ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
     List<QProfile> profiles = newArrayList(ref.qProfiles());
     assertThat(profiles).hasSize(1);
     assertThat(profiles.get(0).key()).isEqualTo("abcd");
@@ -468,7 +490,7 @@ public class ProjectRepositoryLoaderMediumTest {
 
     dbSession.commit();
 
-    ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+    ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
     List<QProfile> profiles = newArrayList(ref.qProfiles());
     assertThat(profiles).hasSize(1);
     assertThat(profiles.get(0).key()).isEqualTo("abcd");
@@ -492,7 +514,7 @@ public class ProjectRepositoryLoaderMediumTest {
 
     dbSession.commit();
 
-    ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()).setProfileName("SonarQube way"));
+    ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()).setProfileName("SonarQube way"));
     List<QProfile> profiles = newArrayList(ref.qProfiles());
     assertThat(profiles).hasSize(1);
     assertThat(profiles.get(0).key()).isEqualTo("abcd");
@@ -513,7 +535,7 @@ public class ProjectRepositoryLoaderMediumTest {
 
     dbSession.commit();
 
-    ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey("project"));
+    ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey("project"));
     List<QProfile> profiles = newArrayList(ref.qProfiles());
     assertThat(profiles).hasSize(1);
     assertThat(profiles.get(0).key()).isEqualTo("abcd");
@@ -538,7 +560,7 @@ public class ProjectRepositoryLoaderMediumTest {
 
     dbSession.commit();
 
-    ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+    ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
     List<QProfile> profiles = newArrayList(ref.qProfiles());
     assertThat(profiles).hasSize(1);
     assertThat(profiles.get(0).key()).isEqualTo("abcd");
@@ -589,7 +611,7 @@ public class ProjectRepositoryLoaderMediumTest {
 
     dbSession.commit();
 
-    ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+    ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
     List<ActiveRule> activeRules = newArrayList(ref.activeRules());
     assertThat(activeRules).hasSize(1);
     assertThat(activeRules.get(0).repositoryKey()).isEqualTo("squid");
@@ -614,7 +636,7 @@ public class ProjectRepositoryLoaderMediumTest {
 
     dbSession.commit();
 
-    ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+    ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
     List<ActiveRule> activeRules = newArrayList(ref.activeRules());
     assertThat(activeRules).extracting("repositoryKey").containsOnly(RuleKey.MANUAL_REPOSITORY_KEY);
     assertThat(activeRules).extracting("ruleKey").containsOnly("manualRuleKey");
@@ -671,7 +693,7 @@ public class ProjectRepositoryLoaderMediumTest {
 
     dbSession.commit();
 
-    ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+    ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
     assertThat(ref.fileDataByPath(project.key())).hasSize(1);
     FileData fileData = ref.fileData(project.key(), file.path());
     assertThat(fileData.hash()).isEqualTo("123456");
@@ -700,7 +722,7 @@ public class ProjectRepositoryLoaderMediumTest {
 
     dbSession.commit();
 
-    ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+    ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
     assertThat(ref.fileData(project.key(), projectFile.path()).hash()).isEqualTo("123456");
     assertThat(ref.fileData(module.key(), moduleFile.path()).hash()).isEqualTo("789456");
   }
@@ -728,7 +750,7 @@ public class ProjectRepositoryLoaderMediumTest {
 
     dbSession.commit();
 
-    ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(module.key()));
+    ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(module.key()));
     assertThat(ref.fileData(module.key(), moduleFile.path()).hash()).isEqualTo("789456");
     assertThat(ref.fileData(project.key(), projectFile.path())).isNull();
   }
diff --git a/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/ProjectReferentials.java b/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/ProjectReferentials.java
deleted file mode 100644 (file)
index 8e83d0f..0000000
+++ /dev/null
@@ -1,118 +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.protocol.input;
-
-import org.sonar.batch.protocol.GsonHelper;
-
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-
-import java.util.*;
-
-/**
- * Container for all project data going from server to batch.
- * This is not an API since server and batch always share the same version.
- */
-public class ProjectReferentials {
-
-  private long timestamp;
-  private Map<String, QProfile> qprofilesByLanguage = new HashMap<String, QProfile>();
-  private Collection<ActiveRule> activeRules = new ArrayList<ActiveRule>();
-  private Map<String, Map<String, String>> settingsByModule = new HashMap<String, Map<String, String>>();
-  private Map<String, Map<String, FileData>> fileDataByModuleAndPath = new HashMap<String, Map<String, FileData>>();
-  private Date lastAnalysisDate;
-
-  public Map<String, String> settings(String moduleKey) {
-    return settingsByModule.containsKey(moduleKey) ? settingsByModule.get(moduleKey) : Collections.<String, String>emptyMap();
-  }
-
-  public ProjectReferentials addSettings(String moduleKey, Map<String, String> settings) {
-    Map<String, String> existingSettings = settingsByModule.get(moduleKey);
-    if (existingSettings == null) {
-      existingSettings = new HashMap<>();
-      settingsByModule.put(moduleKey, existingSettings);
-    }
-    existingSettings.putAll(settings);
-    return this;
-  }
-
-  public Collection<QProfile> qProfiles() {
-    return qprofilesByLanguage.values();
-  }
-
-  public ProjectReferentials addQProfile(QProfile qProfile) {
-    qprofilesByLanguage.put(qProfile.language(), qProfile);
-    return this;
-  }
-
-  public Collection<ActiveRule> activeRules() {
-    return activeRules;
-  }
-
-  public ProjectReferentials addActiveRule(ActiveRule activeRule) {
-    activeRules.add(activeRule);
-    return this;
-  }
-
-  public Map<String, FileData> fileDataByPath(String moduleKey) {
-    return fileDataByModuleAndPath.containsKey(moduleKey) ? fileDataByModuleAndPath.get(moduleKey) : Collections.<String, FileData>emptyMap();
-  }
-
-  public ProjectReferentials addFileData(String moduleKey, String path, FileData fileData) {
-    Map<String, FileData> existingFileDataByPath = fileDataByModuleAndPath.get(moduleKey);
-    if (existingFileDataByPath == null) {
-      existingFileDataByPath = new HashMap<>();
-      fileDataByModuleAndPath.put(moduleKey, existingFileDataByPath);
-    }
-    existingFileDataByPath.put(path, fileData);
-    return this;
-  }
-
-  @CheckForNull
-  public FileData fileData(String projectKey, String path) {
-    return fileDataByPath(projectKey).get(path);
-  }
-
-  public long timestamp() {
-    return timestamp;
-  }
-
-  public void setTimestamp(long timestamp) {
-    this.timestamp = timestamp;
-  }
-
-  @CheckForNull
-  public Date lastAnalysisDate() {
-    return lastAnalysisDate;
-  }
-
-  public void setLastAnalysisDate(@Nullable Date lastAnalysisDate) {
-    this.lastAnalysisDate = lastAnalysisDate;
-  }
-
-  public String toJson() {
-    return GsonHelper.create().toJson(this);
-  }
-
-  public static ProjectReferentials fromJson(String json) {
-    return GsonHelper.create().fromJson(json, ProjectReferentials.class);
-  }
-
-}
diff --git a/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/ProjectRepository.java b/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/ProjectRepository.java
new file mode 100644 (file)
index 0000000..f207a34
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * 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.protocol.input;
+
+import org.sonar.batch.protocol.GsonHelper;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import java.util.*;
+
+/**
+ * Container for all project data going from server to batch.
+ * This is not an API since server and batch always share the same version.
+ */
+public class ProjectRepository {
+
+  private long timestamp;
+  private Map<String, QProfile> qprofilesByLanguage = new HashMap<String, QProfile>();
+  private Collection<ActiveRule> activeRules = new ArrayList<ActiveRule>();
+  private Map<String, Map<String, String>> settingsByModule = new HashMap<String, Map<String, String>>();
+  private Map<String, Map<String, FileData>> fileDataByModuleAndPath = new HashMap<String, Map<String, FileData>>();
+  private Date lastAnalysisDate;
+
+  public Map<String, String> settings(String moduleKey) {
+    return settingsByModule.containsKey(moduleKey) ? settingsByModule.get(moduleKey) : Collections.<String, String>emptyMap();
+  }
+
+  public ProjectRepository addSettings(String moduleKey, Map<String, String> settings) {
+    Map<String, String> existingSettings = settingsByModule.get(moduleKey);
+    if (existingSettings == null) {
+      existingSettings = new HashMap<>();
+      settingsByModule.put(moduleKey, existingSettings);
+    }
+    existingSettings.putAll(settings);
+    return this;
+  }
+
+  public Collection<QProfile> qProfiles() {
+    return qprofilesByLanguage.values();
+  }
+
+  public ProjectRepository addQProfile(QProfile qProfile) {
+    qprofilesByLanguage.put(qProfile.language(), qProfile);
+    return this;
+  }
+
+  public Collection<ActiveRule> activeRules() {
+    return activeRules;
+  }
+
+  public ProjectRepository addActiveRule(ActiveRule activeRule) {
+    activeRules.add(activeRule);
+    return this;
+  }
+
+  public Map<String, FileData> fileDataByPath(String moduleKey) {
+    return fileDataByModuleAndPath.containsKey(moduleKey) ? fileDataByModuleAndPath.get(moduleKey) : Collections.<String, FileData>emptyMap();
+  }
+
+  public ProjectRepository addFileData(String moduleKey, String path, FileData fileData) {
+    Map<String, FileData> existingFileDataByPath = fileDataByModuleAndPath.get(moduleKey);
+    if (existingFileDataByPath == null) {
+      existingFileDataByPath = new HashMap<>();
+      fileDataByModuleAndPath.put(moduleKey, existingFileDataByPath);
+    }
+    existingFileDataByPath.put(path, fileData);
+    return this;
+  }
+
+  @CheckForNull
+  public FileData fileData(String projectKey, String path) {
+    return fileDataByPath(projectKey).get(path);
+  }
+
+  public long timestamp() {
+    return timestamp;
+  }
+
+  public void setTimestamp(long timestamp) {
+    this.timestamp = timestamp;
+  }
+
+  @CheckForNull
+  public Date lastAnalysisDate() {
+    return lastAnalysisDate;
+  }
+
+  public void setLastAnalysisDate(@Nullable Date lastAnalysisDate) {
+    this.lastAnalysisDate = lastAnalysisDate;
+  }
+
+  public String toJson() {
+    return GsonHelper.create().toJson(this);
+  }
+
+  public static ProjectRepository fromJson(String json) {
+    return GsonHelper.create().fromJson(json, ProjectRepository.class);
+  }
+
+}
index 27afc945c299016d1c95b417815af5eb7c4c63c9..10f44f1e39018739a9443a12a62cc966a8de86f7 100644 (file)
  */
 package org.sonar.batch.protocol.input.issues;
 
-import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 
-public class PreviousIssue {
+import java.io.Serializable;
+
+public class PreviousIssue implements Serializable {
 
   private String key;
   private String componentKey;
@@ -30,6 +31,7 @@ public class PreviousIssue {
   private String ruleRepo;
   private Integer line;
   private String message;
+  // For manual issues and when user has overriden severity
   private String overriddenSeverity;
   private String resolution;
   private String status;
@@ -45,7 +47,7 @@ public class PreviousIssue {
     return key;
   }
 
-  public PreviousIssue setComponentKey(@Nullable String key) {
+  public PreviousIssue setComponentKey(String key) {
     this.componentKey = key;
     return this;
   }
@@ -95,7 +97,6 @@ public class PreviousIssue {
     return this;
   }
 
-  @CheckForNull
   public String overriddenSeverity() {
     return overriddenSeverity;
   }
index 14e4e4a378527c2862581c5b4ae2d9625eae3681..056b0077eeae93ee19274dbeba5f6d4d4f8735f0 100644 (file)
@@ -70,7 +70,7 @@ public class PreviousIssueHelper implements Closeable {
     }
   }
 
-  public Iterable<PreviousIssue> getIssues(final Reader reader) {
+  public static Iterable<PreviousIssue> getIssues(final Reader reader) {
 
     return new Iterable<PreviousIssue>() {
       @Override
@@ -80,9 +80,10 @@ public class PreviousIssueHelper implements Closeable {
     };
   }
 
-  private final class PreviousIssueIterator implements Iterator<PreviousIssue> {
+  private final static class PreviousIssueIterator implements Iterator<PreviousIssue> {
 
     private JsonReader jsonreader;
+    private final Gson gson = GsonHelper.create();
 
     public PreviousIssueIterator(Reader reader) {
       try {
diff --git a/sonar-batch-protocol/src/test/java/org/sonar/batch/protocol/input/ProjectReferentialsTest.java b/sonar-batch-protocol/src/test/java/org/sonar/batch/protocol/input/ProjectReferentialsTest.java
deleted file mode 100644 (file)
index 348fcd8..0000000
+++ /dev/null
@@ -1,104 +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.protocol.input;
-
-import org.json.JSONException;
-import org.junit.Test;
-import org.skyscreamer.jsonassert.JSONAssert;
-
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.HashMap;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class ProjectReferentialsTest {
-
-  @Test
-  public void testToJson() throws Exception {
-    ProjectReferentials ref = new ProjectReferentials();
-    assertThat(ref.settings("foo")).isEmpty();
-
-    ref.addQProfile(new QProfile("squid-java", "Java", "java", new SimpleDateFormat("dd/MM/yyyy").parse("14/03/1984")));
-    HashMap<String, String> settings = new HashMap<String, String>();
-    settings.put("prop1", "value1");
-    ref.addSettings("foo", settings);
-    settings = new HashMap<String, String>();
-    settings.put("prop2", "value2");
-    ref.addSettings("foo", settings);
-    ref.settings("foo").put("prop", "value");
-    ActiveRule activeRule = new ActiveRule("repo", "rule", "Rule", "MAJOR", "rule", "java");
-    activeRule.addParam("param1", "value1");
-    ref.addActiveRule(activeRule);
-    ref.setLastAnalysisDate(new SimpleDateFormat("dd/MM/yyyy").parse("31/10/2014"));
-    ref.setTimestamp(10);
-    ref.addFileData("foo", "src/main/java/Foo.java", new FileData("xyz", true, "1=12345,2=3456", "1=345,2=345", "1=henryju,2=gaudin"));
-    ref.addFileData("foo", "src/main/java/Foo2.java", new FileData("xyz", false, "1=12345,2=3456", "1=345,2=345", "1=henryju,2=gaudin"));
-
-    JSONAssert
-      .assertEquals(
-        "{timestamp:10,"
-          + "qprofilesByLanguage:{java:{key:\"squid-java\",name:Java,language:java,rulesUpdatedAt:\"1984-03-14T00:00:00+0100\"}},"
-          + "activeRules:[{repositoryKey:repo,ruleKey:rule,name:Rule,severity:MAJOR,internalKey:rule,language:java,params:{param1:value1}}],"
-          + "settingsByModule:{foo:{prop1:value1,prop2:value2,prop:value}},"
-          + "fileDataByModuleAndPath:{foo:{\"src/main/java/Foo.java\":{hash:xyz,needBlame:true,scmLastCommitDatetimesByLine:\"1\u003d12345,2\u003d3456\",scmRevisionsByLine:\"1\u003d345,2\u003d345\",scmAuthorsByLine:\"1\u003dhenryju,2\u003dgaudin\"},"
-          + "\"src/main/java/Foo2.java\":{hash:xyz,needBlame:false,scmLastCommitDatetimesByLine:\"1\u003d12345,2\u003d3456\",scmRevisionsByLine:\"1\u003d345,2\u003d345\",scmAuthorsByLine:\"1\u003dhenryju,2\u003dgaudin\"}}},"
-          + "lastAnalysisDate:\"2014-10-31T00:00:00+0100\"}",
-        ref.toJson(), true);
-  }
-
-  @Test
-  public void testFromJson() throws JSONException, ParseException {
-    ProjectReferentials ref = ProjectReferentials
-      .fromJson("{timestamp:1,"
-        + "qprofilesByLanguage:{java:{key:\"squid-java\",name:Java,language:java,rulesUpdatedAt:\"1984-03-14T00:00:00+0100\"}},"
-        + "activeRules:[{repositoryKey:repo,ruleKey:rule,name:Rule,severity:MAJOR,internalKey:rule1,language:java,params:{param1:value1}}],"
-        + "settingsByModule:{foo:{prop:value}},"
-        + "fileDataByModuleAndPath:{foo:{\"src/main/java/Foo.java\":{hash:xyz,needBlame:true,scmLastCommitDatetimesByLine:\"1\u003d12345,2\u003d3456\",scmRevisionsByLine:\"1\u003d345,2\u003d345\",scmAuthorsByLine:\"1\u003dhenryju,2\u003dgaudin\"}}},"
-        + "lastAnalysisDate:\"2014-10-31T00:00:00+0100\"}");
-
-    assertThat(ref.timestamp()).isEqualTo(1);
-
-    ActiveRule activeRule = ref.activeRules().iterator().next();
-    assertThat(activeRule.ruleKey()).isEqualTo("rule");
-    assertThat(activeRule.repositoryKey()).isEqualTo("repo");
-    assertThat(activeRule.name()).isEqualTo("Rule");
-    assertThat(activeRule.severity()).isEqualTo("MAJOR");
-    assertThat(activeRule.internalKey()).isEqualTo("rule1");
-    assertThat(activeRule.language()).isEqualTo("java");
-    assertThat(activeRule.params()).containsEntry("param1", "value1");
-    assertThat(activeRule.param("param1")).isEqualTo("value1");
-    QProfile qProfile = ref.qProfiles().iterator().next();
-    assertThat(qProfile.key()).isEqualTo("squid-java");
-    assertThat(qProfile.name()).isEqualTo("Java");
-    assertThat(qProfile.rulesUpdatedAt()).isEqualTo(new SimpleDateFormat("dd/MM/yyyy").parse("14/03/1984"));
-    assertThat(ref.settings("foo")).containsEntry("prop", "value");
-
-    assertThat(ref.fileData("foo2", "src/main/java/Foo3.java")).isNull();
-
-    assertThat(ref.fileData("foo", "src/main/java/Foo.java").hash()).isEqualTo("xyz");
-    assertThat(ref.fileData("foo", "src/main/java/Foo.java").needBlame()).isTrue();
-    assertThat(ref.fileData("foo", "src/main/java/Foo.java").scmAuthorsByLine()).isEqualTo("1=henryju,2=gaudin");
-    assertThat(ref.fileData("foo", "src/main/java/Foo.java").scmLastCommitDatetimesByLine()).isEqualTo("1=12345,2=3456");
-    assertThat(ref.fileData("foo", "src/main/java/Foo.java").scmRevisionsByLine()).isEqualTo("1=345,2=345");
-
-    assertThat(ref.lastAnalysisDate()).isEqualTo(new SimpleDateFormat("dd/MM/yyyy").parse("31/10/2014"));
-  }
-}
diff --git a/sonar-batch-protocol/src/test/java/org/sonar/batch/protocol/input/ProjectRepositoryTest.java b/sonar-batch-protocol/src/test/java/org/sonar/batch/protocol/input/ProjectRepositoryTest.java
new file mode 100644 (file)
index 0000000..d60222b
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * 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.protocol.input;
+
+import org.json.JSONException;
+import org.junit.Test;
+import org.skyscreamer.jsonassert.JSONAssert;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ProjectRepositoryTest {
+
+  @Test
+  public void testToJson() throws Exception {
+    ProjectRepository ref = new ProjectRepository();
+    assertThat(ref.settings("foo")).isEmpty();
+
+    ref.addQProfile(new QProfile("squid-java", "Java", "java", new SimpleDateFormat("dd/MM/yyyy").parse("14/03/1984")));
+    HashMap<String, String> settings = new HashMap<String, String>();
+    settings.put("prop1", "value1");
+    ref.addSettings("foo", settings);
+    settings = new HashMap<String, String>();
+    settings.put("prop2", "value2");
+    ref.addSettings("foo", settings);
+    ref.settings("foo").put("prop", "value");
+    ActiveRule activeRule = new ActiveRule("repo", "rule", "Rule", "MAJOR", "rule", "java");
+    activeRule.addParam("param1", "value1");
+    ref.addActiveRule(activeRule);
+    ref.setLastAnalysisDate(new SimpleDateFormat("dd/MM/yyyy").parse("31/10/2014"));
+    ref.setTimestamp(10);
+    ref.addFileData("foo", "src/main/java/Foo.java", new FileData("xyz", true, "1=12345,2=3456", "1=345,2=345", "1=henryju,2=gaudin"));
+    ref.addFileData("foo", "src/main/java/Foo2.java", new FileData("xyz", false, "1=12345,2=3456", "1=345,2=345", "1=henryju,2=gaudin"));
+
+    JSONAssert
+      .assertEquals(
+        "{timestamp:10,"
+          + "qprofilesByLanguage:{java:{key:\"squid-java\",name:Java,language:java,rulesUpdatedAt:\"1984-03-14T00:00:00+0100\"}},"
+          + "activeRules:[{repositoryKey:repo,ruleKey:rule,name:Rule,severity:MAJOR,internalKey:rule,language:java,params:{param1:value1}}],"
+          + "settingsByModule:{foo:{prop1:value1,prop2:value2,prop:value}},"
+          + "fileDataByModuleAndPath:{foo:{\"src/main/java/Foo.java\":{hash:xyz,needBlame:true,scmLastCommitDatetimesByLine:\"1\u003d12345,2\u003d3456\",scmRevisionsByLine:\"1\u003d345,2\u003d345\",scmAuthorsByLine:\"1\u003dhenryju,2\u003dgaudin\"},"
+          + "\"src/main/java/Foo2.java\":{hash:xyz,needBlame:false,scmLastCommitDatetimesByLine:\"1\u003d12345,2\u003d3456\",scmRevisionsByLine:\"1\u003d345,2\u003d345\",scmAuthorsByLine:\"1\u003dhenryju,2\u003dgaudin\"}}},"
+          + "lastAnalysisDate:\"2014-10-31T00:00:00+0100\"}",
+        ref.toJson(), true);
+  }
+
+  @Test
+  public void testFromJson() throws JSONException, ParseException {
+    ProjectRepository ref = ProjectRepository
+      .fromJson("{timestamp:1,"
+        + "qprofilesByLanguage:{java:{key:\"squid-java\",name:Java,language:java,rulesUpdatedAt:\"1984-03-14T00:00:00+0100\"}},"
+        + "activeRules:[{repositoryKey:repo,ruleKey:rule,name:Rule,severity:MAJOR,internalKey:rule1,language:java,params:{param1:value1}}],"
+        + "settingsByModule:{foo:{prop:value}},"
+        + "fileDataByModuleAndPath:{foo:{\"src/main/java/Foo.java\":{hash:xyz,needBlame:true,scmLastCommitDatetimesByLine:\"1\u003d12345,2\u003d3456\",scmRevisionsByLine:\"1\u003d345,2\u003d345\",scmAuthorsByLine:\"1\u003dhenryju,2\u003dgaudin\"}}},"
+        + "lastAnalysisDate:\"2014-10-31T00:00:00+0100\"}");
+
+    assertThat(ref.timestamp()).isEqualTo(1);
+
+    ActiveRule activeRule = ref.activeRules().iterator().next();
+    assertThat(activeRule.ruleKey()).isEqualTo("rule");
+    assertThat(activeRule.repositoryKey()).isEqualTo("repo");
+    assertThat(activeRule.name()).isEqualTo("Rule");
+    assertThat(activeRule.severity()).isEqualTo("MAJOR");
+    assertThat(activeRule.internalKey()).isEqualTo("rule1");
+    assertThat(activeRule.language()).isEqualTo("java");
+    assertThat(activeRule.params()).containsEntry("param1", "value1");
+    assertThat(activeRule.param("param1")).isEqualTo("value1");
+    QProfile qProfile = ref.qProfiles().iterator().next();
+    assertThat(qProfile.key()).isEqualTo("squid-java");
+    assertThat(qProfile.name()).isEqualTo("Java");
+    assertThat(qProfile.rulesUpdatedAt()).isEqualTo(new SimpleDateFormat("dd/MM/yyyy").parse("14/03/1984"));
+    assertThat(ref.settings("foo")).containsEntry("prop", "value");
+
+    assertThat(ref.fileData("foo2", "src/main/java/Foo3.java")).isNull();
+
+    assertThat(ref.fileData("foo", "src/main/java/Foo.java").hash()).isEqualTo("xyz");
+    assertThat(ref.fileData("foo", "src/main/java/Foo.java").needBlame()).isTrue();
+    assertThat(ref.fileData("foo", "src/main/java/Foo.java").scmAuthorsByLine()).isEqualTo("1=henryju,2=gaudin");
+    assertThat(ref.fileData("foo", "src/main/java/Foo.java").scmLastCommitDatetimesByLine()).isEqualTo("1=12345,2=3456");
+    assertThat(ref.fileData("foo", "src/main/java/Foo.java").scmRevisionsByLine()).isEqualTo("1=345,2=345");
+
+    assertThat(ref.lastAnalysisDate()).isEqualTo(new SimpleDateFormat("dd/MM/yyyy").parse("31/10/2014"));
+  }
+}
index 333a041f13f3d733cd1507fa184eca873d76b605..dca499c686966f54ed1765e702a400b88e95871c 100644 (file)
@@ -26,6 +26,7 @@ import org.sonar.batch.design.FileTangleIndexDecorator;
 import org.sonar.batch.design.MavenDependenciesSensor;
 import org.sonar.batch.design.ProjectDsmDecorator;
 import org.sonar.batch.design.SubProjectDsmDecorator;
+import org.sonar.batch.issue.tracking.IssueTracking;
 import org.sonar.batch.maven.DefaultMavenPluginExecutor;
 import org.sonar.batch.maven.MavenProjectBootstrapper;
 import org.sonar.batch.maven.MavenProjectBuilder;
@@ -66,6 +67,9 @@ public class BatchComponents {
 
       LinesSensor.class,
 
+      // Issues tracking
+      IssueTracking.class,
+
       // Reports
       ConsoleReport.class,
       JSONReport.class,
index 75071b09ef14d5e1fc91239ccf843fc07d224bb3..d3749c4cf7f16d211cea435e8b3467452953d46a 100644 (file)
@@ -35,11 +35,13 @@ import org.sonar.batch.components.PastSnapshotFinderByDays;
 import org.sonar.batch.components.PastSnapshotFinderByPreviousAnalysis;
 import org.sonar.batch.components.PastSnapshotFinderByPreviousVersion;
 import org.sonar.batch.components.PastSnapshotFinderByVersion;
-import org.sonar.batch.referential.DefaultGlobalReferentialsLoader;
-import org.sonar.batch.referential.DefaultProjectReferentialsLoader;
-import org.sonar.batch.referential.GlobalReferentialsLoader;
-import org.sonar.batch.referential.GlobalReferentialsProvider;
-import org.sonar.batch.referential.ProjectReferentialsLoader;
+import org.sonar.batch.repository.DefaultGlobalReferentialsLoader;
+import org.sonar.batch.repository.DefaultPreviousIssuesLoader;
+import org.sonar.batch.repository.DefaultProjectReferentialsLoader;
+import org.sonar.batch.repository.GlobalReferentialsLoader;
+import org.sonar.batch.repository.GlobalReferentialsProvider;
+import org.sonar.batch.repository.PreviousIssuesLoader;
+import org.sonar.batch.repository.ProjectRepositoriesLoader;
 import org.sonar.batch.user.UserRepository;
 import org.sonar.core.cluster.NullQueue;
 import org.sonar.core.config.Logback;
@@ -113,9 +115,12 @@ public class BootstrapContainer extends ComponentContainer {
     if (getComponentByType(GlobalReferentialsLoader.class) == null) {
       add(DefaultGlobalReferentialsLoader.class);
     }
-    if (getComponentByType(ProjectReferentialsLoader.class) == null) {
+    if (getComponentByType(ProjectRepositoriesLoader.class) == null) {
       add(DefaultProjectReferentialsLoader.class);
     }
+    if (getComponentByType(PreviousIssuesLoader.class) == null) {
+      add(DefaultPreviousIssuesLoader.class);
+    }
   }
 
   private void addDatabaseComponents() {
index 470f0f79686b010efe89cba5c1e466178f608b2e..3b53ae97c191fb91c3995360b03c97a03599e549 100644 (file)
@@ -103,7 +103,7 @@ public class ServerClient implements BatchComponent {
     }
   }
 
-  private InputSupplier<InputStream> doRequest(String pathStartingWithSlash, String requestMethod, @Nullable Integer timeoutMillis) {
+  public InputSupplier<InputStream> doRequest(String pathStartingWithSlash, String requestMethod, @Nullable Integer timeoutMillis) {
     Preconditions.checkArgument(pathStartingWithSlash.startsWith("/"), "Path must start with slash /");
     String path = StringEscapeUtils.escapeHtml(pathStartingWithSlash);
 
index afa2ed7fee317c4e6f2a374141212dceadac0bb3..4a97a6c8ab82b64981171ce8e8c9a0eff5c56167 100644 (file)
@@ -38,6 +38,8 @@ public class ResourceCache implements BatchComponent {
   // dedicated cache for libraries
   private final Map<Library, BatchResource> libraries = Maps.newLinkedHashMap();
 
+  private BatchResource root;
+
   @CheckForNull
   public BatchResource get(String componentKey) {
     return resources.get(componentKey);
@@ -56,10 +58,13 @@ public class ResourceCache implements BatchComponent {
     String componentKey = resource.getEffectiveKey();
     Preconditions.checkState(!Strings.isNullOrEmpty(componentKey), "Missing resource effective key");
     BatchResource parent = parentResource != null ? get(parentResource.getEffectiveKey()) : null;
-    BatchResource batchResource = new BatchResource((long) resources.size() + 1, resource, parent);
+    BatchResource batchResource = new BatchResource(resources.size() + 1, resource, parent);
     if (!(resource instanceof Library)) {
       // Libraries can have the same effective key than a project so we can't cache by effectiveKey
       resources.put(componentKey, batchResource);
+      if (parent == null) {
+        root = batchResource;
+      }
     } else {
       libraries.put((Library) resource, batchResource);
     }
@@ -73,4 +78,8 @@ public class ResourceCache implements BatchComponent {
   public Collection<BatchResource> allLibraries() {
     return libraries.values();
   }
+
+  public BatchResource getRoot() {
+    return root;
+  }
 }
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/FileHashes.java b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/FileHashes.java
new file mode 100644 (file)
index 0000000..49b0405
--- /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.batch.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/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/InitialOpenIssuesSensor.java b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/InitialOpenIssuesSensor.java
new file mode 100644 (file)
index 0000000..a58f5c9
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.batch.issue.tracking;
+
+import org.apache.commons.lang.time.DateUtils;
+import org.apache.ibatis.session.ResultContext;
+import org.apache.ibatis.session.ResultHandler;
+import org.sonar.api.batch.Sensor;
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.resources.Project;
+import org.sonar.core.DryRunIncompatible;
+import org.sonar.core.issue.db.IssueChangeDao;
+import org.sonar.core.issue.db.IssueChangeDto;
+import org.sonar.core.issue.db.IssueDao;
+import org.sonar.core.issue.db.IssueDto;
+
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * Load all the issues referenced during the previous scan.
+ */
+@DryRunIncompatible
+public class InitialOpenIssuesSensor implements Sensor {
+
+  private final InitialOpenIssuesStack initialOpenIssuesStack;
+  private final IssueDao issueDao;
+  private final IssueChangeDao issueChangeDao;
+
+  public InitialOpenIssuesSensor(InitialOpenIssuesStack initialOpenIssuesStack, IssueDao issueDao, IssueChangeDao issueChangeDao) {
+    this.initialOpenIssuesStack = initialOpenIssuesStack;
+    this.issueDao = issueDao;
+    this.issueChangeDao = issueChangeDao;
+  }
+
+  @Override
+  public boolean shouldExecuteOnProject(Project project) {
+    return true;
+  }
+
+  @Override
+  public void analyse(Project project, SensorContext context) {
+    // Adding one second is a hack for resolving conflicts with concurrent user
+    // changes during issue persistence
+    final Date now = DateUtils.addSeconds(DateUtils.truncate(new Date(), Calendar.MILLISECOND), 1);
+
+    issueDao.selectNonClosedIssuesByModule(project.getId(), new ResultHandler() {
+      @Override
+      public void handleResult(ResultContext rc) {
+        IssueDto dto = (IssueDto) rc.getResultObject();
+        dto.setSelectedAt(now.getTime());
+        initialOpenIssuesStack.addIssue(dto);
+      }
+    });
+
+    issueChangeDao.selectChangelogOnNonClosedIssuesByModuleAndType(project.getId(), new ResultHandler() {
+      @Override
+      public void handleResult(ResultContext rc) {
+        IssueChangeDto dto = (IssueChangeDto) rc.getResultObject();
+        initialOpenIssuesStack.addChangelog(dto);
+      }
+    });
+  }
+
+  @Override
+  public String toString() {
+    return getClass().getSimpleName();
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/InitialOpenIssuesStack.java b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/InitialOpenIssuesStack.java
new file mode 100644 (file)
index 0000000..acc9ebf
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * 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.issue.tracking;
+
+import org.sonar.api.BatchExtension;
+import org.sonar.api.batch.InstantiationStrategy;
+import org.sonar.batch.index.Cache;
+import org.sonar.batch.index.Caches;
+import org.sonar.core.issue.db.IssueChangeDto;
+import org.sonar.core.issue.db.IssueDto;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+@InstantiationStrategy(InstantiationStrategy.PER_BATCH)
+public class InitialOpenIssuesStack implements BatchExtension {
+
+  private final Cache<IssueDto> issuesCache;
+  private final Cache<ArrayList<IssueChangeDto>> issuesChangelogCache;
+
+  public InitialOpenIssuesStack(Caches caches) {
+    issuesCache = caches.createCache("last-open-issues");
+    issuesChangelogCache = caches.createCache("issues-changelog");
+  }
+
+  public InitialOpenIssuesStack addIssue(IssueDto issueDto) {
+    issuesCache.put(issueDto.getComponentKey(), issueDto.getKee(), issueDto);
+    return this;
+  }
+
+  public List<PreviousIssue> selectAndRemoveIssues(String componentKey) {
+    Iterable<IssueDto> issues = issuesCache.values(componentKey);
+    List<PreviousIssue> result = newArrayList();
+    for (IssueDto issue : issues) {
+      result.add(new PreviousIssueFromDb(issue));
+    }
+    issuesCache.clear(componentKey);
+    return result;
+  }
+
+  public Iterable<IssueDto> selectAllIssues() {
+    return issuesCache.values();
+  }
+
+  public InitialOpenIssuesStack addChangelog(IssueChangeDto issueChangeDto) {
+    List<IssueChangeDto> changeDtos = issuesChangelogCache.get(issueChangeDto.getIssueKey());
+    if (changeDtos == null) {
+      changeDtos = newArrayList();
+    }
+    changeDtos.add(issueChangeDto);
+    issuesChangelogCache.put(issueChangeDto.getIssueKey(), newArrayList(changeDtos));
+    return this;
+  }
+
+  public List<IssueChangeDto> selectChangelog(String issueKey) {
+    List<IssueChangeDto> changeDtos = issuesChangelogCache.get(issueKey);
+    return changeDtos != null ? changeDtos : Collections.<IssueChangeDto>emptyList();
+  }
+
+  public void clear() {
+    issuesCache.clear();
+    issuesChangelogCache.clear();
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/IssueHandlers.java b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/IssueHandlers.java
new file mode 100644 (file)
index 0000000..f8a98c2
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * 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.issue.tracking;
+
+import org.sonar.api.BatchExtension;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.IssueHandler;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.issue.internal.IssueChangeContext;
+import org.sonar.api.user.User;
+import org.sonar.core.issue.IssueUpdater;
+import org.sonar.core.user.DefaultUser;
+
+import javax.annotation.Nullable;
+
+public class IssueHandlers implements BatchExtension {
+  private final IssueHandler[] handlers;
+  private final DefaultContext context;
+
+  public IssueHandlers(IssueUpdater updater, IssueHandler[] handlers) {
+    this.handlers = handlers;
+    this.context = new DefaultContext(updater);
+  }
+
+  public IssueHandlers(IssueUpdater updater) {
+    this(updater, new IssueHandler[0]);
+  }
+
+  public void execute(DefaultIssue issue, IssueChangeContext changeContext) {
+    context.reset(issue, changeContext);
+    for (IssueHandler handler : handlers) {
+      handler.onIssue(context);
+    }
+  }
+
+  static class DefaultContext implements IssueHandler.Context {
+    private final IssueUpdater updater;
+    private DefaultIssue issue;
+    private IssueChangeContext changeContext;
+
+    private DefaultContext(IssueUpdater updater) {
+      this.updater = updater;
+    }
+
+    private void reset(DefaultIssue i, IssueChangeContext changeContext) {
+      this.issue = i;
+      this.changeContext = changeContext;
+    }
+
+    @Override
+    public Issue issue() {
+      return issue;
+    }
+
+    @Override
+    public boolean isNew() {
+      return issue.isNew();
+    }
+
+    @Override
+    public boolean isEndOfLife() {
+      return issue.isEndOfLife();
+    }
+
+    @Override
+    public IssueHandler.Context setLine(@Nullable Integer line) {
+      updater.setLine(issue, line);
+      return this;
+    }
+
+    @Override
+    public IssueHandler.Context setMessage(@Nullable String s) {
+      updater.setMessage(issue, s, changeContext);
+      return this;
+    }
+
+    @Override
+    public IssueHandler.Context setSeverity(String severity) {
+      updater.setSeverity(issue, severity, changeContext);
+      return this;
+    }
+
+    @Override
+    public IssueHandler.Context setAuthorLogin(@Nullable String login) {
+      updater.setAuthorLogin(issue, login, changeContext);
+      return this;
+    }
+
+    @Override
+    public IssueHandler.Context setEffortToFix(@Nullable Double d) {
+      updater.setEffortToFix(issue, d, changeContext);
+      return this;
+    }
+
+    @Override
+    public IssueHandler.Context setAttribute(String key, @Nullable String value) {
+      throw new UnsupportedOperationException("TODO");
+    }
+
+    @Override
+    public IssueHandler.Context assign(@Nullable String assignee) {
+      User user = null;
+      if(assignee != null) {
+        user = new DefaultUser().setLogin(assignee).setName(assignee);
+      }
+      updater.assign(issue, user, changeContext);
+      return this;
+    }
+
+    @Override
+    public IssueHandler.Context assign(@Nullable User user) {
+      updater.assign(issue, user, changeContext);
+      return this;
+    }
+
+    @Override
+    public IssueHandler.Context addComment(String text) {
+      updater.addComment(issue, text, changeContext);
+      return this;
+    }
+  }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/IssueTracking.java b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/IssueTracking.java
new file mode 100644 (file)
index 0000000..2c1c49a
--- /dev/null
@@ -0,0 +1,337 @@
+/*
+ * 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.issue.tracking;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Objects;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import org.sonar.api.BatchComponent;
+import org.sonar.api.batch.InstantiationStrategy;
+import org.sonar.api.issue.internal.DefaultIssue;
+
+import javax.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+@InstantiationStrategy(InstantiationStrategy.PER_BATCH)
+public class IssueTracking implements BatchComponent {
+
+  /**
+   * @param sourceHashHolder Null when working on resource that is not a file (directory/project)
+   */
+  public IssueTrackingResult track(@Nullable SourceHashHolder sourceHashHolder, Collection<PreviousIssue> previousIssues, Collection<DefaultIssue> newIssues) {
+    IssueTrackingResult result = new IssueTrackingResult();
+
+    if (sourceHashHolder != null) {
+      setChecksumOnNewIssues(newIssues, sourceHashHolder);
+    }
+
+    // Map new issues with old ones
+    mapIssues(newIssues, previousIssues, sourceHashHolder, result);
+    return result;
+  }
+
+  private void setChecksumOnNewIssues(Collection<DefaultIssue> issues, SourceHashHolder sourceHashHolder) {
+    if (issues.isEmpty()) {
+      return;
+    }
+    for (DefaultIssue issue : issues) {
+      Integer line = issue.line();
+      if (line != null) {
+        issue.setChecksum(sourceHashHolder.getHashedSource().getHash(line));
+      }
+    }
+  }
+
+  @VisibleForTesting
+  void mapIssues(Collection<DefaultIssue> newIssues, @Nullable Collection<PreviousIssue> previousIssues, @Nullable SourceHashHolder sourceHashHolder, IssueTrackingResult result) {
+    boolean hasLastScan = false;
+
+    if (previousIssues != null) {
+      hasLastScan = true;
+      mapLastIssues(newIssues, previousIssues, result);
+    }
+
+    // If each new issue matches an old one we can stop the matching mechanism
+    if (result.matched().size() != newIssues.size()) {
+      if (sourceHashHolder != null && hasLastScan) {
+        FileHashes hashedReference = sourceHashHolder.getHashedReference();
+        if (hashedReference != null) {
+          mapNewissues(hashedReference, sourceHashHolder.getHashedSource(), newIssues, result);
+        }
+      }
+      mapIssuesOnSameRule(newIssues, result);
+    }
+  }
+
+  private void mapLastIssues(Collection<DefaultIssue> newIssues, Collection<PreviousIssue> previousIssues, IssueTrackingResult result) {
+    for (PreviousIssue lastIssue : previousIssues) {
+      result.addUnmatched(lastIssue);
+    }
+
+    // Match the key of the issue. (For manual issues)
+    for (DefaultIssue newIssue : newIssues) {
+      mapIssue(newIssue, result.unmatchedByKeyForRule(newIssue.ruleKey()).get(newIssue.key()), result);
+    }
+
+    // Try first to match issues on same rule with same line and with same checksum (but not necessarily with same message)
+    for (DefaultIssue newIssue : newIssues) {
+      if (isNotAlreadyMapped(newIssue, result)) {
+        mapIssue(
+          newIssue,
+          findLastIssueWithSameLineAndChecksum(newIssue, result),
+          result);
+      }
+    }
+  }
+
+  private void mapNewissues(FileHashes hashedReference, FileHashes hashedSource, Collection<DefaultIssue> newIssues, IssueTrackingResult result) {
+
+    IssueTrackingBlocksRecognizer rec = new IssueTrackingBlocksRecognizer(hashedReference, hashedSource);
+
+    RollingFileHashes a = RollingFileHashes.create(hashedReference, 5);
+    RollingFileHashes b = RollingFileHashes.create(hashedSource, 5);
+
+    Multimap<Integer, DefaultIssue> newIssuesByLines = newIssuesByLines(newIssues, rec, result);
+    Multimap<Integer, PreviousIssue> lastIssuesByLines = lastIssuesByLines(result.unmatched(), rec);
+
+    Map<Integer, HashOccurrence> map = Maps.newHashMap();
+
+    for (Integer line : lastIssuesByLines.keySet()) {
+      int hash = a.getHash(line);
+      HashOccurrence hashOccurrence = map.get(hash);
+      if (hashOccurrence == null) {
+        // first occurrence in A
+        hashOccurrence = new HashOccurrence();
+        hashOccurrence.lineA = line;
+        hashOccurrence.countA = 1;
+        map.put(hash, hashOccurrence);
+      } else {
+        hashOccurrence.countA++;
+      }
+    }
+
+    for (Integer line : newIssuesByLines.keySet()) {
+      int hash = b.getHash(line);
+      HashOccurrence hashOccurrence = map.get(hash);
+      if (hashOccurrence != null) {
+        hashOccurrence.lineB = line;
+        hashOccurrence.countB++;
+      }
+    }
+
+    for (HashOccurrence hashOccurrence : map.values()) {
+      if (hashOccurrence.countA == 1 && hashOccurrence.countB == 1) {
+        // Guaranteed that lineA has been moved to lineB, so we can map all issues on lineA to all issues on lineB
+        map(newIssuesByLines.get(hashOccurrence.lineB), lastIssuesByLines.get(hashOccurrence.lineA), result);
+        lastIssuesByLines.removeAll(hashOccurrence.lineA);
+        newIssuesByLines.removeAll(hashOccurrence.lineB);
+      }
+    }
+
+    // Check if remaining number of lines exceeds threshold
+    if (lastIssuesByLines.keySet().size() * newIssuesByLines.keySet().size() < 250000) {
+      List<LinePair> possibleLinePairs = Lists.newArrayList();
+      for (Integer oldLine : lastIssuesByLines.keySet()) {
+        for (Integer newLine : newIssuesByLines.keySet()) {
+          int weight = rec.computeLengthOfMaximalBlock(oldLine, newLine);
+          possibleLinePairs.add(new LinePair(oldLine, newLine, weight));
+        }
+      }
+      Collections.sort(possibleLinePairs, LINE_PAIR_COMPARATOR);
+      for (LinePair linePair : possibleLinePairs) {
+        // High probability that lineA has been moved to lineB, so we can map all Issues on lineA to all Issues on lineB
+        map(newIssuesByLines.get(linePair.lineB), lastIssuesByLines.get(linePair.lineA), result);
+      }
+    }
+  }
+
+  private void mapIssuesOnSameRule(Collection<DefaultIssue> newIssues, IssueTrackingResult result) {
+    // Try then to match issues on same rule with same message and with same checksum
+    for (DefaultIssue newIssue : newIssues) {
+      if (isNotAlreadyMapped(newIssue, result)) {
+        mapIssue(
+          newIssue,
+          findLastIssueWithSameChecksumAndMessage(newIssue, result.unmatchedByKeyForRule(newIssue.ruleKey()).values()),
+          result);
+      }
+    }
+
+    // Try then to match issues on same rule with same line and with same message
+    for (DefaultIssue newIssue : newIssues) {
+      if (isNotAlreadyMapped(newIssue, result)) {
+        mapIssue(
+          newIssue,
+          findLastIssueWithSameLineAndMessage(newIssue, result.unmatchedByKeyForRule(newIssue.ruleKey()).values()),
+          result);
+      }
+    }
+
+    // Last check: match issue if same rule and same checksum but different line and different message
+    // See SONAR-2812
+    for (DefaultIssue newIssue : newIssues) {
+      if (isNotAlreadyMapped(newIssue, result)) {
+        mapIssue(
+          newIssue,
+          findLastIssueWithSameChecksum(newIssue, result.unmatchedByKeyForRule(newIssue.ruleKey()).values()),
+          result);
+      }
+    }
+  }
+
+  private void map(Collection<DefaultIssue> newIssues, Collection<PreviousIssue> previousIssues, IssueTrackingResult result) {
+    for (DefaultIssue newIssue : newIssues) {
+      if (isNotAlreadyMapped(newIssue, result)) {
+        for (PreviousIssue previousIssue : previousIssues) {
+          if (isNotAlreadyMapped(previousIssue, result) && Objects.equal(newIssue.ruleKey(), previousIssue.ruleKey())) {
+            mapIssue(newIssue, previousIssue, result);
+            break;
+          }
+        }
+      }
+    }
+  }
+
+  private Multimap<Integer, DefaultIssue> newIssuesByLines(Collection<DefaultIssue> newIssues, IssueTrackingBlocksRecognizer rec, IssueTrackingResult result) {
+    Multimap<Integer, DefaultIssue> newIssuesByLines = LinkedHashMultimap.create();
+    for (DefaultIssue newIssue : newIssues) {
+      if (isNotAlreadyMapped(newIssue, result) && rec.isValidLineInSource(newIssue.line())) {
+        newIssuesByLines.put(newIssue.line(), newIssue);
+      }
+    }
+    return newIssuesByLines;
+  }
+
+  private Multimap<Integer, PreviousIssue> lastIssuesByLines(Collection<PreviousIssue> previousIssues, IssueTrackingBlocksRecognizer rec) {
+    Multimap<Integer, PreviousIssue> previousIssuesByLines = LinkedHashMultimap.create();
+    for (PreviousIssue previousIssue : previousIssues) {
+      if (rec.isValidLineInReference(previousIssue.line())) {
+        previousIssuesByLines.put(previousIssue.line(), previousIssue);
+      }
+    }
+    return previousIssuesByLines;
+  }
+
+  private PreviousIssue findLastIssueWithSameChecksum(DefaultIssue newIssue, Collection<PreviousIssue> previousIssues) {
+    for (PreviousIssue previousIssue : previousIssues) {
+      if (isSameChecksum(newIssue, previousIssue)) {
+        return previousIssue;
+      }
+    }
+    return null;
+  }
+
+  private PreviousIssue findLastIssueWithSameLineAndMessage(DefaultIssue newIssue, Collection<PreviousIssue> previousIssues) {
+    for (PreviousIssue previousIssue : previousIssues) {
+      if (isSameLine(newIssue, previousIssue) && isSameMessage(newIssue, previousIssue)) {
+        return previousIssue;
+      }
+    }
+    return null;
+  }
+
+  private PreviousIssue findLastIssueWithSameChecksumAndMessage(DefaultIssue newIssue, Collection<PreviousIssue> previousIssues) {
+    for (PreviousIssue previousIssue : previousIssues) {
+      if (isSameChecksum(newIssue, previousIssue) && isSameMessage(newIssue, previousIssue)) {
+        return previousIssue;
+      }
+    }
+    return null;
+  }
+
+  private PreviousIssue findLastIssueWithSameLineAndChecksum(DefaultIssue newIssue, IssueTrackingResult result) {
+    Collection<PreviousIssue> sameRuleAndSameLineAndSameChecksum = result.unmatchedForRuleAndForLineAndForChecksum(newIssue.ruleKey(), newIssue.line(), newIssue.checksum());
+    if (!sameRuleAndSameLineAndSameChecksum.isEmpty()) {
+      return sameRuleAndSameLineAndSameChecksum.iterator().next();
+    }
+    return null;
+  }
+
+  private boolean isNotAlreadyMapped(PreviousIssue previousIssue, IssueTrackingResult result) {
+    return result.unmatched().contains(previousIssue);
+  }
+
+  private boolean isNotAlreadyMapped(DefaultIssue newIssue, IssueTrackingResult result) {
+    return !result.isMatched(newIssue);
+  }
+
+  private boolean isSameChecksum(DefaultIssue newIssue, PreviousIssue previousIssue) {
+    return Objects.equal(previousIssue.checksum(), newIssue.checksum());
+  }
+
+  private boolean isSameLine(DefaultIssue newIssue, PreviousIssue previousIssue) {
+    return Objects.equal(previousIssue.line(), newIssue.line());
+  }
+
+  private boolean isSameMessage(DefaultIssue newIssue, PreviousIssue previousIssue) {
+    return Objects.equal(newIssue.message(), previousIssue.message());
+  }
+
+  private void mapIssue(DefaultIssue issue, @Nullable PreviousIssue ref, IssueTrackingResult result) {
+    if (ref != null) {
+      result.setMatch(issue, ref);
+    }
+  }
+
+  @Override
+  public String toString() {
+    return getClass().getSimpleName();
+  }
+
+  private static class LinePair {
+    int lineA;
+    int lineB;
+    int weight;
+
+    public LinePair(int lineA, int lineB, int weight) {
+      this.lineA = lineA;
+      this.lineB = lineB;
+      this.weight = weight;
+    }
+  }
+
+  private static class HashOccurrence {
+    int lineA;
+    int lineB;
+    int countA;
+    int countB;
+  }
+
+  private static final Comparator<LinePair> LINE_PAIR_COMPARATOR = new Comparator<LinePair>() {
+    @Override
+    public int compare(LinePair o1, LinePair o2) {
+      int weightDiff = o2.weight - o1.weight;
+      if (weightDiff != 0) {
+        return weightDiff;
+      } else {
+        return Math.abs(o1.lineA - o1.lineB) - Math.abs(o2.lineA - o2.lineB);
+      }
+    }
+  };
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/IssueTrackingBlocksRecognizer.java b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/IssueTrackingBlocksRecognizer.java
new file mode 100644 (file)
index 0000000..c7e01c9
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.issue.tracking;
+
+import javax.annotation.Nullable;
+
+public class IssueTrackingBlocksRecognizer {
+
+  private final FileHashes a;
+  private final FileHashes b;
+
+  public IssueTrackingBlocksRecognizer(FileHashes a, FileHashes b) {
+    this.a = a;
+    this.b = b;
+  }
+
+  public boolean isValidLineInReference(@Nullable Integer line) {
+    return (line != null) && (0 <= line - 1) && (line - 1 < a.length());
+  }
+
+  public boolean isValidLineInSource(@Nullable Integer line) {
+    return (line != null) && (0 <= line - 1) && (line - 1 < b.length());
+  }
+
+  /**
+   * @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 (!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() && a.getHash(ai).equals(b.getHash(bi))) {
+      ai++;
+      bi++;
+      length++;
+    }
+    ai = startA;
+    bi = startB;
+    while (ai > 0 && bi > 0 && a.getHash(ai).equals(b.getHash(bi))) {
+      ai--;
+      bi--;
+      length++;
+    }
+    // Note that position (startA, startB) was counted twice
+    return length - 1;
+  }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/IssueTrackingDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/IssueTrackingDecorator.java
new file mode 100644 (file)
index 0000000..8bc86e3
--- /dev/null
@@ -0,0 +1,280 @@
+/*
+ * 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.issue.tracking;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.Decorator;
+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.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;
+import org.sonar.api.rules.ActiveRule;
+import org.sonar.api.rules.Rule;
+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.LastLineHashes;
+import org.sonar.batch.scan.filesystem.InputPathCache;
+import org.sonar.core.DryRunIncompatible;
+import org.sonar.core.issue.IssueUpdater;
+import org.sonar.core.issue.db.IssueChangeDto;
+import org.sonar.core.issue.db.IssueDto;
+import org.sonar.core.issue.workflow.IssueWorkflow;
+
+import java.util.Collection;
+
+@DependsUpon(DecoratorBarriers.ISSUES_ADDED)
+@DependedUpon(DecoratorBarriers.ISSUES_TRACKED)
+@DryRunIncompatible
+public class IssueTrackingDecorator implements Decorator {
+
+  private static final Logger LOG = LoggerFactory.getLogger(IssueTrackingDecorator.class);
+
+  private final IssueCache issueCache;
+  private final InitialOpenIssuesStack initialOpenIssues;
+  private final IssueTracking tracking;
+  private final LastLineHashes lastLineHashes;
+  private final IssueHandlers handlers;
+  private final IssueWorkflow workflow;
+  private final IssueUpdater updater;
+  private final IssueChangeContext changeContext;
+  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,
+    LastLineHashes lastLineHashes,
+    IssueHandlers handlers, IssueWorkflow workflow,
+    IssueUpdater updater,
+    Project project,
+    ResourcePerspectives perspectives,
+    RulesProfile rulesProfile,
+    RuleFinder ruleFinder, InputPathCache inputPathCache) {
+    this.issueCache = issueCache;
+    this.initialOpenIssues = initialOpenIssues;
+    this.tracking = tracking;
+    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;
+    this.ruleFinder = ruleFinder;
+  }
+
+  @Override
+  public boolean shouldExecuteOnProject(Project project) {
+    return true;
+  }
+
+  @Override
+  public void decorate(Resource resource, DecoratorContext context) {
+    Issuable issuable = perspectives.as(Issuable.class, resource);
+    if (issuable != null) {
+      doDecorate(resource);
+    }
+  }
+
+  @VisibleForTesting
+  void doDecorate(Resource resource) {
+    Collection<DefaultIssue> issues = Lists.newArrayList();
+    for (Issue issue : issueCache.byComponent(resource.getEffectiveKey())) {
+      issues.add((DefaultIssue) issue);
+    }
+    issueCache.clear(resource.getEffectiveKey());
+    // issues = all the issues created by rule engines during this module scan and not excluded by filters
+
+    // all the issues that are not closed in db before starting this module scan, including manual issues
+    Collection<PreviousIssue> dbOpenIssues = initialOpenIssues.selectAndRemoveIssues(resource.getEffectiveKey());
+
+    SourceHashHolder sourceHashHolder = null;
+    if (ResourceUtils.isFile(resource)) {
+      File sonarFile = (File) resource;
+      InputFile file = inputPathCache.getFile(project.getEffectiveKey(), sonarFile.getPath());
+      if (file == null) {
+        throw new IllegalStateException("Resource " + resource + " was not found in InputPath cache");
+      }
+      sourceHashHolder = new SourceHashHolder((DefaultInputFile) file, lastLineHashes);
+    }
+
+    IssueTrackingResult trackingResult = tracking.track(sourceHashHolder, dbOpenIssues, issues);
+
+    // unmatched = issues that have been resolved + issues on disabled/removed rules + manual issues
+    addUnmatched(trackingResult.unmatched(), sourceHashHolder, issues);
+
+    mergeMatched(trackingResult);
+
+    if (ResourceUtils.isProject(resource)) {
+      // issues that relate to deleted components
+      addIssuesOnDeletedComponents(issues);
+    }
+
+    for (DefaultIssue issue : issues) {
+      workflow.doAutomaticTransition(issue, changeContext);
+      handlers.execute(issue, changeContext);
+      issueCache.put(issue);
+    }
+  }
+
+  @VisibleForTesting
+  protected void mergeMatched(IssueTrackingResult result) {
+    for (DefaultIssue issue : result.matched()) {
+      IssueDto ref = ((PreviousIssueFromDb) result.matching(issue)).getDto();
+
+      // invariant fields
+      issue.setKey(ref.getKee());
+      issue.setCreationDate(ref.getIssueCreationDate());
+      issue.setUpdateDate(ref.getIssueUpdateDate());
+      issue.setCloseDate(ref.getIssueCloseDate());
+
+      // non-persisted fields
+      issue.setNew(false);
+      issue.setEndOfLife(false);
+      issue.setOnDisabledRule(false);
+      issue.setSelectedAt(ref.getSelectedAt());
+
+      // fields to update with old values
+      issue.setActionPlanKey(ref.getActionPlanKey());
+      issue.setResolution(ref.getResolution());
+      issue.setStatus(ref.getStatus());
+      issue.setAssignee(ref.getAssignee());
+      issue.setAuthorLogin(ref.getAuthorLogin());
+      issue.setTags(ref.getTags());
+
+      if (ref.getIssueAttributes() != null) {
+        issue.setAttributes(KeyValueFormat.parse(ref.getIssueAttributes()));
+      }
+
+      // populate existing changelog
+      Collection<IssueChangeDto> issueChangeDtos = initialOpenIssues.selectChangelog(issue.key());
+      for (IssueChangeDto issueChangeDto : issueChangeDtos) {
+        issue.addChange(issueChangeDto.toFieldDiffs());
+      }
+
+      // fields to update with current values
+      if (ref.isManualSeverity()) {
+        issue.setManualSeverity(true);
+        issue.setSeverity(ref.getSeverity());
+      } else {
+        updater.setPastSeverity(issue, ref.getSeverity(), changeContext);
+      }
+      updater.setPastLine(issue, ref.getLine());
+      updater.setPastMessage(issue, ref.getMessage(), changeContext);
+      updater.setPastEffortToFix(issue, ref.getEffortToFix(), changeContext);
+      Long debtInMinutes = ref.getDebt();
+      Duration previousTechnicalDebt = debtInMinutes != null ? Duration.create(debtInMinutes) : null;
+      updater.setPastTechnicalDebt(issue, previousTechnicalDebt, changeContext);
+      updater.setPastProject(issue, ref.getProjectKey(), changeContext);
+    }
+  }
+
+  private void addUnmatched(Collection<PreviousIssue> unmatchedIssues, SourceHashHolder sourceHashHolder, Collection<DefaultIssue> issues) {
+    for (PreviousIssue unmatchedIssue : unmatchedIssues) {
+      IssueDto unmatchedDto = ((PreviousIssueFromDb) unmatchedIssue).getDto();
+      DefaultIssue unmatched = unmatchedDto.toDefaultIssue();
+      if (StringUtils.isNotBlank(unmatchedDto.getReporter()) && !Issue.STATUS_CLOSED.equals(unmatchedDto.getStatus())) {
+        relocateManualIssue(unmatched, unmatchedDto, sourceHashHolder);
+      }
+      updateUnmatchedIssue(unmatched, false /* manual issues can be kept open */);
+      issues.add(unmatched);
+    }
+  }
+
+  private void addIssuesOnDeletedComponents(Collection<DefaultIssue> issues) {
+    for (IssueDto deadDto : initialOpenIssues.selectAllIssues()) {
+      DefaultIssue dead = deadDto.toDefaultIssue();
+      updateUnmatchedIssue(dead, true);
+      issues.add(dead);
+    }
+    initialOpenIssues.clear();
+  }
+
+  private void updateUnmatchedIssue(DefaultIssue issue, boolean forceEndOfLife) {
+    issue.setNew(false);
+
+    boolean manualIssue = !Strings.isNullOrEmpty(issue.reporter());
+    Rule rule = ruleFinder.findByKey(issue.ruleKey());
+    if (manualIssue) {
+      // Manual rules are not declared in Quality profiles, so no need to check ActiveRule
+      boolean isRemovedRule = rule == null || Rule.STATUS_REMOVED.equals(rule.getStatus());
+      issue.setEndOfLife(forceEndOfLife || isRemovedRule);
+      issue.setOnDisabledRule(isRemovedRule);
+    } else {
+      ActiveRule activeRule = rulesProfile.getActiveRule(issue.ruleKey().repository(), issue.ruleKey().rule());
+      issue.setEndOfLife(true);
+      issue.setOnDisabledRule(activeRule == null || rule == null || Rule.STATUS_REMOVED.equals(rule.getStatus()));
+    }
+  }
+
+  private void relocateManualIssue(DefaultIssue newIssue, IssueDto oldIssue, SourceHashHolder sourceHashHolder) {
+    LOG.debug("Trying to relocate manual issue {}", oldIssue.getKee());
+
+    Integer previousLine = oldIssue.getLine();
+    if (previousLine == null) {
+      LOG.debug("Cannot relocate issue at resource level");
+      return;
+    }
+
+    Collection<Integer> newLinesWithSameHash = sourceHashHolder.getNewLinesMatching(previousLine);
+    LOG.debug("Found the following lines with same hash: {}", newLinesWithSameHash);
+    if (newLinesWithSameHash.isEmpty()) {
+      if (previousLine > sourceHashHolder.getHashedSource().length()) {
+        LOG.debug("Old issue line {} is out of new source, closing and removing line number", previousLine);
+        newIssue.setLine(null);
+        updater.setStatus(newIssue, Issue.STATUS_CLOSED, changeContext);
+        updater.setResolution(newIssue, Issue.RESOLUTION_REMOVED, changeContext);
+        updater.setPastLine(newIssue, previousLine);
+        updater.setPastMessage(newIssue, oldIssue.getMessage(), changeContext);
+        updater.setPastEffortToFix(newIssue, oldIssue.getEffortToFix(), changeContext);
+      }
+    } else if (newLinesWithSameHash.size() == 1) {
+      Integer newLine = newLinesWithSameHash.iterator().next();
+      LOG.debug("Relocating issue to line {}", newLine);
+
+      newIssue.setLine(newLine);
+      updater.setPastLine(newIssue, previousLine);
+      updater.setPastMessage(newIssue, oldIssue.getMessage(), changeContext);
+      updater.setPastEffortToFix(newIssue, oldIssue.getEffortToFix(), changeContext);
+    }
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/IssueTrackingResult.java b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/IssueTrackingResult.java
new file mode 100644 (file)
index 0000000..f7b4e8d
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.batch.issue.tracking;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.rule.RuleKey;
+
+import javax.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+class IssueTrackingResult {
+  private final Map<String, PreviousIssue> unmatchedByKey = new HashMap<>();
+  private final Map<RuleKey, Map<String, PreviousIssue>> unmatchedByRuleAndKey = new HashMap<>();
+  private final Map<RuleKey, Map<Integer, Multimap<String, PreviousIssue>>> unmatchedByRuleAndLineAndChecksum = new HashMap<>();
+  private final Map<DefaultIssue, PreviousIssue> matched = Maps.newIdentityHashMap();
+
+  Collection<PreviousIssue> unmatched() {
+    return unmatchedByKey.values();
+  }
+
+  Map<String, PreviousIssue> unmatchedByKeyForRule(RuleKey ruleKey) {
+    return unmatchedByRuleAndKey.containsKey(ruleKey) ? unmatchedByRuleAndKey.get(ruleKey) : Collections.<String, PreviousIssue>emptyMap();
+  }
+
+  Collection<PreviousIssue> unmatchedForRuleAndForLineAndForChecksum(RuleKey ruleKey, @Nullable Integer line, @Nullable String checksum) {
+    if (!unmatchedByRuleAndLineAndChecksum.containsKey(ruleKey)) {
+      return Collections.emptyList();
+    }
+    Map<Integer, Multimap<String, PreviousIssue>> unmatchedForRule = unmatchedByRuleAndLineAndChecksum.get(ruleKey);
+    Integer lineNotNull = line != null ? line : 0;
+    if (!unmatchedForRule.containsKey(lineNotNull)) {
+      return Collections.emptyList();
+    }
+    Multimap<String, PreviousIssue> unmatchedForRuleAndLine = unmatchedForRule.get(lineNotNull);
+    String checksumNotNull = StringUtils.defaultString(checksum, "");
+    if (!unmatchedForRuleAndLine.containsKey(checksumNotNull)) {
+      return Collections.emptyList();
+    }
+    return unmatchedForRuleAndLine.get(checksumNotNull);
+  }
+
+  Collection<DefaultIssue> matched() {
+    return matched.keySet();
+  }
+
+  boolean isMatched(DefaultIssue issue) {
+    return matched.containsKey(issue);
+  }
+
+  PreviousIssue matching(DefaultIssue issue) {
+    return matched.get(issue);
+  }
+
+  void addUnmatched(PreviousIssue i) {
+    unmatchedByKey.put(i.key(), i);
+    RuleKey ruleKey = i.ruleKey();
+    if (!unmatchedByRuleAndKey.containsKey(ruleKey)) {
+      unmatchedByRuleAndKey.put(ruleKey, new HashMap<String, PreviousIssue>());
+      unmatchedByRuleAndLineAndChecksum.put(ruleKey, new HashMap<Integer, Multimap<String, PreviousIssue>>());
+    }
+    unmatchedByRuleAndKey.get(ruleKey).put(i.key(), i);
+    Map<Integer, Multimap<String, PreviousIssue>> unmatchedForRule = unmatchedByRuleAndLineAndChecksum.get(ruleKey);
+    Integer lineNotNull = lineNotNull(i);
+    if (!unmatchedForRule.containsKey(lineNotNull)) {
+      unmatchedForRule.put(lineNotNull, HashMultimap.<String, PreviousIssue>create());
+    }
+    Multimap<String, PreviousIssue> unmatchedForRuleAndLine = unmatchedForRule.get(lineNotNull);
+    String checksumNotNull = StringUtils.defaultString(i.checksum(), "");
+    unmatchedForRuleAndLine.put(checksumNotNull, i);
+  }
+
+  private Integer lineNotNull(PreviousIssue i) {
+    Integer line = i.line();
+    return line != null ? line : 0;
+  }
+
+  void setMatch(DefaultIssue issue, PreviousIssue matching) {
+    matched.put(issue, matching);
+    RuleKey ruleKey = matching.ruleKey();
+    unmatchedByRuleAndKey.get(ruleKey).remove(matching.key());
+    unmatchedByKey.remove(matching.key());
+    Integer lineNotNull = lineNotNull(matching);
+    String checksumNotNull = StringUtils.defaultString(matching.checksum(), "");
+    unmatchedByRuleAndLineAndChecksum.get(ruleKey).get(lineNotNull).get(checksumNotNull).remove(matching);
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/LocalIssueTracking.java b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/LocalIssueTracking.java
new file mode 100644 (file)
index 0000000..99542db
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * 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.issue.tracking;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import org.sonar.api.BatchComponent;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.rule.ActiveRule;
+import org.sonar.api.batch.rule.ActiveRules;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.issue.internal.IssueChangeContext;
+import org.sonar.api.resources.File;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.ResourceUtils;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.batch.index.BatchResource;
+import org.sonar.batch.index.ResourceCache;
+import org.sonar.batch.issue.IssueCache;
+import org.sonar.batch.scan.LastLineHashes;
+import org.sonar.batch.scan.filesystem.InputPathCache;
+import org.sonar.core.issue.IssueUpdater;
+import org.sonar.core.issue.workflow.IssueWorkflow;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class LocalIssueTracking implements BatchComponent {
+
+  private final IssueCache issueCache;
+  private final IssueTracking tracking;
+  private final LastLineHashes lastLineHashes;
+  private final IssueWorkflow workflow;
+  private final IssueUpdater updater;
+  private final IssueChangeContext changeContext;
+  private final ActiveRules activeRules;
+  private final InputPathCache inputPathCache;
+  private final ResourceCache resourceCache;
+  private final PreviousIssueRepository previousIssueCache;
+
+  public LocalIssueTracking(ResourceCache resourceCache, IssueCache issueCache, IssueTracking tracking,
+    LastLineHashes lastLineHashes, IssueWorkflow workflow, IssueUpdater updater,
+    ActiveRules activeRules, InputPathCache inputPathCache, PreviousIssueRepository previousIssueCache) {
+    this.resourceCache = resourceCache;
+    this.issueCache = issueCache;
+    this.tracking = tracking;
+    this.lastLineHashes = lastLineHashes;
+    this.workflow = workflow;
+    this.updater = updater;
+    this.inputPathCache = inputPathCache;
+    this.previousIssueCache = previousIssueCache;
+    this.changeContext = IssueChangeContext.createScan(((Project) resourceCache.getRoot().resource()).getAnalysisDate());
+    this.activeRules = activeRules;
+  }
+
+  public void execute() {
+    previousIssueCache.load();
+
+    for (BatchResource component : resourceCache.all()) {
+      trackIssues(component);
+    }
+  }
+
+  public void trackIssues(BatchResource component) {
+
+    Collection<DefaultIssue> issues = Lists.newArrayList();
+    for (Issue issue : issueCache.byComponent(component.resource().getEffectiveKey())) {
+      issues.add((DefaultIssue) issue);
+    }
+    issueCache.clear(component.resource().getEffectiveKey());
+    // issues = all the issues created by rule engines during this module scan and not excluded by filters
+
+    // all the issues that are not closed in db before starting this module scan, including manual issues
+    Collection<PreviousIssue> previousIssues = new ArrayList<>();
+    for (org.sonar.batch.protocol.input.issues.PreviousIssue previousIssue : previousIssueCache.byComponent(component)) {
+      previousIssues.add(new PreviousIssueFromWs(previousIssue));
+    }
+
+    SourceHashHolder sourceHashHolder = null;
+    if (ResourceUtils.isFile(component.resource())) {
+      File sonarFile = (File) component.resource();
+      InputFile file = inputPathCache.getFile(component.parent().parent().resource().getEffectiveKey(), sonarFile.getPath());
+      if (file == null) {
+        throw new IllegalStateException("Resource " + component.resource() + " was not found in InputPath cache");
+      }
+      sourceHashHolder = new SourceHashHolder((DefaultInputFile) file, lastLineHashes);
+    }
+
+    IssueTrackingResult trackingResult = tracking.track(sourceHashHolder, previousIssues, issues);
+
+    // unmatched = issues that have been resolved + issues on disabled/removed rules + manual issues
+    addUnmatched(trackingResult.unmatched(), sourceHashHolder, issues);
+
+    mergeMatched(trackingResult);
+
+    if (ResourceUtils.isRootProject(component.resource())) {
+      // issues that relate to deleted components
+      addIssuesOnDeletedComponents(issues);
+    }
+
+    for (DefaultIssue issue : issues) {
+      workflow.doAutomaticTransition(issue, changeContext);
+      issueCache.put(issue);
+    }
+  }
+
+  @VisibleForTesting
+  protected void mergeMatched(IssueTrackingResult result) {
+    for (DefaultIssue issue : result.matched()) {
+      org.sonar.batch.protocol.input.issues.PreviousIssue ref = ((PreviousIssueFromWs) result.matching(issue)).getDto();
+
+      // invariant fields
+      issue.setKey(ref.key());
+
+      // non-persisted fields
+      issue.setNew(false);
+      issue.setEndOfLife(false);
+      issue.setOnDisabledRule(false);
+
+      // fields to update with old values
+      issue.setResolution(ref.resolution());
+      issue.setStatus(ref.status());
+      issue.setAssignee(ref.assigneeLogin());
+
+      String overriddenSeverity = ref.overriddenSeverity();
+      if (overriddenSeverity != null) {
+        // Severity overriden by user
+        issue.setSeverity(overriddenSeverity);
+      }
+    }
+  }
+
+  private void addUnmatched(Collection<PreviousIssue> unmatchedIssues, SourceHashHolder sourceHashHolder, Collection<DefaultIssue> issues) {
+    for (PreviousIssue unmatchedIssue : unmatchedIssues) {
+      org.sonar.batch.protocol.input.issues.PreviousIssue unmatchedPreviousIssue = ((PreviousIssueFromWs) unmatchedIssue).getDto();
+      ActiveRule activeRule = activeRules.find(unmatchedIssue.ruleKey());
+      DefaultIssue unmatched = toUnmatchedIssue(unmatchedPreviousIssue);
+      if (activeRule != null && !Issue.STATUS_CLOSED.equals(unmatchedPreviousIssue.status())) {
+        relocateManualIssue(unmatched, unmatchedIssue, sourceHashHolder);
+      }
+      updateUnmatchedIssue(unmatched, false /* manual issues can be kept open */);
+      issues.add(unmatched);
+    }
+  }
+
+  private void addIssuesOnDeletedComponents(Collection<DefaultIssue> issues) {
+    for (org.sonar.batch.protocol.input.issues.PreviousIssue previous : previousIssueCache.issuesOnMissingComponents()) {
+      DefaultIssue dead = toUnmatchedIssue(previous);
+      updateUnmatchedIssue(dead, true);
+      issues.add(dead);
+    }
+  }
+
+  private DefaultIssue toUnmatchedIssue(org.sonar.batch.protocol.input.issues.PreviousIssue previous) {
+    DefaultIssue issue = new DefaultIssue();
+    issue.setKey(previous.key());
+    issue.setStatus(previous.status());
+    issue.setResolution(previous.resolution());
+    issue.setMessage(previous.message());
+    issue.setLine(previous.line());
+    String overriddenSeverity = previous.overriddenSeverity();
+    if (overriddenSeverity != null) {
+      issue.setSeverity(overriddenSeverity);
+    } else {
+      ActiveRule activeRule = activeRules.find(RuleKey.of(previous.ruleRepo(), previous.ruleKey()));
+      if (activeRule != null) {
+        // FIXME if rule was removed we can't guess what was the severity of the issue
+        issue.setSeverity(activeRule.severity());
+      }
+    }
+    issue.setAssignee(previous.assigneeLogin());
+    issue.setComponentKey(previous.componentKey());
+    issue.setManualSeverity(overriddenSeverity != null);
+    issue.setRuleKey(RuleKey.of(previous.ruleRepo(), previous.ruleKey()));
+    issue.setNew(false);
+    return issue;
+  }
+
+  private void updateUnmatchedIssue(DefaultIssue issue, boolean forceEndOfLife) {
+    ActiveRule activeRule = activeRules.find(issue.ruleKey());
+    boolean isRemovedRule = activeRule == null;
+    issue.setEndOfLife(forceEndOfLife || isRemovedRule);
+    issue.setOnDisabledRule(isRemovedRule);
+  }
+
+  private void relocateManualIssue(DefaultIssue newIssue, PreviousIssue oldIssue, SourceHashHolder sourceHashHolder) {
+    Integer previousLine = oldIssue.line();
+    if (previousLine == null) {
+      return;
+    }
+
+    Collection<Integer> newLinesWithSameHash = sourceHashHolder.getNewLinesMatching(previousLine);
+    if (newLinesWithSameHash.isEmpty()) {
+      if (previousLine > sourceHashHolder.getHashedSource().length()) {
+        newIssue.setLine(null);
+        updater.setStatus(newIssue, Issue.STATUS_CLOSED, changeContext);
+        updater.setResolution(newIssue, Issue.RESOLUTION_REMOVED, changeContext);
+        updater.setPastLine(newIssue, previousLine);
+        updater.setPastMessage(newIssue, oldIssue.message(), changeContext);
+      }
+    } else if (newLinesWithSameHash.size() == 1) {
+      Integer newLine = newLinesWithSameHash.iterator().next();
+      newIssue.setLine(newLine);
+      updater.setPastLine(newIssue, previousLine);
+      updater.setPastMessage(newIssue, oldIssue.message(), changeContext);
+    }
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/PreviousIssue.java b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/PreviousIssue.java
new file mode 100644 (file)
index 0000000..dcef8ca
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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.issue.tracking;
+
+import org.sonar.api.rule.RuleKey;
+
+import javax.annotation.CheckForNull;
+
+public interface PreviousIssue {
+
+  String key();
+
+  RuleKey ruleKey();
+
+  /**
+   * Null for issue with no line
+   */
+  @CheckForNull
+  String checksum();
+
+  /**
+   * Global issues have no line
+   */
+  @CheckForNull
+  Integer line();
+
+  @CheckForNull
+  String message();
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/PreviousIssueFromDb.java b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/PreviousIssueFromDb.java
new file mode 100644 (file)
index 0000000..f20f420
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.batch.issue.tracking;
+
+import org.sonar.api.rule.RuleKey;
+import org.sonar.core.issue.db.IssueDto;
+
+public class PreviousIssueFromDb implements PreviousIssue {
+
+  private IssueDto dto;
+
+  public PreviousIssueFromDb(IssueDto dto) {
+    this.dto = dto;
+  }
+
+  public IssueDto getDto() {
+    return dto;
+  }
+
+  @Override
+  public String key() {
+    return dto.getKee();
+  }
+
+  @Override
+  public RuleKey ruleKey() {
+    return dto.getRuleKey();
+  }
+
+  @Override
+  public String checksum() {
+    return dto.getChecksum();
+  }
+
+  @Override
+  public Integer line() {
+    return dto.getLine();
+  }
+
+  @Override
+  public String message() {
+    return dto.getMessage();
+  }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/PreviousIssueFromWs.java b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/PreviousIssueFromWs.java
new file mode 100644 (file)
index 0000000..a9a435a
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * 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.issue.tracking;
+
+import org.sonar.api.rule.RuleKey;
+
+public class PreviousIssueFromWs implements PreviousIssue {
+
+  private org.sonar.batch.protocol.input.issues.PreviousIssue dto;
+
+  public PreviousIssueFromWs(org.sonar.batch.protocol.input.issues.PreviousIssue dto) {
+    this.dto = dto;
+  }
+
+  public org.sonar.batch.protocol.input.issues.PreviousIssue getDto() {
+    return dto;
+  }
+
+  @Override
+  public String key() {
+    return dto.key();
+  }
+
+  @Override
+  public RuleKey ruleKey() {
+    return RuleKey.of(dto.ruleRepo(), dto.ruleKey());
+  }
+
+  @Override
+  public String checksum() {
+    return dto.checksum();
+  }
+
+  @Override
+  public Integer line() {
+    return dto.line();
+  }
+
+  @Override
+  public String message() {
+    return dto.message();
+  }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/PreviousIssueRepository.java b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/PreviousIssueRepository.java
new file mode 100644 (file)
index 0000000..6bad06c
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * 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.issue.tracking;
+
+import com.google.common.base.Function;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.BatchComponent;
+import org.sonar.api.batch.InstantiationStrategy;
+import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.api.utils.TimeProfiler;
+import org.sonar.batch.index.BatchResource;
+import org.sonar.batch.index.Cache;
+import org.sonar.batch.index.Caches;
+import org.sonar.batch.index.ResourceCache;
+import org.sonar.batch.protocol.input.issues.PreviousIssue;
+import org.sonar.batch.repository.PreviousIssuesLoader;
+
+@InstantiationStrategy(InstantiationStrategy.PER_BATCH)
+public class PreviousIssueRepository implements BatchComponent {
+
+  private static final Logger LOG = LoggerFactory.getLogger(PreviousIssueRepository.class);
+
+  private final Caches caches;
+  private Cache<PreviousIssue> issuesCache;
+  private final PreviousIssuesLoader previousIssuesLoader;
+  private final ProjectReactor reactor;
+  private final ResourceCache resourceCache;
+
+  public PreviousIssueRepository(Caches caches, PreviousIssuesLoader previousIssuesLoader, ProjectReactor reactor, ResourceCache resourceCache) {
+    this.caches = caches;
+    this.previousIssuesLoader = previousIssuesLoader;
+    this.reactor = reactor;
+    this.resourceCache = resourceCache;
+  }
+
+  public void load() {
+    TimeProfiler profiler = new TimeProfiler(LOG).start("Load previous issues");
+    try {
+      this.issuesCache = caches.createCache("previousIssues");
+      previousIssuesLoader.load(reactor, new Function<PreviousIssue, Void>() {
+
+        @Override
+        public Void apply(PreviousIssue issue) {
+          String componentKey = issue.componentKey();
+          BatchResource r = resourceCache.get(componentKey);
+          if (r == null) {
+            // Deleted resource
+            issuesCache.put(0, issue.key(), issue);
+          }
+          issuesCache.put(r.batchId(), issue.key(), issue);
+          return null;
+        }
+      });
+    } finally {
+      profiler.stop();
+    }
+  }
+
+  public Iterable<PreviousIssue> byComponent(BatchResource component) {
+    return issuesCache.values(component.batchId());
+  }
+
+  public Iterable<PreviousIssue> issuesOnMissingComponents() {
+    return issuesCache.values(0);
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/RollingFileHashes.java b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/RollingFileHashes.java
new file mode 100644 (file)
index 0000000..84beecd
--- /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.batch.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/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/SourceHashHolder.java b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/SourceHashHolder.java
new file mode 100644 (file)
index 0000000..38f0af7
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * 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.issue.tracking;
+
+import com.google.common.collect.ImmutableSet;
+import org.sonar.api.batch.fs.InputFile.Status;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.batch.scan.LastLineHashes;
+
+import javax.annotation.CheckForNull;
+
+import java.util.Collection;
+
+public class SourceHashHolder {
+
+  private final LastLineHashes lastSnapshots;
+
+  private FileHashes hashedReference;
+  private FileHashes hashedSource;
+  private DefaultInputFile inputFile;
+
+  public SourceHashHolder(DefaultInputFile inputFile, LastLineHashes lastSnapshots) {
+    this.inputFile = inputFile;
+    this.lastSnapshots = lastSnapshots;
+  }
+
+  private void initHashes() {
+    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;
+      }
+    }
+  }
+
+  @CheckForNull
+  public FileHashes getHashedReference() {
+    initHashes();
+    return hashedReference;
+  }
+
+  public FileHashes getHashedSource() {
+    initHashes();
+    return hashedSource;
+  }
+
+  public Collection<Integer> getNewLinesMatching(Integer originLine) {
+    FileHashes reference = getHashedReference();
+    if (reference == null) {
+      return ImmutableSet.of();
+    } else {
+      return getHashedSource().getLinesForHash(reference.getHash(originLine));
+    }
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/package-info.java b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/package-info.java
new file mode 100644 (file)
index 0000000..8df05a1
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.batch.issue.tracking;
+
+import javax.annotation.ParametersAreNonnullByDefault;
index 1afdd61be6d7c5b9a85dafcea0362df6120e2b4b..27bbc64ef0afa8819c3eca58d68cc6321ae2a4fc 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.batch.mediumtest;
 
+import com.google.common.base.Function;
 import org.apache.commons.io.Charsets;
 import org.sonar.api.SonarPlugin;
 import org.sonar.api.batch.bootstrap.ProjectReactor;
@@ -32,9 +33,11 @@ import org.sonar.batch.bootstrapper.Batch;
 import org.sonar.batch.bootstrapper.EnvironmentInformation;
 import org.sonar.batch.protocol.input.ActiveRule;
 import org.sonar.batch.protocol.input.GlobalReferentials;
-import org.sonar.batch.protocol.input.ProjectReferentials;
-import org.sonar.batch.referential.GlobalReferentialsLoader;
-import org.sonar.batch.referential.ProjectReferentialsLoader;
+import org.sonar.batch.protocol.input.ProjectRepository;
+import org.sonar.batch.protocol.input.issues.PreviousIssue;
+import org.sonar.batch.repository.GlobalReferentialsLoader;
+import org.sonar.batch.repository.PreviousIssuesLoader;
+import org.sonar.batch.repository.ProjectRepositoriesLoader;
 import org.sonar.core.plugins.DefaultPluginMetadata;
 import org.sonar.core.plugins.RemotePlugin;
 
@@ -65,6 +68,7 @@ public class BatchMediumTester {
     private final FakeGlobalReferentialsLoader globalRefProvider = new FakeGlobalReferentialsLoader();
     private final FakeProjectReferentialsLoader projectRefProvider = new FakeProjectReferentialsLoader();
     private final FakePluginsReferential pluginsReferential = new FakePluginsReferential();
+    private final FakePreviousIssuesLoader previousIssues = new FakePreviousIssuesLoader();
     private final Map<String, String> bootstrapProperties = new HashMap<String, String>();
 
     public BatchMediumTester build() {
@@ -114,6 +118,11 @@ public class BatchMediumTester {
       return this;
     }
 
+    public BatchMediumTesterBuilder addPreviousIssue(PreviousIssue issue) {
+      previousIssues.getPreviousIssues().add(issue);
+      return this;
+    }
+
   }
 
   public void start() {
@@ -132,6 +141,7 @@ public class BatchMediumTester {
         builder.pluginsReferential,
         builder.globalRefProvider,
         builder.projectRefProvider,
+        builder.previousIssues,
         new DefaultDebtModel())
       .setBootstrapProperties(builder.bootstrapProperties)
       .build();
@@ -217,12 +227,12 @@ public class BatchMediumTester {
     }
   }
 
-  private static class FakeProjectReferentialsLoader implements ProjectReferentialsLoader {
+  private static class FakeProjectReferentialsLoader implements ProjectRepositoriesLoader {
 
-    private ProjectReferentials ref = new ProjectReferentials();
+    private ProjectRepository ref = new ProjectRepository();
 
     @Override
-    public ProjectReferentials load(ProjectReactor reactor, TaskProperties taskProperties) {
+    public ProjectRepository load(ProjectReactor reactor, TaskProperties taskProperties) {
       return ref;
     }
 
@@ -272,4 +282,22 @@ public class BatchMediumTester {
 
   }
 
+  private static class FakePreviousIssuesLoader implements PreviousIssuesLoader {
+
+    List<PreviousIssue> previousIssues = new ArrayList<>();
+
+    public List<PreviousIssue> getPreviousIssues() {
+      return previousIssues;
+    }
+
+    @Override
+    public void load(ProjectReactor reactor, Function<PreviousIssue, Void> consumer) {
+      for (PreviousIssue previousIssue : previousIssues) {
+        consumer.apply(previousIssue);
+      }
+
+    }
+
+  }
+
 }
index de4bd65a3b4e5e667717e56066c7e339eb818fc9..80c2fe8c078dbdf125556e40af88271db729f97e 100644 (file)
@@ -25,6 +25,7 @@ import org.sonar.batch.events.BatchStepEvent;
 import org.sonar.batch.events.EventBus;
 import org.sonar.batch.index.DefaultIndex;
 import org.sonar.batch.issue.ignore.scanner.IssueExclusionsLoader;
+import org.sonar.batch.issue.tracking.LocalIssueTracking;
 import org.sonar.batch.rule.QProfileVerifier;
 import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem;
 import org.sonar.batch.scan.filesystem.FileSystemLogger;
@@ -46,13 +47,14 @@ public final class PreviewPhaseExecutor implements PhaseExecutor {
   private final QProfileVerifier profileVerifier;
   private final IssueExclusionsLoader issueExclusionsLoader;
   private final IssuesReports issuesReport;
+  private final LocalIssueTracking localIssueTracking;
 
   public PreviewPhaseExecutor(Phases phases,
     MavenPluginsConfigurator mavenPluginsConfigurator, InitializersExecutor initializersExecutor,
     SensorsExecutor sensorsExecutor,
     SensorContext sensorContext, DefaultIndex index,
     EventBus eventBus, ProjectInitializer pi, FileSystemLogger fsLogger, IssuesReports jsonReport, DefaultModuleFileSystem fs, QProfileVerifier profileVerifier,
-    IssueExclusionsLoader issueExclusionsLoader) {
+    IssueExclusionsLoader issueExclusionsLoader, LocalIssueTracking localIssueTracking) {
     this.phases = phases;
     this.mavenPluginsConfigurator = mavenPluginsConfigurator;
     this.initializersExecutor = initializersExecutor;
@@ -66,6 +68,7 @@ public final class PreviewPhaseExecutor implements PhaseExecutor {
     this.fs = fs;
     this.profileVerifier = profileVerifier;
     this.issueExclusionsLoader = issueExclusionsLoader;
+    this.localIssueTracking = localIssueTracking;
   }
 
   /**
@@ -95,6 +98,9 @@ public final class PreviewPhaseExecutor implements PhaseExecutor {
     }
 
     if (module.isRoot()) {
+
+      localIssueTracking();
+
       issuesReport();
     }
 
@@ -102,6 +108,13 @@ public final class PreviewPhaseExecutor implements PhaseExecutor {
     eventBus.fireEvent(new ProjectAnalysisEvent(module, false));
   }
 
+  private void localIssueTracking() {
+    String stepName = "Local Issue Tracking";
+    eventBus.fireEvent(new BatchStepEvent(stepName, true));
+    localIssueTracking.execute();
+    eventBus.fireEvent(new BatchStepEvent(stepName, false));
+  }
+
   private void issuesReport() {
     String stepName = "Issues Reports";
     eventBus.fireEvent(new BatchStepEvent(stepName, true));
diff --git a/sonar-batch/src/main/java/org/sonar/batch/referential/DefaultGlobalReferentialsLoader.java b/sonar-batch/src/main/java/org/sonar/batch/referential/DefaultGlobalReferentialsLoader.java
deleted file mode 100644 (file)
index 17fd682..0000000
+++ /dev/null
@@ -1,40 +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.referential;
-
-import org.sonar.batch.bootstrap.ServerClient;
-import org.sonar.batch.protocol.input.GlobalReferentials;
-
-public class DefaultGlobalReferentialsLoader implements GlobalReferentialsLoader {
-
-  private static final String BATCH_GLOBAL_URL = "/batch/global";
-
-  private final ServerClient serverClient;
-
-  public DefaultGlobalReferentialsLoader(ServerClient serverClient) {
-    this.serverClient = serverClient;
-  }
-
-  @Override
-  public GlobalReferentials load() {
-    return GlobalReferentials.fromJson(serverClient.request(BATCH_GLOBAL_URL));
-  }
-
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/referential/DefaultProjectReferentialsLoader.java b/sonar-batch/src/main/java/org/sonar/batch/referential/DefaultProjectReferentialsLoader.java
deleted file mode 100644 (file)
index 90a10bc..0000000
+++ /dev/null
@@ -1,177 +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.referential;
-
-import com.google.common.collect.Maps;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.batch.bootstrap.ProjectDefinition;
-import org.sonar.api.batch.bootstrap.ProjectReactor;
-import org.sonar.api.database.DatabaseSession;
-import org.sonar.api.database.model.MeasureModel;
-import org.sonar.api.database.model.ResourceModel;
-import org.sonar.api.database.model.Snapshot;
-import org.sonar.api.measures.CoreMetrics;
-import org.sonar.api.measures.Metric;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.batch.bootstrap.AnalysisMode;
-import org.sonar.batch.bootstrap.ServerClient;
-import org.sonar.batch.bootstrap.TaskProperties;
-import org.sonar.batch.protocol.input.FileData;
-import org.sonar.batch.protocol.input.ProjectReferentials;
-import org.sonar.batch.rule.ModuleQProfiles;
-
-import javax.annotation.CheckForNull;
-import javax.persistence.NoResultException;
-import javax.persistence.Query;
-
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-public class DefaultProjectReferentialsLoader implements ProjectReferentialsLoader {
-
-  private static final Logger LOG = LoggerFactory.getLogger(DefaultProjectReferentialsLoader.class);
-
-  private static final String BATCH_PROJECT_URL = "/batch/project";
-
-  private final ServerClient serverClient;
-  private final AnalysisMode analysisMode;
-  private final DatabaseSession session;
-
-  public DefaultProjectReferentialsLoader(DatabaseSession session, ServerClient serverClient, AnalysisMode analysisMode) {
-    this.session = session;
-    this.serverClient = serverClient;
-    this.analysisMode = analysisMode;
-  }
-
-  public DefaultProjectReferentialsLoader(ServerClient serverClient, AnalysisMode analysisMode) {
-    this.session = null;
-    this.serverClient = serverClient;
-    this.analysisMode = analysisMode;
-  }
-
-  @Override
-  public ProjectReferentials load(ProjectReactor reactor, TaskProperties taskProperties) {
-    String projectKey = reactor.getRoot().getKeyWithBranch();
-    String url = BATCH_PROJECT_URL + "?key=" + ServerClient.encodeForUrl(projectKey);
-    if (taskProperties.properties().containsKey(ModuleQProfiles.SONAR_PROFILE_PROP)) {
-      LOG.warn("Ability to set quality profile from command line using '" + ModuleQProfiles.SONAR_PROFILE_PROP
-        + "' is deprecated and will be dropped in a future SonarQube version. Please configure quality profile used by your project on SonarQube server.");
-      url += "&profile=" + ServerClient.encodeForUrl(taskProperties.properties().get(ModuleQProfiles.SONAR_PROFILE_PROP));
-    }
-    url += "&preview=" + analysisMode.isPreview();
-    ProjectReferentials ref = ProjectReferentials.fromJson(serverClient.request(url));
-
-    if (session != null) {
-      for (ProjectDefinition module : reactor.getProjects()) {
-
-        for (Entry<String, FileData> fileDataByPaths : ref.fileDataByPath(module.getKeyWithBranch()).entrySet()) {
-          String path = fileDataByPaths.getKey();
-          FileData fileData = fileDataByPaths.getValue();
-          String lastCommits = null;
-          String revisions = null;
-          String authors = null;
-          List<Object[]> measuresByKey = query(projectKey + ":" + path, CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE_KEY, CoreMetrics.SCM_REVISIONS_BY_LINE_KEY,
-            CoreMetrics.SCM_AUTHORS_BY_LINE_KEY);
-          for (Object[] measureByKey : measuresByKey) {
-            if (measureByKey[0].equals(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE_KEY)) {
-              lastCommits = ((MeasureModel) measureByKey[1]).getData(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE);
-            } else if (measureByKey[0].equals(CoreMetrics.SCM_REVISIONS_BY_LINE_KEY)) {
-              revisions = ((MeasureModel) measureByKey[1]).getData(CoreMetrics.SCM_REVISIONS_BY_LINE);
-            } else if (measureByKey[0].equals(CoreMetrics.SCM_AUTHORS_BY_LINE_KEY)) {
-              authors = ((MeasureModel) measureByKey[1]).getData(CoreMetrics.SCM_AUTHORS_BY_LINE);
-            }
-          }
-          ref.addFileData(module.getKeyWithBranch(), path, new FileData(fileData.hash(), authors == null, lastCommits, revisions, authors));
-        }
-      }
-      ref.setLastAnalysisDate(lastSnapshotCreationDate(projectKey));
-    }
-    return ref;
-  }
-
-  public List<Object[]> query(String resourceKey, String... metricKeys) {
-    StringBuilder sb = new StringBuilder();
-    Map<String, Object> params = Maps.newHashMap();
-
-    sb.append("SELECT met.key, m");
-    sb.append(" FROM ")
-      .append(MeasureModel.class.getSimpleName())
-      .append(" m, ")
-      .append(Metric.class.getSimpleName())
-      .append(" met, ")
-      .append(ResourceModel.class.getSimpleName())
-      .append(" r, ")
-      .append(Snapshot.class.getSimpleName())
-      .append(" s WHERE met.id=m.metricId AND m.snapshotId=s.id AND s.resourceId=r.id AND r.key=:kee AND s.status=:status AND s.qualifier<>:lib");
-    params.put("kee", resourceKey);
-    params.put("status", Snapshot.STATUS_PROCESSED);
-    params.put("lib", Qualifiers.LIBRARY);
-
-    sb.append(" AND m.characteristicId IS NULL");
-    sb.append(" AND m.personId IS NULL");
-    sb.append(" AND m.ruleId IS NULL AND m.rulePriority IS NULL");
-    if (metricKeys.length > 0) {
-      sb.append(" AND met.key IN (:metricKeys) ");
-      params.put("metricKeys", Arrays.asList(metricKeys));
-    }
-    sb.append(" AND s.last=true ");
-    sb.append(" ORDER BY s.createdAt ");
-
-    Query jpaQuery = session.createQuery(sb.toString());
-
-    for (Map.Entry<String, Object> entry : params.entrySet()) {
-      jpaQuery.setParameter(entry.getKey(), entry.getValue());
-    }
-    return jpaQuery.getResultList();
-  }
-
-  @CheckForNull
-  Date lastSnapshotCreationDate(String resourceKey) {
-    StringBuilder sb = new StringBuilder();
-    Map<String, Object> params = Maps.newHashMap();
-
-    sb.append("SELECT s.buildDate");
-    sb.append(" FROM ")
-      .append(ResourceModel.class.getSimpleName())
-      .append(" r, ")
-      .append(Snapshot.class.getSimpleName())
-      .append(" s WHERE s.resourceId=r.id AND r.key=:kee AND s.status=:status AND s.qualifier<>:lib");
-    params.put("kee", resourceKey);
-    params.put("status", Snapshot.STATUS_PROCESSED);
-    params.put("lib", Qualifiers.LIBRARY);
-
-    sb.append(" AND s.last=true ");
-
-    Query jpaQuery = session.createQuery(sb.toString());
-
-    for (Map.Entry<String, Object> entry : params.entrySet()) {
-      jpaQuery.setParameter(entry.getKey(), entry.getValue());
-    }
-    try {
-      return (Date) jpaQuery.getSingleResult();
-    } catch (NoResultException e) {
-      return null;
-    }
-  }
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/referential/GlobalReferentialsLoader.java b/sonar-batch/src/main/java/org/sonar/batch/referential/GlobalReferentialsLoader.java
deleted file mode 100644 (file)
index d355867..0000000
+++ /dev/null
@@ -1,28 +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.referential;
-
-import org.sonar.batch.protocol.input.GlobalReferentials;
-
-public interface GlobalReferentialsLoader {
-
-  GlobalReferentials load();
-
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/referential/GlobalReferentialsProvider.java b/sonar-batch/src/main/java/org/sonar/batch/referential/GlobalReferentialsProvider.java
deleted file mode 100644 (file)
index 5e10e6c..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.batch.referential;
-
-import org.picocontainer.injectors.ProviderAdapter;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.utils.TimeProfiler;
-import org.sonar.batch.protocol.input.GlobalReferentials;
-
-public class GlobalReferentialsProvider extends ProviderAdapter {
-
-  private static final Logger LOG = LoggerFactory.getLogger(GlobalReferentialsProvider.class);
-
-  private GlobalReferentials globalReferentials;
-
-  public GlobalReferentials provide(GlobalReferentialsLoader loader) {
-    if (globalReferentials == null) {
-      TimeProfiler profiler = new TimeProfiler(LOG).start("Load global referentials");
-      try {
-        globalReferentials = loader.load();
-      } finally {
-        profiler.stop();
-      }
-    }
-    return globalReferentials;
-  }
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/referential/ProjectReferentialsLoader.java b/sonar-batch/src/main/java/org/sonar/batch/referential/ProjectReferentialsLoader.java
deleted file mode 100644 (file)
index e4bad9f..0000000
+++ /dev/null
@@ -1,30 +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.referential;
-
-import org.sonar.api.batch.bootstrap.ProjectReactor;
-import org.sonar.batch.bootstrap.TaskProperties;
-import org.sonar.batch.protocol.input.ProjectReferentials;
-
-public interface ProjectReferentialsLoader {
-
-  ProjectReferentials load(ProjectReactor reactor, TaskProperties taskProperties);
-
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/referential/ProjectReferentialsProvider.java b/sonar-batch/src/main/java/org/sonar/batch/referential/ProjectReferentialsProvider.java
deleted file mode 100644 (file)
index 03486bf..0000000
+++ /dev/null
@@ -1,47 +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.referential;
-
-import org.picocontainer.injectors.ProviderAdapter;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.batch.bootstrap.ProjectReactor;
-import org.sonar.api.utils.TimeProfiler;
-import org.sonar.batch.bootstrap.TaskProperties;
-import org.sonar.batch.protocol.input.ProjectReferentials;
-
-public class ProjectReferentialsProvider extends ProviderAdapter {
-
-  private static final Logger LOG = LoggerFactory.getLogger(ProjectReferentialsProvider.class);
-
-  private ProjectReferentials projectReferentials;
-
-  public ProjectReferentials provide(ProjectReferentialsLoader loader, ProjectReactor reactor, TaskProperties taskProps) {
-    if (projectReferentials == null) {
-      TimeProfiler profiler = new TimeProfiler(LOG).start("Load project referentials");
-      try {
-        projectReferentials = loader.load(reactor, taskProps);
-      } finally {
-        profiler.stop();
-      }
-    }
-    return projectReferentials;
-  }
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/referential/package-info.java b/sonar-batch/src/main/java/org/sonar/batch/referential/package-info.java
deleted file mode 100644 (file)
index 1a50ff5..0000000
+++ /dev/null
@@ -1,23 +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.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.batch.referential;
-
-import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultGlobalReferentialsLoader.java b/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultGlobalReferentialsLoader.java
new file mode 100644 (file)
index 0000000..91012f9
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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.repository;
+
+import org.sonar.batch.bootstrap.ServerClient;
+import org.sonar.batch.protocol.input.GlobalReferentials;
+
+public class DefaultGlobalReferentialsLoader implements GlobalReferentialsLoader {
+
+  private static final String BATCH_GLOBAL_URL = "/batch/global";
+
+  private final ServerClient serverClient;
+
+  public DefaultGlobalReferentialsLoader(ServerClient serverClient) {
+    this.serverClient = serverClient;
+  }
+
+  @Override
+  public GlobalReferentials load() {
+    return GlobalReferentials.fromJson(serverClient.request(BATCH_GLOBAL_URL));
+  }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultPreviousIssuesLoader.java b/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultPreviousIssuesLoader.java
new file mode 100644 (file)
index 0000000..d2831e4
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.batch.repository;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Function;
+import com.google.common.io.InputSupplier;
+import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.batch.bootstrap.ServerClient;
+import org.sonar.batch.protocol.input.issues.PreviousIssue;
+import org.sonar.batch.protocol.input.issues.PreviousIssueHelper;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+public class DefaultPreviousIssuesLoader implements PreviousIssuesLoader {
+
+  private final ServerClient serverClient;
+
+  public DefaultPreviousIssuesLoader(ServerClient serverClient) {
+    this.serverClient = serverClient;
+  }
+
+  @Override
+  public void load(ProjectReactor reactor, Function<PreviousIssue, Void> consumer) {
+    InputSupplier<InputStream> request = serverClient.doRequest("/batch/issues?key=" + ServerClient.encodeForUrl(reactor.getRoot().getKeyWithBranch()), "GET", null);
+    try (InputStream is = request.getInput(); Reader reader = new InputStreamReader(is, Charsets.UTF_8)) {
+      for (PreviousIssue issue : PreviousIssueHelper.getIssues(reader)) {
+        consumer.apply(issue);
+      }
+    } catch (IOException e) {
+      throw new IllegalStateException("Unable to get previous issues", e);
+    }
+  }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultProjectReferentialsLoader.java b/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultProjectReferentialsLoader.java
new file mode 100644 (file)
index 0000000..de45aea
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * 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.repository;
+
+import com.google.common.collect.Maps;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.bootstrap.ProjectDefinition;
+import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.database.model.MeasureModel;
+import org.sonar.api.database.model.ResourceModel;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.batch.bootstrap.AnalysisMode;
+import org.sonar.batch.bootstrap.ServerClient;
+import org.sonar.batch.bootstrap.TaskProperties;
+import org.sonar.batch.protocol.input.FileData;
+import org.sonar.batch.protocol.input.ProjectRepository;
+import org.sonar.batch.rule.ModuleQProfiles;
+
+import javax.annotation.CheckForNull;
+import javax.persistence.NoResultException;
+import javax.persistence.Query;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public class DefaultProjectReferentialsLoader implements ProjectRepositoriesLoader {
+
+  private static final Logger LOG = LoggerFactory.getLogger(DefaultProjectReferentialsLoader.class);
+
+  private static final String BATCH_PROJECT_URL = "/batch/project";
+
+  private final ServerClient serverClient;
+  private final AnalysisMode analysisMode;
+  private final DatabaseSession session;
+
+  public DefaultProjectReferentialsLoader(DatabaseSession session, ServerClient serverClient, AnalysisMode analysisMode) {
+    this.session = session;
+    this.serverClient = serverClient;
+    this.analysisMode = analysisMode;
+  }
+
+  public DefaultProjectReferentialsLoader(ServerClient serverClient, AnalysisMode analysisMode) {
+    this.session = null;
+    this.serverClient = serverClient;
+    this.analysisMode = analysisMode;
+  }
+
+  @Override
+  public ProjectRepository load(ProjectReactor reactor, TaskProperties taskProperties) {
+    String projectKey = reactor.getRoot().getKeyWithBranch();
+    String url = BATCH_PROJECT_URL + "?key=" + ServerClient.encodeForUrl(projectKey);
+    if (taskProperties.properties().containsKey(ModuleQProfiles.SONAR_PROFILE_PROP)) {
+      LOG.warn("Ability to set quality profile from command line using '" + ModuleQProfiles.SONAR_PROFILE_PROP
+        + "' is deprecated and will be dropped in a future SonarQube version. Please configure quality profile used by your project on SonarQube server.");
+      url += "&profile=" + ServerClient.encodeForUrl(taskProperties.properties().get(ModuleQProfiles.SONAR_PROFILE_PROP));
+    }
+    url += "&preview=" + analysisMode.isPreview();
+    ProjectRepository ref = ProjectRepository.fromJson(serverClient.request(url));
+
+    if (session != null) {
+      for (ProjectDefinition module : reactor.getProjects()) {
+
+        for (Entry<String, FileData> fileDataByPaths : ref.fileDataByPath(module.getKeyWithBranch()).entrySet()) {
+          String path = fileDataByPaths.getKey();
+          FileData fileData = fileDataByPaths.getValue();
+          String lastCommits = null;
+          String revisions = null;
+          String authors = null;
+          List<Object[]> measuresByKey = query(projectKey + ":" + path, CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE_KEY, CoreMetrics.SCM_REVISIONS_BY_LINE_KEY,
+            CoreMetrics.SCM_AUTHORS_BY_LINE_KEY);
+          for (Object[] measureByKey : measuresByKey) {
+            if (measureByKey[0].equals(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE_KEY)) {
+              lastCommits = ((MeasureModel) measureByKey[1]).getData(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE);
+            } else if (measureByKey[0].equals(CoreMetrics.SCM_REVISIONS_BY_LINE_KEY)) {
+              revisions = ((MeasureModel) measureByKey[1]).getData(CoreMetrics.SCM_REVISIONS_BY_LINE);
+            } else if (measureByKey[0].equals(CoreMetrics.SCM_AUTHORS_BY_LINE_KEY)) {
+              authors = ((MeasureModel) measureByKey[1]).getData(CoreMetrics.SCM_AUTHORS_BY_LINE);
+            }
+          }
+          ref.addFileData(module.getKeyWithBranch(), path, new FileData(fileData.hash(), authors == null, lastCommits, revisions, authors));
+        }
+      }
+      ref.setLastAnalysisDate(lastSnapshotCreationDate(projectKey));
+    }
+    return ref;
+  }
+
+  public List<Object[]> query(String resourceKey, String... metricKeys) {
+    StringBuilder sb = new StringBuilder();
+    Map<String, Object> params = Maps.newHashMap();
+
+    sb.append("SELECT met.key, m");
+    sb.append(" FROM ")
+      .append(MeasureModel.class.getSimpleName())
+      .append(" m, ")
+      .append(Metric.class.getSimpleName())
+      .append(" met, ")
+      .append(ResourceModel.class.getSimpleName())
+      .append(" r, ")
+      .append(Snapshot.class.getSimpleName())
+      .append(" s WHERE met.id=m.metricId AND m.snapshotId=s.id AND s.resourceId=r.id AND r.key=:kee AND s.status=:status AND s.qualifier<>:lib");
+    params.put("kee", resourceKey);
+    params.put("status", Snapshot.STATUS_PROCESSED);
+    params.put("lib", Qualifiers.LIBRARY);
+
+    sb.append(" AND m.characteristicId IS NULL");
+    sb.append(" AND m.personId IS NULL");
+    sb.append(" AND m.ruleId IS NULL AND m.rulePriority IS NULL");
+    if (metricKeys.length > 0) {
+      sb.append(" AND met.key IN (:metricKeys) ");
+      params.put("metricKeys", Arrays.asList(metricKeys));
+    }
+    sb.append(" AND s.last=true ");
+    sb.append(" ORDER BY s.createdAt ");
+
+    Query jpaQuery = session.createQuery(sb.toString());
+
+    for (Map.Entry<String, Object> entry : params.entrySet()) {
+      jpaQuery.setParameter(entry.getKey(), entry.getValue());
+    }
+    return jpaQuery.getResultList();
+  }
+
+  @CheckForNull
+  Date lastSnapshotCreationDate(String resourceKey) {
+    StringBuilder sb = new StringBuilder();
+    Map<String, Object> params = Maps.newHashMap();
+
+    sb.append("SELECT s.buildDate");
+    sb.append(" FROM ")
+      .append(ResourceModel.class.getSimpleName())
+      .append(" r, ")
+      .append(Snapshot.class.getSimpleName())
+      .append(" s WHERE s.resourceId=r.id AND r.key=:kee AND s.status=:status AND s.qualifier<>:lib");
+    params.put("kee", resourceKey);
+    params.put("status", Snapshot.STATUS_PROCESSED);
+    params.put("lib", Qualifiers.LIBRARY);
+
+    sb.append(" AND s.last=true ");
+
+    Query jpaQuery = session.createQuery(sb.toString());
+
+    for (Map.Entry<String, Object> entry : params.entrySet()) {
+      jpaQuery.setParameter(entry.getKey(), entry.getValue());
+    }
+    try {
+      return (Date) jpaQuery.getSingleResult();
+    } catch (NoResultException e) {
+      return null;
+    }
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/GlobalReferentialsLoader.java b/sonar-batch/src/main/java/org/sonar/batch/repository/GlobalReferentialsLoader.java
new file mode 100644 (file)
index 0000000..1dbbb52
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * 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.repository;
+
+import org.sonar.batch.protocol.input.GlobalReferentials;
+
+public interface GlobalReferentialsLoader {
+
+  GlobalReferentials load();
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/GlobalReferentialsProvider.java b/sonar-batch/src/main/java/org/sonar/batch/repository/GlobalReferentialsProvider.java
new file mode 100644 (file)
index 0000000..d3c99dc
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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.repository;
+
+import org.picocontainer.injectors.ProviderAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.utils.TimeProfiler;
+import org.sonar.batch.protocol.input.GlobalReferentials;
+
+public class GlobalReferentialsProvider extends ProviderAdapter {
+
+  private static final Logger LOG = LoggerFactory.getLogger(GlobalReferentialsProvider.class);
+
+  private GlobalReferentials globalReferentials;
+
+  public GlobalReferentials provide(GlobalReferentialsLoader loader) {
+    if (globalReferentials == null) {
+      TimeProfiler profiler = new TimeProfiler(LOG).start("Load global referentials");
+      try {
+        globalReferentials = loader.load();
+      } finally {
+        profiler.stop();
+      }
+    }
+    return globalReferentials;
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/PreviousIssuesLoader.java b/sonar-batch/src/main/java/org/sonar/batch/repository/PreviousIssuesLoader.java
new file mode 100644 (file)
index 0000000..fe706da
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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.repository;
+
+import com.google.common.base.Function;
+import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.batch.protocol.input.issues.PreviousIssue;
+
+public interface PreviousIssuesLoader {
+
+  void load(ProjectReactor reactor, Function<PreviousIssue, Void> consumer);
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/ProjectRepositoriesLoader.java b/sonar-batch/src/main/java/org/sonar/batch/repository/ProjectRepositoriesLoader.java
new file mode 100644 (file)
index 0000000..75caa6f
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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.repository;
+
+import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.batch.bootstrap.TaskProperties;
+import org.sonar.batch.protocol.input.ProjectRepository;
+
+public interface ProjectRepositoriesLoader {
+
+  ProjectRepository load(ProjectReactor reactor, TaskProperties taskProperties);
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/ProjectRepositoriesProvider.java b/sonar-batch/src/main/java/org/sonar/batch/repository/ProjectRepositoriesProvider.java
new file mode 100644 (file)
index 0000000..c9cf9e4
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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.repository;
+
+import org.picocontainer.injectors.ProviderAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.api.utils.TimeProfiler;
+import org.sonar.batch.bootstrap.TaskProperties;
+import org.sonar.batch.protocol.input.ProjectRepository;
+
+public class ProjectRepositoriesProvider extends ProviderAdapter {
+
+  private static final Logger LOG = LoggerFactory.getLogger(ProjectRepositoriesProvider.class);
+
+  private ProjectRepository projectReferentials;
+
+  public ProjectRepository provide(ProjectRepositoriesLoader loader, ProjectReactor reactor, TaskProperties taskProps) {
+    if (projectReferentials == null) {
+      TimeProfiler profiler = new TimeProfiler(LOG).start("Load project referentials");
+      try {
+        projectReferentials = loader.load(reactor, taskProps);
+      } finally {
+        profiler.stop();
+      }
+    }
+    return projectReferentials;
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/package-info.java b/sonar-batch/src/main/java/org/sonar/batch/repository/package-info.java
new file mode 100644 (file)
index 0000000..353f019
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.batch.repository;
+
+import javax.annotation.ParametersAreNonnullByDefault;
index a539b74407355d0fcebf623ec5f1315100d7f0eb..4ae65303f0e4329b0aef19a4252cd618a5e2201b 100644 (file)
@@ -25,26 +25,26 @@ import org.sonar.api.batch.rule.internal.ActiveRulesBuilder;
 import org.sonar.api.batch.rule.internal.NewActiveRule;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.batch.protocol.input.ActiveRule;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
 
 import java.util.Map.Entry;
 
 /**
  * Loads the rules that are activated on the Quality profiles
- * used by the current module and build {@link org.sonar.api.batch.rule.ActiveRules}.
+ * used by the current project and build {@link org.sonar.api.batch.rule.ActiveRules}.
  */
 public class ActiveRulesProvider extends ProviderAdapter {
 
   private ActiveRules singleton = null;
 
-  public ActiveRules provide(ProjectReferentials ref) {
+  public ActiveRules provide(ProjectRepository ref) {
     if (singleton == null) {
       singleton = load(ref);
     }
     return singleton;
   }
 
-  private ActiveRules load(ProjectReferentials ref) {
+  private ActiveRules load(ProjectRepository ref) {
     ActiveRulesBuilder builder = new ActiveRulesBuilder();
     for (ActiveRule activeRule : ref.activeRules()) {
       NewActiveRule newActiveRule = builder.create(RuleKey.of(activeRule.repositoryKey(), activeRule.ruleKey()));
index dba6cd74a9b97939cbfce5202673abb85be63793..badcc7878c603cee6b9504ce0a68b961ec528570 100644 (file)
@@ -21,7 +21,7 @@ package org.sonar.batch.rule;
 
 import com.google.common.collect.ImmutableMap;
 import org.sonar.api.BatchComponent;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
 
 import javax.annotation.CheckForNull;
 
@@ -36,7 +36,7 @@ public class ModuleQProfiles implements BatchComponent {
   public static final String SONAR_PROFILE_PROP = "sonar.profile";
   private final Map<String, QProfile> byLanguage;
 
-  public ModuleQProfiles(ProjectReferentials ref) {
+  public ModuleQProfiles(ProjectRepository ref) {
     ImmutableMap.Builder<String, QProfile> builder = ImmutableMap.builder();
 
     for (org.sonar.batch.protocol.input.QProfile qProfile : ref.qProfiles()) {
index 59dc45098d513a1871983d40c44eb5b5075c403d..c54806dd3476ccaddc8f78127318ef037441c3ce 100644 (file)
@@ -19,8 +19,6 @@
  */
 package org.sonar.batch.scan;
 
-import org.sonar.batch.sensor.AnalyzerOptimizer;
-
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.sonar.api.BatchComponent;
@@ -59,6 +57,9 @@ import org.sonar.batch.issue.ignore.pattern.IssueExclusionPatternInitializer;
 import org.sonar.batch.issue.ignore.pattern.IssueInclusionPatternInitializer;
 import org.sonar.batch.issue.ignore.scanner.IssueExclusionsLoader;
 import org.sonar.batch.issue.ignore.scanner.IssueExclusionsRegexpScanner;
+import org.sonar.batch.issue.tracking.InitialOpenIssuesSensor;
+import org.sonar.batch.issue.tracking.IssueHandlers;
+import org.sonar.batch.issue.tracking.IssueTrackingDecorator;
 import org.sonar.batch.language.LanguageDistributionDecorator;
 import org.sonar.batch.phases.DecoratorsExecutor;
 import org.sonar.batch.phases.DefaultPhaseExecutor;
@@ -75,7 +76,6 @@ import org.sonar.batch.qualitygate.QualityGateVerifier;
 import org.sonar.batch.report.ComponentsPublisher;
 import org.sonar.batch.report.IssuesPublisher;
 import org.sonar.batch.report.PublishReportJob;
-import org.sonar.batch.rule.ActiveRulesProvider;
 import org.sonar.batch.rule.ModuleQProfiles;
 import org.sonar.batch.rule.QProfileDecorator;
 import org.sonar.batch.rule.QProfileEventsDecorator;
@@ -97,6 +97,7 @@ import org.sonar.batch.scan.filesystem.ProjectFileSystemAdapter;
 import org.sonar.batch.scan.filesystem.StatusDetectionFactory;
 import org.sonar.batch.scan.maven.MavenPluginsConfigurator;
 import org.sonar.batch.scan.report.IssuesReports;
+import org.sonar.batch.sensor.AnalyzerOptimizer;
 import org.sonar.batch.sensor.DefaultSensorContext;
 import org.sonar.batch.sensor.DefaultSensorStorage;
 import org.sonar.batch.sensor.coverage.CoverageExclusions;
@@ -185,7 +186,6 @@ public class ModuleScanContainer extends ComponentContainer {
 
       // rules
       ModuleQProfiles.class,
-      new ActiveRulesProvider(),
       new RulesProfileProvider(),
       QProfileSensor.class,
       QProfileDecorator.class,
@@ -229,6 +229,11 @@ public class ModuleScanContainer extends ComponentContainer {
       SqaleRatingDecorator.class,
       SqaleRatingSettings.class,
 
+      // Issue tracking
+      IssueTrackingDecorator.class,
+      IssueHandlers.class,
+      InitialOpenIssuesSensor.class,
+
       QProfileEventsDecorator.class,
 
       TimeMachineConfiguration.class);
index f7c2d9fa27d7458cfd5f445ec102a01f211165ff..83b1c328deb34138f52c216e1206642c8adeb5f0 100644 (file)
@@ -27,7 +27,7 @@ import org.sonar.api.config.Settings;
 import org.sonar.api.utils.MessageException;
 import org.sonar.batch.bootstrap.AnalysisMode;
 import org.sonar.batch.bootstrap.GlobalSettings;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
 
 import java.util.List;
 
@@ -36,10 +36,10 @@ import java.util.List;
  */
 public class ModuleSettings extends Settings {
 
-  private final ProjectReferentials projectReferentials;
+  private final ProjectRepository projectReferentials;
   private AnalysisMode analysisMode;
 
-  public ModuleSettings(GlobalSettings batchSettings, ProjectDefinition project, ProjectReferentials projectReferentials,
+  public ModuleSettings(GlobalSettings batchSettings, ProjectDefinition project, ProjectRepository projectReferentials,
     AnalysisMode analysisMode) {
     super(batchSettings.getDefinitions());
     this.projectReferentials = projectReferentials;
index 0a3c22ef77a193eea1fa55ab569afb97a4b6e7c7..d6a47de9952d5b2c6b63ecc791c205f98cb1f75c 100644 (file)
@@ -59,11 +59,15 @@ import org.sonar.batch.index.ResourcePersister;
 import org.sonar.batch.index.SourcePersister;
 import org.sonar.batch.issue.DefaultProjectIssues;
 import org.sonar.batch.issue.IssueCache;
+import org.sonar.batch.issue.tracking.InitialOpenIssuesStack;
+import org.sonar.batch.issue.tracking.LocalIssueTracking;
+import org.sonar.batch.issue.tracking.PreviousIssueRepository;
 import org.sonar.batch.languages.DefaultLanguagesReferential;
 import org.sonar.batch.mediumtest.ScanTaskObservers;
 import org.sonar.batch.phases.GraphPersister;
 import org.sonar.batch.profiling.PhasesSumUpTimeProfiler;
-import org.sonar.batch.referential.ProjectReferentialsProvider;
+import org.sonar.batch.repository.ProjectRepositoriesProvider;
+import org.sonar.batch.rule.ActiveRulesProvider;
 import org.sonar.batch.rule.RulesProvider;
 import org.sonar.batch.scan.filesystem.InputPathCache;
 import org.sonar.batch.scan.maven.FakeMavenPluginExecutor;
@@ -131,7 +135,7 @@ public class ProjectScanContainer extends ComponentContainer {
 
   private void addBatchComponents() {
     add(
-      new ProjectReferentialsProvider(),
+      new ProjectRepositoriesProvider(),
       DefaultResourceCreationLock.class,
       CodeColorizers.class,
       DefaultNotificationManager.class,
@@ -148,6 +152,9 @@ public class ProjectScanContainer extends ComponentContainer {
       InputPathCache.class,
       PathResolver.class,
 
+      // rules
+      new ActiveRulesProvider(),
+
       // issues
       IssueUpdater.class,
       FunctionExecutor.class,
@@ -155,6 +162,8 @@ public class ProjectScanContainer extends ComponentContainer {
       IssueCache.class,
       DefaultProjectIssues.class,
       IssueChangelogDebtCalculator.class,
+      LocalIssueTracking.class,
+      PreviousIssueRepository.class,
 
       // tests
       TestPlanPerspectiveLoader.class,
@@ -203,6 +212,9 @@ public class ProjectScanContainer extends ComponentContainer {
       // technical debt
       DefaultTechnicalDebtModel.class,
 
+      // Issue tracking
+      InitialOpenIssuesStack.class,
+
       ProjectLock.class);
   }
 
index 72218e95ce3cb24b0e4be321976135cef27621eb..f6539dbfa7f607bf8a9ebf8de36fd4c6eb2e3ad1 100644 (file)
@@ -28,18 +28,18 @@ import org.sonar.api.config.Settings;
 import org.sonar.api.utils.MessageException;
 import org.sonar.batch.bootstrap.AnalysisMode;
 import org.sonar.batch.bootstrap.GlobalSettings;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
 
 public class ProjectSettings extends Settings {
 
   private static final Logger LOG = LoggerFactory.getLogger(ProjectSettings.class);
 
   private final GlobalSettings globalSettings;
-  private final ProjectReferentials projectReferentials;
+  private final ProjectRepository projectReferentials;
   private final AnalysisMode mode;
 
   public ProjectSettings(ProjectReactor reactor, GlobalSettings globalSettings, PropertyDefinitions propertyDefinitions,
-    ProjectReferentials projectReferentials, AnalysisMode mode) {
+    ProjectRepository projectReferentials, AnalysisMode mode) {
     super(propertyDefinitions);
     this.mode = mode;
     getEncryption().setPathToSecretKey(globalSettings.getString(CoreProperties.ENCRYPTION_SECRET_KEY_PATH));
index 8843e6049894c039192c39028a2513a054a0d01d..883268fcc012e7ed98aa6ac314c3f95bd09cea8a 100644 (file)
@@ -22,13 +22,13 @@ package org.sonar.batch.scan.filesystem;
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.batch.fs.InputFile;
 import org.sonar.batch.protocol.input.FileData;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
 
 class StatusDetection {
 
-  private final ProjectReferentials projectReferentials;
+  private final ProjectRepository projectReferentials;
 
-  StatusDetection(ProjectReferentials projectReferentials) {
+  StatusDetection(ProjectRepository projectReferentials) {
     this.projectReferentials = projectReferentials;
   }
 
index 830cb346c09f0885c72fdc2957fc00f65d7a3f42..15e19d6457b3ca5c19f5bf7522878b0128659964 100644 (file)
 package org.sonar.batch.scan.filesystem;
 
 import org.sonar.api.BatchComponent;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
 
 public class StatusDetectionFactory implements BatchComponent {
 
-  private final ProjectReferentials projectReferentials;
+  private final ProjectRepository projectReferentials;
 
-  public StatusDetectionFactory(ProjectReferentials projectReferentials) {
+  public StatusDetectionFactory(ProjectRepository projectReferentials) {
     this.projectReferentials = projectReferentials;
   }
 
index 00f6839bfdb84cd0e6f469ca92648f30fa9b5618..fc42a20e18c0a9e2b0e0bd796499f88f01fb441b 100644 (file)
@@ -34,7 +34,7 @@ import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
 import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.utils.TimeProfiler;
 import org.sonar.batch.protocol.input.FileData;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
 import org.sonar.core.DryRunIncompatible;
 
 import java.util.LinkedList;
@@ -48,10 +48,10 @@ public final class ScmSensor implements Sensor {
   private final ProjectDefinition projectDefinition;
   private final ScmConfiguration configuration;
   private final FileSystem fs;
-  private final ProjectReferentials projectReferentials;
+  private final ProjectRepository projectReferentials;
 
   public ScmSensor(ProjectDefinition projectDefinition, ScmConfiguration configuration,
-    ProjectReferentials projectReferentials, FileSystem fs) {
+    ProjectRepository projectReferentials, FileSystem fs) {
     this.projectDefinition = projectDefinition;
     this.configuration = configuration;
     this.projectReferentials = projectReferentials;
diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/InitialOpenIssuesSensorTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/InitialOpenIssuesSensorTest.java
new file mode 100644 (file)
index 0000000..85c1e43
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.batch.issue.tracking;
+
+import org.apache.ibatis.session.ResultHandler;
+import org.junit.Test;
+import org.sonar.api.resources.Project;
+import org.sonar.core.issue.db.IssueChangeDao;
+import org.sonar.core.issue.db.IssueDao;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class InitialOpenIssuesSensorTest {
+
+  InitialOpenIssuesStack stack = mock(InitialOpenIssuesStack.class);
+  IssueDao issueDao = mock(IssueDao.class);
+  IssueChangeDao issueChangeDao = mock(IssueChangeDao.class);
+
+  InitialOpenIssuesSensor sensor = new InitialOpenIssuesSensor(stack, issueDao, issueChangeDao);
+
+  @Test
+  public void should_select_module_open_issues() {
+    Project project = new Project("key");
+    project.setId(1);
+    sensor.analyse(project, null);
+
+    verify(issueDao).selectNonClosedIssuesByModule(eq(1), any(ResultHandler.class));
+  }
+
+  @Test
+  public void should_select_module_open_issues_changelog() {
+    Project project = new Project("key");
+    project.setId(1);
+    sensor.analyse(project, null);
+
+    verify(issueChangeDao).selectChangelogOnNonClosedIssuesByModuleAndType(eq(1), any(ResultHandler.class));
+  }
+
+  @Test
+  public void test_toString() throws Exception {
+    assertThat(sensor.toString()).isEqualTo("InitialOpenIssuesSensor");
+
+  }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/InitialOpenIssuesStackTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/InitialOpenIssuesStackTest.java
new file mode 100644 (file)
index 0000000..34adce3
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * 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.issue.tracking;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.CoreProperties;
+import org.sonar.batch.bootstrap.BootstrapProperties;
+import org.sonar.batch.bootstrap.TempFolderProvider;
+import org.sonar.batch.index.Caches;
+import org.sonar.core.issue.db.IssueChangeDto;
+import org.sonar.core.issue.db.IssueDto;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class InitialOpenIssuesStackTest {
+
+  @ClassRule
+  public static TemporaryFolder temp = new TemporaryFolder();
+
+  public static Caches createCacheOnTemp(TemporaryFolder temp) {
+    BootstrapProperties bootstrapSettings = new BootstrapProperties(Collections.<String, String>emptyMap());
+    try {
+      bootstrapSettings.properties().put(CoreProperties.WORKING_DIRECTORY, temp.newFolder().getAbsolutePath());
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    return new Caches(new TempFolderProvider().provide(bootstrapSettings));
+  }
+
+  InitialOpenIssuesStack stack;
+  Caches caches;
+
+  @Before
+  public void before() throws Exception {
+    caches = createCacheOnTemp(temp);
+    caches.start();
+    stack = new InitialOpenIssuesStack(caches);
+  }
+
+  @After
+  public void after() {
+    caches.stop();
+  }
+
+  @Test
+  public void get_and_remove_issues() {
+    IssueDto issueDto = new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-1");
+    stack.addIssue(issueDto);
+
+    List<PreviousIssue> issueDtos = stack.selectAndRemoveIssues("org.struts.Action");
+    assertThat(issueDtos).hasSize(1);
+    assertThat(issueDtos.get(0).key()).isEqualTo("ISSUE-1");
+
+    assertThat(stack.selectAllIssues()).isEmpty();
+  }
+
+  @Test
+  public void get_and_remove_with_many_issues_on_same_resource() {
+    stack.addIssue(new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-1"));
+    stack.addIssue(new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-2"));
+
+    List<PreviousIssue> issueDtos = stack.selectAndRemoveIssues("org.struts.Action");
+    assertThat(issueDtos).hasSize(2);
+
+    assertThat(stack.selectAllIssues()).isEmpty();
+  }
+
+  @Test
+  public void get_and_remove_do_nothing_if_resource_not_found() {
+    stack.addIssue(new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-1"));
+
+    List<PreviousIssue> issueDtos = stack.selectAndRemoveIssues("Other");
+    assertThat(issueDtos).hasSize(0);
+
+    assertThat(stack.selectAllIssues()).hasSize(1);
+  }
+
+  @Test
+  public void select_changelog() {
+    stack.addChangelog(new IssueChangeDto().setKey("CHANGE-1").setIssueKey("ISSUE-1"));
+    stack.addChangelog(new IssueChangeDto().setKey("CHANGE-2").setIssueKey("ISSUE-1"));
+
+    List<IssueChangeDto> issueChangeDtos = stack.selectChangelog("ISSUE-1");
+    assertThat(issueChangeDtos).hasSize(2);
+    assertThat(issueChangeDtos.get(0).getKey()).isEqualTo("CHANGE-1");
+    assertThat(issueChangeDtos.get(1).getKey()).isEqualTo("CHANGE-2");
+  }
+
+  @Test
+  public void return_empty_changelog() {
+    assertThat(stack.selectChangelog("ISSUE-1")).isEmpty();
+  }
+
+  @Test
+  public void clear_issues() {
+    stack.addIssue(new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-1"));
+
+    assertThat(stack.selectAllIssues()).hasSize(1);
+
+    // issues are not removed
+    assertThat(stack.selectAllIssues()).hasSize(1);
+
+    stack.clear();
+    assertThat(stack.selectAllIssues()).isEmpty();
+  }
+
+  @Test
+  public void clear_issues_changelog() {
+    stack.addChangelog(new IssueChangeDto().setKey("CHANGE-1").setIssueKey("ISSUE-1"));
+
+    assertThat(stack.selectChangelog("ISSUE-1")).hasSize(1);
+
+    stack.clear();
+    assertThat(stack.selectChangelog("ISSUE-1")).isEmpty();
+  }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/IssueHandlersTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/IssueHandlersTest.java
new file mode 100644 (file)
index 0000000..8b210bd
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.batch.issue.tracking;
+
+import org.sonar.batch.issue.tracking.IssueHandlers;
+
+import org.junit.Test;
+import org.mockito.ArgumentMatcher;
+import org.sonar.api.issue.IssueHandler;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.issue.internal.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
+
+import java.util.Date;
+
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.*;
+
+public class IssueHandlersTest {
+  @Test
+  public void should_execute_handlers() throws Exception {
+    IssueHandler h1 = mock(IssueHandler.class);
+    IssueHandler h2 = mock(IssueHandler.class);
+    IssueUpdater updater = mock(IssueUpdater.class);
+
+    IssueHandlers handlers = new IssueHandlers(updater, new IssueHandler[]{h1, h2});
+    final DefaultIssue issue = new DefaultIssue();
+    handlers.execute(issue, IssueChangeContext.createScan(new Date()));
+
+    verify(h1).onIssue(argThat(new ArgumentMatcher<IssueHandler.Context>() {
+      @Override
+      public boolean matches(Object o) {
+        return ((IssueHandler.Context) o).issue() == issue;
+      }
+    }));
+  }
+
+  @Test
+  public void test_no_handlers() {
+    IssueUpdater updater = mock(IssueUpdater.class);
+    IssueHandlers handlers = new IssueHandlers(updater);
+    handlers.execute(new DefaultIssue(), IssueChangeContext.createScan(new Date()));
+    verifyZeroInteractions(updater);
+  }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/IssueTrackingBlocksRecognizerTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/IssueTrackingBlocksRecognizerTest.java
new file mode 100644 (file)
index 0000000..30bf045
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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.issue.tracking;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class IssueTrackingBlocksRecognizerTest {
+
+  @Test
+  public void test() {
+    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(FileHashes a, FileHashes b, int ai, int bi) {
+    IssueTrackingBlocksRecognizer rec = new IssueTrackingBlocksRecognizer(a, b);
+    return rec.computeLengthOfMaximalBlock(ai, bi);
+  }
+
+  private static FileHashes t(String text) {
+    String[] array = new String[text.length()];
+    for (int i = 0; i < text.length(); i++) {
+      array[i] = "" + text.charAt(i);
+    }
+    return FileHashes.create(array);
+  }
+
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/IssueTrackingDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/IssueTrackingDecoratorTest.java
new file mode 100644 (file)
index 0000000..7691dbf
--- /dev/null
@@ -0,0 +1,582 @@
+/*
+ * 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.issue.tracking;
+
+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.fs.internal.DefaultInputFile;
+import org.sonar.api.component.ResourcePerspectives;
+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.rule.RuleKey;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.api.utils.Duration;
+import org.sonar.api.utils.System2;
+import org.sonar.batch.issue.IssueCache;
+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;
+import org.sonar.core.issue.workflow.IssueWorkflow;
+import org.sonar.java.api.JavaClass;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.assertj.core.api.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.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 {
+
+  IssueTrackingDecorator decorator;
+  IssueCache issueCache = mock(IssueCache.class, RETURNS_MOCKS);
+  InitialOpenIssuesStack initialOpenIssues = mock(InitialOpenIssuesStack.class);
+  IssueTracking tracking = mock(IssueTracking.class, RETURNS_MOCKS);
+  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() {
+    decorator = new IssueTrackingDecorator(
+      issueCache,
+      initialOpenIssues,
+      tracking,
+      lastSnapshots,
+      handlers,
+      workflow,
+      updater,
+      new Project("foo"),
+      perspectives,
+      profile,
+      ruleFinder,
+      inputPathCache);
+  }
+
+  @Test
+  public void should_execute_on_project() {
+    Project project = mock(Project.class);
+    assertThat(decorator.shouldExecuteOnProject(project)).isTrue();
+  }
+
+  @Test
+  public void should_not_be_executed_on_classes_not_methods() throws Exception {
+    DecoratorContext context = mock(DecoratorContext.class);
+    decorator.decorate(JavaClass.create("org.foo.Bar"), context);
+    verifyZeroInteractions(context, issueCache, tracking, handlers, workflow);
+  }
+
+  @Test
+  public void should_process_open_issues() throws Exception {
+    Resource file = File.create("Action.java").setEffectiveKey("struts:Action.java").setId(123);
+    final DefaultIssue issue = new DefaultIssue();
+
+    // INPUT : one issue, no open issues during previous scan, no filtering
+    when(issueCache.byComponent("struts:Action.java")).thenReturn(Arrays.asList(issue));
+    List<PreviousIssue> dbIssues = Collections.emptyList();
+    when(initialOpenIssues.selectAndRemoveIssues("struts:Action.java")).thenReturn(dbIssues);
+    when(inputPathCache.getFile("foo", "Action.java")).thenReturn(mock(DefaultInputFile.class));
+    decorator.doDecorate(file);
+
+    // Apply filters, track, apply transitions, notify extensions then update cache
+    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;
+        return issues.size() == 1 && issues.get(0) == issue;
+      }
+    }));
+    verify(workflow).doAutomaticTransition(eq(issue), any(IssueChangeContext.class));
+    verify(handlers).execute(eq(issue), any(IssueChangeContext.class));
+    verify(issueCache).put(issue);
+  }
+
+  @Test
+  public void should_register_unmatched_issues_as_end_of_life() throws Exception {
+    // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
+    Resource file = File.create("Action.java").setEffectiveKey("struts:Action.java").setId(123);
+
+    // INPUT : one issue existing during previous scan
+    PreviousIssue unmatchedIssue = new PreviousIssueFromDb(new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle"));
+
+    IssueTrackingResult trackingResult = new IssueTrackingResult();
+    trackingResult.addUnmatched(unmatchedIssue);
+
+    when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
+    when(inputPathCache.getFile("foo", "Action.java")).thenReturn(mock(DefaultInputFile.class));
+
+    decorator.doDecorate(file);
+
+    verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
+    verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
+
+    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+    verify(issueCache).put(argument.capture());
+
+    DefaultIssue issue = argument.getValue();
+    assertThat(issue.key()).isEqualTo("ABCDE");
+    assertThat(issue.isNew()).isFalse();
+    assertThat(issue.isEndOfLife()).isTrue();
+  }
+
+  @Test
+  public void manual_issues_should_be_moved_if_matching_line_found() throws Exception {
+    // INPUT : one issue existing during previous scan
+    PreviousIssue unmatchedIssue = new PreviousIssueFromDb(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"));
+
+    IssueTrackingResult trackingResult = new IssueTrackingResult();
+    trackingResult.addUnmatched(unmatchedIssue);
+
+    String originalSource = "public interface Action {\n"
+      + "   void method1();\n"
+      + "   void method2();\n"
+      + "   void method3();\n"
+      + "   void method4();\n"
+      + "   void method5();\n" // Original issue here
+      + "}";
+    String newSource = "public interface Action {\n"
+      + "   void method5();\n" // New issue here
+      + "   void method1();\n"
+      + "   void method2();\n"
+      + "   void method3();\n"
+      + "   void method4();\n"
+      + "}";
+    Resource file = mockHashes(originalSource, newSource);
+
+    when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
+
+    decorator.doDecorate(file);
+
+    verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
+    verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
+
+    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+    verify(issueCache).put(argument.capture());
+
+    DefaultIssue issue = argument.getValue();
+    assertThat(issue.line()).isEqualTo(2);
+    assertThat(issue.key()).isEqualTo("ABCDE");
+    assertThat(issue.isNew()).isFalse();
+    assertThat(issue.isEndOfLife()).isFalse();
+    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 {
+
+    // INPUT : one issue existing during previous scan
+    PreviousIssue unmatchedIssue = new PreviousIssueFromDb(new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("CLOSED").setRuleKey("manual", "Performance"));
+    when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
+
+    IssueTrackingResult trackingResult = new IssueTrackingResult();
+    trackingResult.addUnmatched(unmatchedIssue);
+
+    String originalSource = "public interface Action {}";
+    Resource file = mockHashes(originalSource, originalSource);
+
+    when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
+
+    decorator.doDecorate(file);
+
+    verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
+    verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
+
+    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+    verify(issueCache).put(argument.capture());
+
+    DefaultIssue issue = argument.getValue();
+    assertThat(issue.line()).isEqualTo(1);
+    assertThat(issue.key()).isEqualTo("ABCDE");
+    assertThat(issue.isNew()).isFalse();
+    assertThat(issue.isEndOfLife()).isFalse();
+    assertThat(issue.isOnDisabledRule()).isFalse();
+    assertThat(issue.status()).isEqualTo("CLOSED");
+  }
+
+  @Test
+  public void manual_issues_should_be_untouched_if_line_is_null() throws Exception {
+
+    // INPUT : one issue existing during previous scan
+    PreviousIssue unmatchedIssue = new PreviousIssueFromDb(new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(null).setStatus("OPEN").setRuleKey("manual", "Performance"));
+    when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
+
+    IssueTrackingResult trackingResult = new IssueTrackingResult();
+    trackingResult.addUnmatched(unmatchedIssue);
+
+    String originalSource = "public interface Action {}";
+    Resource file = mockHashes(originalSource, originalSource);
+
+    when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
+
+    decorator.doDecorate(file);
+
+    verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
+    verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
+
+    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+    verify(issueCache).put(argument.capture());
+
+    DefaultIssue issue = argument.getValue();
+    assertThat(issue.line()).isEqualTo(null);
+    assertThat(issue.key()).isEqualTo("ABCDE");
+    assertThat(issue.isNew()).isFalse();
+    assertThat(issue.isEndOfLife()).isFalse();
+    assertThat(issue.isOnDisabledRule()).isFalse();
+    assertThat(issue.status()).isEqualTo("OPEN");
+  }
+
+  @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
+
+    // INPUT : one issue existing during previous scan
+    final int issueOnLine = 6;
+    PreviousIssue unmatchedIssue = new PreviousIssueFromDb(new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(issueOnLine).setStatus("OPEN")
+      .setRuleKey("manual", "Performance"));
+    when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
+
+    IssueTrackingResult trackingResult = new IssueTrackingResult();
+    trackingResult.addUnmatched(unmatchedIssue);
+
+    String originalSource = "public interface Action {\n"
+      + "   void method1();\n"
+      + "   void method2();\n"
+      + "   void method3();\n"
+      + "   void method4();\n"
+      + "   void method5();\n" // Original issue here
+      + "}";
+    String newSource = "public interface Action {\n"
+      + "   void method1();\n"
+      + "   void method2();\n"
+      + "   void method3();\n"
+      + "   void method4();\n"
+      + "   void method6();\n" // Poof, no method5 anymore
+      + "}";
+
+    Resource file = mockHashes(originalSource, newSource);
+
+    when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
+
+    decorator.doDecorate(file);
+
+    verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
+    verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
+
+    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+    verify(issueCache).put(argument.capture());
+
+    DefaultIssue issue = argument.getValue();
+    assertThat(issue.line()).isEqualTo(issueOnLine);
+    assertThat(issue.key()).isEqualTo("ABCDE");
+    assertThat(issue.isNew()).isFalse();
+    assertThat(issue.isEndOfLife()).isFalse();
+    assertThat(issue.isOnDisabledRule()).isFalse();
+  }
+
+  @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
+
+    // INPUT : one issue existing during previous scan
+    final int issueOnLine = 3;
+    PreviousIssue unmatchedIssue = new PreviousIssueFromDb(new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(issueOnLine).setStatus("OPEN")
+      .setRuleKey("manual", "Performance"));
+    when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
+
+    IssueTrackingResult trackingResult = new IssueTrackingResult();
+    trackingResult.addUnmatched(unmatchedIssue);
+
+    String originalSource = "public class Action {\n"
+      + "   void method1() {\n"
+      + "     notify();\n" // initial issue
+      + "   }\n"
+      + "}";
+    String newSource = "public class Action {\n"
+      + "   \n"
+      + "   void method1() {\n" // new issue will appear here
+      + "     notify();\n"
+      + "   }\n"
+      + "   void method2() {\n"
+      + "     notify();\n"
+      + "   }\n"
+      + "}";
+    Resource file = mockHashes(originalSource, newSource);
+
+    when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
+
+    decorator.doDecorate(file);
+
+    verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
+    verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
+
+    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+    verify(issueCache).put(argument.capture());
+
+    DefaultIssue issue = argument.getValue();
+    assertThat(issue.line()).isEqualTo(issueOnLine);
+    assertThat(issue.key()).isEqualTo("ABCDE");
+    assertThat(issue.isNew()).isFalse();
+    assertThat(issue.isEndOfLife()).isFalse();
+    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
+
+    // INPUT : one issue existing during previous scan
+    PreviousIssue unmatchedIssue = new PreviousIssueFromDb(new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("OPEN").setRuleKey("manual", "Performance"));
+    when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance").setStatus(Rule.STATUS_REMOVED));
+
+    IssueTrackingResult trackingResult = new IssueTrackingResult();
+    trackingResult.addUnmatched(unmatchedIssue);
+
+    String source = "public interface Action {}";
+    Resource file = mockHashes(source, source);
+
+    when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
+
+    decorator.doDecorate(file);
+
+    verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
+    verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
+
+    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+    verify(issueCache).put(argument.capture());
+
+    DefaultIssue issue = argument.getValue();
+    assertThat(issue.key()).isEqualTo("ABCDE");
+    assertThat(issue.isNew()).isFalse();
+    assertThat(issue.isEndOfLife()).isTrue();
+    assertThat(issue.isOnDisabledRule()).isTrue();
+  }
+
+  @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
+
+    // INPUT : one issue existing during previous scan
+    PreviousIssue unmatchedIssue = new PreviousIssueFromDb(new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("OPEN").setRuleKey("manual", "Performance"));
+    when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(null);
+
+    IssueTrackingResult trackingResult = new IssueTrackingResult();
+    trackingResult.addUnmatched(unmatchedIssue);
+
+    String source = "public interface Action {}";
+    Resource file = mockHashes(source, source);
+
+    when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
+
+    decorator.doDecorate(file);
+
+    verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
+    verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
+
+    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+    verify(issueCache).put(argument.capture());
+
+    DefaultIssue issue = argument.getValue();
+    assertThat(issue.key()).isEqualTo("ABCDE");
+    assertThat(issue.isNew()).isFalse();
+    assertThat(issue.isEndOfLife()).isTrue();
+    assertThat(issue.isOnDisabledRule()).isTrue();
+  }
+
+  @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
+
+    // INPUT : one issue existing during previous scan
+    PreviousIssue unmatchedIssue = new PreviousIssueFromDb(new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(6).setStatus("OPEN").setRuleKey("manual", "Performance"));
+    when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(null);
+
+    IssueTrackingResult trackingResult = new IssueTrackingResult();
+    trackingResult.addUnmatched(unmatchedIssue);
+
+    String originalSource = "public interface Action {\n"
+      + "   void method1();\n"
+      + "   void method2();\n"
+      + "   void method3();\n"
+      + "   void method4();\n"
+      + "   void method5();\n"
+      + "}";
+    String newSource = "public interface Action {\n"
+      + "   void method1();\n"
+      + "   void method2();\n"
+      + "}";
+    Resource file = mockHashes(originalSource, newSource);
+
+    when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
+
+    decorator.doDecorate(file);
+
+    verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
+    verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
+
+    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+    verify(issueCache).put(argument.capture());
+
+    DefaultIssue issue = argument.getValue();
+    verify(updater).setResolution(eq(issue), eq(Issue.RESOLUTION_REMOVED), any(IssueChangeContext.class));
+    verify(updater).setStatus(eq(issue), eq(Issue.STATUS_CLOSED), any(IssueChangeContext.class));
+
+    assertThat(issue.key()).isEqualTo("ABCDE");
+    assertThat(issue.isNew()).isFalse();
+    assertThat(issue.isEndOfLife()).isTrue();
+    assertThat(issue.isOnDisabledRule()).isTrue();
+  }
+
+  @Test
+  public void should_register_issues_on_deleted_components() throws Exception {
+    Project project = new Project("struts");
+    DefaultIssue openIssue = new DefaultIssue();
+    when(issueCache.byComponent("struts")).thenReturn(Arrays.asList(openIssue));
+    IssueDto deadIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle");
+    when(initialOpenIssues.selectAllIssues()).thenReturn(Arrays.asList(deadIssue));
+
+    decorator.doDecorate(project);
+
+    // the dead issue must be closed -> apply automatic transition, notify handlers and add to cache
+    verify(workflow, times(2)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
+    verify(handlers, times(2)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
+    verify(issueCache, times(2)).put(any(DefaultIssue.class));
+
+    verify(issueCache).put(argThat(new ArgumentMatcher<DefaultIssue>() {
+      @Override
+      public boolean matches(Object o) {
+        DefaultIssue dead = (DefaultIssue) o;
+        return "ABCDE".equals(dead.key()) && !dead.isNew() && dead.isEndOfLife();
+      }
+    }));
+  }
+
+  @Test
+  public void merge_matched_issue() throws Exception {
+    PreviousIssue previousIssue = new PreviousIssueFromDb(new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle")
+      .setLine(10).setSeverity("MAJOR").setMessage("Message").setEffortToFix(1.5).setDebt(1L).setProjectKey("sample"));
+    DefaultIssue issue = new DefaultIssue();
+
+    IssueTrackingResult trackingResult = mock(IssueTrackingResult.class);
+    when(trackingResult.matched()).thenReturn(newArrayList(issue));
+    when(trackingResult.matching(eq(issue))).thenReturn(previousIssue);
+    decorator.mergeMatched(trackingResult);
+
+    verify(updater).setPastSeverity(eq(issue), eq("MAJOR"), any(IssueChangeContext.class));
+    verify(updater).setPastLine(eq(issue), eq(10));
+    verify(updater).setPastMessage(eq(issue), eq("Message"), any(IssueChangeContext.class));
+    verify(updater).setPastEffortToFix(eq(issue), eq(1.5), any(IssueChangeContext.class));
+    verify(updater).setPastTechnicalDebt(eq(issue), eq(Duration.create(1L)), any(IssueChangeContext.class));
+    verify(updater).setPastProject(eq(issue), eq("sample"), any(IssueChangeContext.class));
+  }
+
+  @Test
+  public void merge_matched_issue_on_manual_severity() throws Exception {
+    PreviousIssue previousIssue = new PreviousIssueFromDb(new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle")
+      .setLine(10).setManualSeverity(true).setSeverity("MAJOR").setMessage("Message").setEffortToFix(1.5).setDebt(1L));
+    DefaultIssue issue = new DefaultIssue();
+
+    IssueTrackingResult trackingResult = mock(IssueTrackingResult.class);
+    when(trackingResult.matched()).thenReturn(newArrayList(issue));
+    when(trackingResult.matching(eq(issue))).thenReturn(previousIssue);
+    decorator.mergeMatched(trackingResult);
+
+    assertThat(issue.manualSeverity()).isTrue();
+    assertThat(issue.severity()).isEqualTo("MAJOR");
+    verify(updater, never()).setPastSeverity(eq(issue), anyString(), any(IssueChangeContext.class));
+  }
+
+  @Test
+  public void merge_issue_changelog_with_previous_changelog() throws Exception {
+    when(initialOpenIssues.selectChangelog("ABCDE")).thenReturn(newArrayList(new IssueChangeDto().setIssueKey("ABCD").setCreatedAt(System2.INSTANCE.now())));
+
+    PreviousIssue previousIssue = new PreviousIssueFromDb(new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle")
+      .setLine(10).setMessage("Message").setEffortToFix(1.5).setDebt(1L).setCreatedAt(System2.INSTANCE.now()));
+    DefaultIssue issue = new DefaultIssue();
+
+    IssueTrackingResult trackingResult = mock(IssueTrackingResult.class);
+    when(trackingResult.matched()).thenReturn(newArrayList(issue));
+    when(trackingResult.matching(eq(issue))).thenReturn(previousIssue);
+    decorator.mergeMatched(trackingResult);
+
+    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;
+  }
+
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/IssueTrackingTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/IssueTrackingTest.java
new file mode 100644 (file)
index 0000000..2efb24c
--- /dev/null
@@ -0,0 +1,372 @@
+/*
+ * 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.issue.tracking;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+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.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.LastLineHashes;
+import org.sonar.core.issue.db.IssueDto;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+public class IssueTrackingTest {
+
+  IssueTracking tracking;
+  Resource project;
+  SourceHashHolder sourceHashHolder;
+  LastLineHashes lastSnapshots;
+  long violationId = 0;
+
+  @Before
+  public void before() {
+    lastSnapshots = mock(LastLineHashes.class);
+
+    project = mock(Project.class);
+    tracking = new IssueTracking();
+  }
+
+  @Test
+  public void key_should_be_the_prioritary_field_to_check() {
+    PreviousIssueFromDb referenceIssue1 = newReferenceIssue("message", 10, "squid", "AvoidCycle", "checksum1");
+    referenceIssue1.getDto().setKee("100");
+    PreviousIssueFromDb referenceIssue2 = newReferenceIssue("message", 10, "squid", "AvoidCycle", "checksum2");
+    referenceIssue2.getDto().setKee("200");
+
+    // exactly the fields of referenceIssue1 but not the same key
+    DefaultIssue newIssue = newDefaultIssue("message", 10, RuleKey.of("squid", "AvoidCycle"), "checksum1").setKey("200");
+
+    IssueTrackingResult result = new IssueTrackingResult();
+    tracking.mapIssues(newArrayList(newIssue), Lists.<PreviousIssue>newArrayList(referenceIssue1, referenceIssue2), null, result);
+    // same key
+    assertThat(result.matching(newIssue)).isSameAs(referenceIssue2);
+  }
+
+  @Test
+  public void checksum_should_have_greater_priority_than_line() {
+    PreviousIssue referenceIssue1 = newReferenceIssue("message", 1, "squid", "AvoidCycle", "checksum1");
+    PreviousIssue referenceIssue2 = newReferenceIssue("message", 3, "squid", "AvoidCycle", "checksum2");
+
+    DefaultIssue newIssue1 = newDefaultIssue("message", 3, RuleKey.of("squid", "AvoidCycle"), "checksum1");
+    DefaultIssue newIssue2 = newDefaultIssue("message", 5, RuleKey.of("squid", "AvoidCycle"), "checksum2");
+
+    IssueTrackingResult result = new IssueTrackingResult();
+    tracking.mapIssues(newArrayList(newIssue1, newIssue2), newArrayList(referenceIssue1, referenceIssue2), null, result);
+    assertThat(result.matching(newIssue1)).isSameAs(referenceIssue1);
+    assertThat(result.matching(newIssue2)).isSameAs(referenceIssue2);
+  }
+
+  /**
+   * SONAR-2928
+   */
+  @Test
+  public void same_rule_and_null_line_and_checksum_but_different_messages() {
+    DefaultIssue newIssue = newDefaultIssue("new message", null, RuleKey.of("squid", "AvoidCycle"), "checksum1");
+    PreviousIssue referenceIssue = newReferenceIssue("old message", null, "squid", "AvoidCycle", "checksum1");
+
+    IssueTrackingResult result = new IssueTrackingResult();
+    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() {
+    DefaultIssue newIssue = newDefaultIssue("new message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum1");
+    PreviousIssue referenceIssue = newReferenceIssue("old message", 1, "squid", "AvoidCycle", "checksum1");
+
+    IssueTrackingResult result = new IssueTrackingResult();
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result);
+    assertThat(result.matching(newIssue)).isSameAs(referenceIssue);
+  }
+
+  @Test
+  public void same_rule_and_line_message() {
+    DefaultIssue newIssue = newDefaultIssue("message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum1");
+    PreviousIssue referenceIssue = newReferenceIssue("message", 1, "squid", "AvoidCycle", "checksum2");
+
+    IssueTrackingResult result = new IssueTrackingResult();
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result);
+    assertThat(result.matching(newIssue)).isSameAs(referenceIssue);
+  }
+
+  @Test
+  public void should_ignore_reference_measure_without_checksum() {
+    DefaultIssue newIssue = newDefaultIssue("message", 1, RuleKey.of("squid", "AvoidCycle"), null);
+    PreviousIssue referenceIssue = newReferenceIssue("message", 1, "squid", "NullDeref", null);
+
+    IssueTrackingResult result = new IssueTrackingResult();
+    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() {
+    DefaultIssue newIssue = newDefaultIssue("message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum1");
+    PreviousIssue referenceIssue = newReferenceIssue("message", 2, "squid", "AvoidCycle", "checksum1");
+
+    IssueTrackingResult result = new IssueTrackingResult();
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result);
+    assertThat(result.matching(newIssue)).isSameAs(referenceIssue);
+  }
+
+  /**
+   * SONAR-2812
+   */
+  @Test
+  public void same_checksum_and_rule_but_different_line_and_different_message() {
+    DefaultIssue newIssue = newDefaultIssue("new message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum1");
+    PreviousIssue referenceIssue = newReferenceIssue("old message", 2, "squid", "AvoidCycle", "checksum1");
+
+    IssueTrackingResult result = new IssueTrackingResult();
+    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() {
+    DefaultIssue newIssue = newDefaultIssue("message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum1");
+    PreviousIssue referenceIssue = newReferenceIssue("message", 2, "squid", "AvoidCycle", "checksum2");
+
+    IssueTrackingResult result = new IssueTrackingResult();
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result);
+    assertThat(result.matching(newIssue)).isNull();
+  }
+
+  @Test
+  public void should_not_track_issue_if_different_rule() {
+    DefaultIssue newIssue = newDefaultIssue("message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum1");
+    PreviousIssue referenceIssue = newReferenceIssue("message", 1, "squid", "NullDeref", "checksum1");
+
+    IssueTrackingResult result = new IssueTrackingResult();
+    tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result);
+    assertThat(result.matching(newIssue)).isNull();
+  }
+
+  @Test
+  public void should_compare_issues_with_database_format() {
+    // 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");
+    PreviousIssue referenceIssue = newReferenceIssue("message", 1, "squid", "AvoidCycle", "checksum2");
+
+    IssueTrackingResult result = new IssueTrackingResult();
+    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 {
+    initLastHashes("example2-v1", "example2-v2");
+
+    DefaultIssue newIssue = newDefaultIssue("Indentation", 9, RuleKey.of("squid", "AvoidCycle"), "foo");
+    PreviousIssue referenceIssue = newReferenceIssue("2 branches need to be covered", null, "squid", "AvoidCycle", null);
+
+    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 {
+    initLastHashes("example2-v1", "example2-v2");
+
+    DefaultIssue newIssue = newDefaultIssue("1 branch need to be covered", null, RuleKey.of("squid", "AvoidCycle"), "foo");
+    PreviousIssue referenceIssue = newReferenceIssue("Indentationd", 7, "squid", "AvoidCycle", null);
+
+    IssueTrackingResult result = tracking.track(sourceHashHolder, newArrayList(referenceIssue), newArrayList(newIssue));
+
+    assertThat(result.matched()).isEmpty();
+  }
+
+  /**
+   * SONAR-2928
+   */
+  @Test
+  public void issue_not_associated_with_line() throws Exception {
+    initLastHashes("example2-v1", "example2-v2");
+
+    DefaultIssue newIssue = newDefaultIssue("1 branch need to be covered", null, RuleKey.of("squid", "AvoidCycle"), null);
+    PreviousIssue referenceIssue = newReferenceIssue("2 branches need to be covered", null, "squid", "AvoidCycle", null);
+
+    IssueTrackingResult result = tracking.track(sourceHashHolder, newArrayList(referenceIssue), newArrayList(newIssue));
+
+    assertThat(result.matching(newIssue)).isEqualTo(referenceIssue);
+  }
+
+  /**
+   * SONAR-3072
+   */
+  @Test
+  public void should_track_issues_based_on_blocks_recognition_on_example1() throws Exception {
+    initLastHashes("example1-v1", "example1-v2");
+
+    PreviousIssue referenceIssue1 = newReferenceIssue("Indentation", 7, "squid", "AvoidCycle", null);
+    PreviousIssue referenceIssue2 = newReferenceIssue("Indentation", 11, "squid", "AvoidCycle", null);
+
+    DefaultIssue newIssue1 = newDefaultIssue("Indentation", 9, RuleKey.of("squid", "AvoidCycle"), null);
+    DefaultIssue newIssue2 = newDefaultIssue("Indentation", 13, RuleKey.of("squid", "AvoidCycle"), null);
+    DefaultIssue newIssue3 = newDefaultIssue("Indentation", 17, RuleKey.of("squid", "AvoidCycle"), null);
+    DefaultIssue newIssue4 = newDefaultIssue("Indentation", 21, RuleKey.of("squid", "AvoidCycle"), null);
+
+    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();
+    assertThat(result.matching(newIssue3)).isSameAs(referenceIssue1);
+    assertThat(result.matching(newIssue4)).isSameAs(referenceIssue2);
+  }
+
+  /**
+   * SONAR-3072
+   */
+  @Test
+  public void should_track_issues_based_on_blocks_recognition_on_example2() throws Exception {
+    initLastHashes("example2-v1", "example2-v2");
+
+    PreviousIssue referenceIssue1 = newReferenceIssue("SystemPrintln", 5, "squid", "AvoidCycle", null);
+
+    DefaultIssue newIssue1 = newDefaultIssue("SystemPrintln", 6, RuleKey.of("squid", "AvoidCycle"), null);
+    DefaultIssue newIssue2 = newDefaultIssue("SystemPrintln", 10, RuleKey.of("squid", "AvoidCycle"), null);
+    DefaultIssue newIssue3 = newDefaultIssue("SystemPrintln", 14, RuleKey.of("squid", "AvoidCycle"), null);
+
+    IssueTrackingResult result = new IssueTrackingResult();
+    tracking.mapIssues(
+      Arrays.asList(newIssue1, newIssue2, newIssue3),
+      Arrays.asList(referenceIssue1),
+      sourceHashHolder, result);
+
+    assertThat(result.matching(newIssue1)).isNull();
+    assertThat(result.matching(newIssue2)).isSameAs(referenceIssue1);
+    assertThat(result.matching(newIssue3)).isNull();
+  }
+
+  @Test
+  public void should_track_issues_based_on_blocks_recognition_on_example3() throws Exception {
+    initLastHashes("example3-v1", "example3-v2");
+
+    PreviousIssue referenceIssue1 = newReferenceIssue("Avoid unused local variables such as 'j'.", 6, "squid", "AvoidCycle", "63c11570fc0a76434156be5f8138fa03");
+    PreviousIssue referenceIssue2 = newReferenceIssue("Avoid unused private methods such as 'myMethod()'.", 13, "squid", "NullDeref", "ef23288705d1ef1e512448ace287586e");
+    PreviousIssue referenceIssue3 = newReferenceIssue("Method 'avoidUtilityClass' is not designed for extension - needs to be abstract, final or empty.", 9, "pmd",
+      "UnusedLocalVariable", "ed5cdd046fda82727d6fedd1d8e3a310");
+
+    // New issue
+    DefaultIssue newIssue1 = newDefaultIssue("Avoid unused local variables such as 'msg'.", 18, RuleKey.of("squid", "AvoidCycle"), "a24254126be2bf1a9b9a8db43f633733");
+    // Same as referenceIssue2
+    DefaultIssue newIssue2 = newDefaultIssue("Avoid unused private methods such as 'myMethod()'.", 13, RuleKey.of("squid", "NullDeref"), "ef23288705d1ef1e512448ace287586e");
+    // Same as referenceIssue3
+    DefaultIssue newIssue3 = newDefaultIssue("Method 'avoidUtilityClass' is not designed for extension - needs to be abstract, final or empty.", 9,
+      RuleKey.of("pmd", "UnusedLocalVariable"), "ed5cdd046fda82727d6fedd1d8e3a310");
+    // New issue
+    DefaultIssue newIssue4 = newDefaultIssue("Method 'newViolation' is not designed for extension - needs to be abstract, final or empty.", 17,
+      RuleKey.of("pmd", "UnusedLocalVariable"), "7d58ac9040c27e4ca2f11a0269e251e2");
+    // Same as referenceIssue1
+    DefaultIssue newIssue5 = newDefaultIssue("Avoid unused local variables such as 'j'.", 6, RuleKey.of("squid", "AvoidCycle"), "4432a2675ec3e1620daefe38386b51ef");
+
+    IssueTrackingResult result = new IssueTrackingResult();
+    tracking.mapIssues(
+      Arrays.asList(newIssue1, newIssue2, newIssue3, newIssue4, newIssue5),
+      Arrays.asList(referenceIssue1, referenceIssue2, referenceIssue3),
+      sourceHashHolder, result);
+
+    assertThat(result.matching(newIssue1)).isNull();
+    assertThat(result.matching(newIssue2)).isSameAs(referenceIssue2);
+    assertThat(result.matching(newIssue3)).isSameAs(referenceIssue3);
+    assertThat(result.matching(newIssue4)).isNull();
+    assertThat(result.matching(newIssue5)).isSameAs(referenceIssue1);
+  }
+
+  @Test
+  public void dont_load_checksum_if_no_new_issue() throws Exception {
+    sourceHashHolder = mock(SourceHashHolder.class);
+
+    PreviousIssue referenceIssue = newReferenceIssue("2 branches need to be covered", null, "squid", "AvoidCycle", null);
+
+    tracking.track(sourceHashHolder, newArrayList(referenceIssue), Collections.<DefaultIssue>emptyList());
+
+    verifyZeroInteractions(lastSnapshots, sourceHashHolder);
+  }
+
+  private static String load(String name) throws IOException {
+    return Resources.toString(IssueTrackingTest.class.getResource("IssueTrackingTest/" + name + ".txt"), Charsets.UTF_8);
+  }
+
+  private DefaultIssue newDefaultIssue(String message, Integer line, RuleKey ruleKey, String checksum) {
+    return new DefaultIssue().setMessage(message).setLine(line).setRuleKey(ruleKey).setChecksum(checksum).setStatus(Issue.STATUS_OPEN);
+  }
+
+  private PreviousIssueFromDb newReferenceIssue(String message, Integer lineId, String ruleRepo, String ruleKey, String lineChecksum) {
+    IssueDto referenceIssue = new IssueDto();
+    Long id = violationId++;
+    referenceIssue.setId(id);
+    referenceIssue.setKee(Long.toString(id));
+    referenceIssue.setLine(lineId);
+    referenceIssue.setMessage(message);
+    referenceIssue.setRuleKey(ruleRepo, ruleKey);
+    referenceIssue.setChecksum(lineChecksum);
+    referenceIssue.setResolution(null);
+    referenceIssue.setStatus(Issue.STATUS_OPEN);
+    return new PreviousIssueFromDb(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;
+  }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/RollingFileHashesTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/RollingFileHashesTest.java
new file mode 100644 (file)
index 0000000..25abe18
--- /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.batch.issue.tracking;
+
+import org.junit.Test;
+
+import static org.apache.commons.codec.digest.DigestUtils.md5Hex;
+import static org.assertj.core.api.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/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/SourceHashHolderTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/tracking/SourceHashHolderTest.java
new file mode 100644 (file)
index 0000000..6ac6645
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * 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.issue.tracking;
+
+import org.junit.Before;
+import org.junit.Test;
+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.assertj.core.api.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;
+
+  LastLineHashes lastSnapshots;
+  DefaultInputFile file;
+
+  @Before
+  public void setUp() {
+    lastSnapshots = mock(LastLineHashes.class);
+    file = mock(DefaultInputFile.class);
+
+    sourceHashHolder = new SourceHashHolder(file, lastSnapshots);
+  }
+
+  @Test
+  public void should_lazy_load_line_hashes() {
+    final String source = "source";
+    when(file.lineHashes()).thenReturn(new byte[][] {md5(source), null});
+
+    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.getHashedSource().getHash(1)).isEqualTo(md5Hex(source));
+    Mockito.verifyNoMoreInteractions(file);
+  }
+
+  @Test
+  public void should_lazy_load_reference_hashes_when_status_changed() {
+    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.CHANGED);
+    when(lastSnapshots.getLineHashes(key)).thenReturn(new String[] {md5Hex(source)});
+
+    assertThat(sourceHashHolder.getHashedReference().getHash(1)).isEqualTo(md5Hex(source));
+    verify(lastSnapshots).getLineHashes(key);
+
+    assertThat(sourceHashHolder.getHashedReference().getHash(1)).isEqualTo(md5Hex(source));
+    Mockito.verifyNoMoreInteractions(lastSnapshots);
+  }
+
+  @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 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.getHashedReference()).isNull();
+    Mockito.verifyNoMoreInteractions(lastSnapshots);
+  }
+
+}
index 89f926e296f1cb5efa9389c68640a756421a35bd..a6bed59f2fdda98f28cebbabd8a71fc990acf8e0 100644 (file)
@@ -66,7 +66,7 @@ public class IssuesMediumTest {
       .newScanTask(new File(projectDir, "sonar-project.properties"))
       .start();
 
-    assertThat(result.issues()).hasSize(26);
+    assertThat(result.issues()).hasSize(14);
   }
 
   @Test
@@ -78,7 +78,7 @@ public class IssuesMediumTest {
       .property("sonar.xoo.internalKey", "OneIssuePerLine.internal")
       .start();
 
-    assertThat(result.issues()).hasSize(26 /* 26 lines */+ 3 /* 3 files */);
+    assertThat(result.issues()).hasSize(14 /* 8 + 6 lines */+ 2 /* 2 files */);
   }
 
   @Test
@@ -103,7 +103,7 @@ public class IssuesMediumTest {
       .property("sonar.issue.ignore.allfile.1.fileRegexp", "object")
       .start();
 
-    assertThat(result.issues()).hasSize(20);
+    assertThat(result.issues()).hasSize(8);
   }
 
   @Test
index 23f6b90f2c43f1eeb077a3d20e7704aae2ffe5fb..04c588ad63f13eb5c39edd454a41be1c39e8cd67 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.batch.mediumtest.issues;
 
 import com.google.common.collect.ImmutableMap;
+import org.apache.commons.codec.digest.DigestUtils;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -27,6 +28,7 @@ import org.junit.rules.TemporaryFolder;
 import org.sonar.batch.mediumtest.BatchMediumTester;
 import org.sonar.batch.mediumtest.TaskResult;
 import org.sonar.batch.protocol.input.ActiveRule;
+import org.sonar.batch.protocol.input.issues.PreviousIssue;
 import org.sonar.xoo.XooPlugin;
 
 import java.io.File;
@@ -43,6 +45,13 @@ public class ReportsMediumTest {
     .addDefaultQProfile("xoo", "Sonar Way")
     .activateRule(new ActiveRule("xoo", "OneIssuePerLine", "One issue per line", "MAJOR", "OneIssuePerLine.internal", "xoo"))
     .bootstrapProperties(ImmutableMap.of("sonar.analysis.mode", "sensor"))
+    .addPreviousIssue(new PreviousIssue().setKey("xyz")
+      .setComponentKey("sample:xources/hello/HelloJava.xoo")
+      .setRuleKey("xoo", "OneIssuePerLine")
+      .setLine(1)
+      .setOverriddenSeverity("MAJOR")
+      .setChecksum(DigestUtils.md5Hex("packagehello;"))
+      .setStatus("OPEN"))
     .build();
 
   @Before
@@ -64,7 +73,7 @@ public class ReportsMediumTest {
       .property("sonar.issuesReport.console.enable", "true")
       .start();
 
-    assertThat(result.issues()).hasSize(26);
+    assertThat(result.issues()).hasSize(14);
   }
 
 }
diff --git a/sonar-batch/src/test/java/org/sonar/batch/referential/DefaultProjectReferentialsLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/referential/DefaultProjectReferentialsLoaderTest.java
deleted file mode 100644 (file)
index 2a66a09..0000000
+++ /dev/null
@@ -1,86 +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.referential;
-
-import com.google.common.collect.Maps;
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.batch.bootstrap.ProjectDefinition;
-import org.sonar.api.batch.bootstrap.ProjectReactor;
-import org.sonar.api.database.DatabaseSession;
-import org.sonar.batch.bootstrap.AnalysisMode;
-import org.sonar.batch.bootstrap.ServerClient;
-import org.sonar.batch.bootstrap.TaskProperties;
-import org.sonar.batch.rule.ModuleQProfiles;
-
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class DefaultProjectReferentialsLoaderTest {
-
-  private DefaultProjectReferentialsLoader loader;
-  private ServerClient serverClient;
-  private AnalysisMode analysisMode;
-  private ProjectReactor reactor;
-  private TaskProperties taskProperties;
-
-  @Before
-  public void prepare() {
-    serverClient = mock(ServerClient.class);
-    analysisMode = mock(AnalysisMode.class);
-    loader = new DefaultProjectReferentialsLoader(mock(DatabaseSession.class), serverClient, analysisMode);
-    loader = spy(loader);
-    doReturn(null).when(loader).lastSnapshotCreationDate(anyString());
-    when(serverClient.request(anyString())).thenReturn("{}");
-    taskProperties = new TaskProperties(Maps.<String, String>newHashMap(), "");
-  }
-
-  @Test
-  public void passPreviewParameter() {
-    reactor = new ProjectReactor(ProjectDefinition.create().setKey("foo"));
-    when(analysisMode.isPreview()).thenReturn(false);
-    loader.load(reactor, taskProperties);
-    verify(serverClient).request("/batch/project?key=foo&preview=false");
-
-    when(analysisMode.isPreview()).thenReturn(true);
-    loader.load(reactor, taskProperties);
-    verify(serverClient).request("/batch/project?key=foo&preview=true");
-  }
-
-  @Test
-  public void passAndEncodeProjectKeyParameter() {
-    reactor = new ProjectReactor(ProjectDefinition.create().setKey("foo bàr"));
-    loader.load(reactor, taskProperties);
-    verify(serverClient).request("/batch/project?key=foo+b%C3%A0r&preview=false");
-  }
-
-  @Test
-  public void passAndEncodeProfileParameter() {
-    reactor = new ProjectReactor(ProjectDefinition.create().setKey("foo"));
-    taskProperties.properties().put(ModuleQProfiles.SONAR_PROFILE_PROP, "my-profile#2");
-    loader.load(reactor, taskProperties);
-    verify(serverClient).request("/batch/project?key=foo&profile=my-profile%232&preview=false");
-  }
-
-}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultProjectReferentialsLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultProjectReferentialsLoaderTest.java
new file mode 100644 (file)
index 0000000..108151f
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.batch.repository;
+
+import org.sonar.batch.repository.DefaultProjectReferentialsLoader;
+
+import com.google.common.collect.Maps;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.batch.bootstrap.ProjectDefinition;
+import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.batch.bootstrap.AnalysisMode;
+import org.sonar.batch.bootstrap.ServerClient;
+import org.sonar.batch.bootstrap.TaskProperties;
+import org.sonar.batch.rule.ModuleQProfiles;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class DefaultProjectReferentialsLoaderTest {
+
+  private DefaultProjectReferentialsLoader loader;
+  private ServerClient serverClient;
+  private AnalysisMode analysisMode;
+  private ProjectReactor reactor;
+  private TaskProperties taskProperties;
+
+  @Before
+  public void prepare() {
+    serverClient = mock(ServerClient.class);
+    analysisMode = mock(AnalysisMode.class);
+    loader = new DefaultProjectReferentialsLoader(mock(DatabaseSession.class), serverClient, analysisMode);
+    loader = spy(loader);
+    doReturn(null).when(loader).lastSnapshotCreationDate(anyString());
+    when(serverClient.request(anyString())).thenReturn("{}");
+    taskProperties = new TaskProperties(Maps.<String, String>newHashMap(), "");
+  }
+
+  @Test
+  public void passPreviewParameter() {
+    reactor = new ProjectReactor(ProjectDefinition.create().setKey("foo"));
+    when(analysisMode.isPreview()).thenReturn(false);
+    loader.load(reactor, taskProperties);
+    verify(serverClient).request("/batch/project?key=foo&preview=false");
+
+    when(analysisMode.isPreview()).thenReturn(true);
+    loader.load(reactor, taskProperties);
+    verify(serverClient).request("/batch/project?key=foo&preview=true");
+  }
+
+  @Test
+  public void passAndEncodeProjectKeyParameter() {
+    reactor = new ProjectReactor(ProjectDefinition.create().setKey("foo bàr"));
+    loader.load(reactor, taskProperties);
+    verify(serverClient).request("/batch/project?key=foo+b%C3%A0r&preview=false");
+  }
+
+  @Test
+  public void passAndEncodeProfileParameter() {
+    reactor = new ProjectReactor(ProjectDefinition.create().setKey("foo"));
+    taskProperties.properties().put(ModuleQProfiles.SONAR_PROFILE_PROP, "my-profile#2");
+    loader.load(reactor, taskProperties);
+    verify(serverClient).request("/batch/project?key=foo&profile=my-profile%232&preview=false");
+  }
+
+}
index 6a8e64d6ccfe175d2d501633c1ffe972498884a9..2c03cd472f462bf375a6b0b484b96c2eca516e01 100644 (file)
@@ -29,7 +29,7 @@ import org.sonar.api.config.PropertyDefinitions;
 import org.sonar.api.utils.MessageException;
 import org.sonar.batch.bootstrap.AnalysisMode;
 import org.sonar.batch.bootstrap.GlobalSettings;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
 
 import java.util.List;
 
@@ -42,12 +42,12 @@ public class ModuleSettingsTest {
   @Rule
   public ExpectedException thrown = ExpectedException.none();
 
-  ProjectReferentials projectRef;
+  ProjectRepository projectRef;
   private AnalysisMode mode;
 
   @Before
   public void before() {
-    projectRef = new ProjectReferentials();
+    projectRef = new ProjectRepository();
     mode = mock(AnalysisMode.class);
   }
 
index ca6a0aa84e56ad7bba8ab5eeb7026ebdccbd6b0a..3326102185e93d041db4a57e4ba071f310f52ebb 100644 (file)
@@ -19,6 +19,8 @@
  */
 package org.sonar.batch.scan;
 
+import org.sonar.batch.repository.ProjectRepositoriesLoader;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.sonar.api.BatchExtension;
@@ -41,8 +43,7 @@ import org.sonar.batch.bootstrap.GlobalSettings;
 import org.sonar.batch.bootstrap.TaskProperties;
 import org.sonar.batch.profiling.PhasesSumUpTimeProfiler;
 import org.sonar.batch.protocol.input.GlobalReferentials;
-import org.sonar.batch.protocol.input.ProjectReferentials;
-import org.sonar.batch.referential.ProjectReferentialsLoader;
+import org.sonar.batch.protocol.input.ProjectRepository;
 import org.sonar.batch.scan.maven.MavenPluginExecutor;
 
 import java.util.Collections;
@@ -72,10 +73,10 @@ public class ProjectScanContainerTest {
     GlobalReferentials globalRef = new GlobalReferentials();
     settings = new GlobalSettings(bootstrapProperties, new PropertyDefinitions(), globalRef, analysisMode);
     parentContainer.add(settings);
-    ProjectReferentialsLoader projectReferentialsLoader = new ProjectReferentialsLoader() {
+    ProjectRepositoriesLoader projectReferentialsLoader = new ProjectRepositoriesLoader() {
       @Override
-      public ProjectReferentials load(ProjectReactor reactor, TaskProperties taskProperties) {
-        return new ProjectReferentials();
+      public ProjectRepository load(ProjectReactor reactor, TaskProperties taskProperties) {
+        return new ProjectRepository();
       }
     };
     parentContainer.add(projectReferentialsLoader);
index c75f7aafb3d3d2b78609827a5855d218bd75283e..69db0207a8bdea5f8e0551411225e39b77f08e7c 100644 (file)
@@ -33,7 +33,7 @@ import org.sonar.batch.bootstrap.AnalysisMode;
 import org.sonar.batch.bootstrap.BootstrapProperties;
 import org.sonar.batch.bootstrap.GlobalSettings;
 import org.sonar.batch.protocol.input.GlobalReferentials;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
 
 import java.util.Collections;
 
@@ -46,7 +46,7 @@ public class ProjectSettingsTest {
   @Rule
   public ExpectedException thrown = ExpectedException.none();
 
-  ProjectReferentials projectRef;
+  ProjectRepository projectRef;
   ProjectDefinition project = ProjectDefinition.create().setKey("struts");
   GlobalSettings bootstrapProps;
 
@@ -54,7 +54,7 @@ public class ProjectSettingsTest {
 
   @Before
   public void prepare() {
-    projectRef = new ProjectReferentials();
+    projectRef = new ProjectRepository();
     mode = mock(AnalysisMode.class);
     bootstrapProps = new GlobalSettings(new BootstrapProperties(Collections.<String, String>emptyMap()), new PropertyDefinitions(), new GlobalReferentials(), mode);
   }
index e6c73e80fd7f8af2fa1c60e085fe0279e037a8a3..7b01635b67e70380fd34515d15220d237f1118a8 100644 (file)
@@ -77,9 +77,9 @@ public class FileMetadataTest {
     assertThat(metadata.lines).isEqualTo(4);
     assertThat(metadata.hash).isEqualTo(md5Hex("föo\nbàr\n\u1D11Ebaßz\n"));
     assertThat(metadata.originalLineOffsets).containsOnly(0, 5, 10, 18);
-    assertThat(metadata.lineHashes[0]).containsOnly(md5("föo"));
-    assertThat(metadata.lineHashes[1]).containsOnly(md5("bàr"));
-    assertThat(metadata.lineHashes[2]).containsOnly(md5("\u1D11Ebaßz"));
+    assertThat(metadata.lineHashes[0]).containsExactly(md5("föo"));
+    assertThat(metadata.lineHashes[1]).containsExactly(md5("bàr"));
+    assertThat(metadata.lineHashes[2]).containsExactly(md5("\u1D11Ebaßz"));
     assertThat(metadata.lineHashes[3]).isNull();
   }
 
@@ -92,9 +92,9 @@ public class FileMetadataTest {
     assertThat(metadata.lines).isEqualTo(4);
     assertThat(metadata.hash).isEqualTo(md5Hex("föo\nbàr\n\u1D11Ebaßz\n"));
     assertThat(metadata.originalLineOffsets).containsOnly(0, 5, 10, 18);
-    assertThat(metadata.lineHashes[0]).containsOnly(md5("föo"));
-    assertThat(metadata.lineHashes[1]).containsOnly(md5("bàr"));
-    assertThat(metadata.lineHashes[2]).containsOnly(md5("\u1D11Ebaßz"));
+    assertThat(metadata.lineHashes[0]).containsExactly(md5("föo"));
+    assertThat(metadata.lineHashes[1]).containsExactly(md5("bàr"));
+    assertThat(metadata.lineHashes[2]).containsExactly(md5("\u1D11Ebaßz"));
     assertThat(metadata.lineHashes[3]).isNull();
   }
 
index 5b1badcd0a52357f2e90ee7a67f0c48017af414d..ddc9d1d376b390f838ec246c38ada87324d4c987 100644 (file)
@@ -20,7 +20,7 @@
 package org.sonar.batch.scan.filesystem;
 
 import org.junit.Test;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
@@ -28,7 +28,7 @@ import static org.mockito.Mockito.mock;
 public class StatusDetectionFactoryTest {
   @Test
   public void testCreate() throws Exception {
-    StatusDetectionFactory factory = new StatusDetectionFactory(mock(ProjectReferentials.class));
+    StatusDetectionFactory factory = new StatusDetectionFactory(mock(ProjectRepository.class));
     StatusDetection detection = factory.create();
     assertThat(detection).isNotNull();
   }
index df3a94ce132689f11f6965005441ceccdd00f495..ca3282fc393e1f2a49f5cffaab6af5d70936bf9a 100644 (file)
@@ -22,14 +22,14 @@ package org.sonar.batch.scan.filesystem;
 import org.junit.Test;
 import org.sonar.api.batch.fs.InputFile;
 import org.sonar.batch.protocol.input.FileData;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
 public class StatusDetectionTest {
   @Test
   public void detect_status() throws Exception {
-    ProjectReferentials ref = new ProjectReferentials();
+    ProjectRepository ref = new ProjectRepository();
     ref.addFileData("foo", "src/Foo.java", new FileData("ABCDE", true, null, null, null));
     ref.addFileData("foo", "src/Bar.java", new FileData("FGHIJ", true, null, null, null));
     StatusDetection statusDetection = new StatusDetection(ref);
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/issue/tracking/IssueTrackingTest/example1-v1.txt b/sonar-batch/src/test/resources/org/sonar/batch/issue/tracking/IssueTrackingTest/example1-v1.txt
new file mode 100644 (file)
index 0000000..1920333
--- /dev/null
@@ -0,0 +1,12 @@
+package example1;
+
+public class Toto {
+
+    public void doSomething() {
+        // doSomething
+        }
+
+    public void doSomethingElse() {
+        // doSomethingElse
+        }
+}
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/issue/tracking/IssueTrackingTest/example1-v2.txt b/sonar-batch/src/test/resources/org/sonar/batch/issue/tracking/IssueTrackingTest/example1-v2.txt
new file mode 100644 (file)
index 0000000..2315324
--- /dev/null
@@ -0,0 +1,22 @@
+package example1;
+
+public class Toto {
+
+    public Toto(){}
+
+    public void doSomethingNew() {
+        // doSomethingNew
+        }
+
+    public void doSomethingElseNew() {
+        // doSomethingElseNew
+        }
+
+    public void doSomething() {
+        // doSomething
+        }
+
+    public void doSomethingElse() {
+        // doSomethingElse
+        }
+}
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/issue/tracking/IssueTrackingTest/example2-v1.txt b/sonar-batch/src/test/resources/org/sonar/batch/issue/tracking/IssueTrackingTest/example2-v1.txt
new file mode 100644 (file)
index 0000000..a920afe
--- /dev/null
@@ -0,0 +1,7 @@
+package example2;
+
+public class Toto {
+  void method1() {
+    System.out.println("toto");
+  }
+}
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/issue/tracking/IssueTrackingTest/example2-v2.txt b/sonar-batch/src/test/resources/org/sonar/batch/issue/tracking/IssueTrackingTest/example2-v2.txt
new file mode 100644 (file)
index 0000000..c5c8250
--- /dev/null
@@ -0,0 +1,16 @@
+package example2;
+
+public class Toto {
+
+  void method2() {
+    System.out.println("toto");
+  }
+
+  void method1() {
+    System.out.println("toto");
+  }
+
+  void method3() {
+    System.out.println("toto");
+  }
+}
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/issue/tracking/IssueTrackingTest/example3-v1.txt b/sonar-batch/src/test/resources/org/sonar/batch/issue/tracking/IssueTrackingTest/example3-v1.txt
new file mode 100644 (file)
index 0000000..facdcbc
--- /dev/null
@@ -0,0 +1,16 @@
+package sample;
+
+public class Sample {
+       
+       public Sample(int i) {
+               int j = i+1; // violation: unused local variable
+       }
+
+       public boolean avoidUtilityClass() {
+               return true;
+       }
+
+       private String myMethod() { // violation : unused private method
+               return "hello";
+       }
+}
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/issue/tracking/IssueTrackingTest/example3-v2.txt b/sonar-batch/src/test/resources/org/sonar/batch/issue/tracking/IssueTrackingTest/example3-v2.txt
new file mode 100644 (file)
index 0000000..91db843
--- /dev/null
@@ -0,0 +1,20 @@
+package sample;
+
+public class Sample {
+
+       public Sample(int i) {
+               int j = i+1; // still the same violation: unused local variable
+       }
+       
+       public boolean avoidUtilityClass() {
+               return true;
+       }
+       
+       private String myMethod() { // violation "unused private method" is fixed because it's called in newViolation
+               return "hello";
+       }
+
+  public void newViolation() {
+    String msg = myMethod(); // new violation : msg is an unused variable
+  }
+}
index cd8bf7c1a36a3502a9afcd411eff01d6f6f794f9..2b1ecfa0b33abdb7de501a75f4ecc6c203cbd33f 100644 (file)
@@ -48,7 +48,7 @@ import java.util.SortedSet;
  * for example :
  * <pre>
  * DefaultFileSystem fs = new DefaultFileSystem();
- * fs.add(new DefaultInputFile("src/foo/bar.php"));
+ * fs.add(new DefaultInputFile("myprojectKey", "src/foo/bar.php"));
  * </pre>
  *
  * @since 4.2