]> source.dussan.org Git - sonarqube.git/commitdiff
Move issue workflow from sonar-core to sonar-server
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Wed, 24 Feb 2016 15:19:32 +0000 (16:19 +0100)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Thu, 25 Feb 2016 09:18:41 +0000 (10:18 +0100)
64 files changed:
server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueLifecycle.java
server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java
server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java
server/sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java
server/sonar-server/src/main/java/org/sonar/server/issue/workflow/Function.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/workflow/FunctionExecutor.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsBeingClosed.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsManual.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/workflow/OrCondition.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetCloseDate.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetClosed.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetResolution.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/workflow/State.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/workflow/StateMachine.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/workflow/Transition.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/workflow/UnsetAssignee.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/workflow/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueActionsWriter.java
server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseData.java
server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
server/sonar-server/src/test/java/org/sonar/server/computation/issue/IssueLifecycleTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/IssueServiceMediumTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/TransitionActionTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/workflow/IsBeingClosedTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/workflow/IsManualTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/workflow/OrConditionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/workflow/SetCloseDateTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/workflow/SetClosedTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/workflow/SetResolutionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/workflow/StateMachineTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/workflow/StateTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/workflow/TransitionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/workflow/UnsetAssigneeTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueActionsWriterTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/ws/ShowActionTest.java
sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
sonar-core/src/main/java/org/sonar/core/issue/workflow/Function.java [deleted file]
sonar-core/src/main/java/org/sonar/core/issue/workflow/FunctionExecutor.java [deleted file]
sonar-core/src/main/java/org/sonar/core/issue/workflow/IsBeingClosed.java [deleted file]
sonar-core/src/main/java/org/sonar/core/issue/workflow/IsManual.java [deleted file]
sonar-core/src/main/java/org/sonar/core/issue/workflow/IssueWorkflow.java [deleted file]
sonar-core/src/main/java/org/sonar/core/issue/workflow/OrCondition.java [deleted file]
sonar-core/src/main/java/org/sonar/core/issue/workflow/SetCloseDate.java [deleted file]
sonar-core/src/main/java/org/sonar/core/issue/workflow/SetClosed.java [deleted file]
sonar-core/src/main/java/org/sonar/core/issue/workflow/SetResolution.java [deleted file]
sonar-core/src/main/java/org/sonar/core/issue/workflow/State.java [deleted file]
sonar-core/src/main/java/org/sonar/core/issue/workflow/StateMachine.java [deleted file]
sonar-core/src/main/java/org/sonar/core/issue/workflow/Transition.java [deleted file]
sonar-core/src/main/java/org/sonar/core/issue/workflow/UnsetAssignee.java [deleted file]
sonar-core/src/main/java/org/sonar/core/issue/workflow/package-info.java [deleted file]
sonar-core/src/test/java/org/sonar/core/issue/workflow/IsBeingClosedTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/issue/workflow/IsManualTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/issue/workflow/IssueWorkflowTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/issue/workflow/OrConditionTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/issue/workflow/SetCloseDateTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/issue/workflow/SetClosedTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/issue/workflow/SetResolutionTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/issue/workflow/StateMachineTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/issue/workflow/StateTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/issue/workflow/TransitionTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/issue/workflow/UnsetAssigneeTest.java [deleted file]

index 1e9b9cd90da071602cddcc1ac3acb9da1334eff3..cc01fdf86112590398add1898622304b96d30f08 100644 (file)
@@ -26,7 +26,7 @@ import org.sonar.api.issue.Issue;
 import org.sonar.core.issue.DefaultIssue;
 import org.sonar.core.issue.IssueChangeContext;
 import org.sonar.core.issue.IssueUpdater;
-import org.sonar.core.issue.workflow.IssueWorkflow;
+import org.sonar.server.issue.workflow.IssueWorkflow;
 import org.sonar.core.util.Uuids;
 import org.sonar.server.computation.analysis.AnalysisMetadataHolder;
 
index 6112565f2aa94fc09354d55f3f86d8d238be6133..1689511d5252f31be4298ccc26104c893d16896a 100644 (file)
@@ -41,7 +41,7 @@ import org.sonar.core.issue.ActionPlanStats;
 import org.sonar.core.issue.DefaultActionPlan;
 import org.sonar.core.issue.DefaultIssueComment;
 import org.sonar.core.issue.FieldDiffs;
-import org.sonar.core.issue.workflow.Transition;
+import org.sonar.server.issue.workflow.Transition;
 import org.sonar.db.component.ResourceDao;
 import org.sonar.db.component.ResourceDto;
 import org.sonar.db.component.ResourceQuery;
index 3d5ef020ea5c2427b6d7f161d479c9080e0bb43e..c9bee6d5f740bb9e720fecfed27f45d0394490ea 100644 (file)
@@ -45,8 +45,8 @@ import org.sonar.core.issue.DefaultIssue;
 import org.sonar.core.issue.DefaultIssueBuilder;
 import org.sonar.core.issue.IssueChangeContext;
 import org.sonar.core.issue.IssueUpdater;
-import org.sonar.core.issue.workflow.IssueWorkflow;
-import org.sonar.core.issue.workflow.Transition;
+import org.sonar.server.issue.workflow.IssueWorkflow;
+import org.sonar.server.issue.workflow.Transition;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.component.ComponentDto;
index 81b3e6cecc8e6071cf43bd201943d63bcd4e7e7a..9fd9ec3500f079f35f6084736f4c701d51cceabc 100644 (file)
@@ -28,8 +28,8 @@ import org.apache.commons.lang.StringUtils;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.server.ServerSide;
 import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.workflow.IssueWorkflow;
