Added GroupMembership for group membership checks.

This replaces existing uses of a Set to determine membership and
will make it possible to integrate with group systems that do not
allow efficient enumentation of memberships.

Change-Id: Iba9842ca1a355fc100fb4a02d7954d89032cdba0
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
index 32938c9..b347ead 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
@@ -185,11 +185,6 @@
     }
   }
 
-  private Set<AccountGroup.UUID> groupsOf(Account account) {
-    IdentifiedUser user = userFactory.create(account.getId());
-    return new HashSet<AccountGroup.UUID>(user.getEffectiveGroups());
-  }
-
   public void suggestAccountGroup(final String query, final int limit,
       final AsyncCallback<List<GroupReference>> callback) {
     run(callback, new Action<List<GroupReference>>() {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AgreementInfoFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AgreementInfoFactory.java
index 2288e24..39712e4 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AgreementInfoFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AgreementInfoFactory.java
@@ -59,7 +59,7 @@
 
     final List<AccountGroupAgreement> groupAccepted =
         new ArrayList<AccountGroupAgreement>();
-    for (final AccountGroup.UUID groupUUID : user.getEffectiveGroups()) {
+    for (final AccountGroup.UUID groupUUID : user.getEffectiveGroups().getKnownGroups()) {
       AccountGroup group = groupCache.get(groupUUID);
       if (group == null) {
         continue;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/GetUserFilter.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/GetUserFilter.java
index b0ddc3a..f54b3c5 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/GetUserFilter.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/GetUserFilter.java
@@ -36,8 +36,6 @@
 /**
  * Stores as a request attribute, so the {@link HttpLog} can include the the
  * user for the request outside of the request scope.
- *
- * @author cranger@google.com (Colby Ranger)
  */
 @Singleton
 public class GetUserFilter implements Filter {
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupAccess.java
index 69dd0e4..9e88244 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupAccess.java
@@ -15,9 +15,6 @@
 package com.google.gerrit.reviewdb.server;
 
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroup.ExternalNameKey;
-import com.google.gerrit.reviewdb.client.AccountGroup.Id;
-import com.google.gerrit.reviewdb.client.AccountGroup.UUID;
 import com.google.gwtorm.server.Access;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.PrimaryKey;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java
index 1dbe65e..5d36b33 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java
@@ -18,6 +18,8 @@
 import com.google.gerrit.reviewdb.client.AccountProjectWatch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.account.GroupMembership;
+import com.google.gerrit.server.account.ListGroupMembership;
 import com.google.inject.Inject;
 
 import java.util.Collection;
@@ -32,8 +34,8 @@
   }
 
   @Override
-  public Set<AccountGroup.UUID> getEffectiveGroups() {
-    return Collections.singleton(AccountGroup.ANONYMOUS_USERS);
+  public GroupMembership getEffectiveGroups() {
+    return new ListGroupMembership(Collections.singleton(AccountGroup.ANONYMOUS_USERS));
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
index c489543..f057c3a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
@@ -14,10 +14,10 @@
 
 package com.google.gerrit.server;
 
-import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.account.GroupMembership;
 import com.google.inject.servlet.RequestScoped;
 
 import java.util.Collection;
@@ -60,7 +60,7 @@
    *
    * @return active groups for this user.
    */
-  public abstract Set<AccountGroup.UUID> getEffectiveGroups();
+  public abstract GroupMembership getEffectiveGroups();
 
   /** Set of changes starred by this user. */
   public abstract Set<Change.Id> getStarredChanges();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
index 26101e5..6e519c4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
@@ -24,7 +24,8 @@
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.CapabilityControl;
-import com.google.gerrit.server.account.GroupIncludeCache;
+import com.google.gerrit.server.account.GroupMembership;
+import com.google.gerrit.server.account.MaterializedGroupMembership;
 import com.google.gerrit.server.account.Realm;
 import com.google.gerrit.server.config.AnonymousCowardName;
 import com.google.gerrit.server.config.AuthConfig;
@@ -52,9 +53,7 @@
 import java.util.Date;
 import java.util.HashSet;
 import java.util.Iterator;
-import java.util.LinkedList;
 import java.util.List;
-import java.util.Queue;
 import java.util.Set;
 import java.util.TimeZone;
 
@@ -71,7 +70,7 @@
     private final Provider<String> canonicalUrl;
     private final Realm realm;
     private final AccountCache accountCache;
-    private final GroupIncludeCache groupIncludeCache;
+    private final MaterializedGroupMembership.Factory groupMembershipFactory;
 
     @Inject
     GenericFactory(
@@ -80,14 +79,14 @@
         final @AnonymousCowardName String anonymousCowardName,
         final @CanonicalWebUrl Provider<String> canonicalUrl,
         final Realm realm, final AccountCache accountCache,
-        final GroupIncludeCache groupIncludeCache) {
+        final MaterializedGroupMembership.Factory groupMembershipFactory) {
       this.capabilityControlFactory = capabilityControlFactory;
       this.authConfig = authConfig;
       this.anonymousCowardName = anonymousCowardName;
       this.canonicalUrl = canonicalUrl;
       this.realm = realm;
       this.accountCache = accountCache;
-      this.groupIncludeCache = groupIncludeCache;
+      this.groupMembershipFactory = groupMembershipFactory;
     }
 
     public IdentifiedUser create(final Account.Id id) {
@@ -97,14 +96,14 @@
     public IdentifiedUser create(Provider<ReviewDb> db, Account.Id id) {
       return new IdentifiedUser(capabilityControlFactory, AccessPath.UNKNOWN,
           authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
-          groupIncludeCache, null, db, id);
+          groupMembershipFactory, null, db, id);
     }
 
     public IdentifiedUser create(AccessPath accessPath,
         Provider<SocketAddress> remotePeerProvider, Account.Id id) {
       return new IdentifiedUser(capabilityControlFactory, accessPath,
           authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
-          groupIncludeCache, remotePeerProvider, null, id);
+          groupMembershipFactory, remotePeerProvider, null, id);
     }
   }
 
@@ -122,7 +121,7 @@
     private final Provider<String> canonicalUrl;
     private final Realm realm;
     private final AccountCache accountCache;
-    private final GroupIncludeCache groupIncludeCache;
+    private final MaterializedGroupMembership.Factory groupMembershipFactory;
 
     private final Provider<SocketAddress> remotePeerProvider;
     private final Provider<ReviewDb> dbProvider;
@@ -134,7 +133,7 @@
         final @AnonymousCowardName String anonymousCowardName,
         final @CanonicalWebUrl Provider<String> canonicalUrl,
         final Realm realm, final AccountCache accountCache,
-        final GroupIncludeCache groupIncludeCache,
+        final MaterializedGroupMembership.Factory groupMembershipFactory,
 
         final @RemotePeer Provider<SocketAddress> remotePeerProvider,
         final Provider<ReviewDb> dbProvider) {
@@ -144,7 +143,7 @@
       this.canonicalUrl = canonicalUrl;
       this.realm = realm;
       this.accountCache = accountCache;
-      this.groupIncludeCache = groupIncludeCache;
+      this.groupMembershipFactory = groupMembershipFactory;
 
       this.remotePeerProvider = remotePeerProvider;
       this.dbProvider = dbProvider;
@@ -154,7 +153,7 @@
         final Account.Id id) {
       return new IdentifiedUser(capabilityControlFactory, accessPath,
           authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
-          groupIncludeCache, remotePeerProvider, dbProvider, id);
+          groupMembershipFactory, remotePeerProvider, dbProvider, id);
     }
   }
 
@@ -186,7 +185,7 @@
   private final Provider<String> canonicalUrl;
   private final Realm realm;
   private final AccountCache accountCache;
-  private final GroupIncludeCache groupIncludeCache;
+  private final MaterializedGroupMembership.Factory groupMembershipFactory;
   private final AuthConfig authConfig;
   private final String anonymousCowardName;
 
@@ -200,7 +199,7 @@
 
   private AccountState state;
   private Set<String> emailAddresses;
-  private Set<AccountGroup.UUID> effectiveGroups;
+  private GroupMembership effectiveGroups;
   private Set<Change.Id> starredChanges;
   private Collection<AccountProjectWatch> notificationFilters;
 
@@ -211,14 +210,14 @@
       final String anonymousCowardName,
       final Provider<String> canonicalUrl,
       final Realm realm, final AccountCache accountCache,
-      final GroupIncludeCache groupIncludeCache,
+      final MaterializedGroupMembership.Factory groupMembershipFactory,
       @Nullable final Provider<SocketAddress> remotePeerProvider,
       @Nullable final Provider<ReviewDb> dbProvider, final Account.Id id) {
     super(capabilityControlFactory, accessPath);
     this.canonicalUrl = canonicalUrl;
     this.realm = realm;
     this.accountCache = accountCache;
-    this.groupIncludeCache = groupIncludeCache;
+    this.groupMembershipFactory = groupMembershipFactory;
     this.authConfig = authConfig;
     this.anonymousCowardName = anonymousCowardName;
     this.remotePeerProvider = remotePeerProvider;
@@ -270,39 +269,18 @@
   }
 
   @Override
-  public Set<AccountGroup.UUID> getEffectiveGroups() {
+  public GroupMembership getEffectiveGroups() {
     if (effectiveGroups == null) {
-      Set<AccountGroup.UUID> seedGroups;
-
       if (authConfig.isIdentityTrustable(state().getExternalIds())) {
-        seedGroups = realm.groups(state());
+        effectiveGroups = realm.groups(state());
       } else {
-        seedGroups = registeredGroups;
+        effectiveGroups = groupMembershipFactory.create(registeredGroups);
       }
-
-      effectiveGroups = getIncludedGroups(seedGroups);
     }
 
     return effectiveGroups;
   }
 
-  private Set<AccountGroup.UUID> getIncludedGroups(Set<AccountGroup.UUID> seedGroups) {
-    Set<AccountGroup.UUID> includes = new HashSet<AccountGroup.UUID> (seedGroups);
-    Queue<AccountGroup.UUID> groupQueue = new LinkedList<AccountGroup.UUID> (seedGroups);
-
-    while (groupQueue.size() > 0) {
-      AccountGroup.UUID id = groupQueue.remove();
-
-      for (final AccountGroup.UUID groupId : groupIncludeCache.getByInclude(id)) {
-        if (includes.add(groupId)) {
-          groupQueue.add(groupId);
-        }
-      }
-    }
-
-    return Collections.unmodifiableSet(includes);
-  }
-
   @Override
   public Set<Change.Id> getStarredChanges() {
     if (starredChanges == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java
index 4bcda2e..352f540 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.reviewdb.client.AccountProjectWatch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.account.GroupMembership;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
@@ -45,8 +46,8 @@
   }
 
   @Override
-  public Set<AccountGroup.UUID> getEffectiveGroups() {
-    return Collections.emptySet();
+  public GroupMembership getEffectiveGroups() {
+    return GroupMembership.EMPTY;
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ReplicationUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/ReplicationUser.java
index e0db5fa6..fc6ac9c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ReplicationUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ReplicationUser.java
@@ -18,47 +18,35 @@
 import com.google.gerrit.reviewdb.client.AccountProjectWatch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.account.GroupMembership;
+import com.google.gerrit.server.account.ListGroupMembership;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.Set;
 
 public class ReplicationUser extends CurrentUser {
   /** Magic set of groups enabling read of any project and reference. */
-  public static final Set<AccountGroup.UUID> EVERYTHING_VISIBLE =
-      Collections.unmodifiableSet(new HashSet<AccountGroup.UUID>(0));
+  public static final GroupMembership EVERYTHING_VISIBLE =
+      new ListGroupMembership(Collections.<AccountGroup.UUID>emptySet());
 
   public interface Factory {
-    ReplicationUser create(@Assisted Set<AccountGroup.UUID> authGroups);
+    ReplicationUser create(@Assisted GroupMembership authGroups);
   }
 
-  private final Set<AccountGroup.UUID> effectiveGroups;
+  private final GroupMembership effectiveGroups;
 
   @Inject
   protected ReplicationUser(CapabilityControl.Factory capabilityControlFactory,
-      @Assisted Set<AccountGroup.UUID> authGroups) {
+      @Assisted GroupMembership authGroups) {
     super(capabilityControlFactory, AccessPath.REPLICATION);
-
-    if (authGroups == EVERYTHING_VISIBLE) {
-      effectiveGroups = EVERYTHING_VISIBLE;
-
-    } else if (authGroups.isEmpty()) {
-      effectiveGroups = Collections.emptySet();
-
-    } else {
-      effectiveGroups = copy(authGroups);
-    }
-  }
-
-  private static Set<AccountGroup.UUID> copy(Set<AccountGroup.UUID> groups) {
-    return Collections.unmodifiableSet(new HashSet<AccountGroup.UUID>(groups));
+    effectiveGroups = authGroups;
   }
 
   @Override
-  public Set<AccountGroup.UUID> getEffectiveGroups() {
+  public GroupMembership getEffectiveGroups() {
     return effectiveGroups;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java
index 113fe8b..e6bdf0b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java
@@ -80,10 +80,8 @@
         Set<AccountGroup.UUID> usersGroups = groupsOf(otherUser);
         usersGroups.remove(AccountGroup.ANONYMOUS_USERS);
         usersGroups.remove(AccountGroup.REGISTERED_USERS);
-        for (AccountGroup.UUID myGroup : currentUser.getEffectiveGroups()) {
-          if (usersGroups.contains(myGroup)) {
-            return true;
-          }
+        if (currentUser.getEffectiveGroups().containsAnyOf(usersGroups)) {
+          return true;
         }
         break;
       }
@@ -111,7 +109,6 @@
   }
 
   private Set<AccountGroup.UUID> groupsOf(Account account) {
-    IdentifiedUser user = userFactory.create(account.getId());
-    return new HashSet<AccountGroup.UUID>(user.getEffectiveGroups());
+    return userFactory.create(account.getId()).getEffectiveGroups().getKnownGroups();
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
index 34c5ac9..9f17671 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.account;
 
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.data.PermissionRange;
@@ -31,7 +33,6 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 /** Access control management for server-wide capabilities. */
 public class CapabilityControl {
@@ -129,7 +130,7 @@
     // the 'CI Servers' actually use the BATCH queue while everyone else gets
     // to use the INTERACTIVE queue without additional grants.
     //
-    Set<AccountGroup.UUID> groups = user.getEffectiveGroups();
+    GroupMembership groups = user.getEffectiveGroups();
     boolean batch = false;
     for (PermissionRule r : capabilities.priority) {
       if (match(groups, r)) {
@@ -198,7 +199,7 @@
       return rules;
     }
 
-    Set<AccountGroup.UUID> groups = user.getEffectiveGroups();
+    GroupMembership groups = user.getEffectiveGroups();
     if (rules.size() == 1) {
       if (!match(groups, rules.get(0))) {
         rules = Collections.emptyList();
@@ -222,16 +223,17 @@
   }
 
   private boolean matchAny(List<PermissionRule> rules) {
-    Set<AccountGroup.UUID> groups = user.getEffectiveGroups();
-    for (PermissionRule rule : rules) {
-      if (match(groups, rule)) {
-        return true;
-      }
-    }
-    return false;
+    Iterable<AccountGroup.UUID> ids = Iterables.transform(rules,
+        new Function<PermissionRule, AccountGroup.UUID>() {
+          @Override
+          public AccountGroup.UUID apply(PermissionRule rule) {
+            return rule.getGroup().getUUID();
+          }
+        });
+    return user.getEffectiveGroups().containsAnyOf(ids);
   }
 
-  private static boolean match(Set<AccountGroup.UUID> groups,
+  private static boolean match(GroupMembership groups,
       PermissionRule rule) {
     return groups.contains(rule.getGroup().getUUID());
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
index 79a0b86..56a95ba 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
@@ -20,17 +20,21 @@
 import com.google.inject.Inject;
 
 import java.util.Collections;
+import java.util.List;
 import java.util.Set;
 
 public class DefaultRealm implements Realm {
   private final EmailExpander emailExpander;
   private final AccountByEmailCache byEmail;
+  private final MaterializedGroupMembership.Factory groupMembershipFactory;
 
   @Inject
   DefaultRealm(final EmailExpander emailExpander,
-      final AccountByEmailCache byEmail) {
+      final AccountByEmailCache byEmail,
+      final MaterializedGroupMembership.Factory groupMembershipFactory) {
     this.emailExpander = emailExpander;
     this.byEmail = byEmail;
+    this.groupMembershipFactory = groupMembershipFactory;
   }
 
   @Override
@@ -57,8 +61,8 @@
   }
 
   @Override
-  public Set<AccountGroup.UUID> groups(final AccountState who) {
-    return who.getInternalGroups();
+  public GroupMembership groups(final AccountState who) {
+    return groupMembershipFactory.create(who.getInternalGroups());
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembership.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembership.java
new file mode 100644
index 0000000..9bb571e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembership.java
@@ -0,0 +1,51 @@
+// Copyright (C) 2012 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.google.gerrit.server.account;
+
+import com.google.gerrit.reviewdb.client.AccountGroup;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Implementations of GroupMembership provide methods to test
+ * the presence of a user in a particular group.
+ */
+public interface GroupMembership {
+
+  public static final GroupMembership EMPTY =
+      new ListGroupMembership(Collections.<AccountGroup.UUID>emptySet());
+
+  /**
+   * Returns {@code true} when the user this object was created for is a member
+   * of the specified group.
+   */
+  boolean contains(AccountGroup.UUID groupId);
+
+  /**
+   * Returns {@code true} when the user this object was created for is a member
+   * of any of the specified group.
+   */
+  boolean containsAnyOf(Iterable<AccountGroup.UUID> groupIds);
+
+  /**
+   * Returns the set of groups that can be determined by the implementation.
+   * This may not return all groups the {@link #contains(AccountGroup.UUID)}
+   * would return {@code true} for, but will at least contain all top level
+   * groups. This restriction stems from the API of some group systems, which
+   * make it expensive to enumate the members of a group.
+   */
+  Set<AccountGroup.UUID> getKnownGroups();
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/ListGroupMembership.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/ListGroupMembership.java
new file mode 100644
index 0000000..237d381
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/ListGroupMembership.java
@@ -0,0 +1,52 @@
+// Copyright (C) 2012 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.google.gerrit.server.account;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+
+import java.util.Set;
+
+/**
+ * GroupMembership over an explicit list.
+ */
+public class ListGroupMembership implements GroupMembership {
+
+  private final Set<AccountGroup.UUID> groups;
+
+  public ListGroupMembership(Iterable<AccountGroup.UUID> groupIds) {
+    this.groups = ImmutableSet.copyOf(groupIds);
+  }
+
+  @Override
+  public boolean contains(AccountGroup.UUID groupId) {
+    return groups.contains(groupId);
+  }
+
+  @Override
+  public boolean containsAnyOf(Iterable<AccountGroup.UUID> groupIds) {
+    for (AccountGroup.UUID groupId : groupIds) {
+      if (contains(groupId)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public Set<AccountGroup.UUID> getKnownGroups() {
+    return ImmutableSet.copyOf(groups);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/MaterializedGroupMembership.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/MaterializedGroupMembership.java
new file mode 100644
index 0000000..81ff656
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/MaterializedGroupMembership.java
@@ -0,0 +1,93 @@
+// Copyright (C) 2012 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.google.gerrit.server.account;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import java.util.Collections;
+import java.util.Queue;
+import java.util.Set;
+
+/**
+ * Creates a GroupMembership object from materialized collection of groups.
+ */
+public class MaterializedGroupMembership implements GroupMembership {
+  public interface Factory {
+    MaterializedGroupMembership create(Iterable<AccountGroup.UUID> groupIds);
+  }
+
+  private final GroupIncludeCache groupIncludeCache;
+  private final Set<AccountGroup.UUID> includes;
+  private final Queue<AccountGroup.UUID> groupQueue;
+
+  @Inject
+  MaterializedGroupMembership(
+      GroupIncludeCache groupIncludeCache,
+      @Assisted Iterable<AccountGroup.UUID> seedGroups) {
+    this.groupIncludeCache = groupIncludeCache;
+    this.includes = Sets.newHashSet(seedGroups);
+    this.groupQueue = Lists.newLinkedList(seedGroups);
+  }
+
+  @Override
+  public boolean contains(AccountGroup.UUID id) {
+    if (id == null) {
+      return false;
+    }
+    if (includes.contains(id)) {
+      return true;
+    }
+    return findIncludedGroup(Collections.singleton(id));
+  }
+
+  @Override
+  public boolean containsAnyOf(Iterable<AccountGroup.UUID> ids) {
+    Set<AccountGroup.UUID> query = Sets.newHashSet();
+    for (AccountGroup.UUID groupId : ids) {
+      if (includes.contains(groupId)) {
+        return true;
+      }
+      query.add(groupId);
+    }
+
+    return findIncludedGroup(query);
+  }
+
+  private boolean findIncludedGroup(Set<AccountGroup.UUID> query) {
+    boolean found = false;
+    while (!found && !groupQueue.isEmpty()) {
+      AccountGroup.UUID id = groupQueue.remove();
+
+      for (final AccountGroup.UUID groupId : groupIncludeCache.getByInclude(id)) {
+        if (includes.add(groupId)) {
+          groupQueue.add(groupId);
+          found |= query.contains(groupId);
+        }
+      }
+    }
+
+    return found;
+  }
+
+  @Override
+  public Set<AccountGroup.UUID> getKnownGroups() {
+    findIncludedGroup(Collections.<AccountGroup.UUID>emptySet()); // find all
+    return Sets.newHashSet(includes);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java
index abbef74..fc7c0be 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java
@@ -31,7 +31,7 @@
 
   public void onCreateAccount(AuthRequest who, Account account);
 
-  public Set<AccountGroup.UUID> groups(AccountState who);
+  public GroupMembership groups(AccountState who);
 
   /**
    * Locate an account whose local username is the given account name.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/VisibleGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/VisibleGroups.java
index a3a0c49..2358d57 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/VisibleGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/VisibleGroups.java
@@ -91,7 +91,8 @@
       NoSuchGroupException {
     if (identifiedUser.get().getAccountId().equals(user.getAccountId())
         || identifiedUser.get().getCapabilities().canAdministrateServer()) {
-      final Set<AccountGroup.UUID> effective = user.getEffectiveGroups();
+      final Set<AccountGroup.UUID> effective =
+          user.getEffectiveGroups().getKnownGroups();
       final Set<AccountGroup> groups =
           new TreeSet<AccountGroup>(new GroupComparator());
       for (final AccountGroup.UUID groupId : effective) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
index 055e08d..e085d1e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
@@ -16,6 +16,7 @@
 
 import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_GERRIT;
 
+import com.google.common.collect.Iterables;
 import com.google.gerrit.common.data.ParameterizedString;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountExternalId;
@@ -26,6 +27,8 @@
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.account.EmailExpander;
+import com.google.gerrit.server.account.GroupMembership;
+import com.google.gerrit.server.account.MaterializedGroupMembership;
 import com.google.gerrit.server.account.Realm;
 import com.google.gerrit.server.auth.AuthenticationUnavailableException;
 import com.google.gerrit.server.auth.ldap.Helper.LdapSchema;
@@ -72,6 +75,7 @@
   private final Config config;
 
   private final Cache<String, Set<AccountGroup.UUID>> membershipCache;
+  private final MaterializedGroupMembership.Factory groupMembershipFactory;
 
   @Inject
   LdapRealm(
@@ -80,13 +84,15 @@
       final EmailExpander emailExpander,
       @Named(LdapModule.GROUP_CACHE) final Cache<String, Set<AccountGroup.UUID>> membershipCache,
       @Named(LdapModule.USERNAME_CACHE) final Cache<String, Account.Id> usernameCache,
-      @GerritServerConfig final Config config) {
+      @GerritServerConfig final Config config,
+      final MaterializedGroupMembership.Factory groupMembershipFactory) {
     this.helper = helper;
     this.authConfig = authConfig;
     this.emailExpander = emailExpander;
     this.usernameCache = usernameCache;
     this.membershipCache = membershipCache;
     this.config = config;
+    this.groupMembershipFactory = groupMembershipFactory;
 
     this.readOnlyAccountFields = new HashSet<Account.FieldName>();
 
@@ -254,14 +260,12 @@
   }
 
   @Override
-  public Set<AccountGroup.UUID> groups(final AccountState who) {
-    final HashSet<AccountGroup.UUID> r = new HashSet<AccountGroup.UUID>();
-    r.addAll(membershipCache.get(findId(who.getExternalIds())));
-    r.addAll(who.getInternalGroups());
-    return r;
+  public GroupMembership groups(final AccountState who) {
+    return groupMembershipFactory.create(Iterables.concat(
+        membershipCache.get(findId(who.getExternalIds())),
+        who.getInternalGroups()));
   }
 
-
   private static String findId(final Collection<AccountExternalId> ids) {
     for (final AccountExternalId i : ids) {
       if (i.isScheme(AccountExternalId.SCHEME_GERRIT)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
index 57deee1..d3c64d9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
@@ -19,6 +19,8 @@
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.AccountGroupName;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.account.GroupMembership;
+import com.google.gerrit.server.account.ListGroupMembership;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
 
@@ -310,7 +312,7 @@
    * @return the actual groups resolved from the database. If no groups are
    *         found, returns an empty {@code Set}, never {@code null}.
    */
-  public static Set<AccountGroup.UUID> groupsFor(
+  public static GroupMembership groupsFor(
       SchemaFactory<ReviewDb> dbfactory, String[] groupNames, Logger log,
       String groupNotFoundWarning) {
     final Set<AccountGroup.UUID> result = new HashSet<AccountGroup.UUID>();
@@ -339,7 +341,7 @@
     } catch (OrmException e) {
       log.error("Database error, cannot load groups", e);
     }
-    return result;
+    return new ListGroupMembership(result);
   }
 
   /**
@@ -352,7 +354,7 @@
    * @return the actual groups resolved from the database. If no groups are
    *         found, returns an empty {@code Set}, never {@code null}.
    */
-  public static Set<AccountGroup.UUID> groupsFor(
+  public static GroupMembership groupsFor(
       SchemaFactory<ReviewDb> dbfactory, String[] groupNames, Logger log) {
     return groupsFor(dbfactory, groupNames, log,
         "Group \"{0}\" not in database, skipping.");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index d37063e..90a0890 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -35,6 +35,7 @@
 import com.google.gerrit.server.account.GroupCacheImpl;
 import com.google.gerrit.server.account.GroupIncludeCacheImpl;
 import com.google.gerrit.server.account.GroupInfoCacheFactory;
+import com.google.gerrit.server.account.MaterializedGroupMembership;
 import com.google.gerrit.server.account.Realm;
 import com.google.gerrit.server.auth.ldap.LdapModule;
 import com.google.gerrit.server.events.EventFactory;
@@ -118,6 +119,7 @@
     factory(CapabilityControl.Factory.class);
     factory(GroupInfoCacheFactory.Factory.class);
     factory(ProjectState.Factory.class);
+    factory(MaterializedGroupMembership.Factory.class);
     bind(PermissionCollection.Factory.class);
     bind(AccountVisibility.class)
         .toProvider(AccountVisibilityProvider.class)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java
index 15711af..a52c2ed 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java
@@ -40,7 +40,7 @@
   protected GroupSetProvider(@GerritServerConfig Config config,
       SchemaFactory<ReviewDb> db, String section, String subsection, String name) {
     String[] groupNames = config.getStringList(section, subsection, name);
-    groupIds = unmodifiableSet(groupsFor(db, groupNames, log));
+    groupIds = unmodifiableSet(groupsFor(db, groupNames, log).getKnownGroups());
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java
index 5cccaf0..aca7098 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.ReplicationUser;
+import com.google.gerrit.server.account.GroupMembership;
 import com.google.gerrit.server.config.ConfigUtil;
 import com.google.gerrit.server.config.FactoryModule;
 import com.google.gerrit.server.config.SitePaths;
@@ -404,7 +405,7 @@
 
       String[] authGroupNames =
           cfg.getStringList("remote", rc.getName(), "authGroup");
-      final Set<AccountGroup.UUID> authGroups;
+      final GroupMembership authGroups;
       if (authGroupNames.length > 0) {
         authGroups = ConfigUtil.groupsFor(db, authGroupNames, //
             log, "Group \"{0}\" not in database, removing from authGroup");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
index 619bc06..a2bc483 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
@@ -70,7 +70,7 @@
 
     /** Is the from user in an email squelching group? */
     final IdentifiedUser user =  args.identifiedUserFactory.create(id);
-    final Set<AccountGroup.UUID> gids = user.getEffectiveGroups();
+    final Set<AccountGroup.UUID> gids = user.getEffectiveGroups().getKnownGroups();
     for (final AccountGroup.UUID gid : gids) {
       AccountGroup group = args.groupCache.get(gid);
       if (group != null && group.isEmailOnlyAuthors()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailTokenVerifier.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailTokenVerifier.java
index 1f7fdd7..4307854 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailTokenVerifier.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailTokenVerifier.java
@@ -1,4 +1,16 @@
-// Copyright 2012 Google Inc. All Rights Reserved.
+// Copyright (C) 2012 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.google.gerrit.server.mail;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
index 0d3b70b..4d6eb8e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
@@ -1,4 +1,16 @@
-// Copyright 2012 Google Inc. All Rights Reserved.
+// Copyright (C) 2012 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.google.gerrit.server.mail;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
index b5f16aa..29a6432 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
@@ -281,7 +281,7 @@
     ContributorAgreement bestCla = null;
     try {
 
-      OUTER: for (AccountGroup.UUID groupUUID : iUser.getEffectiveGroups()) {
+      OUTER: for (AccountGroup.UUID groupUUID : iUser.getEffectiveGroups().getKnownGroups()) {
         AccountGroup group = groupCache.get(groupUUID);
         if (group == null) {
           continue;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
index 3dd17b1..6b4780a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
@@ -25,6 +25,7 @@
 import com.google.gerrit.rules.RulesCache;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.CapabilityCollection;
+import com.google.gerrit.server.account.GroupMembership;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.ProjectConfig;
@@ -237,13 +238,13 @@
    * @return true if any of the groups listed in {@code groups} was declared to
    *         be an owner of this project, or one of its parent projects..
    */
-  boolean isOwner(Set<AccountGroup.UUID> groups) {
+  boolean isOwner(GroupMembership groups) {
     Set<Project.NameKey> seen = new HashSet<Project.NameKey>();
     seen.add(getProject().getNameKey());
 
     ProjectState s = this;
     do {
-      if (CollectionsUtil.isAnyIncludedIn(s.localOwners, groups)) {
+      if (groups.containsAnyOf(s.localOwners)) {
         return true;
       }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java
index 15a3ae5..413e6c4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java
@@ -30,7 +30,7 @@
       return ((IdentifiedUser) user).getAccountId().toString();
     }
     if (user instanceof SingleGroupUser) {
-      return "group:" + ((SingleGroupUser) user).getEffectiveGroups() //
+      return "group:" + ((SingleGroupUser) user).getEffectiveGroups().getKnownGroups() //
           .iterator().next().toString();
     }
     return user.toString();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SingleGroupUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SingleGroupUser.java
index 1dd2a7f..cdce217 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SingleGroupUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SingleGroupUser.java
@@ -20,13 +20,15 @@
 import com.google.gerrit.server.AccessPath;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.account.GroupMembership;
+import com.google.gerrit.server.account.ListGroupMembership;
 
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Set;
 
 final class SingleGroupUser extends CurrentUser {
-  private final Set<AccountGroup.UUID> groups;
+  private final GroupMembership groups;
 
   SingleGroupUser(CapabilityControl.Factory capabilityControlFactory,
       AccountGroup.UUID groupId) {
@@ -36,11 +38,11 @@
   SingleGroupUser(CapabilityControl.Factory capabilityControlFactory,
       Set<AccountGroup.UUID> groups) {
     super(capabilityControlFactory, AccessPath.UNKNOWN);
-    this.groups = groups;
+    this.groups = new ListGroupMembership(groups);
   }
 
   @Override
-  public Set<AccountGroup.UUID> getEffectiveGroups() {
+  public GroupMembership getEffectiveGroups() {
     return groups;
   }
 
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
index 49bccb4..77150e6 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
@@ -20,6 +20,9 @@
 import static com.google.gerrit.common.data.Permission.READ;
 import static com.google.gerrit.common.data.Permission.SUBMIT;
 
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
 import com.google.gerrit.common.data.Capable;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.data.PermissionRange;
@@ -35,6 +38,8 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.CapabilityControl;
 import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.GroupMembership;
+import com.google.gerrit.server.account.ListGroupMembership;
 import com.google.gerrit.server.cache.ConcurrentHashMapCache;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.FactoryModule;
@@ -49,6 +54,7 @@
 
 import org.eclipse.jgit.lib.Config;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -400,18 +406,19 @@
 
   private class MockUser extends CurrentUser {
     private final String username;
-    private final Set<AccountGroup.UUID> groups;
+    private final GroupMembership groups;
 
     MockUser(String name, AccountGroup.UUID[] groupId) {
       super(RefControlTest.this.capabilityControlFactory, AccessPath.UNKNOWN);
       username = name;
-      groups = new HashSet<AccountGroup.UUID>(Arrays.asList(groupId));
-      groups.add(registered);
-      groups.add(anonymous);
+      ArrayList<AccountGroup.UUID> groupIds = Lists.newArrayList(groupId);
+      groupIds.add(registered);
+      groupIds.add(anonymous);
+      groups = new ListGroupMembership(groupIds);
     }
 
     @Override
-    public Set<AccountGroup.UUID> getEffectiveGroups() {
+    public GroupMembership getEffectiveGroups() {
       return groups;
     }