Merge branch 'stable-2.15'

* stable-2.15:
  Fix name of commons-io dependency
  Fix name of prolog dependency
  Format BUILD file with buildifier

Change-Id: I11275980a4fa628483f46e1b3ff4d05f52002961
diff --git a/BUILD b/BUILD
index 0371641..8c3dff9 100644
--- a/BUILD
+++ b/BUILD
@@ -1,29 +1,42 @@
 load("//lib/prolog:prolog.bzl", "prolog_cafe_library")
 load("//tools/bzl:junit.bzl", "junit_tests")
-load("//tools/bzl:plugin.bzl", "PLUGIN_DEPS", "PLUGIN_TEST_DEPS", "gerrit_plugin")
+load(
+    "//tools/bzl:plugin.bzl",
+    "PLUGIN_DEPS",
+    "PLUGIN_DEPS_NEVERLINK",
+    "PLUGIN_TEST_DEPS",
+    "gerrit_plugin",
+)
+
+MODULE = ["src/main/java/com/googlesource/gerrit/plugins/findowners/Module.java"]
 
 java_library(
     name = "find-owners-lib",
-    srcs = glob(["src/main/java/**/*.java"]),
-    deps = PLUGIN_DEPS + ["@prolog-runtime//jar"],
+    srcs = glob(
+        ["src/main/java/**/*.java"],
+        exclude = MODULE,
+    ),
+    deps = PLUGIN_DEPS_NEVERLINK + [
+        "@prolog-runtime//jar:neverlink",
+    ],
 )
 
 prolog_cafe_library(
     name = "find-owners-prolog-rules",
     srcs = glob(["src/main/prolog/*.pl"]),
-    deps = [
+    deps = PLUGIN_DEPS_NEVERLINK + [
         ":find-owners-lib",
-        "//gerrit-server:prolog-common",
     ],
 )
 
 gerrit_plugin(
     name = "find-owners",
-    srcs = glob(["src/main/java/**/Module.java"]),
+    srcs = MODULE,
     manifest_entries = [
         "Gerrit-PluginName: find-owners",
         "Gerrit-ReloadMode: restart",
         "Gerrit-Module: com.googlesource.gerrit.plugins.findowners.Module",
+        "Gerrit-BatchModule: com.googlesource.gerrit.plugins.findowners.PredicateModule",
         "Implementation-Title: Find-Owners plugin",
         "Implementation-URL: https://gerrit.googlesource.com/plugins/find-owners",
     ],
@@ -37,11 +50,11 @@
 junit_tests(
     name = "findowners_tests",
     srcs = glob(["src/test/java/**/*.java"]),
-    # resources = glob(['src/test/resources/**/*']),
     tags = ["findowners"],
     deps = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [
         "@commons-io//jar",
         ":find-owners-lib",
         ":find-owners-prolog-rules",
+        ":find-owners__plugin",
     ],
 )
diff --git a/src/main/java/com/googlesource/gerrit/plugins/findowners/Action.java b/src/main/java/com/googlesource/gerrit/plugins/findowners/Action.java
index d232382..361cecc 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/findowners/Action.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/findowners/Action.java
@@ -14,13 +14,15 @@
 
 package com.googlesource.gerrit.plugins.findowners;
 
+import static java.util.stream.Collectors.toList;
+
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Streams;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.extensions.webui.UiAction;
-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.server.ReviewDb;
@@ -32,6 +34,8 @@
 import com.google.gerrit.server.change.RevisionResource;
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
@@ -44,6 +48,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import org.eclipse.jgit.lib.Repository;
 import org.slf4j.Logger;
@@ -60,6 +65,7 @@
   private GitRepositoryManager repoManager;
   private Provider<CurrentUser> userProvider;
   private SchemaFactory<ReviewDb> reviewDbProvider;
+  private ProjectCache projectCache;
 
   static class Parameters {
     Boolean debug; // REST API "debug" parameter, or null
@@ -75,13 +81,15 @@
       ChangeData.Factory changeDataFactory,
       AccountCache accountCache,
       Emails emails,
-      GitRepositoryManager repoManager) {
+      GitRepositoryManager repoManager,
+      ProjectCache projectCache) {
     this.userProvider = userProvider;
     this.reviewDbProvider = reviewDbProvider;
     this.changeDataFactory = changeDataFactory;
     this.accountCache = accountCache;
     this.emails = emails;
     this.repoManager = repoManager;
+    this.projectCache = projectCache;
     Config.setVariables(pluginName, configFactory);
     Cache.getInstance(); // Create a single Cache.
   }
@@ -90,8 +98,8 @@
   Action() {}
 
   private String getUserName() {
-    if (userProvider != null && userProvider.get().getUserName() != null) {
-      return userProvider.get().getUserName();
+    if (userProvider != null && userProvider.get().getUserName().isPresent()) {
+      return userProvider.get().getUserName().get();
     }
     return "?";
   }
@@ -136,17 +144,21 @@
 
   /** Returns reviewer emails got from ChangeData. */
   static List<String> getReviewers(ChangeData changeData, AccountCache accountCache) {
-    List<String> result = new ArrayList<>();
     try {
-      for (Account.Id id : changeData.reviewers().all()) {
-        Account account = accountCache.get(id).getAccount();
-        result.add(account.getPreferredEmail());
-      }
+      // Reviewers may have no preferred email, skip them if the preferred email is not set.
+      return changeData
+          .reviewers()
+          .all()
+          .stream()
+          .map(accountCache::get)
+          .flatMap(Streams::stream)
+          .map(a -> a.getAccount().getPreferredEmail())
+          .filter(Objects::nonNull)
+          .collect(toList());
     } catch (OrmException e) {
       log.error("Exception for " + Config.getChangeId(changeData), e);
-      result = new ArrayList<>();
+      return new ArrayList<>();
     }
-    return result;
   }
 
   /** Returns the current patchset number or the given patchsetNum if it is valid. */
@@ -171,12 +183,16 @@
       Repository repository, Parameters params, ChangeData changeData)
       throws OrmException, BadRequestException, IOException {
     int patchset = getValidPatchsetNum(changeData, params.patchset);
-    OwnersDb db = Cache.getInstance().get(accountCache, emails, repository, changeData, patchset);
+    ProjectState projectState = projectCache.get(changeData.project());
+    OwnersDb db =
+        Cache.getInstance()
+            .get(projectState, accountCache, emails, repository, changeData, patchset);
     Collection<String> changedFiles = changeData.currentFilePaths();
     Map<String, Set<String>> file2Owners = db.findOwners(changedFiles);
 
     Boolean addDebugMsg = (params.debug != null) ? params.debug : Config.getAddDebugMsg();
-    RestResult obj = new RestResult(Config.getMinOwnerVoteLevel(changeData), addDebugMsg);
+    RestResult obj =
+        new RestResult(Config.getMinOwnerVoteLevel(projectState, changeData), addDebugMsg);
     obj.change = changeData.getId().get();
     obj.patchset = patchset;
     obj.ownerRevision = db.revision;
@@ -219,7 +235,14 @@
       if (needFindOwners && !Config.getAlwaysShowButton()) {
         needFindOwners = false; // Show button only if some owner is found.
         try (Repository repo = repoManager.openRepository(change.getProject())) {
-          OwnersDb db = Cache.getInstance().get(accountCache, emails, repo, changeData);
+          OwnersDb db =
+              Cache.getInstance()
+                  .get(
+                      projectCache.get(resource.getProject()),
+                      accountCache,
+                      emails,
+                      repo,
+                      changeData);
           log.trace("getDescription db key = " + db.key);
           needFindOwners = db.getNumOwners() > 0;
         }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/findowners/Cache.java b/src/main/java/com/googlesource/gerrit/plugins/findowners/Cache.java
index aa43896..3f48166 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/findowners/Cache.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/findowners/Cache.java
@@ -17,9 +17,9 @@
 import static java.util.concurrent.TimeUnit.SECONDS;
 
 import com.google.common.cache.CacheBuilder;
-import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.Emails;
+import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gwtorm.server.OrmException;
 import java.io.IOException;
@@ -88,48 +88,59 @@
   }
 
   /** Returns a cached or new OwnersDb, for the current patchset. */
-  OwnersDb get(AccountCache accountCache, Emails emails, Repository repo, ChangeData changeData)
+  OwnersDb get(
+      ProjectState projectState,
+      AccountCache accountCache,
+      Emails emails,
+      Repository repo,
+      ChangeData changeData)
       throws OrmException, IOException {
-    return get(accountCache, emails, repo, changeData, changeData.currentPatchSet().getId().get());
+    return get(
+        projectState,
+        accountCache,
+        emails,
+        repo,
+        changeData,
+        changeData.currentPatchSet().getId().get());
   }
 
   /** Returns a cached or new OwnersDb, for the specified patchset. */
   OwnersDb get(
+      ProjectState projectState,
       AccountCache accountCache,
       Emails emails,
       Repository repository,
       ChangeData changeData,
       int patchset)
       throws OrmException, IOException {
-    Project.NameKey project = changeData.change().getProject();
     String branch = changeData.change().getDest().get();
     String dbKey = Cache.makeKey(changeData.getId().get(), patchset, branch);
     // TODO: get changed files of the given patchset?
     return get(
+        projectState,
         accountCache,
         emails,
         dbKey,
         repository,
         changeData,
-        project,
         branch,
         changeData.currentFilePaths());
   }
 
   /** Returns a cached or new OwnersDb, for the specified branch and changed files. */
   OwnersDb get(
+      ProjectState projectState,
       AccountCache accountCache,
       Emails emails,
       String key,
       Repository repository,
       ChangeData changeData,
-      Project.NameKey project,
       String branch,
       Collection<String> files) {
     if (dbCache == null) { // Do not cache OwnersDb
       log.trace("Create new OwnersDb, key=" + key);
       return new OwnersDb(
-          accountCache, emails, key, repository, changeData, project, branch, files);
+          projectState, accountCache, emails, key, repository, changeData, branch, files);
     }
     try {
       log.trace("Get from cash " + dbCache + ", key=" + key + ", cache size=" + dbCache.size());
@@ -140,13 +151,13 @@
             public OwnersDb call() {
               log.trace("Create new OwnersDb, key=" + key);
               return new OwnersDb(
-                  accountCache, emails, key, repository, changeData, project, branch, files);
+                  projectState, accountCache, emails, key, repository, changeData, branch, files);
             }
           });
     } catch (ExecutionException e) {
       log.error("Cache.get has exception for " + Config.getChangeId(changeData), e);
       return new OwnersDb(
-          accountCache, emails, key, repository, changeData, project, branch, files);
+          projectState, accountCache, emails, key, repository, changeData, branch, files);
     }
   }
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/findowners/Checker.java b/src/main/java/com/googlesource/gerrit/plugins/findowners/Checker.java
index ae163d5..54861a1 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/findowners/Checker.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/findowners/Checker.java
@@ -16,15 +16,17 @@
 
 import com.google.gerrit.reviewdb.client.Change.Status;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.rules.StoredValues;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.Emails;
