Allow using submit requirement predicates

Update the plugin to allow submit requirement predicates along with
regular change query operators. Organizations using this plugin may
have the need to use submit requirements as tasks to ensure they are
met before the change is ready for CI. One such example is the need
to use submit requirements from code-owners plugin as tasks.

Change-Id: Ia17bdd51e5a98b34ae816794a98cb49663d36d61
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/IsTrueOperator.java b/src/main/java/com/googlesource/gerrit/plugins/task/IsTrueOperator.java
new file mode 100644
index 0000000..472b1e7
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/task/IsTrueOperator.java
@@ -0,0 +1,61 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.task;
+
+import com.google.gerrit.extensions.annotations.Exports;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder;
+import com.google.gerrit.server.query.change.SubmitRequirementPredicate;
+import com.google.inject.AbstractModule;
+
+// TODO: Remove this class when up-merging to Gerrit v3.6+ as it supports
+//       the 'is:true' submit requirement
+public class IsTrueOperator implements ChangeQueryBuilder.ChangeIsOperandFactory {
+
+  public static final String TRUE = "true";
+
+  public static class Module extends AbstractModule {
+    @Override
+    protected void configure() {
+      bind(ChangeQueryBuilder.ChangeIsOperandFactory.class)
+          .annotatedWith(Exports.named(TRUE))
+          .to(IsTrueOperator.class);
+    }
+  }
+
+  public class TruePredicate extends SubmitRequirementPredicate {
+
+    public TruePredicate() {
+      super("is", TRUE);
+    }
+
+    @Override
+    public boolean match(ChangeData data) {
+      return true;
+    }
+
+    @Override
+    public int getCost() {
+      return 1;
+    }
+  }
+
+  @Override
+  public Predicate<ChangeData> create(ChangeQueryBuilder builder) throws QueryParseException {
+    return new IsTrueOperator.TruePredicate();
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/Modules.java b/src/main/java/com/googlesource/gerrit/plugins/task/Modules.java
index d28e529..6bedb5e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/task/Modules.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/task/Modules.java
@@ -46,6 +46,8 @@
           .annotatedWith(Exports.named("task"))
           .to(TaskAttributeFactory.class);
 
+      install(new IsTrueOperator.Module());
+
       bind(DynamicBean.class).annotatedWith(Exports.named(GetChange.class)).to(MyOptions.class);
       bind(DynamicBean.class).annotatedWith(Exports.named(Query.class)).to(MyOptions.class);
       bind(DynamicBean.class).annotatedWith(Exports.named(QueryChanges.class)).to(MyOptions.class);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/PredicateCache.java b/src/main/java/com/googlesource/gerrit/plugins/task/PredicateCache.java
index 3c79982..268b1a3 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/task/PredicateCache.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/task/PredicateCache.java
@@ -26,10 +26,10 @@
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeIndexPredicate;
-import com.google.gerrit.server.query.change.ChangeQueryBuilder;
 import com.google.gerrit.server.query.change.DestinationPredicate;
 import com.google.gerrit.server.query.change.RegexProjectPredicate;
 import com.google.gerrit.server.query.change.RegexRefPredicate;
+import com.google.gerrit.server.query.change.SubmitRequirementChangeQueryBuilder;
 import com.google.inject.Inject;
 import com.googlesource.gerrit.plugins.task.statistics.HitHashMap;
 import com.googlesource.gerrit.plugins.task.statistics.StopWatch;
@@ -44,7 +44,7 @@
     protected Object predicatesByQueryCache;
   }
 
-  protected final ChangeQueryBuilder cqb;
+  protected final SubmitRequirementChangeQueryBuilder srcqb;
   protected final Set<String> cacheableByBranchPredicateClassNames;
   protected final CurrentUser user;
   protected final HitHashMap<String, ThrowingProvider<Predicate<ChangeData>, QueryParseException>>
@@ -57,9 +57,9 @@
       @GerritServerConfig Config config,
       @PluginName String pluginName,
       CurrentUser user,
-      ChangeQueryBuilder cqb) {
+      SubmitRequirementChangeQueryBuilder srcqb) {
     this.user = user;
-    this.cqb = cqb;
+    this.srcqb = srcqb;
     cacheableByBranchPredicateClassNames =
         new HashSet<>(
             Arrays.asList(
@@ -88,7 +88,7 @@
     }
     // never seen 'query' before
     try (StopWatch stopWatch = predicatesByQuery.createLoadingStopWatch(query, isVisible)) {
-      Predicate<ChangeData> pred = cqb.parse(query);
+      Predicate<ChangeData> pred = srcqb.parse(query);
       predicatesByQuery.put(query, new ThrowingProvider.Entry<>(pred));
       return pred;
     } catch (QueryParseException e) {
diff --git a/src/main/resources/Documentation/test/task_states.md b/src/main/resources/Documentation/test/task_states.md
index 315b05f..f4f8842 100644
--- a/src/main/resources/Documentation/test/task_states.md
+++ b/src/main/resources/Documentation/test/task_states.md
@@ -73,6 +73,26 @@
    "status" : "FAIL"
 }
 
+[root "Root PASS SR"]
+  pass = is:true_task
+
+{
+   "applicable" : true,
+   "hasPass" : true,
+   "name" : "Root PASS SR",
+   "status" : "PASS"
+}
+
+[root "Root FAIL SR"]
+  fail = is:true_task
+
+{
+   "applicable" : true,
+   "hasPass" : true,
+   "name" : "Root FAIL SR",
+   "status" : "FAIL"
+}
+
 [root "Root straight PASS"]
   applicable = is:open
   pass = is:open