CombinedCheckState: factor out code for counting check states

Currently, the "IN_PROGRESS" combined check state doesn't
work well for required and optional check states. The
problem is as an "enum", "IN_PROGRESS" can only have one
"passing" state (true or false), which affects whether
we should block submission or not.

But whether a "IN_PROGRESS" is passing or not depends on
whether there are required check states pending. That
means it can be true or false.

To resolve this, we have two possibilities:
1- split "IN_PROGRESS" into two, e.g.
"IN_PROGRESS_BLOCKING" and "IN_PROGRESS_NOT_BLOCKING".
But these names would probably be long and could
confuse users.

2- this commit proposes to keep the current combined
check state definition, but doesn't hard-code the
"passing" state in the "enum". This commit is a no-op
for Checks#getCombinedCheckState but it makes it
much easier to share the code for extracting the
"CheckStateCount".

The follow-up commit will teach the submittable check
to use "CheckStateCount" information and remove
the "pass" boolean from "CombinedCheckState" in the
end.

Change-Id: Ia85f9f9b9d947699f3c34216642814121a772b1e
diff --git a/java/com/google/gerrit/plugins/checks/api/CombinedCheckState.java b/java/com/google/gerrit/plugins/checks/api/CombinedCheckState.java
index 2919c02..0975441 100644
--- a/java/com/google/gerrit/plugins/checks/api/CombinedCheckState.java
+++ b/java/com/google/gerrit/plugins/checks/api/CombinedCheckState.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.plugins.checks.api;
 
+import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableListMultimap;
 import java.util.Map;
 
@@ -62,35 +63,37 @@
    */
   public static CombinedCheckState combine(
       ImmutableListMultimap<CheckState, Boolean> statesAndRequired) {
-    int inProgressCount = 0;
-    int failedOptionalCount = 0;
-    int successfulCount = 0;
-    for (Map.Entry<CheckState, Boolean> e : statesAndRequired.entries()) {
-      CheckState state = e.getKey();
-      if (state.isInProgress()) {
-        inProgressCount++;
-      } else if (state == CheckState.FAILED) {
-        if (e.getValue()) {
-          return CombinedCheckState.FAILED;
-        }
-        failedOptionalCount++;
-      } else if (state == CheckState.SUCCESSFUL) {
-        successfulCount++;
-      } else if (state == CheckState.NOT_RELEVANT) {
-        continue;
-      } else {
-        throw new IllegalStateException("invalid state: " + state);
-      }
+    CheckStateCount checkStateCount = CheckStateCount.create(statesAndRequired);
+    return combine(checkStateCount);
+  }
+
+  /**
+   * Combines multiple per-check states into a single combined state based on the count result of
+   * each check state.
+   *
+   * <p>See documentation of specific enum values for precise semantics.
+   *
+   * @param checkStateCount count of check states.
+   * @return combined state.
+   */
+  private static CombinedCheckState combine(CheckStateCount checkStateCount) {
+    if (checkStateCount.failedRequiredCount() > 0) {
+      return FAILED;
     }
-    if (inProgressCount > 0) {
+
+    if (checkStateCount.inProgressOptionalCount() > 0
+        || checkStateCount.inProgressRequiredCount() > 0) {
       return IN_PROGRESS;
     }
-    if (failedOptionalCount > 0) {
+
+    if (checkStateCount.failedOptionalCount() > 0) {
       return WARNING;
     }
-    if (successfulCount > 0) {
+
+    if (checkStateCount.successfulCount() > 0) {
       return SUCCESSFUL;
     }
+
     return NOT_RELEVANT;
   }
 
@@ -111,4 +114,85 @@
   public boolean isPassing() {
     return passing;
   }
+
+  @AutoValue
+  public abstract static class CheckStateCount {
+    /**
+     * Get the count of each {@link CheckState}.
+     *
+     * @param statesAndRequired map of state to a list of booleans, one per check, indicating
+     *     whether that particular check is required in the context of a particular change.
+     * @return the {@link CheckStateCount} of the given state map.
+     */
+    public static CheckStateCount create(
+        ImmutableListMultimap<CheckState, Boolean> statesAndRequired) {
+      int failedRequiredCount = 0;
+      int failedOptionalCount = 0;
+      int inProgressRequiredCount = 0;
+      int inProgressOptionalCount = 0;
+      int successfulCount = 0;
+
+      for (Map.Entry<CheckState, Boolean> checkStateAndRequiredState :
+          statesAndRequired.entries()) {
+        CheckState state = checkStateAndRequiredState.getKey();
+        if (state.isInProgress()) {
+          if (checkStateAndRequiredState.getValue()) {
+            inProgressRequiredCount++;
+          } else {
+            inProgressOptionalCount++;
+          }
+        } else if (state == CheckState.FAILED) {
+          if (checkStateAndRequiredState.getValue()) {
+            failedRequiredCount++;
+          } else {
+            failedOptionalCount++;
+          }
+        } else if (state == CheckState.SUCCESSFUL) {
+          successfulCount++;
+        } else if (state != CheckState.NOT_RELEVANT) {
+          throw new IllegalStateException("invalid state: " + state);
+        }
+      }
+
+      return new AutoValue_CombinedCheckState_CheckStateCount.Builder()
+          .failedRequiredCount(failedRequiredCount)
+          .failedOptionalCount(failedOptionalCount)
+          .inProgressRequiredCount(inProgressRequiredCount)
+          .inProgressOptionalCount(inProgressOptionalCount)
+          .successfulCount(successfulCount)
+          .build();
+    }
+
+    /** Count of the failed check states which are required for submission. */
+    public abstract int failedRequiredCount();
+
+    /** Count of the failed check states which are not required for submission. */
+    public abstract int failedOptionalCount();
+
+    /** Count of the in-progress check states which are required for submission. */
+    public abstract int inProgressRequiredCount();
+
+    /** Count of the in-progress check states which are not required for submission. */
+    public abstract int inProgressOptionalCount();
+
+    /** Count of the successful check states. */
+    public abstract int successfulCount();
+
+    public abstract Builder toBuilder();
+
+    @AutoValue.Builder
+    public abstract static class Builder {
+      public abstract Builder inProgressRequiredCount(int inProgressRequiredCount);
+
+      public abstract Builder inProgressOptionalCount(int inProgressOptionalCount);
+
+      public abstract Builder failedRequiredCount(int failedRequiredCount);
+
+      public abstract Builder failedOptionalCount(int failedOptionalCount);
+
+      public abstract Builder successfulCount(int successfulCount);
+
+      public abstract CheckStateCount build();
+    }
+  }
 }