+import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.rules.StoredValues;
 import com.google.gwtorm.server.OrmException;
 import com.googlecode.prolog_cafe.lang.Prolog;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import org.eclipse.jgit.lib.Repository;
 import org.slf4j.Logger;
@@ -54,16 +56,24 @@
     Map<String, Integer> map = new HashMap<>();
     for (PatchSetApproval p : changeData.currentApprovals()) {
       if (p.getValue() != 0) {
-        map.put(
-            accountCache.get(p.getAccountId()).getAccount().getPreferredEmail(),
-            Integer.valueOf(p.getValue()));
+        // Reviewers may have no preferred email, skip them if the preferred email is not set.
+        Optional<String> preferredEmail =
+            accountCache.get(p.getAccountId()).map(a -> a.getAccount().getPreferredEmail());
+        if (preferredEmail.isPresent()) {
+          map.put(preferredEmail.get(), Integer.valueOf(p.getValue()));
+        }
       }
     }
     // Give CL author a default minVoteLevel vote.
-    String author =
-        accountCache.get(changeData.change().getOwner()).getAccount().getPreferredEmail();
-    if (!map.containsKey(author) || map.get(author) == 0) {
-      map.put(author, minVoteLevel);
+    // The preferred email of the author may not be set. Pushing changes only requires an email in
+    // the external IDs, but the preferred email may still be null (also emails may have been
+    // deleted after creating the change). Skip the author if it doesn't have a preferred email.
+    Optional<String> author =
+        accountCache
+            .get(changeData.change().getOwner())
+            .map(a -> a.getAccount().getPreferredEmail());
+    if (author.isPresent() && (!map.containsKey(author.get()) || map.get(author.get()) == 0)) {
+      map.put(author.get(), minVoteLevel);
     }
     return map;
   }
