Add a get dependency REST api

Add a REST api to get zuul cross project dependency info and refactor
the UI code to use this new api.  This makes the UI code much
simpler.
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chound/DependencyInfo.java b/src/main/java/com/googlesource/gerrit/plugins/chound/DependencyInfo.java
new file mode 100644
index 0000000..347f2a1
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chound/DependencyInfo.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2015 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.chound;
+
+import java.util.List;
+
+public class DependencyInfo {
+  public List<String> dependsOn;
+  public List<String> neededBy;
+  public boolean cycle;
+}
\ No newline at end of file
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chound/GetDependency.java b/src/main/java/com/googlesource/gerrit/plugins/chound/GetDependency.java
new file mode 100644
index 0000000..3f0afeb
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chound/GetDependency.java
@@ -0,0 +1,97 @@
+// Copyright (C) 2015 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.chound;
+
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.change.ChangesCollection;
+import com.google.gerrit.server.change.RevisionResource;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.query.change.QueryChanges;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Singleton
+public class GetDependency implements RestReadView<RevisionResource> {
+  private final ChangesCollection changes;
+  private final GitRepositoryManager repoManager;
+
+  @Inject
+  GetDependency(ChangesCollection changes, GitRepositoryManager repoManager) {
+    this.changes = changes;
+    this.repoManager = repoManager;
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public DependencyInfo apply(RevisionResource rsrc)
+      throws RepositoryNotFoundException, IOException, BadRequestException,
+      AuthException, OrmException {
+
+    DependencyInfo out = new DependencyInfo();
+    out.dependsOn = new ArrayList<>();
+    out.neededBy = new ArrayList<>();
+
+    // get depends on info
+    Project.NameKey p = rsrc.getChange().getProject();
+    try (Repository repo = repoManager.openRepository(p);
+        RevWalk rw = new RevWalk(repo)) {
+      String rev = rsrc.getPatchSet().getRevision().get();
+      RevCommit commit = rw.parseCommit(ObjectId.fromString(rev));
+      String commitMsg = commit.getFullMessage();
+      Pattern pattern = Pattern.compile("[Dd]epends-[Oo]n:? (I[0-9a-f]{8,40})",
+          Pattern.DOTALL);
+      Matcher matcher = pattern.matcher(commitMsg);
+      while (matcher.find()) {
+        out.dependsOn.add(matcher.group(1));
+      }
+    }
+
+    // get needed by info
+    Change.Key chgKey = rsrc.getChange().getKey();
+    QueryChanges query = changes.list();
+    String neededByQuery = "message:" + chgKey + " -change:" + chgKey;
+    query.addQuery(neededByQuery);
+    List<ChangeInfo> changes =
+        (List<ChangeInfo>) query.apply(TopLevelResource.INSTANCE);
+    // check for dependency cycles
+    for (ChangeInfo change : changes) {
+      if (out.dependsOn.contains(change.changeId)) {
+        out.cycle = true;
+      }
+      out.neededBy.add(change.changeId);
+    }
+
+    return out;
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chound/Module.java b/src/main/java/com/googlesource/gerrit/plugins/chound/Module.java
index 460d954..13fc143 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chound/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chound/Module.java
@@ -14,6 +14,8 @@
 
 package com.googlesource.gerrit.plugins.chound;
 
+import static com.google.gerrit.server.change.RevisionResource.REVISION_KIND;
+
 import com.google.gerrit.extensions.restapi.RestApiModule;
 import com.google.inject.AbstractModule;
 
@@ -24,6 +26,7 @@
     install(new RestApiModule() {
       @Override
       protected void configure() {
+        get(REVISION_KIND, "dependency").to(GetDependency.class);
       }
     });
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chound/client/DependencyInfo.java b/src/main/java/com/googlesource/gerrit/plugins/chound/client/DependencyInfo.java
new file mode 100644
index 0000000..e1d163c
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chound/client/DependencyInfo.java
@@ -0,0 +1,14 @@
+package com.googlesource.gerrit.plugins.chound.client;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+public class DependencyInfo extends JavaScriptObject {
+
+  public final native JsArrayString dependsOn() /*-{ return this.depends_on; }-*/;
+  public final native JsArrayString neededBy() /*-{ return this.needed_by; }-*/;
+  public final native boolean cycle() /*-{ return this.cycle; }-*/;
+
+  protected DependencyInfo() {
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chound/client/LabelPanel.java b/src/main/java/com/googlesource/gerrit/plugins/chound/client/LabelPanel.java
index 9be57e6..c12ceb8 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chound/client/LabelPanel.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chound/client/LabelPanel.java
@@ -16,13 +16,10 @@
 
 import com.google.gerrit.client.GerritUiExtensionPoint;
 import com.google.gerrit.client.info.ChangeInfo;
-import com.google.gerrit.client.info.ChangeInfo.CommitInfo;
 import com.google.gerrit.client.info.ChangeInfo.RevisionInfo;
-import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.plugin.client.Plugin;
 import com.google.gerrit.plugin.client.extension.Panel;
 import com.google.gerrit.plugin.client.rpc.RestApi;
-import com.google.gwt.regexp.shared.MatchResult;
-import com.google.gwt.regexp.shared.RegExp;
 import com.google.gwt.user.client.rpc.AsyncCallback;
 import com.google.gwt.user.client.ui.Grid;
 import com.google.gwt.user.client.ui.HorizontalPanel;
@@ -45,30 +42,13 @@
     final RevisionInfo rev =
         panel.getObject(GerritUiExtensionPoint.Key.REVISION_INFO).cast();
 
-    // revision might not be available when using inline edit
-    if (rev != null && !rev.isEdit()) {
-      new RestApi("changes").id(change.id()).view("revisions").id(rev.id())
-          .view("commit").get(new AsyncCallback<CommitInfo>() {
-            @Override
-            public void onSuccess(CommitInfo result) {
-              if (result != null) {
-                displayDependsOn(result);
-              }
-            }
-
-            @Override
-            public void onFailure(Throwable caught) {
-              // never invoked
-            }
-          });
-    }
-    new RestApi("changes").view("?q=message:" + change.changeId())
-        .view("+NOT+change:" + change.changeId())
-        .get(new AsyncCallback<NativeMap<ChangeInfo>>() {
+    new RestApi("changes").id(change.id()).view("revisions").id(rev.id())
+        .view(Plugin.get().getPluginName(), "dependency")
+        .get(new AsyncCallback<DependencyInfo>() {
           @Override
-          public void onSuccess(NativeMap<ChangeInfo> result) {
-            if (result != null && !result.isEmpty()) {
-              displayNeededBy(result);
+          public void onSuccess(DependencyInfo result) {
+            if (result != null) {
+              display(result);
             }
           }
 
@@ -79,41 +59,30 @@
         });
   }
 
-  private void displayDependsOn(CommitInfo result) {
+  private void display(DependencyInfo result) {
     int row = 0;
     int column = 1;
     Grid grid = new Grid(row, column);
-    String message = result.message();
-    if (message.toLowerCase().contains("depends-on:")) {
-      MatchResult matcher;
-      RegExp pattern =
-          RegExp.compile("[Dd]epends-[Oo]n:? (I[0-9a-f]{8,40})", "g");
-      while ((matcher = pattern.exec(message)) != null) {
-        HorizontalPanel p = new HorizontalPanel();
-        p.addStyleName("infoBlock");
-        Label label = new Label("Depends-on");
-        label.setWidth("72px");
-        p.add(label);
-        p.add(new CopyableLabel(matcher.getGroup(1)));
-        grid.insertRow(row);
-        grid.setWidget(row, 0, p);
-        row++;
-      }
-      add(grid);
+    // show depends-on ids
+    for (int i=0; i < result.dependsOn().length(); i++) {
+      HorizontalPanel p = new HorizontalPanel();
+      p.addStyleName("infoBlock");
+      Label label = new Label("Depends-on");
+      label.setWidth("72px");
+      p.add(label);
+      p.add(new CopyableLabel(result.dependsOn().get(i)));
+      grid.insertRow(row);
+      grid.setWidget(row, 0, p);
+      row++;
     }
-  }
-
-  private void displayNeededBy(NativeMap<ChangeInfo> result) {
-    int row = 0;
-    int column = 1;
-    Grid grid = new Grid(row, column);
-    for (String key : result.keySet()) {
+    // show needed-by ids
+    for (int i=0; i < result.neededBy().length(); i++) {
       HorizontalPanel p = new HorizontalPanel();
       p.addStyleName("infoBlock");
       Label label = new Label("Needed-by");
       label.setWidth("72px");
       p.add(label);
-      p.add(new CopyableLabel(result.get(key).changeId()));
+      p.add(new CopyableLabel(result.neededBy().get(i)));
       grid.insertRow(row);
       grid.setWidget(row, 0, p);
       row++;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chound/chound.css b/src/main/java/com/googlesource/gerrit/plugins/chound/public/chound.css
similarity index 100%
rename from src/main/java/com/googlesource/gerrit/plugins/chound/chound.css
rename to src/main/java/com/googlesource/gerrit/plugins/chound/public/chound.css
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index 3fd5340..fa3a50d 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -1,3 +1 @@
-The @PLUGIN@ plugin tracks down referenced Gerrit changes from the
-commit message and displays it on the change screen.
-
+The @PLUGIN@ plugin shows Zuul cross project dependencies on the Gerrit UI.
diff --git a/src/main/resources/Documentation/rest-api-changes.md b/src/main/resources/Documentation/rest-api-changes.md
new file mode 100644
index 0000000..c35fa3a
--- /dev/null
+++ b/src/main/resources/Documentation/rest-api-changes.md
@@ -0,0 +1,70 @@
+@PLUGIN@ - /changes/ REST API
+==============================
+
+This page describes the '/changes/' REST endpoints that are added by
+the @PLUGIN@ plugin.
+
+Please also take note of the general information on the
+[REST API](../../../Documentation/rest-api.html).
+
+<a id="plugin-endpoints"> @PLUGIN@ Endpoints
+--------------------------------------------
+
+### <a id="get-dependency"> Get Dependency
+
+__GET__ /changes/{change-id}/revisions/{revision-id}/@PLUGIN@~dependency
+
+Gets the zuul [dependency](#dependency-info) for a change.  Please refer to the
+general [changes rest api](../../../Documentation/rest-api-changes.html#get-review)
+for additional info on this request.
+
+#### Request
+
+```
+  GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/@PLUGIN@~dependency HTTP/1.0
+```
+
+#### Response
+
+```
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json; charset=UTF-8
+
+  )]}'
+  {
+    "depends_on": [
+      "Ic79ed94daa9b58527139aadba1b0d59d1f54754b",
+      "I66853bf0c18e60f8de14d44dfb7c2ca1c3793111"
+    ],
+    "needed_by": [
+      "I66853bf0c18e60f8de14d44dfb7c2ca1c379311d"
+    ],
+    "cycle": false
+  }
+```
+
+<a id="json-entities">JSON Entities
+-----------------------------------
+
+### <a id="dependency-info"></a>DependencyInfo
+
+The `DependencyInfo` entity shows zuul dependencies on a patch set.
+
+|Field Name |Description|
+|:----------|:----------|
+|depends_on |List of changes that this change depends on|
+|needed-by  |List of changes that is dependent on this change|
+|cycle      |Whether this change is in a circular dependency chain|
+
+
+SEE ALSO
+--------
+
+* [Change related REST endpoints](../../../Documentation/rest-api-changes.html)
+* [Plugin Development](../../../Documentation/dev-plugins.html)
+* [REST API Development](../../../Documentation/dev-rest-api.html)
+
+GERRIT
+------
+Part of [Gerrit Code Review](../../../Documentation/index.html)