-import org.sonar.core.issue.workflow.Transition;
+import org.sonar.server.issue.workflow.IssueWorkflow;
+import org.sonar.server.issue.workflow.Transition;
 import org.sonar.server.user.UserSession;
 
 @ServerSide
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/Function.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/Function.java
new file mode 100644 (file)
index 0000000..6d2f2ac
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+import javax.annotation.Nullable;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.user.User;
+
+interface Function {
+  interface Context {
+    Issue issue();
+
+    Context setAssignee(@Nullable User user);
+
+    Context setResolution(@Nullable String s);
+
+    Context setCloseDate(boolean b);
+
+    Context setLine(@Nullable Integer line);
+  }
+
+  void execute(Context context);
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/FunctionExecutor.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/FunctionExecutor.java
new file mode 100644 (file)
index 0000000..9b9c88b
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+import javax.annotation.Nullable;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.user.User;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
+
+@ServerSide
+public class FunctionExecutor {
+
+  private final IssueUpdater updater;
+
+  public FunctionExecutor(IssueUpdater updater) {
+    this.updater = updater;
+  }
+
+  public void execute(Function[] functions, DefaultIssue issue, IssueChangeContext changeContext) {
+    if (functions.length > 0) {
+      FunctionContext functionContext = new FunctionContext(updater, issue, changeContext);
+      for (Function function : functions) {
+        function.execute(functionContext);
+      }
+    }
+  }
+
+  static class FunctionContext implements Function.Context {
+    private final IssueUpdater updater;
+    private final DefaultIssue issue;
+    private final IssueChangeContext changeContext;
+
+    FunctionContext(IssueUpdater updater, DefaultIssue issue, IssueChangeContext changeContext) {
+      this.updater = updater;
+      this.issue = issue;
+      this.changeContext = changeContext;
+    }
+
+    @Override
+    public Issue issue() {
+      return issue;
+    }
+
+    @Override
+    public Function.Context setAssignee(@Nullable User user) {
+      updater.assign(issue, user, changeContext);
+      return this;
+    }
+
+    @Override
+    public Function.Context setResolution(@Nullable String s) {
+      updater.setResolution(issue, s, changeContext);
+      return this;
+    }
+
+    @Override
+    public Function.Context setCloseDate(boolean b) {
+      updater.setCloseDate(issue, b ? changeContext.date() : null, changeContext);
+      return this;
+    }
+
+    @Override
+    public Function.Context setLine(@Nullable Integer line) {
+      updater.setLine(issue, line);
+      return this;
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsBeingClosed.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsBeingClosed.java
new file mode 100644 (file)
index 0000000..186060e
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.condition.Condition;
+import org.sonar.core.issue.DefaultIssue;
+
+enum IsBeingClosed implements Condition {
+  INSTANCE;
+
+  @Override
+  public boolean matches(Issue issue) {
+    return ((DefaultIssue) issue).isBeingClosed();
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsManual.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsManual.java
new file mode 100644 (file)
index 0000000..56336ad
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.condition.Condition;
+
+enum IsManual implements Condition {
+  INSTANCE;
+
+  @Override
+  public boolean matches(Issue issue) {
+    return issue.ruleKey().isManual();
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java
new file mode 100644 (file)
index 0000000..bb65c57
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+import java.util.List;
+import org.picocontainer.Startable;
+import org.sonar.api.issue.DefaultTransitions;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.condition.HasResolution;
+import org.sonar.api.issue.condition.NotCondition;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
+
+@ServerSide
+public class IssueWorkflow implements Startable {
+
+  private final FunctionExecutor functionExecutor;
+  private final IssueUpdater updater;
+  private StateMachine machine;
+
+  public IssueWorkflow(FunctionExecutor functionExecutor, IssueUpdater updater) {
+    this.functionExecutor = functionExecutor;
+    this.updater = updater;
+  }
+
+  @Override
+  public void start() {
+    StateMachine.Builder builder = StateMachine.builder()
+      // order is important for UI
+      .states(Issue.STATUS_OPEN, Issue.STATUS_CONFIRMED, Issue.STATUS_REOPENED, Issue.STATUS_RESOLVED, Issue.STATUS_CLOSED);
+
+    buildManualTransitions(builder);
+    buildAutomaticTransitions(builder);
+    machine = builder.build();
+  }
+
+  private void buildManualTransitions(StateMachine.Builder builder) {
+    builder.transition(Transition.builder(DefaultTransitions.CONFIRM)
+      .from(Issue.STATUS_OPEN).to(Issue.STATUS_CONFIRMED)
+      .functions(new SetResolution(null))
+      .build())
+      .transition(Transition.builder(DefaultTransitions.CONFIRM)
+        .from(Issue.STATUS_REOPENED).to(Issue.STATUS_CONFIRMED)
+        .functions(new SetResolution(null))
+        .build())
+      .transition(Transition.builder(DefaultTransitions.UNCONFIRM)
+        .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_REOPENED)
+        .functions(new SetResolution(null))
+        .build())
+      .transition(Transition.builder(DefaultTransitions.RESOLVE)
+        .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED)
+        .functions(new SetResolution(Issue.RESOLUTION_FIXED))
+        .build())
+      .transition(Transition.builder(DefaultTransitions.RESOLVE)
+        .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED)
+        .functions(new SetResolution(Issue.RESOLUTION_FIXED))
+        .build())
+      .transition(Transition.builder(DefaultTransitions.RESOLVE)
+        .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED)
+        .functions(new SetResolution(Issue.RESOLUTION_FIXED))
+        .build())
+      .transition(Transition.builder(DefaultTransitions.REOPEN)
+        .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_REOPENED)
+        .functions(new SetResolution(null))
+        .build())
+      .transition(Transition.builder(DefaultTransitions.REOPEN)
+        .conditions(IsManual.INSTANCE)
+        .from(Issue.STATUS_CLOSED).to(Issue.STATUS_REOPENED)
+        .functions(new SetResolution(null), new SetCloseDate(false))
+        .build())
+
+    // resolve as false-positive
+      .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE)
+        .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED)
+        .functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE)
+        .requiredProjectPermission(UserRole.ISSUE_ADMIN)
+        .build())
+      .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE)
+        .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED)
+        .functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE)
+        .requiredProjectPermission(UserRole.ISSUE_ADMIN)
+        .build())
+      .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE)
+        .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED)
+        .functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE)
+        .requiredProjectPermission(UserRole.ISSUE_ADMIN)
+        .build())
+
+    // resolve as won't fix
+      .transition(Transition.builder(DefaultTransitions.WONT_FIX)
+        .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED)
+        .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE)
+        .requiredProjectPermission(UserRole.ISSUE_ADMIN)
+        .build())
+      .transition(Transition.builder(DefaultTransitions.WONT_FIX)
+        .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED)
+        .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE)
+        .requiredProjectPermission(UserRole.ISSUE_ADMIN)
+        .build())
+      .transition(Transition.builder(DefaultTransitions.WONT_FIX)
+        .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED)
+        .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE)
+        .requiredProjectPermission(UserRole.ISSUE_ADMIN)
+        .build());
+
+  }
+
+  private void buildAutomaticTransitions(StateMachine.Builder builder) {
+    // Close the "end of life" issues (disabled/deleted rule, deleted component)
+    builder
+      .transition(Transition.builder("automaticclose")
+        .from(Issue.STATUS_OPEN).to(Issue.STATUS_CLOSED)
+        .conditions(IsBeingClosed.INSTANCE)
+        .functions(SetClosed.INSTANCE, new SetCloseDate(true))
+        .automatic()
+        .build())
+      .transition(Transition.builder("automaticclose")
+        .from(Issue.STATUS_REOPENED).to(Issue.STATUS_CLOSED)
+        .conditions(IsBeingClosed.INSTANCE)
+        .functions(SetClosed.INSTANCE, new SetCloseDate(true))
+        .automatic()
+        .build())
+      .transition(Transition.builder("automaticclose")
+        .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_CLOSED)
+        .conditions(IsBeingClosed.INSTANCE)
+        .functions(SetClosed.INSTANCE, new SetCloseDate(true))
+        .automatic()
+        .build())
+      .transition(Transition.builder("automaticclose")
+        .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_CLOSED)
+        .conditions(new OrCondition(IsBeingClosed.INSTANCE, IsManual.INSTANCE))
+        .functions(SetClosed.INSTANCE, new SetCloseDate(true))
+        .automatic()
+        .build())
+
+    // Reopen issues that are marked as resolved but that are still alive.
+    // Manual issues are kept resolved.
+      .transition(Transition.builder("automaticreopen")
+        .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_REOPENED)
+        .conditions(new NotCondition(IsBeingClosed.INSTANCE), new HasResolution(Issue.RESOLUTION_FIXED), new NotCondition(IsManual.INSTANCE))
+        .functions(new SetResolution(null), new SetCloseDate(false))
+        .automatic()
+        .build());
+  }
+
+  @Override
+  public void stop() {
+    // nothing to do
+  }
+
+  public boolean doTransition(DefaultIssue issue, String transitionKey, IssueChangeContext issueChangeContext) {
+    Transition transition = stateOf(issue).transition(transitionKey);
+    if (transition != null && !transition.automatic()) {
+      functionExecutor.execute(transition.functions(), issue, issueChangeContext);
+      updater.setStatus(issue, transition.to(), issueChangeContext);
+      return true;
+    }
+    return false;
+  }
+
+  public List<Transition> outTransitions(Issue issue) {
+    State state = machine.state(issue.status());
+    if (state == null) {
+      throw new IllegalArgumentException("Unknown status: " + issue.status());
+    }
+    return state.outManualTransitions(issue);
+  }
+
+  public void doAutomaticTransition(DefaultIssue issue, IssueChangeContext issueChangeContext) {
+    Transition transition = stateOf(issue).outAutomaticTransition(issue);
+    if (transition != null) {
+      functionExecutor.execute(transition.functions(), issue, issueChangeContext);
+      updater.setStatus(issue, transition.to(), issueChangeContext);
+    }
+  }
+
+  public List<String> statusKeys() {
+    return machine.stateKeys();
+  }
+
+  private State stateOf(DefaultIssue issue) {
+    State state = machine.state(issue.status());
+    if (state == null) {
+      throw new IllegalStateException("Unknown status: " + issue.status() + " [issue=" + issue.key() + "]");
+    }
+    return state;
+  }
+
+  StateMachine machine() {
+    return machine;
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/OrCondition.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/OrCondition.java
new file mode 100644 (file)
index 0000000..9eb9239
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.condition.Condition;
+
+public class OrCondition implements Condition {
+  private final Condition[] conditions;
+
+  public OrCondition(Condition... conditions) {
+    this.conditions = conditions;
+  }
+
+  @Override
+  public boolean matches(Issue issue) {
+    for (Condition condition : conditions) {
+      if (condition.matches(issue)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetCloseDate.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetCloseDate.java
new file mode 100644 (file)
index 0000000..c5d4216
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+class SetCloseDate implements Function {
+  private final boolean set;
+
+  public SetCloseDate(boolean set) {
+    this.set = set;
+  }
+
+  @Override
+  public void execute(Context context) {
+    context.setCloseDate(set);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetClosed.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetClosed.java
new file mode 100644 (file)
index 0000000..cb2b9f2
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+import org.sonar.api.issue.Issue;
+import org.sonar.core.issue.DefaultIssue;
+
+public enum SetClosed implements Function {
+  INSTANCE;
+
+  @Override
+  public void execute(Context context) {
+    DefaultIssue issue = (DefaultIssue) context.issue();
+    if (issue.isOnDisabledRule()) {
+      context.setResolution(Issue.RESOLUTION_REMOVED);
+    } else {
+      context.setResolution(Issue.RESOLUTION_FIXED);
+    }
+
+    // closed issues are not "tracked" -> the line number does not evolve anymore
+    // when code changes. That's misleading for end-users, so line number
+    // is unset.
+    context.setLine(null);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetResolution.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetResolution.java
new file mode 100644 (file)
index 0000000..b0a5992
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+import javax.annotation.Nullable;
+
+public class SetResolution implements Function {
+  private final String resolution;
+
+  public SetResolution(@Nullable String resolution) {
+    this.resolution = resolution;
+  }
+
+  @Override
+  public void execute(Context context) {
+    context.setResolution(resolution);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/State.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/State.java
new file mode 100644 (file)
index 0000000..60510a4
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.issue.Issue;
+
+public class State {
+  private final String key;
+  private final Transition[] outTransitions;
+
+  public State(String key, Transition[] outTransitions) {
+    Preconditions.checkArgument(!Strings.isNullOrEmpty(key), "State key must be set");
+    Preconditions.checkArgument(StringUtils.isAllUpperCase(key), "State key must be upper-case");
+    checkDuplications(outTransitions, key);
+
+    this.key = key;
+    this.outTransitions = outTransitions;
+  }
+
+  private static void checkDuplications(Transition[] transitions, String stateKey) {
+    Set<String> keys = Sets.newHashSet();
+    for (Transition transition : transitions) {
+      if (keys.contains(transition.key())) {
+        throw new IllegalArgumentException("Transition '" + transition.key() +
+          "' is declared several times from the originating state '" + stateKey + "'");
+      }
+      keys.add(transition.key());
+    }
+  }
+
+  public List<Transition> outManualTransitions(Issue issue) {
+    List<Transition> result = Lists.newArrayList();
+    for (Transition transition : outTransitions) {
+      if (!transition.automatic() && transition.supports(issue)) {
+        result.add(transition);
+      }
+    }
+    return result;
+  }
+
+  @CheckForNull
+  public Transition outAutomaticTransition(Issue issue) {
+    Transition result = null;
+    for (Transition transition : outTransitions) {
+      if (transition.automatic() && transition.supports(issue)) {
+        if (result == null) {
+          result = transition;
+        } else {
+          throw new IllegalStateException("Several automatic transitions are available for issue: " + issue);
+        }
+      }
+    }
+    return result;
+  }
+
+  Transition transition(String transitionKey) {
+    for (Transition transition : outTransitions) {
+      if (transitionKey.equals(transition.key())) {
+        return transition;
+      }
+    }
+    throw new IllegalStateException("Transition from state " + key + " does not exist: " + transitionKey);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/StateMachine.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/StateMachine.java
new file mode 100644 (file)
index 0000000..f643ec9
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Sets;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+
+public class StateMachine {
+
+  private final List<String> keys;
+  private final Map<String, State> byKey;
+
+  private StateMachine(Builder builder) {
+    this.keys = ImmutableList.copyOf(builder.states);
+    ImmutableMap.Builder<String, State> mapBuilder = ImmutableMap.builder();
+    for (String stateKey : builder.states) {
+      List<Transition> outTransitions = builder.outTransitions.get(stateKey);
+      State state = new State(stateKey, outTransitions.toArray(new Transition[outTransitions.size()]));
+      mapBuilder.put(stateKey, state);
+    }
+    byKey = mapBuilder.build();
+  }
+
+  @CheckForNull
+  public State state(String stateKey) {
+    return byKey.get(stateKey);
+  }
+
+  public List<String> stateKeys() {
+    return keys;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+    private final Set<String> states = Sets.newLinkedHashSet();
+    // transitions per originating state
+    private final ListMultimap<String, Transition> outTransitions = ArrayListMultimap.create();
+
+    private Builder() {
+    }
+
+    public Builder states(String... keys) {
+      states.addAll(Arrays.asList(keys));
+      return this;
+    }
+
+    public Builder transition(Transition transition) {
+      Preconditions.checkArgument(states.contains(transition.from()), "Originating state does not exist: " + transition.from());
+      Preconditions.checkArgument(states.contains(transition.to()), "Destination state does not exist: " + transition.to());
+      outTransitions.put(transition.from(), transition);
+      return this;
+    }
+
+    public StateMachine build() {
+      Preconditions.checkArgument(!states.isEmpty(), "At least one state is required");
+      return new StateMachine(this);
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/Transition.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/Transition.java
new file mode 100644 (file)
index 0000000..a065939
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.condition.Condition;
+
+public class Transition {
+  private final String key;
+  private final String from;
+  private final String to;
+  private final Condition[] conditions;
+  private final Function[] functions;
+  private final boolean automatic;
+  private String requiredProjectPermission;
+
+  private Transition(TransitionBuilder builder) {
+    key = builder.key;
+    from = builder.from;
+    to = builder.to;
+    conditions = builder.conditions.toArray(new Condition[builder.conditions.size()]);
+    functions = builder.functions.toArray(new Function[builder.functions.size()]);
+    automatic = builder.automatic;
+    requiredProjectPermission = builder.requiredProjectPermission;
+  }
+
+  public String key() {
+    return key;
+  }
+
+  String from() {
+    return from;
+  }
+
+  String to() {
+    return to;
+  }
+
+  Condition[] conditions() {
+    return conditions;
+  }
+
+  Function[] functions() {
+    return functions;
+  }
+
+  boolean automatic() {
+    return automatic;
+  }
+
+  public boolean supports(Issue issue) {
+    for (Condition condition : conditions) {
+      if (!condition.matches(issue)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  public String requiredProjectPermission() {
+    return requiredProjectPermission;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    Transition that = (Transition) o;
+    if (!from.equals(that.from)) {
+      return false;
+    }
+    if (!key.equals(that.key)) {
+      return false;
+    }
+    return to.equals(that.to);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = key.hashCode();
+    result = 31 * result + from.hashCode();
+    result = 31 * result + to.hashCode();
+    return result;
+  }
+
+  @Override
+  public String toString() {
+    return String.format("%s->%s->%s", from, key, to);
+  }
+
+  public static Transition create(String key, String from, String to) {
+    return builder(key).from(from).to(to).build();
+  }
+
+  public static TransitionBuilder builder(String key) {
+    return new TransitionBuilder(key);
+  }
+
+  public static class TransitionBuilder {
+    private final String key;
+    private String from;
+    private String to;
+    private List<Condition> conditions = Lists.newArrayList();
+    private List<Function> functions = Lists.newArrayList();
+    private boolean automatic = false;
+    private String requiredProjectPermission;
+
+    private TransitionBuilder(String key) {
+      this.key = key;
+    }
+
+    public TransitionBuilder from(String from) {
+      this.from = from;
+      return this;
+    }
+
+    public TransitionBuilder to(String to) {
+      this.to = to;
+      return this;
+    }
+
+    public TransitionBuilder conditions(Condition... c) {
+      this.conditions.addAll(Arrays.asList(c));
+      return this;
+    }
+
+    public TransitionBuilder functions(Function... f) {
+      this.functions.addAll(Arrays.asList(f));
+      return this;
+    }
+
+    public TransitionBuilder automatic() {
+      this.automatic = true;
+      return this;
+    }
+
+    public TransitionBuilder requiredProjectPermission(String requiredProjectPermission) {
+      this.requiredProjectPermission = requiredProjectPermission;
+      return this;
+    }
+
+    public Transition build() {
+      Preconditions.checkArgument(!Strings.isNullOrEmpty(key), "Transition key must be set");
+      Preconditions.checkArgument(StringUtils.isAllLowerCase(key), "Transition key must be lower-case");
+      Preconditions.checkArgument(!Strings.isNullOrEmpty(from), "Originating status must be set");
+      Preconditions.checkArgument(!Strings.isNullOrEmpty(to), "Destination status must be set");
+      return new Transition(this);
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/UnsetAssignee.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/UnsetAssignee.java
new file mode 100644 (file)
index 0000000..8ac7cd5
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+enum UnsetAssignee implements Function {
+  INSTANCE;
+
+  @Override
+  public void execute(Context context) {
+    context.setAssignee(null);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/package-info.java
new file mode 100644 (file)
index 0000000..cf6d81f
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.issue.workflow;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
index 7c6e1dcf4c7c5456d96528dbee24db37d18f1f69..6de68b3d926d55d9b4af8388e4c21ac2c8a2a3e4 100644 (file)
@@ -22,7 +22,7 @@ package org.sonar.server.issue.ws;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.server.ServerSide;
 import org.sonar.api.utils.text.JsonWriter;
-import org.sonar.core.issue.workflow.Transition;
+import org.sonar.server.issue.workflow.Transition;
 import org.sonar.server.issue.ActionService;
 import org.sonar.server.issue.IssueService;
 import org.sonar.server.user.UserSession;
index 76597e927977ef145d9d6364b2894a285cfb7d2b..a24ccaad198a95b619341f4a3d1da3b761dfc6b9 100644 (file)
@@ -29,7 +29,7 @@ import java.util.Map;
 import java.util.Set;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
-import org.sonar.core.issue.workflow.Transition;
+import org.sonar.server.issue.workflow.Transition;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.issue.ActionPlanDto;
 import org.sonar.db.issue.IssueChangeDto;
index 8bc24de962260ddb275343f45bcee2a60dc8c663..246fe4eb118da0ecccd59c554b122a229ec4c046 100644 (file)
@@ -35,7 +35,7 @@ import org.sonar.api.utils.DateUtils;
 import org.sonar.api.utils.Duration;
 import org.sonar.api.utils.Durations;
 import org.sonar.api.utils.Paging;
-import org.sonar.core.issue.workflow.Transition;
+import org.sonar.server.issue.workflow.Transition;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.issue.ActionPlanDto;
 import org.sonar.db.issue.IssueChangeDto;
index 02fb6cd3345e790fd7ee86f5a22f930a44fa1e69..9236429f5babf26a3c3e9a04da5654bda8cc6644 100644 (file)
@@ -32,8 +32,8 @@ import org.sonar.api.rules.XMLRuleParser;
 import org.sonar.api.server.rule.RulesDefinitionXmlLoader;
 import org.sonar.core.component.DefaultResourceTypes;
 import org.sonar.core.issue.IssueUpdater;
-import org.sonar.core.issue.workflow.FunctionExecutor;
-import org.sonar.core.issue.workflow.IssueWorkflow;
+import org.sonar.server.issue.workflow.FunctionExecutor;
+import org.sonar.server.issue.workflow.IssueWorkflow;
 import org.sonar.core.timemachine.Periods;
 import org.sonar.core.user.DefaultUserFinder;
 import org.sonar.core.user.DeprecatedUserFinder;
index 19eb16134df7a6b2121ac2e428bbaac7fac7df91..844d0ff8bbf1795847f134d3c7979dc10236581d 100644 (file)
@@ -26,7 +26,7 @@ import org.sonar.api.utils.Duration;
 import org.sonar.core.issue.DefaultIssue;
 import org.sonar.core.issue.IssueChangeContext;
 import org.sonar.core.issue.IssueUpdater;
-import org.sonar.core.issue.workflow.IssueWorkflow;
+import org.sonar.server.issue.workflow.IssueWorkflow;
 import org.sonar.db.protobuf.DbCommons;
 import org.sonar.db.protobuf.DbIssues;
 
index b0c47aa7ea02dab4246d3c7cfe0da7825dfa6ad3..4544480729ca23910f16774bb881fbce14f5ff86 100644 (file)
@@ -36,7 +36,7 @@ import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.Severity;
 import org.sonar.api.security.DefaultGroups;
 import org.sonar.api.web.UserRole;
-import org.sonar.core.issue.workflow.Transition;
+import org.sonar.server.issue.workflow.Transition;
 import org.sonar.core.permission.GlobalPermissions;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
index 087531cff55e27df3f9360cf5458829f08dee582..2ea9787433bd974fad9d7f00134d3e46f0de7dda 100644 (file)
@@ -27,8 +27,8 @@ import org.junit.Test;
 import org.sonar.api.issue.Issue;
 import org.sonar.core.issue.DefaultIssue;
 import org.sonar.core.issue.IssueChangeContext;
-import org.sonar.core.issue.workflow.IssueWorkflow;
-import org.sonar.core.issue.workflow.Transition;
+import org.sonar.server.issue.workflow.IssueWorkflow;
+import org.sonar.server.issue.workflow.Transition;
 import org.sonar.server.tester.UserSessionRule;
 
 import static com.google.common.collect.Lists.newArrayList;
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/IsBeingClosedTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/IsBeingClosedTest.java
new file mode 100644 (file)
index 0000000..62ebe57
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+import org.junit.Test;
+import org.sonar.core.issue.DefaultIssue;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.issue.workflow.IsBeingClosed.INSTANCE;
+
+public class IsBeingClosedTest {
+
+  @Test
+  public void should_be_end_of_life() {
+    DefaultIssue issue = new DefaultIssue();
+    assertThat(INSTANCE.matches(issue.setBeingClosed(true))).isTrue();
+    assertThat(INSTANCE.matches(issue.setBeingClosed(false))).isFalse();
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/IsManualTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/IsManualTest.java
new file mode 100644 (file)
index 0000000..c845803
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+import org.junit.Test;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.core.issue.DefaultIssue;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.issue.workflow.IsManual.INSTANCE;
+
+public class IsManualTest {
+
+  @Test
+  public void should_match() {
+    DefaultIssue issue = new DefaultIssue();
+    assertThat(INSTANCE.matches(issue.setRuleKey(RuleKey.of(RuleKey.MANUAL_REPOSITORY_KEY, "R1")))).isTrue();
+    assertThat(INSTANCE.matches(issue.setRuleKey(RuleKey.of("java", "R1")))).isFalse();
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowTest.java
new file mode 100644 (file)
index 0000000..1900c2c
--- /dev/null
@@ -0,0 +1,432 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.time.DateUtils;
+import org.junit.Test;
+import org.sonar.api.issue.DefaultTransitions;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE;
+import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
+import static org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX;
+import static org.sonar.api.issue.Issue.STATUS_CLOSED;
+import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
+import static org.sonar.api.issue.Issue.STATUS_OPEN;
+import static org.sonar.api.issue.Issue.STATUS_REOPENED;
+import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
+import static org.sonar.api.rule.RuleKey.MANUAL_REPOSITORY_KEY;
+
+public class IssueWorkflowTest {
+
+  IssueUpdater updater = new IssueUpdater();
+  IssueWorkflow workflow = new IssueWorkflow(new FunctionExecutor(updater), updater);
+
+  @Test
+  public void init_state_machine() {
+    assertThat(workflow.machine()).isNull();
+    workflow.start();
+    assertThat(workflow.machine()).isNotNull();
+    assertThat(workflow.machine().state(STATUS_OPEN)).isNotNull();
+    assertThat(workflow.machine().state(STATUS_CONFIRMED)).isNotNull();
+    assertThat(workflow.machine().state(STATUS_CLOSED)).isNotNull();
+    assertThat(workflow.machine().state(STATUS_REOPENED)).isNotNull();
+    assertThat(workflow.machine().state(STATUS_RESOLVED)).isNotNull();
+    workflow.stop();
+  }
+
+  @Test
+  public void list_statuses() {
+    workflow.start();
+    // order is important for UI
+    assertThat(workflow.statusKeys()).containsSequence(STATUS_OPEN, STATUS_CONFIRMED, STATUS_REOPENED, STATUS_RESOLVED, STATUS_CLOSED);
+  }
+
+  @Test
+  public void list_out_transitions_from_status_open() {
+    workflow.start();
+
+    DefaultIssue issue = new DefaultIssue().setStatus(STATUS_OPEN);
+    List<Transition> transitions = workflow.outTransitions(issue);
+    assertThat(keys(transitions)).containsOnly("confirm", "falsepositive", "resolve", "wontfix");
+  }
+
+  @Test
+  public void list_out_transitions_from_status_confirmed() {
+    workflow.start();
+
+    DefaultIssue issue = new DefaultIssue().setStatus(STATUS_CONFIRMED);
+    List<Transition> transitions = workflow.outTransitions(issue);
+    assertThat(keys(transitions)).containsOnly("unconfirm", "falsepositive", "resolve", "wontfix");
+  }
+
+  @Test
+  public void list_out_transitions_from_status_resolved() {
+    workflow.start();
+
+    DefaultIssue issue = new DefaultIssue().setStatus(STATUS_RESOLVED);
+    List<Transition> transitions = workflow.outTransitions(issue);
+    assertThat(keys(transitions)).containsOnly("reopen");
+  }
+
+  @Test
+  public void list_out_transitions_from_status_reopen() {
+    workflow.start();
+
+    DefaultIssue issue = new DefaultIssue().setStatus(STATUS_REOPENED);
+    List<Transition> transitions = workflow.outTransitions(issue);
+    assertThat(keys(transitions)).containsOnly("confirm", "resolve", "falsepositive", "wontfix");
+  }
+
+  @Test
+  public void list_no_out_transition_from_status_closed() {
+    workflow.start();
+
+    DefaultIssue issue = new DefaultIssue().setStatus(STATUS_CLOSED).setRuleKey(RuleKey.of("java", "R1  "));
+    List<Transition> transitions = workflow.outTransitions(issue);
+    assertThat(transitions).isEmpty();
+  }
+
+  @Test
+  public void list_out_transitions_from_status_closed_on_manual_issue() {
+    workflow.start();
+
+    // Manual issue because of reporter
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setStatus(STATUS_CLOSED)
+      .setRuleKey(RuleKey.of("manual", "Performance"))
+      .setReporter("simon");
+
+    List<Transition> transitions = workflow.outTransitions(issue);
+    assertThat(keys(transitions)).containsOnly("reopen");
+  }
+
+  @Test
+  public void fail_if_unknown_status_when_listing_transitions() {
+    workflow.start();
+
+    DefaultIssue issue = new DefaultIssue().setStatus("xxx");
+    try {
+      workflow.outTransitions(issue);
+      fail();
+    } catch (IllegalArgumentException e) {
+      assertThat(e).hasMessage("Unknown status: xxx");
+    }
+  }
+
+  @Test
+  public void automatically_close_resolved_issue() {
+    workflow.start();
+
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setRuleKey(RuleKey.of("js", "S001"))
+      .setResolution(RESOLUTION_FIXED)
+      .setStatus(STATUS_RESOLVED)
+      .setNew(false)
+      .setBeingClosed(true);
+    Date now = new Date();
+    workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
+    assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
+    assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
+    assertThat(issue.closeDate()).isNotNull();
+    assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
+  }
+
+  @Test
+  public void close_open_dead_issue() {
+    workflow.start();
+
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setResolution(null)
+      .setStatus(STATUS_OPEN)
+      .setNew(false)
+      .setBeingClosed(true);
+    Date now = new Date();
+    workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
+    assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
+    assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
+    assertThat(issue.closeDate()).isNotNull();
+    assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
+  }
+
+  @Test
+  public void close_reopened_dead_issue() {
+    workflow.start();
+
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setResolution(null)
+      .setStatus(STATUS_REOPENED)
+      .setNew(false)
+      .setBeingClosed(true);
+    Date now = new Date();
+    workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
+    assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
+    assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
+    assertThat(issue.closeDate()).isNotNull();
+    assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
+  }
+
+  @Test
+  public void close_confirmed_dead_issue() {
+    workflow.start();
+
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setResolution(null)
+      .setStatus(STATUS_CONFIRMED)
+      .setNew(false)
+      .setBeingClosed(true);
+    Date now = new Date();
+    workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
+    assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
+    assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
+    assertThat(issue.closeDate()).isNotNull();
+    assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
+  }
+
+  @Test
+  public void fail_if_unknown_status_on_automatic_trans() {
+    workflow.start();
+
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setResolution(RESOLUTION_FIXED)
+      .setStatus("xxx")
+      .setNew(false)
+      .setBeingClosed(true);
+    try {
+      workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(new Date()));
+      fail();
+    } catch (IllegalStateException e) {
+      assertThat(e).hasMessage("Unknown status: xxx [issue=ABCDE]");
+    }
+  }
+
+  @Test
+  public void flag_as_false_positive() {
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setStatus(STATUS_OPEN)
+      .setRuleKey(RuleKey.of("squid", "AvoidCycle"))
+      .setAssignee("morgan");
+
+    workflow.start();
+    workflow.doTransition(issue, DefaultTransitions.FALSE_POSITIVE, IssueChangeContext.createScan(new Date()));
+
+    assertThat(issue.resolution()).isEqualTo(RESOLUTION_FALSE_POSITIVE);
+    assertThat(issue.status()).isEqualTo(STATUS_RESOLVED);
+
+    // should remove assignee
+    assertThat(issue.assignee()).isNull();
+  }
+
+  @Test
+  public void wont_fix() {
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setStatus(STATUS_OPEN)
+      .setRuleKey(RuleKey.of("squid", "AvoidCycle"))
+      .setAssignee("morgan");
+
+    workflow.start();
+    workflow.doTransition(issue, DefaultTransitions.WONT_FIX, IssueChangeContext.createScan(new Date()));
+
+    assertThat(issue.resolution()).isEqualTo(RESOLUTION_WONT_FIX);
+    assertThat(issue.status()).isEqualTo(STATUS_RESOLVED);
+
+    // should remove assignee
+    assertThat(issue.assignee()).isNull();
+  }
+
+  /**
+   * User marks the manual issue as resolved -> issue is automatically
+   * closed.
+   */
+  @Test
+  public void automatically_close_resolved_manual_issue() {
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setStatus(STATUS_OPEN)
+      .setRuleKey(RuleKey.of(MANUAL_REPOSITORY_KEY, "Performance"));
+
+    workflow.start();
+
+    assertThat(workflow.outTransitions(issue)).containsOnly(
+      Transition.create("confirm", "OPEN", "CONFIRMED"),
+      Transition.create("resolve", "OPEN", "RESOLVED"),
+      Transition.create("falsepositive", "OPEN", "RESOLVED"),
+      Transition.create("wontfix", "OPEN", "RESOLVED"));
+
+    workflow.doTransition(issue, "resolve", mock(IssueChangeContext.class));
+    assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
+    assertThat(issue.status()).isEqualTo("RESOLVED");
+
+    assertThat(workflow.outTransitions(issue)).containsOnly(
+      Transition.create("reopen", "RESOLVED", "REOPENED"));
+
+    workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
+    assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
+    assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
+  }
+
+  /**
+   * Manual issue is fixed because the file does not exist anymore
+   * or the tracking engine did not find the associated code
+   * -> the issue is closed
+   */
+  @Test
+  public void automatically_close_manual_issue_on_deleted_code() {
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setStatus(STATUS_OPEN)
+      .setRuleKey(RuleKey.of(MANUAL_REPOSITORY_KEY, "Performance"))
+      .setBeingClosed(true);
+
+    workflow.start();
+
+    workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
+    assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
+    assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
+  }
+
+  /**
+   * Corner-case : the manual issue was marked as resolved by user but at the same 
+   * time the file or the associated line was deleted.
+   */
+  @Test
+  public void automatically_close_resolved_manual_issue_on_deleted_code() {
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setRuleKey(RuleKey.of(MANUAL_REPOSITORY_KEY, "Performance"))
+
+    // resolved by user
+      .setResolution(RESOLUTION_FIXED)
+      .setStatus(STATUS_RESOLVED)
+
+    // but unmatched by tracking engine
+      .setBeingClosed(true);
+
+    workflow.start();
+
+    workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
+    assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
+    assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
+  }
+
+  @Test
+  public void manual_issues_be_confirmed_then_kept_open() {
+    // Manual issue because of reporter
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setStatus(STATUS_OPEN)
+      .setRuleKey(RuleKey.of("manual", "Performance"))
+      .setReporter("simon");
+
+    workflow.start();
+
+    assertThat(workflow.outTransitions(issue)).containsOnly(
+      Transition.create("confirm", "OPEN", "CONFIRMED"),
+      Transition.create("resolve", "OPEN", "RESOLVED"),
+      Transition.create("falsepositive", "OPEN", "RESOLVED"),
+      Transition.create("wontfix", "OPEN", "RESOLVED"));
+
+    workflow.doTransition(issue, "confirm", mock(IssueChangeContext.class));
+    assertThat(issue.resolution()).isNull();
+    assertThat(issue.status()).isEqualTo("CONFIRMED");
+
+    assertThat(workflow.outTransitions(issue)).containsOnly(
+      Transition.create("unconfirm", "CONFIRMED", "REOPENED"),
+      Transition.create("resolve", "CONFIRMED", "RESOLVED"),
+      Transition.create("falsepositive", "CONFIRMED", "RESOLVED"),
+      Transition.create("wontfix", "CONFIRMED", "RESOLVED"));
+
+    // keep confirmed and unresolved
+    workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
+    assertThat(issue.resolution()).isNull();
+    assertThat(issue.status()).isEqualTo("CONFIRMED");
+
+    // unconfirm
+    workflow.doTransition(issue, "unconfirm", mock(IssueChangeContext.class));
+    assertThat(issue.resolution()).isNull();
+    assertThat(issue.status()).isEqualTo("REOPENED");
+  }
+
+  @Test
+  public void manual_issue_on_removed_rule_be_closed() {
+    // Manual issue because of reporter
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setStatus(STATUS_OPEN)
+      .setRuleKey(RuleKey.of("manual", "Performance"))
+      .setReporter("simon")
+      .setBeingClosed(true)
+      .setOnDisabledRule(true);
+
+    workflow.start();
+
+    workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
+    assertThat(issue.resolution()).isEqualTo("REMOVED");
+    assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
+  }
+
+  @Test
+  public void manual_issue_on_removed_component_be_closed() {
+    // Manual issue because of reporter
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setStatus(STATUS_OPEN)
+      .setRuleKey(RuleKey.of("manual", "Performance"))
+      .setReporter("simon")
+      .setBeingClosed(true)
+      .setOnDisabledRule(false);
+
+    workflow.start();
+
+    workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
+    assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
+    assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
+  }
+
+  private Collection<String> keys(List<Transition> transitions) {
+    return Collections2.transform(transitions, new Function<Transition, String>() {
+      @Override
+      public String apply(@Nullable Transition transition) {
+        return transition.key();
+      }
+    });
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/OrConditionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/OrConditionTest.java
new file mode 100644 (file)
index 0000000..7b72a1a
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+import org.junit.Test;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.condition.Condition;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class OrConditionTest {
+
+  private static final Condition TRUE_CONDITION = new BooleanCondition(true);
+  private static final Condition FALSE_CONDITION = new BooleanCondition(false);
+  Issue issue = mock(Issue.class);
+
+  @Test
+  public void match() {
+    assertThat(new OrCondition(TRUE_CONDITION).matches(issue)).isTrue();
+    assertThat(new OrCondition(FALSE_CONDITION).matches(issue)).isFalse();
+    assertThat(new OrCondition(FALSE_CONDITION, TRUE_CONDITION).matches(issue)).isTrue();
+    assertThat(new OrCondition(FALSE_CONDITION, FALSE_CONDITION).matches(issue)).isFalse();
+  }
+
+  private static class BooleanCondition implements Condition {
+    private final boolean b;
+
+    public BooleanCondition(boolean b) {
+      this.b = b;
+    }
+
+    @Override
+    public boolean matches(Issue issue) {
+      return b;
+    }
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/SetCloseDateTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/SetCloseDateTest.java
new file mode 100644 (file)
index 0000000..09ab2d4
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+import org.junit.Test;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+public class SetCloseDateTest {
+  @Test
+  public void should_set_close_date() {
+    SetCloseDate function = new SetCloseDate(true);
+    Function.Context context = mock(Function.Context.class);
+    function.execute(context);
+    verify(context, times(1)).setCloseDate(true);
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/SetClosedTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/SetClosedTest.java
new file mode 100644 (file)
index 0000000..3638ebd
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+import org.junit.Test;
+import org.sonar.api.issue.Issue;
+import org.sonar.core.issue.DefaultIssue;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.issue.workflow.SetClosed.INSTANCE;
+
+public class SetClosedTest {
+
+  Function.Context context = mock(Function.Context.class);
+
+  @Test
+  public void should_resolve_as_fixed() {
+    Issue issue = new DefaultIssue().setBeingClosed(true).setOnDisabledRule(false);
+    when(context.issue()).thenReturn(issue);
+    INSTANCE.execute(context);
+    verify(context, times(1)).setResolution(Issue.RESOLUTION_FIXED);
+  }
+
+  @Test
+  public void should_resolve_as_removed_when_rule_is_disabled() {
+    Issue issue = new DefaultIssue().setBeingClosed(true).setOnDisabledRule(true);
+    when(context.issue()).thenReturn(issue);
+    INSTANCE.execute(context);
+    verify(context, times(1)).setResolution(Issue.RESOLUTION_REMOVED);
+  }
+
+  @Test
+  public void line_number_must_be_unset() {
+    Issue issue = new DefaultIssue().setBeingClosed(true).setLine(10);
+    when(context.issue()).thenReturn(issue);
+    INSTANCE.execute(context);
+    verify(context).setLine(null);
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/SetResolutionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/SetResolutionTest.java
new file mode 100644 (file)
index 0000000..ed397d3
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+import org.junit.Test;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+public class SetResolutionTest {
+  @Test
+  public void execute() {
+    SetResolution function = new SetResolution("FIXED");
+    Function.Context context = mock(Function.Context.class);
+    function.execute(context);
+    verify(context, times(1)).setResolution("FIXED");
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/StateMachineTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/StateMachineTest.java
new file mode 100644 (file)
index 0000000..222a7d4
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class StateMachineTest {
+  @Test
+  public void keep_order_of_state_keys() {
+    StateMachine machine = StateMachine.builder().states("OPEN", "RESOLVED", "CLOSED").build();
+
+    assertThat(machine.stateKeys()).containsSequence("OPEN", "RESOLVED", "CLOSED");
+  }
+
+  @Test
+  public void stateKey() {
+    StateMachine machine = StateMachine.builder()
+      .states("OPEN", "RESOLVED", "CLOSED")
+      .transition(Transition.builder("resolve").from("OPEN").to("RESOLVED").build())
+      .build();
+
+    assertThat(machine.state("OPEN")).isNotNull();
+    assertThat(machine.state("OPEN").transition("resolve")).isNotNull();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/StateTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/StateTest.java
new file mode 100644 (file)
index 0000000..7677c3e
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+
+public class StateTest {
+
+  Transition t1 = Transition.builder("close").from("OPEN").to("CLOSED").build();
+
+  @Test
+  public void key_should_be_set() {
+    try {
+      new State("", new Transition[0]);
+      fail();
+    } catch (IllegalArgumentException e) {
+      assertThat(e).hasMessage("State key must be set");
+    }
+  }
+
+  @Test
+  public void key_should_be_upper_case() {
+    try {
+      new State("close", new Transition[0]);
+      fail();
+    } catch (IllegalArgumentException e) {
+      assertThat(e).hasMessage("State key must be upper-case");
+    }
+  }
+
+  @Test
+  public void no_duplicated_out_transitions() {
+    try {
+      new State("CLOSE", new Transition[] {t1, t1});
+      fail();
+    } catch (IllegalArgumentException e) {
+      assertThat(e).hasMessage("Transition 'close' is declared several times from the originating state 'CLOSE'");
+    }
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/TransitionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/TransitionTest.java
new file mode 100644 (file)
index 0000000..a8535a0
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+import org.junit.Test;
+import org.sonar.api.issue.condition.Condition;
+import org.sonar.core.issue.DefaultIssue;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TransitionTest {
+
+  Condition condition1 = mock(Condition.class);
+  Condition condition2 = mock(Condition.class);
+  Function function1 = mock(Function.class);
+  Function function2 = mock(Function.class);
+
+  @Test
+  public void test_builder() throws Exception {
+    Transition transition = Transition.builder("close")
+      .from("OPEN").to("CLOSED")
+      .conditions(condition1, condition2)
+      .functions(function1, function2)
+      .build();
+    assertThat(transition.key()).isEqualTo("close");
+    assertThat(transition.from()).isEqualTo("OPEN");
+    assertThat(transition.to()).isEqualTo("CLOSED");
+    assertThat(transition.conditions()).containsOnly(condition1, condition2);
+    assertThat(transition.functions()).containsOnly(function1, function2);
+    assertThat(transition.automatic()).isFalse();
+  }
+
+  @Test
+  public void test_simplest_transition() throws Exception {
+    Transition transition = Transition.builder("close")
+      .from("OPEN").to("CLOSED")
+      .build();
+    assertThat(transition.key()).isEqualTo("close");
+    assertThat(transition.from()).isEqualTo("OPEN");
+    assertThat(transition.to()).isEqualTo("CLOSED");
+    assertThat(transition.conditions()).isEmpty();
+    assertThat(transition.functions()).isEmpty();
+  }
+
+  @Test
+  public void key_should_be_set() {
+    try {
+      Transition.builder("").from("OPEN").to("CLOSED").build();
+      fail();
+    } catch (Exception e) {
+      assertThat(e).hasMessage("Transition key must be set");
+    }
+  }
+
+  @Test
+  public void key_should_be_lower_case() {
+    try {
+      Transition.builder("CLOSE").from("OPEN").to("CLOSED").build();
+      fail();
+    } catch (Exception e) {
+      assertThat(e).hasMessage("Transition key must be lower-case");
+    }
+  }
+
+  @Test
+  public void originating_status_should_be_set() {
+    try {
+      Transition.builder("close").from("").to("CLOSED").build();
+      fail();
+    } catch (Exception e) {
+      assertThat(e).hasMessage("Originating status must be set");
+    }
+  }
+
+  @Test
+  public void destination_status_should_be_set() {
+    try {
+      Transition.builder("close").from("OPEN").to("").build();
+      fail();
+    } catch (Exception e) {
+      assertThat(e).hasMessage("Destination status must be set");
+    }
+  }
+
+  @Test
+  public void should_verify_conditions() {
+    DefaultIssue issue = new DefaultIssue();
+    Transition transition = Transition.builder("close")
+      .from("OPEN").to("CLOSED")
+      .conditions(condition1, condition2)
+      .build();
+
+    when(condition1.matches(issue)).thenReturn(true);
+    when(condition2.matches(issue)).thenReturn(false);
+    assertThat(transition.supports(issue)).isFalse();
+
+    when(condition1.matches(issue)).thenReturn(true);
+    when(condition2.matches(issue)).thenReturn(true);
+    assertThat(transition.supports(issue)).isTrue();
+  }
+
+  @Test
+  public void test_equals_and_hashCode() throws Exception {
+    Transition t1 = Transition.create("resolve", "OPEN", "RESOLVED");
+    Transition t2 = Transition.create("resolve", "REOPENED", "RESOLVED");
+    Transition t3 = Transition.create("confirm", "OPEN", "CONFIRMED");
+
+    assertThat(t1).isNotEqualTo(t2);
+    assertThat(t1).isNotEqualTo(t3);
+    assertThat(t1).isEqualTo(t1);
+
+    assertThat(t1.hashCode()).isEqualTo(t1.hashCode());
+  }
+
+  @Test
+  public void test_toString() throws Exception {
+    Transition t1 = Transition.create("resolve", "OPEN", "RESOLVED");
+    assertThat(t1.toString()).isEqualTo("OPEN->resolve->RESOLVED");
+  }
+
+  @Test
+  public void test_automatic_transition() throws Exception {
+    Transition transition = Transition.builder("close")
+      .from("OPEN").to("CLOSED")
+      .automatic()
+      .build();
+    assertThat(transition.automatic()).isTrue();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/UnsetAssigneeTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/workflow/UnsetAssigneeTest.java
new file mode 100644 (file)
index 0000000..8ed5c83
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+import org.junit.Test;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.sonar.server.issue.workflow.UnsetAssignee.INSTANCE;
+
+public class UnsetAssigneeTest {
+
+  @Test
+  public void unassign() {
+    Function.Context context = mock(Function.Context.class);
+    INSTANCE.execute(context);
+    verify(context, times(1)).setAssignee(null);
+  }
+}
index 1aeb60521bcd26d2069115c90c029c68a30a460d..8db1776c588958780569ccdcc87de87f81711cc3 100644 (file)
@@ -30,7 +30,7 @@ import org.sonar.api.issue.Issue;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.utils.text.JsonWriter;
 import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.workflow.Transition;
+import org.sonar.server.issue.workflow.Transition;
 import org.sonar.server.issue.ActionService;
 import org.sonar.server.issue.IssueService;
 import org.sonar.server.tester.UserSessionRule;
index 973defab5c9cbc6f766e0e34be0906a48c1d1512..67ad2c679d434312cdc249a2fc71b048432982bf 100644 (file)
@@ -41,7 +41,7 @@ import org.sonar.core.issue.DefaultActionPlan;
 import org.sonar.core.issue.DefaultIssue;
 import org.sonar.core.issue.DefaultIssueComment;
 import org.sonar.core.issue.FieldDiffs;
-import org.sonar.core.issue.workflow.Transition;
+import org.sonar.server.issue.workflow.Transition;
 import org.sonar.core.user.DefaultUser;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
index 2e265e2640df200ab933a596d416b227154d9cae..1c7d13a0df5a41bff371a663f7b3d576a71be639 100644 (file)
@@ -91,7 +91,6 @@ import org.sonar.batch.scan.measure.MeasureCache;
 import org.sonar.batch.source.CodeColorizers;
 import org.sonar.batch.test.TestPlanBuilder;
 import org.sonar.batch.test.TestableBuilder;
-import org.sonar.core.issue.workflow.FunctionExecutor;
 import org.sonar.core.metric.BatchMetrics;
 import org.sonar.core.platform.ComponentContainer;
 
@@ -175,7 +174,6 @@ public class ProjectScanContainer extends ComponentContainer {
       new QualityProfileProvider(),
 
       // issues
-      FunctionExecutor.class,
       IssueCache.class,
       DefaultProjectIssues.class,
       IssueTransition.class,
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/Function.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/Function.java
deleted file mode 100644 (file)
index 66bd577..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-import javax.annotation.Nullable;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.user.User;
-
-interface Function {
-  interface Context {
-    Issue issue();
-
-    Context setAssignee(@Nullable User user);
-
-    Context setResolution(@Nullable String s);
-
-    Context setCloseDate(boolean b);
-
-    Context setLine(@Nullable Integer line);
-  }
-
-  void execute(Context context);
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/FunctionExecutor.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/FunctionExecutor.java
deleted file mode 100644 (file)
index 316dfe1..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-import javax.annotation.Nullable;
-import org.sonar.api.batch.BatchSide;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.server.ServerSide;
-import org.sonar.api.user.User;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.IssueChangeContext;
-import org.sonar.core.issue.IssueUpdater;
-
-@BatchSide
-@ServerSide
-public class FunctionExecutor {
-
-  private final IssueUpdater updater;
-
-  public FunctionExecutor(IssueUpdater updater) {
-    this.updater = updater;
-  }
-
-  public void execute(Function[] functions, DefaultIssue issue, IssueChangeContext changeContext) {
-    if (functions.length > 0) {
-      FunctionContext functionContext = new FunctionContext(updater, issue, changeContext);
-      for (Function function : functions) {
-        function.execute(functionContext);
-      }
-    }
-  }
-
-  static class FunctionContext implements Function.Context {
-    private final IssueUpdater updater;
-    private final DefaultIssue issue;
-    private final IssueChangeContext changeContext;
-
-    FunctionContext(IssueUpdater updater, DefaultIssue issue, IssueChangeContext changeContext) {
-      this.updater = updater;
-      this.issue = issue;
-      this.changeContext = changeContext;
-    }
-
-    @Override
-    public Issue issue() {
-      return issue;
-    }
-
-    @Override
-    public Function.Context setAssignee(@Nullable User user) {
-      updater.assign(issue, user, changeContext);
-      return this;
-    }
-
-    @Override
-    public Function.Context setResolution(@Nullable String s) {
-      updater.setResolution(issue, s, changeContext);
-      return this;
-    }
-
-    @Override
-    public Function.Context setCloseDate(boolean b) {
-      updater.setCloseDate(issue, b ? changeContext.date() : null, changeContext);
-      return this;
-    }
-
-    @Override
-    public Function.Context setLine(@Nullable Integer line) {
-      updater.setLine(issue, line);
-      return this;
-    }
-  }
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/IsBeingClosed.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/IsBeingClosed.java
deleted file mode 100644 (file)
index daf3a2e..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-import org.sonar.api.issue.Issue;
-import org.sonar.api.issue.condition.Condition;
-import org.sonar.core.issue.DefaultIssue;
-
-enum IsBeingClosed implements Condition {
-  INSTANCE;
-
-  @Override
-  public boolean matches(Issue issue) {
-    return ((DefaultIssue) issue).isBeingClosed();
-  }
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/IsManual.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/IsManual.java
deleted file mode 100644 (file)
index d47a98a..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-import org.sonar.api.issue.Issue;
-import org.sonar.api.issue.condition.Condition;
-
-enum IsManual implements Condition {
-  INSTANCE;
-
-  @Override
-  public boolean matches(Issue issue) {
-    return issue.ruleKey().isManual();
-  }
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/IssueWorkflow.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/IssueWorkflow.java
deleted file mode 100644 (file)
index 8ceb48e..0000000
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-import java.util.List;
-import org.picocontainer.Startable;
-import org.sonar.api.issue.DefaultTransitions;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.issue.condition.HasResolution;
-import org.sonar.api.issue.condition.NotCondition;
-import org.sonar.api.server.ServerSide;
-import org.sonar.api.web.UserRole;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.IssueChangeContext;
-import org.sonar.core.issue.IssueUpdater;
-
-@ServerSide
-public class IssueWorkflow implements Startable {
-
-  private final FunctionExecutor functionExecutor;
-  private final IssueUpdater updater;
-  private StateMachine machine;
-
-  public IssueWorkflow(FunctionExecutor functionExecutor, IssueUpdater updater) {
-    this.functionExecutor = functionExecutor;
-    this.updater = updater;
-  }
-
-  @Override
-  public void start() {
-    StateMachine.Builder builder = StateMachine.builder()
-      // order is important for UI
-      .states(Issue.STATUS_OPEN, Issue.STATUS_CONFIRMED, Issue.STATUS_REOPENED, Issue.STATUS_RESOLVED, Issue.STATUS_CLOSED);
-
-    buildManualTransitions(builder);
-    buildAutomaticTransitions(builder);
-    machine = builder.build();
-  }
-
-  private void buildManualTransitions(StateMachine.Builder builder) {
-    builder.transition(Transition.builder(DefaultTransitions.CONFIRM)
-      .from(Issue.STATUS_OPEN).to(Issue.STATUS_CONFIRMED)
-      .functions(new SetResolution(null))
-      .build())
-      .transition(Transition.builder(DefaultTransitions.CONFIRM)
-        .from(Issue.STATUS_REOPENED).to(Issue.STATUS_CONFIRMED)
-        .functions(new SetResolution(null))
-        .build())
-      .transition(Transition.builder(DefaultTransitions.UNCONFIRM)
-        .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_REOPENED)
-        .functions(new SetResolution(null))
-        .build())
-      .transition(Transition.builder(DefaultTransitions.RESOLVE)
-        .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED)
-        .functions(new SetResolution(Issue.RESOLUTION_FIXED))
-        .build())
-      .transition(Transition.builder(DefaultTransitions.RESOLVE)
-        .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED)
-        .functions(new SetResolution(Issue.RESOLUTION_FIXED))
-        .build())
-      .transition(Transition.builder(DefaultTransitions.RESOLVE)
-        .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED)
-        .functions(new SetResolution(Issue.RESOLUTION_FIXED))
-        .build())
-      .transition(Transition.builder(DefaultTransitions.REOPEN)
-        .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_REOPENED)
-        .functions(new SetResolution(null))
-        .build())
-      .transition(Transition.builder(DefaultTransitions.REOPEN)
-        .conditions(IsManual.INSTANCE)
-        .from(Issue.STATUS_CLOSED).to(Issue.STATUS_REOPENED)
-        .functions(new SetResolution(null), new SetCloseDate(false))
-        .build())
-
-    // resolve as false-positive
-      .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE)
-        .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED)
-        .functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE)
-        .requiredProjectPermission(UserRole.ISSUE_ADMIN)
-        .build())
-      .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE)
-        .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED)
-        .functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE)
-        .requiredProjectPermission(UserRole.ISSUE_ADMIN)
-        .build())
-      .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE)
-        .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED)
-        .functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE)
-        .requiredProjectPermission(UserRole.ISSUE_ADMIN)
-        .build())
-
-    // resolve as won't fix
-      .transition(Transition.builder(DefaultTransitions.WONT_FIX)
-        .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED)
-        .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE)
-        .requiredProjectPermission(UserRole.ISSUE_ADMIN)
-        .build())
-      .transition(Transition.builder(DefaultTransitions.WONT_FIX)
-        .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED)
-        .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE)
-        .requiredProjectPermission(UserRole.ISSUE_ADMIN)
-        .build())
-      .transition(Transition.builder(DefaultTransitions.WONT_FIX)
-        .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED)
-        .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE)
-        .requiredProjectPermission(UserRole.ISSUE_ADMIN)
-        .build());
-
-  }
-
-  private void buildAutomaticTransitions(StateMachine.Builder builder) {
-    // Close the "end of life" issues (disabled/deleted rule, deleted component)
-    builder
-      .transition(Transition.builder("automaticclose")
-        .from(Issue.STATUS_OPEN).to(Issue.STATUS_CLOSED)
-        .conditions(IsBeingClosed.INSTANCE)
-        .functions(SetClosed.INSTANCE, new SetCloseDate(true))
-        .automatic()
-        .build())
-      .transition(Transition.builder("automaticclose")
-        .from(Issue.STATUS_REOPENED).to(Issue.STATUS_CLOSED)
-        .conditions(IsBeingClosed.INSTANCE)
-        .functions(SetClosed.INSTANCE, new SetCloseDate(true))
-        .automatic()
-        .build())
-      .transition(Transition.builder("automaticclose")
-        .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_CLOSED)
-        .conditions(IsBeingClosed.INSTANCE)
-        .functions(SetClosed.INSTANCE, new SetCloseDate(true))
-        .automatic()
-        .build())
-      .transition(Transition.builder("automaticclose")
-        .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_CLOSED)
-        .conditions(new OrCondition(IsBeingClosed.INSTANCE, IsManual.INSTANCE))
-        .functions(SetClosed.INSTANCE, new SetCloseDate(true))
-        .automatic()
-        .build())
-
-    // Reopen issues that are marked as resolved but that are still alive.
-    // Manual issues are kept resolved.
-      .transition(Transition.builder("automaticreopen")
-        .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_REOPENED)
-        .conditions(new NotCondition(IsBeingClosed.INSTANCE), new HasResolution(Issue.RESOLUTION_FIXED), new NotCondition(IsManual.INSTANCE))
-        .functions(new SetResolution(null), new SetCloseDate(false))
-        .automatic()
-        .build());
-  }
-
-  @Override
-  public void stop() {
-    // nothing to do
-  }
-
-  public boolean doTransition(DefaultIssue issue, String transitionKey, IssueChangeContext issueChangeContext) {
-    Transition transition = stateOf(issue).transition(transitionKey);
-    if (transition != null && !transition.automatic()) {
-      functionExecutor.execute(transition.functions(), issue, issueChangeContext);
-      updater.setStatus(issue, transition.to(), issueChangeContext);
-      return true;
-    }
-    return false;
-  }
-
-  public List<Transition> outTransitions(Issue issue) {
-    State state = machine.state(issue.status());
-    if (state == null) {
-      throw new IllegalArgumentException("Unknown status: " + issue.status());
-    }
-    return state.outManualTransitions(issue);
-  }
-
-  public void doAutomaticTransition(DefaultIssue issue, IssueChangeContext issueChangeContext) {
-    Transition transition = stateOf(issue).outAutomaticTransition(issue);
-    if (transition != null) {
-      functionExecutor.execute(transition.functions(), issue, issueChangeContext);
-      updater.setStatus(issue, transition.to(), issueChangeContext);
-    }
-  }
-
-  public List<String> statusKeys() {
-    return machine.stateKeys();
-  }
-
-  private State stateOf(DefaultIssue issue) {
-    State state = machine.state(issue.status());
-    if (state == null) {
-      throw new IllegalStateException("Unknown status: " + issue.status() + " [issue=" + issue.key() + "]");
-    }
-    return state;
-  }
-
-  StateMachine machine() {
-    return machine;
-  }
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/OrCondition.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/OrCondition.java
deleted file mode 100644 (file)
index 094c5d7..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-import org.sonar.api.issue.Issue;
-import org.sonar.api.issue.condition.Condition;
-
-public class OrCondition implements Condition {
-  private final Condition[] conditions;
-
-  public OrCondition(Condition... conditions) {
-    this.conditions = conditions;
-  }
-
-  @Override
-  public boolean matches(Issue issue) {
-    for (Condition condition : conditions) {
-      if (condition.matches(issue)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetCloseDate.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetCloseDate.java
deleted file mode 100644 (file)
index 46f7145..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-class SetCloseDate implements Function {
-  private final boolean set;
-
-  public SetCloseDate(boolean set) {
-    this.set = set;
-  }
-
-  @Override
-  public void execute(Context context) {
-    context.setCloseDate(set);
-  }
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetClosed.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetClosed.java
deleted file mode 100644 (file)
index 4b0e534..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-import org.sonar.api.issue.Issue;
-import org.sonar.core.issue.DefaultIssue;
-
-public enum SetClosed implements Function {
-  INSTANCE;
-
-  @Override
-  public void execute(Context context) {
-    DefaultIssue issue = (DefaultIssue) context.issue();
-    if (issue.isOnDisabledRule()) {
-      context.setResolution(Issue.RESOLUTION_REMOVED);
-    } else {
-      context.setResolution(Issue.RESOLUTION_FIXED);
-    }
-
-    // closed issues are not "tracked" -> the line number does not evolve anymore
-    // when code changes. That's misleading for end-users, so line number
-    // is unset.
-    context.setLine(null);
-  }
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetResolution.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetResolution.java
deleted file mode 100644 (file)
index 41684d2..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-import javax.annotation.Nullable;
-
-public class SetResolution implements Function {
-  private final String resolution;
-
-  public SetResolution(@Nullable String resolution) {
-    this.resolution = resolution;
-  }
-
-  @Override
-  public void execute(Context context) {
-    context.setResolution(resolution);
-  }
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/State.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/State.java
deleted file mode 100644 (file)
index 8be9b9f..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import java.util.List;
-import java.util.Set;
-import javax.annotation.CheckForNull;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.issue.Issue;
-
-public class State {
-  private final String key;
-  private final Transition[] outTransitions;
-
-  public State(String key, Transition[] outTransitions) {
-    Preconditions.checkArgument(!Strings.isNullOrEmpty(key), "State key must be set");
-    Preconditions.checkArgument(StringUtils.isAllUpperCase(key), "State key must be upper-case");
-    checkDuplications(outTransitions, key);
-
-    this.key = key;
-    this.outTransitions = outTransitions;
-  }
-
-  private static void checkDuplications(Transition[] transitions, String stateKey) {
-    Set<String> keys = Sets.newHashSet();
-    for (Transition transition : transitions) {
-      if (keys.contains(transition.key())) {
-        throw new IllegalArgumentException("Transition '" + transition.key() +
-          "' is declared several times from the originating state '" + stateKey + "'");
-      }
-      keys.add(transition.key());
-    }
-  }
-
-  public List<Transition> outManualTransitions(Issue issue) {
-    List<Transition> result = Lists.newArrayList();
-    for (Transition transition : outTransitions) {
-      if (!transition.automatic() && transition.supports(issue)) {
-        result.add(transition);
-      }
-    }
-    return result;
-  }
-
-  @CheckForNull
-  public Transition outAutomaticTransition(Issue issue) {
-    Transition result = null;
-    for (Transition transition : outTransitions) {
-      if (transition.automatic() && transition.supports(issue)) {
-        if (result == null) {
-          result = transition;
-        } else {
-          throw new IllegalStateException("Several automatic transitions are available for issue: " + issue);
-        }
-      }
-    }
-    return result;
-  }
-
-  Transition transition(String transitionKey) {
-    for (Transition transition : outTransitions) {
-      if (transitionKey.equals(transition.key())) {
-        return transition;
-      }
-    }
-    throw new IllegalStateException("Transition from state " + key + " does not exist: " + transitionKey);
-  }
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/StateMachine.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/StateMachine.java
deleted file mode 100644 (file)
index 215e503..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Sets;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import javax.annotation.CheckForNull;
-
-public class StateMachine {
-
-  private final List<String> keys;
-  private final Map<String, State> byKey;
-
-  private StateMachine(Builder builder) {
-    this.keys = ImmutableList.copyOf(builder.states);
-    ImmutableMap.Builder<String, State> mapBuilder = ImmutableMap.builder();
-    for (String stateKey : builder.states) {
-      List<Transition> outTransitions = builder.outTransitions.get(stateKey);
-      State state = new State(stateKey, outTransitions.toArray(new Transition[outTransitions.size()]));
-      mapBuilder.put(stateKey, state);
-    }
-    byKey = mapBuilder.build();
-  }
-
-  @CheckForNull
-  public State state(String stateKey) {
-    return byKey.get(stateKey);
-  }
-
-  public List<String> stateKeys() {
-    return keys;
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  public static class Builder {
-    private final Set<String> states = Sets.newLinkedHashSet();
-    // transitions per originating state
-    private final ListMultimap<String, Transition> outTransitions = ArrayListMultimap.create();
-
-    private Builder() {
-    }
-
-    public Builder states(String... keys) {
-      states.addAll(Arrays.asList(keys));
-      return this;
-    }
-
-    public Builder transition(Transition transition) {
-      Preconditions.checkArgument(states.contains(transition.from()), "Originating state does not exist: " + transition.from());
-      Preconditions.checkArgument(states.contains(transition.to()), "Destination state does not exist: " + transition.to());
-      outTransitions.put(transition.from(), transition);
-      return this;
-    }
-
-    public StateMachine build() {
-      Preconditions.checkArgument(!states.isEmpty(), "At least one state is required");
-      return new StateMachine(this);
-    }
-  }
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/Transition.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/Transition.java
deleted file mode 100644 (file)
index 1bcc5ed..0000000
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
-import java.util.Arrays;
-import java.util.List;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.issue.condition.Condition;
-
-public class Transition {
-  private final String key;
-  private final String from;
-  private final String to;
-  private final Condition[] conditions;
-  private final Function[] functions;
-  private final boolean automatic;
-  private String requiredProjectPermission;
-
-  private Transition(TransitionBuilder builder) {
-    key = builder.key;
-    from = builder.from;
-    to = builder.to;
-    conditions = builder.conditions.toArray(new Condition[builder.conditions.size()]);
-    functions = builder.functions.toArray(new Function[builder.functions.size()]);
-    automatic = builder.automatic;
-    requiredProjectPermission = builder.requiredProjectPermission;
-  }
-
-  public String key() {
-    return key;
-  }
-
-  String from() {
-    return from;
-  }
-
-  String to() {
-    return to;
-  }
-
-  Condition[] conditions() {
-    return conditions;
-  }
-
-  Function[] functions() {
-    return functions;
-  }
-
-  boolean automatic() {
-    return automatic;
-  }
-
-  public boolean supports(Issue issue) {
-    for (Condition condition : conditions) {
-      if (!condition.matches(issue)) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  public String requiredProjectPermission() {
-    return requiredProjectPermission;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-    Transition that = (Transition) o;
-    if (!from.equals(that.from)) {
-      return false;
-    }
-    if (!key.equals(that.key)) {
-      return false;
-    }
-    return to.equals(that.to);
-  }
-
-  @Override
-  public int hashCode() {
-    int result = key.hashCode();
-    result = 31 * result + from.hashCode();
-    result = 31 * result + to.hashCode();
-    return result;
-  }
-
-  @Override
-  public String toString() {
-    return String.format("%s->%s->%s", from, key, to);
-  }
-
-  public static Transition create(String key, String from, String to) {
-    return builder(key).from(from).to(to).build();
-  }
-
-  public static TransitionBuilder builder(String key) {
-    return new TransitionBuilder(key);
-  }
-
-  public static class TransitionBuilder {
-    private final String key;
-    private String from;
-    private String to;
-    private List<Condition> conditions = Lists.newArrayList();
-    private List<Function> functions = Lists.newArrayList();
-    private boolean automatic = false;
-    private String requiredProjectPermission;
-
-    private TransitionBuilder(String key) {
-      this.key = key;
-    }
-
-    public TransitionBuilder from(String from) {
-      this.from = from;
-      return this;
-    }
-
-    public TransitionBuilder to(String to) {
-      this.to = to;
-      return this;
-    }
-
-    public TransitionBuilder conditions(Condition... c) {
-      this.conditions.addAll(Arrays.asList(c));
-      return this;
-    }
-
-    public TransitionBuilder functions(Function... f) {
-      this.functions.addAll(Arrays.asList(f));
-      return this;
-    }
-
-    public TransitionBuilder automatic() {
-      this.automatic = true;
-      return this;
-    }
-
-    public TransitionBuilder requiredProjectPermission(String requiredProjectPermission) {
-      this.requiredProjectPermission = requiredProjectPermission;
-      return this;
-    }
-
-    public Transition build() {
-      Preconditions.checkArgument(!Strings.isNullOrEmpty(key), "Transition key must be set");
-      Preconditions.checkArgument(StringUtils.isAllLowerCase(key), "Transition key must be lower-case");
-      Preconditions.checkArgument(!Strings.isNullOrEmpty(from), "Originating status must be set");
-      Preconditions.checkArgument(!Strings.isNullOrEmpty(to), "Destination status must be set");
-      return new Transition(this);
-    }
-  }
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/UnsetAssignee.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/UnsetAssignee.java
deleted file mode 100644 (file)
index 258fb59..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-enum UnsetAssignee implements Function {
-  INSTANCE;
-
-  @Override
-  public void execute(Context context) {
-    context.setAssignee(null);
-  }
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/package-info.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/package-info.java
deleted file mode 100644 (file)
index 40a9855..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-import javax.annotation.ParametersAreNonnullByDefault;
-
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/IsBeingClosedTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/IsBeingClosedTest.java
deleted file mode 100644 (file)
index 259193e..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-import org.junit.Test;
-import org.sonar.core.issue.DefaultIssue;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.core.issue.workflow.IsBeingClosed.INSTANCE;
-
-public class IsBeingClosedTest {
-
-  @Test
-  public void should_be_end_of_life() {
-    DefaultIssue issue = new DefaultIssue();
-    assertThat(INSTANCE.matches(issue.setBeingClosed(true))).isTrue();
-    assertThat(INSTANCE.matches(issue.setBeingClosed(false))).isFalse();
-  }
-
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/IsManualTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/IsManualTest.java
deleted file mode 100644 (file)
index 923d8e7..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-import org.junit.Test;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.core.issue.DefaultIssue;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.core.issue.workflow.IsManual.INSTANCE;
-
-public class IsManualTest {
-
-  @Test
-  public void should_match() {
-    DefaultIssue issue = new DefaultIssue();
-    assertThat(INSTANCE.matches(issue.setRuleKey(RuleKey.of(RuleKey.MANUAL_REPOSITORY_KEY, "R1")))).isTrue();
-    assertThat(INSTANCE.matches(issue.setRuleKey(RuleKey.of("java", "R1")))).isFalse();
-  }
-
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/IssueWorkflowTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/IssueWorkflowTest.java
deleted file mode 100644 (file)
index ba3571e..0000000
+++ /dev/null
@@ -1,432 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-import com.google.common.base.Function;
-import com.google.common.collect.Collections2;
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
-import javax.annotation.Nullable;
-import org.apache.commons.lang.time.DateUtils;
-import org.junit.Test;
-import org.sonar.api.issue.DefaultTransitions;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.IssueChangeContext;
-import org.sonar.core.issue.IssueUpdater;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE;
-import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
-import static org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX;
-import static org.sonar.api.issue.Issue.STATUS_CLOSED;
-import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
-import static org.sonar.api.issue.Issue.STATUS_OPEN;
-import static org.sonar.api.issue.Issue.STATUS_REOPENED;
-import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
-import static org.sonar.api.rule.RuleKey.MANUAL_REPOSITORY_KEY;
-
-public class IssueWorkflowTest {
-
-  IssueUpdater updater = new IssueUpdater();
-  IssueWorkflow workflow = new IssueWorkflow(new FunctionExecutor(updater), updater);
-
-  @Test
-  public void init_state_machine() {
-    assertThat(workflow.machine()).isNull();
-    workflow.start();
-    assertThat(workflow.machine()).isNotNull();
-    assertThat(workflow.machine().state(STATUS_OPEN)).isNotNull();
-    assertThat(workflow.machine().state(STATUS_CONFIRMED)).isNotNull();
-    assertThat(workflow.machine().state(STATUS_CLOSED)).isNotNull();
-    assertThat(workflow.machine().state(STATUS_REOPENED)).isNotNull();
-    assertThat(workflow.machine().state(STATUS_RESOLVED)).isNotNull();
-    workflow.stop();
-  }
-
-  @Test
-  public void list_statuses() {
-    workflow.start();
-    // order is important for UI
-    assertThat(workflow.statusKeys()).containsSequence(STATUS_OPEN, STATUS_CONFIRMED, STATUS_REOPENED, STATUS_RESOLVED, STATUS_CLOSED);
-  }
-
-  @Test
-  public void list_out_transitions_from_status_open() {
-    workflow.start();
-
-    DefaultIssue issue = new DefaultIssue().setStatus(STATUS_OPEN);
-    List<Transition> transitions = workflow.outTransitions(issue);
-    assertThat(keys(transitions)).containsOnly("confirm", "falsepositive", "resolve", "wontfix");
-  }
-
-  @Test
-  public void list_out_transitions_from_status_confirmed() {
-    workflow.start();
-
-    DefaultIssue issue = new DefaultIssue().setStatus(STATUS_CONFIRMED);
-    List<Transition> transitions = workflow.outTransitions(issue);
-    assertThat(keys(transitions)).containsOnly("unconfirm", "falsepositive", "resolve", "wontfix");
-  }
-
-  @Test
-  public void list_out_transitions_from_status_resolved() {
-    workflow.start();
-
-    DefaultIssue issue = new DefaultIssue().setStatus(STATUS_RESOLVED);
-    List<Transition> transitions = workflow.outTransitions(issue);
-    assertThat(keys(transitions)).containsOnly("reopen");
-  }
-
-  @Test
-  public void list_out_transitions_from_status_reopen() {
-    workflow.start();
-
-    DefaultIssue issue = new DefaultIssue().setStatus(STATUS_REOPENED);
-    List<Transition> transitions = workflow.outTransitions(issue);
-    assertThat(keys(transitions)).containsOnly("confirm", "resolve", "falsepositive", "wontfix");
-  }
-
-  @Test
-  public void list_no_out_transition_from_status_closed() {
-    workflow.start();
-
-    DefaultIssue issue = new DefaultIssue().setStatus(STATUS_CLOSED).setRuleKey(RuleKey.of("java", "R1  "));
-    List<Transition> transitions = workflow.outTransitions(issue);
-    assertThat(transitions).isEmpty();
-  }
-
-  @Test
-  public void list_out_transitions_from_status_closed_on_manual_issue() {
-    workflow.start();
-
-    // Manual issue because of reporter
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setStatus(STATUS_CLOSED)
-      .setRuleKey(RuleKey.of("manual", "Performance"))
-      .setReporter("simon");
-
-    List<Transition> transitions = workflow.outTransitions(issue);
-    assertThat(keys(transitions)).containsOnly("reopen");
-  }
-
-  @Test
-  public void fail_if_unknown_status_when_listing_transitions() {
-    workflow.start();
-
-    DefaultIssue issue = new DefaultIssue().setStatus("xxx");
-    try {
-      workflow.outTransitions(issue);
-      fail();
-    } catch (IllegalArgumentException e) {
-      assertThat(e).hasMessage("Unknown status: xxx");
-    }
-  }
-
-  @Test
-  public void automatically_close_resolved_issue() {
-    workflow.start();
-
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setRuleKey(RuleKey.of("js", "S001"))
-      .setResolution(RESOLUTION_FIXED)
-      .setStatus(STATUS_RESOLVED)
-      .setNew(false)
-      .setBeingClosed(true);
-    Date now = new Date();
-    workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
-    assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
-    assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
-    assertThat(issue.closeDate()).isNotNull();
-    assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
-  }
-
-  @Test
-  public void close_open_dead_issue() {
-    workflow.start();
-
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setResolution(null)
-      .setStatus(STATUS_OPEN)
-      .setNew(false)
-      .setBeingClosed(true);
-    Date now = new Date();
-    workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
-    assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
-    assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
-    assertThat(issue.closeDate()).isNotNull();
-    assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
-  }
-
-  @Test
-  public void close_reopened_dead_issue() {
-    workflow.start();
-
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setResolution(null)
-      .setStatus(STATUS_REOPENED)
-      .setNew(false)
-      .setBeingClosed(true);
-    Date now = new Date();
-    workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
-    assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
-    assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
-    assertThat(issue.closeDate()).isNotNull();
-    assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
-  }
-
-  @Test
-  public void close_confirmed_dead_issue() {
-    workflow.start();
-
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setResolution(null)
-      .setStatus(STATUS_CONFIRMED)
-      .setNew(false)
-      .setBeingClosed(true);
-    Date now = new Date();
-    workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
-    assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
-    assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
-    assertThat(issue.closeDate()).isNotNull();
-    assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
-  }
-
-  @Test
-  public void fail_if_unknown_status_on_automatic_trans() {
-    workflow.start();
-
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setResolution(RESOLUTION_FIXED)
-      .setStatus("xxx")
-      .setNew(false)
-      .setBeingClosed(true);
-    try {
-      workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(new Date()));
-      fail();
-    } catch (IllegalStateException e) {
-      assertThat(e).hasMessage("Unknown status: xxx [issue=ABCDE]");
-    }
-  }
-
-  @Test
-  public void flag_as_false_positive() {
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setStatus(STATUS_OPEN)
-      .setRuleKey(RuleKey.of("squid", "AvoidCycle"))
-      .setAssignee("morgan");
-
-    workflow.start();
-    workflow.doTransition(issue, DefaultTransitions.FALSE_POSITIVE, IssueChangeContext.createScan(new Date()));
-
-    assertThat(issue.resolution()).isEqualTo(RESOLUTION_FALSE_POSITIVE);
-    assertThat(issue.status()).isEqualTo(STATUS_RESOLVED);
-
-    // should remove assignee
-    assertThat(issue.assignee()).isNull();
-  }
-
-  @Test
-  public void wont_fix() {
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setStatus(STATUS_OPEN)
-      .setRuleKey(RuleKey.of("squid", "AvoidCycle"))
-      .setAssignee("morgan");
-
-    workflow.start();
-    workflow.doTransition(issue, DefaultTransitions.WONT_FIX, IssueChangeContext.createScan(new Date()));
-
-    assertThat(issue.resolution()).isEqualTo(RESOLUTION_WONT_FIX);
-    assertThat(issue.status()).isEqualTo(STATUS_RESOLVED);
-
-    // should remove assignee
-    assertThat(issue.assignee()).isNull();
-  }
-
-  /**
-   * User marks the manual issue as resolved -> issue is automatically
-   * closed.
-   */
-  @Test
-  public void automatically_close_resolved_manual_issue() {
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setStatus(STATUS_OPEN)
-      .setRuleKey(RuleKey.of(MANUAL_REPOSITORY_KEY, "Performance"));
-
-    workflow.start();
-
-    assertThat(workflow.outTransitions(issue)).containsOnly(
-      Transition.create("confirm", "OPEN", "CONFIRMED"),
-      Transition.create("resolve", "OPEN", "RESOLVED"),
-      Transition.create("falsepositive", "OPEN", "RESOLVED"),
-      Transition.create("wontfix", "OPEN", "RESOLVED"));
-
-    workflow.doTransition(issue, "resolve", mock(IssueChangeContext.class));
-    assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
-    assertThat(issue.status()).isEqualTo("RESOLVED");
-
-    assertThat(workflow.outTransitions(issue)).containsOnly(
-      Transition.create("reopen", "RESOLVED", "REOPENED"));
-
-    workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
-    assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
-    assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
-  }
-
-  /**
-   * Manual issue is fixed because the file does not exist anymore
-   * or the tracking engine did not find the associated code
-   * -> the issue is closed
-   */
-  @Test
-  public void automatically_close_manual_issue_on_deleted_code() {
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setStatus(STATUS_OPEN)
-      .setRuleKey(RuleKey.of(MANUAL_REPOSITORY_KEY, "Performance"))
-      .setBeingClosed(true);
-
-    workflow.start();
-
-    workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
-    assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
-    assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
-  }
-
-  /**
-   * Corner-case : the manual issue was marked as resolved by user but at the same 
-   * time the file or the associated line was deleted.
-   */
-  @Test
-  public void automatically_close_resolved_manual_issue_on_deleted_code() {
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setRuleKey(RuleKey.of(MANUAL_REPOSITORY_KEY, "Performance"))
-
-    // resolved by user
-      .setResolution(RESOLUTION_FIXED)
-      .setStatus(STATUS_RESOLVED)
-
-    // but unmatched by tracking engine
-      .setBeingClosed(true);
-
-    workflow.start();
-
-    workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
-    assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
-    assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
-  }
-
-  @Test
-  public void manual_issues_be_confirmed_then_kept_open() {
-    // Manual issue because of reporter
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setStatus(STATUS_OPEN)
-      .setRuleKey(RuleKey.of("manual", "Performance"))
-      .setReporter("simon");
-
-    workflow.start();
-
-    assertThat(workflow.outTransitions(issue)).containsOnly(
-      Transition.create("confirm", "OPEN", "CONFIRMED"),
-      Transition.create("resolve", "OPEN", "RESOLVED"),
-      Transition.create("falsepositive", "OPEN", "RESOLVED"),
-      Transition.create("wontfix", "OPEN", "RESOLVED"));
-
-    workflow.doTransition(issue, "confirm", mock(IssueChangeContext.class));
-    assertThat(issue.resolution()).isNull();
-    assertThat(issue.status()).isEqualTo("CONFIRMED");
-
-    assertThat(workflow.outTransitions(issue)).containsOnly(
-      Transition.create("unconfirm", "CONFIRMED", "REOPENED"),
-      Transition.create("resolve", "CONFIRMED", "RESOLVED"),
-      Transition.create("falsepositive", "CONFIRMED", "RESOLVED"),
-      Transition.create("wontfix", "CONFIRMED", "RESOLVED"));
-
-    // keep confirmed and unresolved
-    workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
-    assertThat(issue.resolution()).isNull();
-    assertThat(issue.status()).isEqualTo("CONFIRMED");
-
-    // unconfirm
-    workflow.doTransition(issue, "unconfirm", mock(IssueChangeContext.class));
-    assertThat(issue.resolution()).isNull();
-    assertThat(issue.status()).isEqualTo("REOPENED");
-  }
-
-  @Test
-  public void manual_issue_on_removed_rule_be_closed() {
-    // Manual issue because of reporter
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setStatus(STATUS_OPEN)
-      .setRuleKey(RuleKey.of("manual", "Performance"))
-      .setReporter("simon")
-      .setBeingClosed(true)
-      .setOnDisabledRule(true);
-
-    workflow.start();
-
-    workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
-    assertThat(issue.resolution()).isEqualTo("REMOVED");
-    assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
-  }
-
-  @Test
-  public void manual_issue_on_removed_component_be_closed() {
-    // Manual issue because of reporter
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setStatus(STATUS_OPEN)
-      .setRuleKey(RuleKey.of("manual", "Performance"))
-      .setReporter("simon")
-      .setBeingClosed(true)
-      .setOnDisabledRule(false);
-
-    workflow.start();
-
-    workflow.doAutomaticTransition(issue, mock(IssueChangeContext.class));
-    assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
-    assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
-  }
-
-  private Collection<String> keys(List<Transition> transitions) {
-    return Collections2.transform(transitions, new Function<Transition, String>() {
-      @Override
-      public String apply(@Nullable Transition transition) {
-        return transition.key();
-      }
-    });
-  }
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/OrConditionTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/OrConditionTest.java
deleted file mode 100644 (file)
index b8f3e50..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-import org.junit.Test;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.issue.condition.Condition;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-
-public class OrConditionTest {
-
-  private static final Condition TRUE_CONDITION = new BooleanCondition(true);
-  private static final Condition FALSE_CONDITION = new BooleanCondition(false);
-  Issue issue = mock(Issue.class);
-
-  @Test
-  public void match() {
-    assertThat(new OrCondition(TRUE_CONDITION).matches(issue)).isTrue();
-    assertThat(new OrCondition(FALSE_CONDITION).matches(issue)).isFalse();
-    assertThat(new OrCondition(FALSE_CONDITION, TRUE_CONDITION).matches(issue)).isTrue();
-    assertThat(new OrCondition(FALSE_CONDITION, FALSE_CONDITION).matches(issue)).isFalse();
-  }
-
-  private static class BooleanCondition implements Condition {
-    private final boolean b;
-
-    public BooleanCondition(boolean b) {
-      this.b = b;
-    }
-
-    @Override
-    public boolean matches(Issue issue) {
-      return b;
-    }
-  }
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/SetCloseDateTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/SetCloseDateTest.java
deleted file mode 100644 (file)
index 92378fa..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-import org.junit.Test;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-public class SetCloseDateTest {
-  @Test
-  public void should_set_close_date() {
-    SetCloseDate function = new SetCloseDate(true);
-    Function.Context context = mock(Function.Context.class);
-    function.execute(context);
-    verify(context, times(1)).setCloseDate(true);
-  }
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/SetClosedTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/SetClosedTest.java
deleted file mode 100644 (file)
index 0c43419..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-import org.junit.Test;
-import org.sonar.api.issue.Issue;
-import org.sonar.core.issue.DefaultIssue;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.sonar.core.issue.workflow.SetClosed.INSTANCE;
-
-public class SetClosedTest {
-
-  Function.Context context = mock(Function.Context.class);
-
-  @Test
-  public void should_resolve_as_fixed() {
-    Issue issue = new DefaultIssue().setBeingClosed(true).setOnDisabledRule(false);
-    when(context.issue()).thenReturn(issue);
-    INSTANCE.execute(context);
-    verify(context, times(1)).setResolution(Issue.RESOLUTION_FIXED);
-  }
-
-  @Test
-  public void should_resolve_as_removed_when_rule_is_disabled() {
-    Issue issue = new DefaultIssue().setBeingClosed(true).setOnDisabledRule(true);
-    when(context.issue()).thenReturn(issue);
-    INSTANCE.execute(context);
-    verify(context, times(1)).setResolution(Issue.RESOLUTION_REMOVED);
-  }
-
-  @Test
-  public void line_number_must_be_unset() {
-    Issue issue = new DefaultIssue().setBeingClosed(true).setLine(10);
-    when(context.issue()).thenReturn(issue);
-    INSTANCE.execute(context);
-    verify(context).setLine(null);
-  }
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/SetResolutionTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/SetResolutionTest.java
deleted file mode 100644 (file)
index c8f690a..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-import org.junit.Test;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-public class SetResolutionTest {
-  @Test
-  public void execute() {
-    SetResolution function = new SetResolution("FIXED");
-    Function.Context context = mock(Function.Context.class);
-    function.execute(context);
-    verify(context, times(1)).setResolution("FIXED");
-  }
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/StateMachineTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/StateMachineTest.java
deleted file mode 100644 (file)
index 4d25dd3..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class StateMachineTest {
-  @Test
-  public void keep_order_of_state_keys() {
-    StateMachine machine = StateMachine.builder().states("OPEN", "RESOLVED", "CLOSED").build();
-
-    assertThat(machine.stateKeys()).containsSequence("OPEN", "RESOLVED", "CLOSED");
-  }
-
-  @Test
-  public void stateKey() {
-    StateMachine machine = StateMachine.builder()
-      .states("OPEN", "RESOLVED", "CLOSED")
-      .transition(Transition.builder("resolve").from("OPEN").to("RESOLVED").build())
-      .build();
-
-    assertThat(machine.state("OPEN")).isNotNull();
-    assertThat(machine.state("OPEN").transition("resolve")).isNotNull();
-  }
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/StateTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/StateTest.java
deleted file mode 100644 (file)
index 660674a..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
-
-public class StateTest {
-
-  Transition t1 = Transition.builder("close").from("OPEN").to("CLOSED").build();
-
-  @Test
-  public void key_should_be_set() {
-    try {
-      new State("", new Transition[0]);
-      fail();
-    } catch (IllegalArgumentException e) {
-      assertThat(e).hasMessage("State key must be set");
-    }
-  }
-
-  @Test
-  public void key_should_be_upper_case() {
-    try {
-      new State("close", new Transition[0]);
-      fail();
-    } catch (IllegalArgumentException e) {
-      assertThat(e).hasMessage("State key must be upper-case");
-    }
-  }
-
-  @Test
-  public void no_duplicated_out_transitions() {
-    try {
-      new State("CLOSE", new Transition[] {t1, t1});
-      fail();
-    } catch (IllegalArgumentException e) {
-      assertThat(e).hasMessage("Transition 'close' is declared several times from the originating state 'CLOSE'");
-    }
-  }
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/TransitionTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/TransitionTest.java
deleted file mode 100644 (file)
index 626dd6b..0000000
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-import org.junit.Test;
-import org.sonar.api.issue.condition.Condition;
-import org.sonar.core.issue.DefaultIssue;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class TransitionTest {
-
-  Condition condition1 = mock(Condition.class);
-  Condition condition2 = mock(Condition.class);
-  Function function1 = mock(Function.class);
-  Function function2 = mock(Function.class);
-
-  @Test
-  public void test_builder() throws Exception {
-    Transition transition = Transition.builder("close")
-      .from("OPEN").to("CLOSED")
-      .conditions(condition1, condition2)
-      .functions(function1, function2)
-      .build();
-    assertThat(transition.key()).isEqualTo("close");
-    assertThat(transition.from()).isEqualTo("OPEN");
-    assertThat(transition.to()).isEqualTo("CLOSED");
-    assertThat(transition.conditions()).containsOnly(condition1, condition2);
-    assertThat(transition.functions()).containsOnly(function1, function2);
-    assertThat(transition.automatic()).isFalse();
-  }
-
-  @Test
-  public void test_simplest_transition() throws Exception {
-    Transition transition = Transition.builder("close")
-      .from("OPEN").to("CLOSED")
-      .build();
-    assertThat(transition.key()).isEqualTo("close");
-    assertThat(transition.from()).isEqualTo("OPEN");
-    assertThat(transition.to()).isEqualTo("CLOSED");
-    assertThat(transition.conditions()).isEmpty();
-    assertThat(transition.functions()).isEmpty();
-  }
-
-  @Test
-  public void key_should_be_set() {
-    try {
-      Transition.builder("").from("OPEN").to("CLOSED").build();
-      fail();
-    } catch (Exception e) {
-      assertThat(e).hasMessage("Transition key must be set");
-    }
-  }
-
-  @Test
-  public void key_should_be_lower_case() {
-    try {
-      Transition.builder("CLOSE").from("OPEN").to("CLOSED").build();
-      fail();
-    } catch (Exception e) {
-      assertThat(e).hasMessage("Transition key must be lower-case");
-    }
-  }
-
-  @Test
-  public void originating_status_should_be_set() {
-    try {
-      Transition.builder("close").from("").to("CLOSED").build();
-      fail();
-    } catch (Exception e) {
-      assertThat(e).hasMessage("Originating status must be set");
-    }
-  }
-
-  @Test
-  public void destination_status_should_be_set() {
-    try {
-      Transition.builder("close").from("OPEN").to("").build();
-      fail();
-    } catch (Exception e) {
-      assertThat(e).hasMessage("Destination status must be set");
-    }
-  }
-
-  @Test
-  public void should_verify_conditions() {
-    DefaultIssue issue = new DefaultIssue();
-    Transition transition = Transition.builder("close")
-      .from("OPEN").to("CLOSED")
-      .conditions(condition1, condition2)
-      .build();
-
-    when(condition1.matches(issue)).thenReturn(true);
-    when(condition2.matches(issue)).thenReturn(false);
-    assertThat(transition.supports(issue)).isFalse();
-
-    when(condition1.matches(issue)).thenReturn(true);
-    when(condition2.matches(issue)).thenReturn(true);
-    assertThat(transition.supports(issue)).isTrue();
-  }
-
-  @Test
-  public void test_equals_and_hashCode() throws Exception {
-    Transition t1 = Transition.create("resolve", "OPEN", "RESOLVED");
-    Transition t2 = Transition.create("resolve", "REOPENED", "RESOLVED");
-    Transition t3 = Transition.create("confirm", "OPEN", "CONFIRMED");
-
-    assertThat(t1).isNotEqualTo(t2);
-    assertThat(t1).isNotEqualTo(t3);
-    assertThat(t1).isEqualTo(t1);
-
-    assertThat(t1.hashCode()).isEqualTo(t1.hashCode());
-  }
-
-  @Test
-  public void test_toString() throws Exception {
-    Transition t1 = Transition.create("resolve", "OPEN", "RESOLVED");
-    assertThat(t1.toString()).isEqualTo("OPEN->resolve->RESOLVED");
-  }
-
-  @Test
-  public void test_automatic_transition() throws Exception {
-    Transition transition = Transition.builder("close")
-      .from("OPEN").to("CLOSED")
-      .automatic()
-      .build();
-    assertThat(transition.automatic()).isTrue();
-  }
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/UnsetAssigneeTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/UnsetAssigneeTest.java
deleted file mode 100644 (file)
index 7977dd1..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.core.issue.workflow;
-
-import org.junit.Test;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.sonar.core.issue.workflow.UnsetAssignee.INSTANCE;
-
-public class UnsetAssigneeTest {
-
-  @Test
-  public void unassign() {
-    Function.Context context = mock(Function.Context.class);
-    INSTANCE.execute(context);
-    verify(context, times(1)).setAssignee(null);
-  }
-}