@@ -104,10 +114,12 @@
     ChangeData changeData = null;
     try {
       changeData = StoredValues.CHANGE_DATA.get(engine);
+      ProjectState projectState = StoredValues.PROJECT_STATE.get(engine);
       AccountCache accountCache = StoredValues.ACCOUNT_CACHE.get(engine);
       Emails emails = StoredValues.EMAILS.get(engine);
       Repository repository = StoredValues.REPOSITORY.get(engine);
-      return new Checker(repository, changeData, minVoteLevel).findApproval(accountCache, emails);
+      return new Checker(repository, changeData, minVoteLevel)
+          .findApproval(projectState, accountCache, emails);
     } catch (OrmException | IOException e) {
       log.error("Exception for " + Config.getChangeId(changeData), e);
       return 0; // owner approval may or may not be required.
@@ -130,18 +142,20 @@
     return (status == Status.ABANDONED || status == Status.MERGED);
   }
 
-  int findApproval(AccountCache accountCache, Emails emails) throws OrmException, IOException {
+  int findApproval(ProjectState projectState, AccountCache accountCache, Emails emails)
+      throws OrmException, IOException {
     if (isExemptFromOwnerApproval(changeData)) {
       return 0;
     }
     // One update to a Gerrit change can call submit_rule or submit_filter
     // many times. So this function should use cached values.
-    OwnersDb db = Cache.getInstance().get(accountCache, emails, repository, changeData);
+    OwnersDb db =
+        Cache.getInstance().get(projectState, accountCache, emails, repository, changeData);
     if (db.getNumOwners() <= 0) {
       return 0;
     }
     if (minVoteLevel <= 0) {
-      minVoteLevel = Config.getMinOwnerVoteLevel(changeData);
+      minVoteLevel = Config.getMinOwnerVoteLevel(projectState, changeData);
     }
     log.trace("findApproval db key = " + db.key);
     return findApproval(accountCache, db);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/findowners/Config.java b/src/main/java/com/googlesource/gerrit/plugins/findowners/Config.java
index e450dfb..c09b3df 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/findowners/Config.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/findowners/Config.java
@@ -15,12 +15,10 @@
 package com.googlesource.gerrit.plugins.findowners;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.config.PluginConfig;
 import com.google.gerrit.server.config.PluginConfigFactory;
-import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gwtorm.server.OrmException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -91,29 +89,31 @@
     return data == null ? "(unknown change)" : ("change c/" + data.getId().get());
   }
 
-  static String getOwnersFileName(Project.NameKey project, ChangeData c) {
-    if (config != null && project != null) {
-      try {
-        String name =
-            config
-                .getFromProjectConfigWithInheritance(project, PLUGIN_NAME)
-                .getString(OWNERS_FILE_NAME, OWNERS);
-        if (name.trim().equals("")) {
-          log.error(
-              "Project "
-                  + project
-                  + " has wrong "
-                  + OWNERS_FILE_NAME
-                  + ": \""
-                  + name
-                  + "\" for "
-                  + getChangeId(c));
-          return OWNERS;
-        }
-        return name;
-      } catch (NoSuchProjectException e) {
-        log.error("Cannot find project " + project + " for " + getChangeId(c), e);
+  static String getDefaultOwnersFileName() {
+    return OWNERS;
+  }
+
+  static String getOwnersFileName(ProjectState projectState, ChangeData c) {
+    if (projectState == null) {
+      log.error("Null projectState for change " + getChangeId(c));
+    } else if (config != null) {
+      String name =
+          config
+              .getFromProjectConfigWithInheritance(projectState, PLUGIN_NAME)
+              .getString(OWNERS_FILE_NAME, OWNERS);
+      if (name.trim().equals("")) {
+        log.error(
+            "Project "
+                + projectState.getProject()
+                + " has wrong "
+                + OWNERS_FILE_NAME
+                + ": \""
+                + name
+                + "\" for "
+                + getChangeId(c));
+        return OWNERS;
       }
+      return name;
     }
     return OWNERS;
   }
@@ -123,17 +123,16 @@
     reportSyntaxError = value;
   }
 
-  static int getMinOwnerVoteLevel(ChangeData changeData) throws OrmException {
-    Project.NameKey project = changeData.change().getProject();
-    try {
-      return (config == null || project == null)
-          ? minOwnerVoteLevel
-          : config
-              .getFromProjectConfigWithInheritance(project, PLUGIN_NAME)
-              .getInt(MIN_OWNER_VOTE_LEVEL, minOwnerVoteLevel);
-    } catch (NoSuchProjectException e) {
-      log.error("Cannot find project " + project + " for " + getChangeId(changeData), e);
+  static int getMinOwnerVoteLevel(ProjectState projectState, ChangeData c) {
+    if (projectState == null) {
+      log.error("Null projectState for change " + getChangeId(c));
       return minOwnerVoteLevel;
+    } else if (config == null) {
+      return minOwnerVoteLevel;
+    } else {
+      return config
+          .getFromProjectConfigWithInheritance(projectState, PLUGIN_NAME)
+          .getInt(MIN_OWNER_VOTE_LEVEL, minOwnerVoteLevel);
     }
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/findowners/GetOwners.java b/src/main/java/com/googlesource/gerrit/plugins/findowners/GetOwners.java
index fd60850..322a3b5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/findowners/GetOwners.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/findowners/GetOwners.java
@@ -25,6 +25,7 @@
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
@@ -58,7 +59,8 @@
       ChangeData.Factory dataFactory,
       AccountCache accountCache,
       Emails emails,
-      GitRepositoryManager repoManager) {
+      GitRepositoryManager repoManager,
+      ProjectCache projectCache) {
     this.action =
         new Action(
             pluginName,
@@ -68,7 +70,8 @@
             dataFactory,
             accountCache,
             emails,
-            repoManager);
+            repoManager,
+            projectCache);
   }
 
   @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/findowners/Module.java b/src/main/java/com/googlesource/gerrit/plugins/findowners/Module.java
index e57bd51..aab4097 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/findowners/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/findowners/Module.java
@@ -17,34 +17,14 @@
 import static com.google.gerrit.server.change.ChangeResource.CHANGE_KIND;
 import static com.google.gerrit.server.change.RevisionResource.REVISION_KIND;
 
-import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.restapi.RestApiModule;
 import com.google.gerrit.extensions.webui.JavaScriptPlugin;
 import com.google.gerrit.extensions.webui.WebUiPlugin;
-import com.google.gerrit.rules.PredicateProvider;
-import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.inject.AbstractModule;
-import com.google.inject.Inject;
 
 /** find-owners plugin module */
 public class Module extends AbstractModule {
-  /** Prolog Predicate Provider. */
-  static class FindOwnersProvider implements PredicateProvider {
-
-    @Inject
-    public FindOwnersProvider(@PluginName String pluginName, PluginConfigFactory configFactory) {
-      Config.setVariables(pluginName, configFactory);
-      Cache.getInstance(); // Create a single Cache.
-    }
-
-    @Override
-    public ImmutableSet<String> getPackages() {
-      return ImmutableSet.of(Config.PROLOG_NAMESPACE);
-    }
-  }
-
   @Override
   protected void configure() {
     install(OwnersValidator.module());
@@ -58,6 +38,7 @@
         });
     DynamicSet.bind(binder(), WebUiPlugin.class)
         .toInstance(new JavaScriptPlugin(Config.PLUGIN_NAME + ".js"));
-    DynamicSet.bind(binder(), PredicateProvider.class).to(FindOwnersProvider.class);
+
+    install(new PredicateModule());
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/findowners/OwnersDb.java b/src/main/java/com/googlesource/gerrit/plugins/findowners/OwnersDb.java
index 2c595b2..bd84c13 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/findowners/OwnersDb.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/findowners/OwnersDb.java
@@ -18,9 +18,9 @@
 
 import com.google.common.collect.Multimap;
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.Emails;
+import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.query.change.ChangeData;
 import java.nio.file.FileSystem;
 import java.nio.file.FileSystems;
@@ -63,19 +63,19 @@
   OwnersDb() {}
 
   OwnersDb(
+      ProjectState projectState,
       AccountCache accountCache,
       Emails emails,
       String key,
       Repository repository,
       ChangeData changeData,
-      Project.NameKey project,
       String branch,
       Collection<String> files) {
     this.accountCache = accountCache;
     this.emails = emails;
     this.key = key;
     preferredEmails.put("*", "*");
-    String ownersFileName = Config.getOwnersFileName(project, changeData);
+    String ownersFileName = Config.getOwnersFileName(projectState, changeData);
     // Some hacked CL could have a target branch that is not created yet.
     ObjectId id = getBranchId(repository, branch, changeData);
     revision = "";
@@ -99,7 +99,7 @@
         }
       }
       try {
-        revision = repository.getRef(branch).getObjectId().getName();
+        revision = repository.exactRef(branch).getObjectId().getName();
       } catch (Exception e) {
         log.error("Fail to get branch revision for " + Config.getChangeId(changeData), e);
       }
@@ -152,7 +152,12 @@
             if (ids == null || ids.size() != 1) {
               errors.add(owner);
             } else {
-              email = accountCache.get(ids.iterator().next()).getAccount().getPreferredEmail();
+              // Accounts may have no preferred email.
+              email =
+                  accountCache
+                      .get(ids.iterator().next())
+                      .map(a -> a.getAccount().getPreferredEmail())
+                      .orElse(null);
             }
           }
         } catch (Exception e) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/findowners/OwnersValidator.java b/src/main/java/com/googlesource/gerrit/plugins/findowners/OwnersValidator.java
index 8d81574..866a2e7 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/findowners/OwnersValidator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/findowners/OwnersValidator.java
@@ -51,7 +51,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.ExecutionException;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import org.eclipse.jgit.diff.RawText;
@@ -142,7 +141,7 @@
           messages = performValidation(receiveEvent.commit, receiveEvent.revWalk, name, false);
         }
       }
