Add inname, description and default field to project query builder

Change-Id: I671a01ff1280763e7f8ae2eafc9fd11cbb1b5275
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectPredicates.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectPredicates.java
index dbaf4a3..379c564 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectPredicates.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectPredicates.java
@@ -20,12 +20,21 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.index.project.ProjectField;
 import com.google.gerrit.server.project.ProjectData;
+import java.util.Locale;
 
 public class ProjectPredicates {
   public static Predicate<ProjectData> name(Project.NameKey nameKey) {
     return new ProjectPredicate(ProjectField.NAME, nameKey.get());
   }
 
+  public static Predicate<ProjectData> inname(String name) {
+    return new ProjectPredicate(ProjectField.NAME_PART, name.toLowerCase(Locale.US));
+  }
+
+  public static Predicate<ProjectData> description(String description) {
+    return new ProjectPredicate(ProjectField.DESCRIPTION, description);
+  }
+
   static class ProjectPredicate extends IndexPredicate<ProjectData> {
     ProjectPredicate(FieldDef<ProjectData, ?> def, String value) {
       super(def, value);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java
index 608ec9a..e9e9c0f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.query.project;
 
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
 import com.google.common.primitives.Ints;
 import com.google.gerrit.index.query.LimitPredicate;
 import com.google.gerrit.index.query.Predicate;
@@ -22,6 +24,7 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.project.ProjectData;
 import com.google.inject.Inject;
+import java.util.List;
 
 /** Parses a query string meant to be applied to project objects. */
 public class ProjectQueryBuilder extends QueryBuilder<ProjectData> {
@@ -41,6 +44,35 @@
   }
 
   @Operator
+  public Predicate<ProjectData> inname(String namePart) {
+    if (namePart.isEmpty()) {
+      return name(namePart);
+    }
+    return ProjectPredicates.inname(namePart);
+  }
+
+  @Operator
+  public Predicate<ProjectData> description(String description) throws QueryParseException {
+    if (Strings.isNullOrEmpty(description)) {
+      throw error("description operator requires a value");
+    }
+
+    return ProjectPredicates.description(description);
+  }
+
+  @Override
+  protected Predicate<ProjectData> defaultField(String query) throws QueryParseException {
+    // Adapt the capacity of this list when adding more default predicates.
+    List<Predicate<ProjectData>> preds = Lists.newArrayListWithCapacity(3);
+    preds.add(name(query));
+    preds.add(inname(query));
+    if (!Strings.isNullOrEmpty(query)) {
+      preds.add(description(query));
+    }
+    return Predicate.or(preds);
+  }
+
+  @Operator
   public Predicate<ProjectData> limit(String query) throws QueryParseException {
     Integer limit = Ints.tryParse(query);
     if (limit == null) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
index 6de8900..8804b96 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
@@ -17,11 +17,13 @@
 import static com.google.common.truth.Truth.assertThat;
 import static java.util.stream.Collectors.toList;
 
+import com.google.common.base.CharMatcher;
 import com.google.gerrit.extensions.api.GerritApi;
 import com.google.gerrit.extensions.api.projects.ProjectInput;
 import com.google.gerrit.extensions.api.projects.Projects.QueryRequest;
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.common.ProjectInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.lifecycle.LifecycleManager;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Project;
@@ -51,6 +53,7 @@
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Locale;
 import org.eclipse.jgit.lib.Config;
 import org.junit.After;
 import org.junit.Before;
@@ -178,6 +181,49 @@
   }
 
   @Test
+  public void byInname() throws Exception {
+    String namePart = getSanitizedMethodName();
+    namePart = CharMatcher.is('_').removeFrom(namePart);
+
+    ProjectInfo project1 = createProject(name("project-" + namePart));
+    ProjectInfo project2 = createProject(name("project-" + namePart + "-2"));
+    ProjectInfo project3 = createProject(name("project-" + namePart + "3"));
+
+    assertQuery("inname:" + namePart, project1, project2, project3);
+    assertQuery("inname:" + namePart.toUpperCase(Locale.US), project1, project2, project3);
+    assertQuery("inname:" + namePart.toLowerCase(Locale.US), project1, project2, project3);
+  }
+
+  @Test
+  public void byDescription() throws Exception {
+    ProjectInfo project1 =
+        createProjectWithDescription(name("project1"), "This is a test project.");
+    ProjectInfo project2 = createProjectWithDescription(name("project2"), "ANOTHER TEST PROJECT.");
+    createProjectWithDescription(name("project3"), "Maintainers of project foo.");
+    assertQuery("description:test", project1, project2);
+
+    assertQuery("description:non-existing");
+
+    exception.expect(BadRequestException.class);
+    exception.expectMessage("description operator requires a value");
+    assertQuery("description:\"\"");
+  }
+
+  @Test
+  public void byDefaultField() throws Exception {
+    ProjectInfo project1 = createProject(name("foo-project"));
+    ProjectInfo project2 = createProject(name("project2"));
+    ProjectInfo project3 =
+        createProjectWithDescription(
+            name("project3"),
+            "decription that contains foo and the UUID of project2: " + project2.id);
+
+    assertQuery("non-existing");
+    assertQuery("foo", project1, project3);
+    assertQuery(project2.id, project2, project3);
+  }
+
+  @Test
   public void withLimit() throws Exception {
     ProjectInfo project1 = createProject(name("project1"));
     ProjectInfo project2 = createProject(name("project2"));