Implement Changes.query() method

Change-Id: If5fbc81dab654537503337fb4243dda3fa4927b4
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
index d656e1e..a01525a 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -15,8 +15,12 @@
 package com.google.gerrit.acceptance.api.change;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.NoHttpd;
@@ -28,6 +32,8 @@
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.ChangeStatus;
 import com.google.gerrit.extensions.common.LabelInfo;
+import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.common.RevisionInfo;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.reviewdb.client.Account;
@@ -37,6 +43,8 @@
 import org.junit.Test;
 
 import java.io.IOException;
+import java.util.EnumSet;
+import java.util.List;
 import java.util.Set;
 
 @NoHttpd
@@ -164,4 +172,88 @@
     assertEquals(in.branch, info.branch);
     assertEquals(in.subject, info.subject);
   }
+
+  @Test
+  public void queryChangesNoQuery() throws Exception {
+    PushOneCommit.Result r1 = createChange();
+    PushOneCommit.Result r2 = createChange();
+    List<ChangeInfo> results = gApi.changes().query().get();
+    assertEquals(2, results.size());
+    assertEquals(r2.getChangeId(), results.get(0).changeId);
+    assertEquals(r1.getChangeId(), results.get(1).changeId);
+  }
+
+  @Test
+  public void queryChangesNoResults() throws Exception {
+    createChange();
+    List<ChangeInfo> results = gApi.changes().query("status:open").get();
+    assertEquals(1, results.size());
+    results = gApi.changes().query("status:closed").get();
+    assertTrue(results.isEmpty());
+  }
+
+  @Test
+  public void queryChangesOneTerm() throws Exception {
+    PushOneCommit.Result r1 = createChange();
+    PushOneCommit.Result r2 = createChange();
+    List<ChangeInfo> results = gApi.changes().query("status:open").get();
+    assertEquals(2, results.size());
+    assertEquals(r2.getChangeId(), results.get(0).changeId);
+    assertEquals(r1.getChangeId(), results.get(1).changeId);
+  }
+
+  @Test
+  public void queryChangesMultipleTerms() throws Exception {
+    PushOneCommit.Result r1 = createChange();
+    createChange();
+    List<ChangeInfo> results = gApi.changes()
+        .query("status:open " + r1.getChangeId())
+        .get();
+    assertEquals(r1.getChangeId(), Iterables.getOnlyElement(results).changeId);
+  }
+
+  @Test
+  public void queryChangesLimit() throws Exception {
+    createChange();
+    PushOneCommit.Result r2 = createChange();
+    List<ChangeInfo> results = gApi.changes().query().withLimit(1).get();
+    assertEquals(1, results.size());
+    assertEquals(r2.getChangeId(), Iterables.getOnlyElement(results).changeId);
+  }
+
+  @Test
+  public void queryChangesStart() throws Exception {
+    PushOneCommit.Result r1 = createChange();
+    createChange();
+    List<ChangeInfo> results = gApi.changes().query().withStart(1).get();
+    assertEquals(r1.getChangeId(), Iterables.getOnlyElement(results).changeId);
+  }
+
+  @Test
+  public void queryChangesNoOptions() throws Exception {
+    PushOneCommit.Result r = createChange();
+    ChangeInfo result = Iterables.getOnlyElement(
+        gApi.changes().query(r.getChangeId()).get());
+    assertNull(result.labels);
+    assertNull(result.messages);
+    assertNull(result.revisions);
+    assertNull(result.actions);
+  }
+
+  @Test
+  public void queryChangesOptions() throws Exception {
+    PushOneCommit.Result r = createChange();
+    ChangeInfo result = Iterables.getOnlyElement(gApi.changes()
+        .query(r.getChangeId())
+        .withOptions(EnumSet.allOf(ListChangesOption.class))
+        .get());
+    assertEquals("Code-Review",
+        Iterables.getOnlyElement(result.labels.keySet()));
+    assertEquals(1, result.messages.size());
+    assertFalse(result.actions.isEmpty());
+
+    RevisionInfo rev = Iterables.getOnlyElement(result.revisions.values());
+    assertEquals(r.getPatchSetId().get(), rev._number);
+    assertFalse(rev.actions.isEmpty());
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java
index 976228f..0718810 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java
@@ -14,11 +14,18 @@
 
 package com.google.gerrit.server.api.changes;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.api.changes.ChangeApi;
 import com.google.gerrit.extensions.api.changes.Changes;
 import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
@@ -27,23 +34,29 @@
 import com.google.gerrit.server.change.ChangesCollection;
 import com.google.gerrit.server.change.CreateChange;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.query.change.QueryChanges;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 
 import java.io.IOException;
+import java.util.List;
 
-class ChangesImpl extends Changes.NotImplemented implements Changes {
+class ChangesImpl implements Changes {
   private final ChangesCollection changes;
   private final ChangeApiImpl.Factory api;
   private final CreateChange.Factory createChangeFactory;
+  private final Provider<QueryChanges> queryProvider;
 
   @Inject
   ChangesImpl(ChangesCollection changes,
       ChangeApiImpl.Factory api,
-      CreateChange.Factory createChangeFactory) {
+      CreateChange.Factory createChangeFactory,
+      Provider<QueryChanges> queryProvider) {
     this.changes = changes;
     this.api = api;
     this.createChangeFactory = createChangeFactory;
+    this.queryProvider = queryProvider;
   }
 
   @Override
@@ -82,4 +95,50 @@
       throw new RestApiException("Cannot create change", e);
     }
   }
+
+  @Override
+  public QueryRequest query() {
+    return new QueryRequest() {
+      @Override
+      public List<ChangeInfo> get() throws RestApiException {
+        return ChangesImpl.this.get(this);
+      }
+    };
+  }
+
+  @Override
+  public QueryRequest query(String query) {
+    return query().withQuery(query);
+  }
+
+  private List<ChangeInfo> get(final QueryRequest q) throws RestApiException {
+    QueryChanges qc = queryProvider.get();
+    if (q.getQuery() != null) {
+      qc.addQuery(q.getQuery());
+    }
+    qc.setLimit(q.getLimit());
+    qc.setStart(q.getStart());
+    for (ListChangesOption option : q.getOptions()) {
+      qc.addOption(option);
+    }
+
+    try {
+      List<?> result = qc.apply(TopLevelResource.INSTANCE);
+      if (result.isEmpty()) {
+        return ImmutableList.of();
+      }
+
+      // Check type safety of result; the extension API should be safer than the
+      // REST API in this case, since it's intended to be used in Java.
+      Object first = checkNotNull(result.iterator().next());
+      checkState(first instanceof ChangeJson.ChangeInfo);
+      @SuppressWarnings("unchecked")
+      List<ChangeJson.ChangeInfo> infos = (List<ChangeJson.ChangeInfo>) result;
+
+      return ImmutableList.copyOf(
+          Lists.transform(infos, ChangeInfoMapper.INSTANCE));
+    } catch (BadRequestException | AuthException | OrmException e) {
+      throw new RestApiException("Cannot query changes", e);
+    }
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
index 41c2e23..fb55b49 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
@@ -106,7 +106,7 @@
   }
 
   @Override
-  public Object apply(TopLevelResource rsrc)
+  public List<?> apply(TopLevelResource rsrc)
       throws BadRequestException, AuthException, OrmException {
     List<List<ChangeInfo>> out;
     try {