-    } catch (NoSuchProjectException | IOException | ExecutionException e) {
+    } catch (NoSuchProjectException | IOException e) {
       throw new CommitValidationException("failed to check owners files", e);
     }
     if (hasError(messages)) {
@@ -153,8 +152,7 @@
 
   @VisibleForTesting
   List<CommitValidationMessage> performValidation(
-      RevCommit c, RevWalk revWalk, String ownersFileName, boolean verbose)
-      throws IOException, ExecutionException {
+      RevCommit c, RevWalk revWalk, String ownersFileName, boolean verbose) throws IOException {
     // Collect all messages from all files.
     List<CommitValidationMessage> messages = new LinkedList<>();
     // Collect all email addresses from all files and check each address only once.
@@ -197,7 +195,7 @@
         if (!wrongEmail) {
           try {
             Collection<Account.Id> ids = email2ids.get(owner);
-            wrongEmail = (ids == null || ids.size() != 1);
+            wrongEmail = (ids == null || ids.isEmpty());
           } catch (Exception e) {
             wrongEmail = true;
           }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/findowners/PredicateModule.java b/src/main/java/com/googlesource/gerrit/plugins/findowners/PredicateModule.java
new file mode 100644
index 0000000..d0fb040
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/findowners/PredicateModule.java
@@ -0,0 +1,46 @@
+// Copyright (C) 2018 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.findowners;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.gerrit.server.rules.PredicateProvider;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+
+/** provides the Prolog predicate, even in a batch mode */
+public class PredicateModule extends AbstractModule {
+  /** Prolog Predicate Provider. */
+  static class FindOwnersProvider implements PredicateProvider {
+
+    @Inject
+    public FindOwnersProvider(@PluginName String pluginName, PluginConfigFactory configFactory) {
+      Config.setVariables(pluginName, configFactory);
+      Cache.getInstance(); // Create a single Cache.
+    }
+
+    @Override
+    public ImmutableSet<String> getPackages() {
+      return ImmutableSet.of(Config.PROLOG_NAMESPACE);
+    }
+  }
+
+  @Override
+  protected void configure() {
+    DynamicSet.bind(binder(), PredicateProvider.class).to(FindOwnersProvider.class);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/findowners/RestResult.java b/src/main/java/com/googlesource/gerrit/plugins/findowners/RestResult.java
index 296f408..15f01de 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/findowners/RestResult.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/findowners/RestResult.java
@@ -57,5 +57,5 @@
     List<String> errors;
     SortedMap<String, List<String>> path2owners;
     SortedMap<String, List<String>> owner2paths;
-  };
+  }
 }
diff --git a/src/main/resources/static/find-owners.js b/src/main/resources/static/find-owners.js
index 0abdabe..24a8554 100644
--- a/src/main/resources/static/find-owners.js
+++ b/src/main/resources/static/find-owners.js
@@ -505,6 +505,19 @@
     }
     return true; // Okay to submit.
   }
