Merge "Reimplement is:watched to inline filter query trees"
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java
index 096e12e..5966dde 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java
@@ -45,9 +45,6 @@
         c += p.getCost();
       }
     }
-    if (t.size() < 2) {
-      throw new IllegalArgumentException("Need at least two predicates");
-    }
     children = t;
     cost = c;
   }
@@ -101,7 +98,7 @@
   }
 
   @Override
-  public final String toString() {
+  public String toString() {
     final StringBuilder r = new StringBuilder();
     r.append("(");
     for (int i = 0; i < getChildCount(); i++) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 7f5e91e..430ae22 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -235,7 +235,7 @@
     }
 
     if ("watched".equalsIgnoreCase(value)) {
-      return new IsWatchedByPredicate(args, currentUser);
+      return new IsWatchedByPredicate(args, currentUser, false);
     }
 
     if ("visible".equalsIgnoreCase(value)) {
@@ -354,10 +354,10 @@
     for (Account.Id id : m) {
       if (currentUser instanceof IdentifiedUser
           && id.equals(((IdentifiedUser) currentUser).getAccountId())) {
-        p.add(new IsWatchedByPredicate(args, currentUser));
+        p.add(new IsWatchedByPredicate(args, currentUser, false));
       } else {
         p.add(new IsWatchedByPredicate(args,
-            args.userFactory.create(args.dbProvider, id)));
+            args.userFactory.create(args.dbProvider, id), true));
       }
     }
     return Predicate.or(p);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
index 25cfae7..a7005d7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
@@ -14,22 +14,18 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.AndPredicate;
 import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.QueryParseException;
-import com.google.gwtorm.server.OrmException;
 
-import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
-class IsWatchedByPredicate extends OperatorPredicate<ChangeData> {
+class IsWatchedByPredicate extends AndPredicate<ChangeData> {
   private static String describe(CurrentUser user) {
     if (user instanceof IdentifiedUser) {
       return ((IdentifiedUser) user).getAccountId().toString();
@@ -37,85 +33,81 @@
     return user.toString();
   }
 
-  private final ChangeQueryBuilder.Arguments args;
   private final CurrentUser user;
 
-  private Map<Project.NameKey, List<Predicate<ChangeData>>> rules;
-
-  IsWatchedByPredicate(ChangeQueryBuilder.Arguments args, CurrentUser user) {
-    super(ChangeQueryBuilder.FIELD_WATCHEDBY, describe(user));
-    this.args = args;
+  IsWatchedByPredicate(ChangeQueryBuilder.Arguments args,
+      CurrentUser user,
+      boolean checkIsVisible) {
+    super(filters(args, user, checkIsVisible));
     this.user = user;
   }
 
-  @Override
-  public boolean match(final ChangeData cd) throws OrmException {
-    if (rules == null) {
-      ChangeQueryBuilder builder = new ChangeQueryBuilder(args, user);
-      rules = new HashMap<Project.NameKey, List<Predicate<ChangeData>>>();
-      for (AccountProjectWatch w : user.getNotificationFilters()) {
-        List<Predicate<ChangeData>> list = rules.get(w.getProjectNameKey());
-        if (list == null) {
-          list = new ArrayList<Predicate<ChangeData>>(4);
-          rules.put(w.getProjectNameKey(), list);
-        }
-
-        Predicate<ChangeData> p = compile(builder, w);
-        if (p != null) {
-          list.add(p);
+  private static List<Predicate<ChangeData>> filters(
+      ChangeQueryBuilder.Arguments args,
+      CurrentUser user,
+      boolean checkIsVisible) {
+    List<Predicate<ChangeData>> r = Lists.newArrayList();
+    ChangeQueryBuilder builder = new ChangeQueryBuilder(args, user);
+    for (AccountProjectWatch w : user.getNotificationFilters()) {
+      Predicate<ChangeData> f = null;
+      if (w.getFilter() != null) {
+        try {
+          f = builder.parse(w.getFilter());
+          if (builder.find(f, IsWatchedByPredicate.class) != null) {
+            // If the query is going to infinite loop, assume it
+            // will never match and return null. Yes this test
+            // prevents you from having a filter that matches what
+            // another user is filtering on. :-)
+           continue;
+          }
+        } catch (QueryParseException e) {
+          continue;
         }
       }
-    }
 
-    if (rules.isEmpty()) {
-      return false;
-    }
+      Predicate<ChangeData> p;
+      if (w.getProjectNameKey().equals(args.allProjectsName)) {
+        p = null;
+      } else {
+        p = builder.project(w.getProjectNameKey().get());
+      }
 
-    Change change = cd.change(args.dbProvider);
-    if (change == null) {
-      return false;
-    }
-
-    Project.NameKey project = change.getDest().getParentKey();
-    List<Predicate<ChangeData>> list = rules.get(project);
-    if (list == null) {
-      list = rules.get(args.allProjectsName);
-    }
-    if (list != null) {
-      for (Predicate<ChangeData> p : list) {
-        if (p.match(cd)) {
-          return true;
-        }
+      if (p != null && f != null) {
+        r.add(and(p, f));
+      } else if (p != null) {
+        r.add(p);
+      } else if (f != null) {
+        r.add(f);
+      } else {
+        r.add(builder.status_open());
       }
     }
-    return false;
+    if (r.isEmpty()) {
+      return none();
+    } else if (checkIsVisible) {
+      return ImmutableList.of(or(r), builder.is_visible());
+    } else {
+      return r;
+    }
   }
 
-  @SuppressWarnings("unchecked")
-  private Predicate<ChangeData> compile(ChangeQueryBuilder builder,
-      AccountProjectWatch w) {
-    Predicate<ChangeData> p = builder.is_visible();
-    if (w.getFilter() != null) {
-      try {
-        p = Predicate.and(builder.parse(w.getFilter()), p);
-        if (builder.find(p, IsWatchedByPredicate.class) != null) {
-          // If the query is going to infinite loop, assume it
-          // will never match and return null. Yes this test
-          // prevents you from having a filter that matches what
-          // another user is filtering on. :-)
-          //
-          return null;
-        }
-        p = args.rewriter.get().rewrite(p);
-      } catch (QueryParseException e) {
-        return null;
-      }
-    }
-    return p;
+  private static List<Predicate<ChangeData>> none() {
+    Predicate<ChangeData> any = any();
+    return ImmutableList.of(not(any));
   }
 
   @Override
   public int getCost() {
     return 1;
   }
+
+  @Override
+  public String toString() {
+    String val = describe(user);
+    if (val.indexOf(' ') < 0) {
+      return ChangeQueryBuilder.FIELD_WATCHEDBY + ":" + val;
+    } else {
+      return ChangeQueryBuilder.FIELD_WATCHEDBY + ":\"" + val + "\"";
+    }
+  }
 }