Clean up account dashboard owner:"Full Name" link

The code to format an operator like owner:$NAME already mostly exists
to support other types of queries, like projects. Reuse that code in
PageLinks rather than building it from scratch again.

Update the rules in PageLinks about what strings must be quoted to be
the exact character class used by Query.g to parse the string. A bare
operator can be any sequence of characters not in the NON_WORD class.

Delete some now unused code related to the old AccountDashboard link.

Default the owner link to status:open. This is faster for servers to
answer, as the set of open changes is often much smaller than all
changes owned by a user. Its also indexed through byOwnerOpen(). Users
that want to see the closed, merged or abandoned changes by the owner
should update the query string to specify what they want to view.

Change-Id: I697cac913e264ade70b314e37317b76e26283b02
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
index 25f4f22a..c0c317c 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
@@ -14,13 +14,11 @@
 
 package com.google.gerrit.common;
 
-import com.google.gerrit.common.data.AccountInfo;
 import com.google.gerrit.common.data.ChangeInfo;
-import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Change.Status;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.Change.Status;
 import com.google.gwtorm.client.KeyUtil;
 
 public class PageLinks {
@@ -60,15 +58,8 @@
   }
 
   public static String toAccountQuery(final String fullname) {
-    return "/q/owner:\"" + KeyUtil.encode(fullname) + "\"," + TOP;
-  }
-
-  public static String toAccountDashboard(final AccountInfo acct) {
-    return toAccountDashboard(acct.getId());
-  }
-
-  public static String toAccountDashboard(final Account.Id acct) {
-    return "/dashboard/" + acct.toString();
+    String query = op("owner", fullname) + " status:open";
+    return toChangeQuery(query, TOP);
   }
 
   public static String toChangeQuery(final String query) {
@@ -76,8 +67,7 @@
   }
 
   public static String toChangeQuery(String query, String page) {
-    query = KeyUtil.encode(query).replaceAll("%3[Aa]", ":");
-    return "/q/" + query + "," + page;
+    return "/q/" + KeyUtil.encode(query) + "," + page;
   }
 
   public static String projectQuery(Project.NameKey proj, Status status) {
@@ -95,11 +85,18 @@
     }
   }
 
-  public static String op(String name, String value) {
-    if (value.indexOf(' ') >= 0) {
-      return name + ":\"" + value + "\"";
+  public static String op(String op, String value) {
+    if (isSingleWord(value)) {
+      return op + ":" + value;
     }
-    return name + ":" + value;
+    return op + ":\"" + value + "\"";
+  }
+
+  private static boolean isSingleWord(String value) {
+    if (value.startsWith("-")) {
+      return false;
+    }
+    return value.matches("[^\u0000-\u0020!\"#$%&'():;?\\[\\]{}~]+");
   }
 
   protected PageLinks() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
index fd6ba2d..e16344b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
@@ -323,6 +323,7 @@
     Cookies.removeCookie("GerritAccount");
   }
 
+  @Override
   public void onModuleLoad() {
     UserAgent.assertNotInIFrame();
 
@@ -332,6 +333,7 @@
         e = URL.encodeQueryString(e);
         e = fixPathImpl(e);
         e = fixColonImpl(e);
+        e = fixDoubleQuote(e);
         return e;
       }
 
@@ -345,6 +347,9 @@
 
       private native String fixColonImpl(String path)
       /*-{ return path.replace(/%3A/g, ":"); }-*/;
+
+      private native String fixDoubleQuote(String path)
+      /*-{ return path.replace(/%22/g, '"'); }-*/;
     });
 
     initHostname();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
index be892a1..dead713 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
@@ -19,7 +19,6 @@
 import com.google.gerrit.client.rpc.NativeList;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
 import com.google.gerrit.client.ui.Screen;
-import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.reviewdb.client.Account;
 
 import java.util.Collections;
@@ -56,7 +55,7 @@
     table.addSection(incoming);
     table.addSection(closed);
     add(table);
-    table.setSavePointerId(PageLinks.toAccountDashboard(ownerId));
+    table.setSavePointerId("owner:" + ownerId);
   }
 
   @Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
index 8b86d50..a362e1c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
@@ -381,10 +381,9 @@
     final FlowPanel fp = new FlowPanel();
     fp.setStyleName(Gerrit.RESOURCES.css().patchSetUserIdentity());
     if (who.getName() != null) {
-      final Account.Id aId = who.getAccount();
-      if (aId != null) {
-        fp.add(new InlineHyperlink(who.getName(), PageLinks.toAccountQuery(who
-            .getName())));
+      if (who.getAccount() != null) {
+        fp.add(new InlineHyperlink(who.getName(),
+            PageLinks.toAccountQuery(who.getName())));
       } else {
         final InlineLabel lbl = new InlineLabel(who.getName());
         lbl.setStyleName(Gerrit.RESOURCES.css().accountName());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountLink.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountLink.java
index a4f4509..1680eaf 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountLink.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountLink.java
@@ -16,7 +16,6 @@
 
 import com.google.gerrit.client.FormatUtil;
 import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.changes.QueryScreen;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.data.AccountInfo;
 import com.google.gerrit.common.data.AccountInfoCache;
@@ -31,24 +30,26 @@
     return ai != null ? new AccountLink(ai) : null;
   }
 
-  private final String query;
-
   public AccountLink(final AccountInfo ai) {
-    this(FormatUtil.name(ai), ai);
+    this(owner(ai), ai);
   }
 
   public AccountLink(final String text, final AccountInfo ai) {
-    super(text, PageLinks.toAccountQuery(FormatUtil.name(ai)));
+    super(text, PageLinks.toAccountQuery(owner(ai)));
     setTitle(FormatUtil.nameEmail(ai));
-    this.query = "owner:\"" + FormatUtil.name(ai) + "\"";
   }
 
-  private Screen createScreen() {
-    return QueryScreen.forQuery(query);
+  private static String owner(AccountInfo ai) {
+    if (ai.getPreferredEmail() != null) {
+      return ai.getPreferredEmail();
+    } else if (ai.getFullName() != null) {
+      return ai.getFullName();
+    }
+    return "" + ai.getId().get();
   }
 
   @Override
   public void go() {
-    Gerrit.display(getTargetHistoryToken(), createScreen());
+    Gerrit.display(getTargetHistoryToken());
   }
 }