+  var actionKey = null;
+  function onShowChangePolyGerrit(change, revision) {
+    var changeActions = self.changeActions();
+    // Hide previous 'Find Owners' button under 'MORE'.
+    changeActions.setActionHidden('revision', 'find-owners~find-owners', true);
+    if (!!actionKey) {
+      changeActions.setActionHidden('revision', actionKey, false);
+    } else {
+      actionKey = changeActions.add('revision', 'Find Owners');
+      changeActions.addTapListener(actionKey,
+          () => popupFindOwnersPage(null, change, revision, false));
+    }
+  }
   function onClick(e) {
     if (pageDiv.style.visibility != 'hidden' && !useContextPopup) {
       var x = event.clientX;
@@ -522,6 +535,10 @@
   } else {
     console.log('WARNING, no handler for the Find Owners button');
   }
+  // When using PolyGerrit, move "Find Owners" button out of the 'MORE' list.
+  if (window.Polymer) {
+    self.on('showchange', onShowChangePolyGerrit);
+  }
   // When the "Submit" button is clicked, call onSubmit.
   self.on('submitchange', onSubmit);
   // Clicks outside the pop up window should close the window.
diff --git a/src/test/java/com/googlesource/gerrit/plugins/findowners/FindOwnersIT.java b/src/test/java/com/googlesource/gerrit/plugins/findowners/FindOwnersIT.java
index 160d5fa..323a861 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/findowners/FindOwnersIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/findowners/FindOwnersIT.java
@@ -15,6 +15,7 @@
 package com.googlesource.gerrit.plugins.findowners;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
 import static com.google.gerrit.reviewdb.client.RefNames.REFS_CONFIG;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
@@ -32,11 +33,13 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.Emails;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.inject.Inject;
 import java.util.Collection;
+import java.util.Optional;
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevObject;
@@ -208,7 +211,9 @@
       assertThat(id1).isEqualTo(id3);
       assertThat(id1).isEqualTo(id4);
       // Action.getReviewers and Checker.getVotes use accountCache to get email address.
-      assertThat(accountCache.get(id1).getAccount().getPreferredEmail()).isEqualTo(emails1[i]);
+      Optional<Account> account = accountCache.get(id1).map(AccountState::getAccount);
+      assertThat(account).named("account %s", id1).isPresent();
+      assertThat(account.get().getPreferredEmail()).isEqualTo(emails1[i]);
     }
     // Wrong or non-existing email address.
     String[] wrongEmails = {"nobody", "@g.com", "nobody@g.com", "*"};
@@ -259,9 +264,9 @@
     PushOneCommit.Result cB = createChange("add tB.c", "tB.c", "Hello B!");
 
     // Default owners file name is "OWNERS".
-    assertThat(Config.getOwnersFileName(null, null)).isEqualTo("OWNERS");
-    assertThat(Config.getOwnersFileName(pA, null)).isEqualTo("OWNERS");
-    assertThat(Config.getOwnersFileName(pB, null)).isEqualTo("OWNERS");
+    assertThat(Config.getDefaultOwnersFileName()).isEqualTo("OWNERS");
+    assertThat(Config.getOwnersFileName(projectCache.get(pA), null)).isEqualTo("OWNERS");
+    assertThat(Config.getOwnersFileName(projectCache.get(pB), null)).isEqualTo("OWNERS");
 
     String ownerX = oneOwnerList("x@x");
     String ownerY = oneOwnerList("y@y");
@@ -273,8 +278,9 @@
     setProjectConfig("ownersFileName", "OWNERS.alpha");
     switchProject(pB);
     setProjectConfig("ownersFileName", "OWNERS.beta");
-    assertThat(Config.getOwnersFileName(pA, null)).isEqualTo("OWNERS.alpha");
-    assertThat(Config.getOwnersFileName(pB, null)).isEqualTo("OWNERS.beta");
+
+    assertThat(Config.getOwnersFileName(projectCache.get(pA), null)).isEqualTo("OWNERS.alpha");
+    assertThat(Config.getOwnersFileName(projectCache.get(pB), null)).isEqualTo("OWNERS.beta");
     String ownerA = oneOwnerList("a@a");
     String ownerB = oneOwnerList("b@b");
     assertThat(getOwnersResponse(cA)).contains(ownerA + ", files:[ tA.c ]");
@@ -283,24 +289,24 @@
     // Change back to OWNERS in Project_A
     switchProject(pA);
     setProjectConfig("ownersFileName", "OWNERS");
-    assertThat(Config.getOwnersFileName(pA, null)).isEqualTo("OWNERS");
+    assertThat(Config.getOwnersFileName(projectCache.get(pA), null)).isEqualTo("OWNERS");
     assertThat(getOwnersResponse(cA)).contains(ownerX + ", files:[ tA.c ]");
     assertThat(getOwnersResponse(cB)).contains(ownerB + ", files:[ tB.c ]");
 
     // Change back to OWNERS.alpha in Project_B, but there is no OWNERS.alpha
     switchProject(pB);
     setProjectConfig("ownersFileName", "OWNERS.alpha");
-    assertThat(Config.getOwnersFileName(pB, null)).isEqualTo("OWNERS.alpha");
+    assertThat(Config.getOwnersFileName(projectCache.get(pB), null)).isEqualTo("OWNERS.alpha");
     assertThat(getOwnersResponse(cA)).contains(ownerX + ", files:[ tA.c ]");
     assertThat(getOwnersResponse(cB)).contains("owners:[], files:[ tB.c ]");
 
     // Do not accept empty string or all-white-spaces for ownersFileName.
     setProjectConfig("ownersFileName", "   ");
-    assertThat(Config.getOwnersFileName(pB, null)).isEqualTo("OWNERS");
+    assertThat(Config.getOwnersFileName(projectCache.get(pB), null)).isEqualTo("OWNERS");
     setProjectConfig("ownersFileName", " \t  ");
-    assertThat(Config.getOwnersFileName(pB, null)).isEqualTo("OWNERS");
+    assertThat(Config.getOwnersFileName(projectCache.get(pB), null)).isEqualTo("OWNERS");
     setProjectConfig("ownersFileName", "O");
-    assertThat(Config.getOwnersFileName(pB, null)).isEqualTo("O");
+    assertThat(Config.getOwnersFileName(projectCache.get(pB), null)).isEqualTo("O");
   }
 
   @Test
@@ -327,7 +333,15 @@
     Action.Parameters param = new Action.Parameters();
     Action action =
         new Action(
-            "find-owners", null, null, null, changeDataFactory, accountCache, emails, repoManager);
+            "find-owners",
+            null,
+            null,
+            null,
+            changeDataFactory,
+            accountCache,
+            emails,
+            repoManager,
+            projectCache);
     Response<RestResult> response = action.apply(db, cr, param);
     RestResult result = response.value();
     verifyRestResult(result, 1, 1, changeInfo._number, false);
@@ -436,9 +450,11 @@
   }
 
   private int checkApproval(PushOneCommit.Result r) throws Exception {
-    Repository repo = repoManager.openRepository(r.getChange().project());
+    Project.NameKey project = r.getChange().project();
+    Repository repo = repoManager.openRepository(project);
     Cache cache = Cache.getInstance().init(0, 0);
-    OwnersDb db = cache.get(accountCache, emails, repo, r.getChange(), 1);
+    OwnersDb db =
+        cache.get(projectCache.get(project), accountCache, emails, repo, r.getChange(), 1);
     Checker c = new Checker(repo, r.getChange(), 1);
     return c.findApproval(accountCache, db);
   }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/findowners/OwnersValidatorTest.java b/src/test/java/com/googlesource/gerrit/plugins/findowners/OwnersValidatorTest.java
index 68e8b3d..b8d445a 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/findowners/OwnersValidatorTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/findowners/OwnersValidatorTest.java
@@ -59,7 +59,7 @@
     Set<String> registered;
 
     MockedEmails() {
-      super(null, null);
+      super(null, null, null);
       registered =
           ImmutableSet.of(
               "u1@g.com", "u2@g.com", "u2.m@g.com", "user1@google.com", "u1+review@g.com");