Convert some value classes to AutoValue

These classes were found by searching for custom hashCode
implementations, and omitting some cases:

 - Classes requiring custom serialization, which is not supported[1][2].
 - Most instances with custom hashCode or equals implementations,
   where the code savings is not as significant.
 - All classes in the extension API. We may migrate these eventually,
   but let's avoid this large backwards incompatible change until
   we're more used to AutoValue elsewhere.
 - All classes in the UI package.[3]

There are likely still more value classes that were missed by this
search that do not implement equals or hashCode.

[1] https://github.com/google/auto/tree/master/value#serialization
[2] This excludes, among other things, all persistent cache keys. It
might be possible to convert persistent caches to use a key
marshalling strategy other than Java serialization, but probably not
without invalidating all existing entries.
[3] This should still be possible as generated classes are generally
GWT compatible.

Change-Id: I96796b9879b7e487b80949b63115ac4032180f8b
diff --git a/gerrit-httpd/BUCK b/gerrit-httpd/BUCK
index 97d28f0..a4d127d 100644
--- a/gerrit-httpd/BUCK
+++ b/gerrit-httpd/BUCK
@@ -26,6 +26,7 @@
     '//lib:gwtorm',
     '//lib:jsch',
     '//lib:mime-util',
+    '//lib/auto:auto-value',
     '//lib/commons:codec',
     '//lib/guice:guice',
     '//lib/guice:guice-assistedinject',
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/AdvertisedObjectsCacheKey.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/AdvertisedObjectsCacheKey.java
index 22c862f..4bf3102 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/AdvertisedObjectsCacheKey.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/AdvertisedObjectsCacheKey.java
@@ -14,29 +14,16 @@
 
 package com.google.gerrit.httpd;
 
+import com.google.auto.value.AutoValue;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Project;
 
-class AdvertisedObjectsCacheKey {
-  private final Account.Id account;
-  private final Project.NameKey project;
-
-  AdvertisedObjectsCacheKey(Account.Id account, Project.NameKey project) {
-    this.account = account;
-    this.project = project;
+@AutoValue
+abstract class AdvertisedObjectsCacheKey {
+  static AdvertisedObjectsCacheKey create(Account.Id account, Project.NameKey project) {
+    return new AutoValue_AdvertisedObjectsCacheKey(account, project);
   }
 
-  @Override
-  public int hashCode() {
-    return account.hashCode();
-  }
-
-  @Override
-  public boolean equals(Object other) {
-    if (other instanceof AdvertisedObjectsCacheKey) {
-      AdvertisedObjectsCacheKey o = (AdvertisedObjectsCacheKey) other;
-      return account.equals(o.account) && project.equals(o.project);
-    }
-    return false;
-  }
+  public abstract Account.Id account();
+  public abstract Project.NameKey project();
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
index 1f26aa3..97de6b5 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
@@ -372,7 +372,7 @@
         return;
       }
 
-      AdvertisedObjectsCacheKey cacheKey = new AdvertisedObjectsCacheKey(
+      AdvertisedObjectsCacheKey cacheKey = AdvertisedObjectsCacheKey.create(
           ((IdentifiedUser) pc.getCurrentUser()).getAccountId(),
           projectName);
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
index 3bfce4f..31ff107 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
@@ -260,7 +260,7 @@
     }
 
     String file = pathInfo.substring(1);
-    PluginResourceKey key = new PluginResourceKey(holder.plugin, file);
+    PluginResourceKey key = PluginResourceKey.create(holder.plugin, file);
     Resource rsc = resourceCache.getIfPresent(key);
     if (rsc != null && req.getHeader(HttpHeaders.IF_MODIFIED_SINCE) == null) {
       rsc.send(req, res);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/PluginResourceKey.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/PluginResourceKey.java
index afe3b79..0a15a4f 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/PluginResourceKey.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/PluginResourceKey.java
@@ -14,34 +14,21 @@
 
 package com.google.gerrit.httpd.plugins;
 
+import com.google.auto.value.AutoValue;
 import com.google.gerrit.httpd.resources.ResourceKey;
 import com.google.gerrit.server.plugins.Plugin;
 
-final class PluginResourceKey implements ResourceKey {
-  private final Plugin.CacheKey plugin;
-  private final String resource;
-
-  PluginResourceKey(Plugin p, String r) {
-    this.plugin = p.getCacheKey();
-    this.resource = r;
+@AutoValue
+abstract class PluginResourceKey implements ResourceKey {
+  static PluginResourceKey create(Plugin p, String r) {
+    return new AutoValue_PluginResourceKey(p.getCacheKey(), r);
   }
 
+  public abstract Plugin.CacheKey plugin();
+  public abstract String resource();
+
   @Override
   public int weigh() {
-    return resource.length() * 2;
-  }
-
-  @Override
-  public int hashCode() {
-    return plugin.hashCode() * 31 + resource.hashCode();
-  }
-
-  @Override
-  public boolean equals(Object other) {
-    if (other instanceof PluginResourceKey) {
-      PluginResourceKey rk = (PluginResourceKey) other;
-      return plugin == rk.plugin && resource.equals(rk.resource);
-    }
-    return false;
+    return resource().length() * 2;
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/audit/AuditEvent.java b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditEvent.java
index f3eb0a0..e25b7cb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/audit/AuditEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditEvent.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.audit;
 
+import com.google.auto.value.AutoValue;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.HashMultimap;
@@ -36,41 +37,14 @@
   public final long elapsed;
   public final UUID uuid;
 
-  public static class UUID {
-
-    protected final String uuid;
-
-    protected UUID() {
-      uuid = String.format("audit:%s", java.util.UUID.randomUUID().toString());
+  @AutoValue
+  public abstract static class UUID {
+    private static UUID create() {
+      return new AutoValue_AuditEvent_UUID(
+          String.format("audit:%s", java.util.UUID.randomUUID().toString()));
     }
 
-    public UUID(final String n) {
-      uuid = n;
-    }
-
-    public String get() {
-      return uuid;
-    }
-
-    @Override
-    public int hashCode() {
-      return uuid.hashCode();
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-      if (this == obj) {
-        return true;
-      }
-      if (obj == null) {
-        return false;
-      }
-      if (!(obj instanceof UUID)) {
-        return false;
-      }
-
-      return uuid.equals(((UUID) obj).uuid);
-    }
+    public abstract String uuid();
   }
 
   /**
@@ -93,7 +67,7 @@
     this.when = when;
     this.timeAtStart = this.when;
     this.params = MoreObjects.firstNonNull(params, EMPTY_PARAMS);
-    this.uuid = new UUID();
+    this.uuid = UUID.create();
     this.result = result;
     this.elapsed = TimeUtil.nowMs() - timeAtStart;
   }
@@ -116,6 +90,6 @@
   @Override
   public String toString() {
     return String.format("AuditEvent UUID:%s, SID:%s, TS:%d, who:%s, what:%s",
-        uuid.get(), sessionId, when, who, what);
+        uuid.uuid(), sessionId, when, who, what);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/AuthUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/AuthUser.java
index 65f1f58..f2c8222 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/AuthUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/AuthUser.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
+import com.google.auto.value.AutoValue;
 import com.google.gerrit.common.Nullable;
 
 /**
@@ -26,40 +27,19 @@
   /**
    * Globally unique identifier for the user.
    */
-  public static final class UUID {
-    private final String uuid;
-
+  @AutoValue
+  public abstract static class UUID {
     /**
      * A new unique identifier.
      *
      * @param uuid the unique identifier.
+     * @return identifier instance.
      */
-    public UUID(String uuid) {
-      this.uuid = checkNotNull(uuid);
+    public static UUID create(String uuid) {
+      return new AutoValue_AuthUser_UUID(uuid);
     }
 
-    /** @return the globally unique identifier. */
-    public String get() {
-      return uuid;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-      if (obj instanceof UUID) {
-        return get().equals(((UUID) obj).get());
-      }
-      return false;
-    }
-
-    @Override
-    public int hashCode() {
-      return get().hashCode();
-    }
-
-    @Override
-    public String toString() {
-      return String.format("AuthUser.UUID[%s]", get());
-    }
+    public abstract String uuid();
   }
 
   private final UUID uuid;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/InternalAuthBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/InternalAuthBackend.java
index 0b4baf2..6ecea5e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/InternalAuthBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/InternalAuthBackend.java
@@ -64,6 +64,6 @@
     }
 
     req.checkPassword(who.getPassword(username));
-    return new AuthUser(new AuthUser.UUID(username), username);
+    return new AuthUser(AuthUser.UUID.create(username), username);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapAuthBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapAuthBackend.java
index eb6249c..d87a2096 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapAuthBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapAuthBackend.java
@@ -91,7 +91,7 @@
           //
           helper.authenticate(m.getDN(), req.getPassword()).close();
         }
-        return new AuthUser(new AuthUser.UUID(username), username);
+        return new AuthUser(AuthUser.UUID.create(username), username);
       } finally {
         try {
           ctx.close();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index d7caf23..09a426a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -525,7 +525,7 @@
   }
 
   private void addLabelDelta(String name, short value) {
-    labelDelta.add(new LabelVote(name, value).format());
+    labelDelta.add(LabelVote.create(name, value).format());
   }
 
   private boolean insertMessage(RevisionResource rsrc, String msg,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
index 130c75d..f562248 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
@@ -442,7 +442,7 @@
     update.putApproval(submit.getLabel(), submit.getValue());
 
     dbProvider.get().patchSetApprovals().upsert(normalized.getNormalized());
-    dbProvider.get().patchSetApprovals().delete(normalized.getDeleted());
+    dbProvider.get().patchSetApprovals().delete(normalized.deleted());
 
     try {
       return saveToBatch(rsrc, update, normalized, timestamp);
@@ -455,11 +455,11 @@
       ChangeUpdate callerUpdate, LabelNormalizer.Result normalized,
       Timestamp timestamp) throws IOException {
     Table<Account.Id, String, Optional<Short>> byUser = HashBasedTable.create();
-    for (PatchSetApproval psa : normalized.getUpdated()) {
+    for (PatchSetApproval psa : normalized.updated()) {
       byUser.put(psa.getAccountId(), psa.getLabel(),
           Optional.of(psa.getValue()));
     }
-    for (PatchSetApproval psa : normalized.getDeleted()) {
+    for (PatchSetApproval psa : normalized.deleted()) {
       byUser.put(psa.getAccountId(), psa.getLabel(), Optional.<Short> absent());
     }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java
index 9127acd..0dc7aa9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java
@@ -16,8 +16,8 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 
+import com.google.auto.value.AutoValue;
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
@@ -37,7 +37,6 @@
 
 import java.util.Collection;
 import java.util.List;
-import java.util.Objects;
 
 /**
  * Normalizes votes on labels according to project config and permissions.
@@ -50,60 +49,25 @@
  */
 @Singleton
 public class LabelNormalizer {
-  public static class Result {
-    private final ImmutableList<PatchSetApproval> unchanged;
-    private final ImmutableList<PatchSetApproval> updated;
-    private final ImmutableList<PatchSetApproval> deleted;
-
+  @AutoValue
+  public abstract static class Result {
     @VisibleForTesting
-    Result(
+    static Result create(
         List<PatchSetApproval> unchanged,
         List<PatchSetApproval> updated,
         List<PatchSetApproval> deleted) {
-      this.unchanged = ImmutableList.copyOf(unchanged);
-      this.updated = ImmutableList.copyOf(updated);
-      this.deleted = ImmutableList.copyOf(deleted);
+      return new AutoValue_LabelNormalizer_Result(
+          ImmutableList.copyOf(unchanged),
+          ImmutableList.copyOf(updated),
+          ImmutableList.copyOf(deleted));
     }
 
-    public ImmutableList<PatchSetApproval> getUnchanged() {
-      return unchanged;
-    }
-
-    public ImmutableList<PatchSetApproval> getUpdated() {
-      return updated;
-    }
-
-    public ImmutableList<PatchSetApproval> getDeleted() {
-      return deleted;
-    }
+    public abstract ImmutableList<PatchSetApproval> unchanged();
+    public abstract ImmutableList<PatchSetApproval> updated();
+    public abstract ImmutableList<PatchSetApproval> deleted();
 
     public Iterable<PatchSetApproval> getNormalized() {
-      return Iterables.concat(unchanged, updated);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (o instanceof Result) {
-        Result r = (Result) o;
-        return Objects.equals(unchanged, r.unchanged)
-            && Objects.equals(updated, r.updated)
-            && Objects.equals(deleted, r.deleted);
-      }
-      return false;
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hash(unchanged, updated, deleted);
-    }
-
-    @Override
-    public String toString() {
-      return MoreObjects.toStringHelper(this)
-          .add("unchanged", unchanged)
-          .add("updated", updated)
-          .add("deleted", deleted)
-          .toString();
+      return Iterables.concat(unchanged(), updated());
     }
   }
 
@@ -174,7 +138,7 @@
         unchanged.add(psa);
       }
     }
-    return new Result(unchanged, updated, deleted);
+    return Result.create(unchanged, updated, deleted);
   }
 
   /**
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index 4a38242..604d995 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -1142,12 +1142,12 @@
     void addLabel(final String token) throws CmdLineException {
       LabelVote v = LabelVote.parse(token);
       try {
-        LabelType.checkName(v.getLabel());
-        ApprovalsUtil.checkLabel(labelTypes, v.getLabel(), v.getValue());
+        LabelType.checkName(v.label());
+        ApprovalsUtil.checkLabel(labelTypes, v.label(), v.value());
       } catch (IllegalArgumentException e) {
         throw clp.reject(e.getMessage());
       }
-      labels.put(v.getLabel(), v.getValue());
+      labels.put(v.label(), v.value());
     }
 
     @Option(name = "--hashtag", aliases = {"-t"}, metaVar = "HASHTAG",
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
index 7144463..b5b3c74 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -310,13 +310,13 @@
         pe.initCause(e);
         throw pe;
       }
-      if (!curr.contains(accountId, l.getLabel())) {
-        curr.put(accountId, l.getLabel(), Optional.of(new PatchSetApproval(
+      if (!curr.contains(accountId, l.label())) {
+        curr.put(accountId, l.label(), Optional.of(new PatchSetApproval(
             new PatchSetApproval.Key(
                 psId,
                 accountId,
-                new LabelId(l.getLabel())),
-            l.getValue(),
+                new LabelId(l.label())),
+            l.value(),
             new Timestamp(commit.getCommitterIdent().getWhen().getTime()))));
       }
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index 23231de..7302425 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -462,8 +462,8 @@
       if (!e.getValue().isPresent()) {
         addFooter(msg, FOOTER_LABEL, '-', e.getKey());
       } else {
-        addFooter(msg, FOOTER_LABEL,
-            new LabelVote(e.getKey(), e.getValue().get()).formatWithEquals());
+        addFooter(msg, FOOTER_LABEL, LabelVote.create(
+            e.getKey(), e.getValue().get()).formatWithEquals());
       }
     }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
index 0039328..a5cf5d6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
@@ -17,8 +17,10 @@
 import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.gerrit.server.project.RefControl.isRE;
 
+import com.google.auto.value.AutoValue;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRule;
@@ -127,7 +129,7 @@
               exclusiveGroupPermissions.contains(permission.getName());
 
           for (PermissionRule rule : permission.getRules()) {
-            SeenRule s = new SeenRule(section, permission, rule);
+            SeenRule s = SeenRule.create(section, permission, rule);
             boolean addRule;
             if (rule.isBlock()) {
               addRule = true;
@@ -149,7 +151,7 @@
                 p.put(permission.getName(), r);
               }
               r.add(rule);
-              ruleProps.put(rule, new ProjectRef(project, section.getName()));
+              ruleProps.put(rule, ProjectRef.create(project, section.getName()));
             }
           }
 
@@ -224,41 +226,19 @@
   }
 
   /** Tracks whether or not a permission has been overridden. */
-  private static class SeenRule {
-    final String refPattern;
-    final String permissionName;
-    final AccountGroup.UUID group;
+  @AutoValue
+  abstract static class SeenRule {
+    public abstract String refPattern();
+    public abstract String permissionName();
+    @Nullable public abstract AccountGroup.UUID group();
 
-    SeenRule(AccessSection section, Permission permission, PermissionRule rule) {
-      refPattern = section.getName();
-      permissionName = permission.getName();
-      group = rule.getGroup().getUUID();
-    }
-
-    @Override
-    public int hashCode() {
-      int hc = refPattern.hashCode();
-      hc = hc * 31 + permissionName.hashCode();
-      if (group != null) {
-        hc = hc * 31 + group.hashCode();
-      }
-      return hc;
-    }
-
-    @Override
-    public boolean equals(Object other) {
-      if (other instanceof SeenRule) {
-        SeenRule a = this;
-        SeenRule b = (SeenRule) other;
-        return a.refPattern.equals(b.refPattern) //
-            && a.permissionName.equals(b.permissionName) //
-            && eq(a.group, b.group);
-      }
-      return false;
-    }
-
-    private boolean eq(AccountGroup.UUID a, AccountGroup.UUID b) {
-      return a != null && b != null && a.equals(b);
+    static SeenRule create(AccessSection section, Permission permission,
+        @Nullable PermissionRule rule) {
+      AccountGroup.UUID group = rule != null && rule.getGroup() != null
+          ? rule.getGroup().getUUID()
+          : null;
+      return new AutoValue_PermissionCollection_SeenRule(
+          section.getName(), permission.getName(), group);
     }
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectRef.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectRef.java
index 0315fad..8d3185d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectRef.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectRef.java
@@ -14,32 +14,15 @@
 
 package com.google.gerrit.server.project;
 
+import com.google.auto.value.AutoValue;
 import com.google.gerrit.reviewdb.client.Project;
 
-class ProjectRef {
+@AutoValue
+abstract class ProjectRef {
+  public abstract Project.NameKey project();
+  public abstract String ref();
 
-  final Project.NameKey project;
-  final String ref;
-
-  ProjectRef(Project.NameKey project, String ref) {
-    this.project = project;
-    this.ref = ref;
-  }
-
-  @Override
-  public boolean equals(Object other) {
-    return other instanceof ProjectRef
-        && project.equals(((ProjectRef) other).project)
-        && ref.equals(((ProjectRef) other).ref);
-  }
-
-  @Override
-  public int hashCode() {
-    return project.hashCode() * 31 + ref.hashCode();
-  }
-
-  @Override
-  public String toString() {
-    return project + ", " + ref;
+  static ProjectRef create(Project.NameKey project, String ref) {
+    return new AutoValue_ProjectRef(project, ref);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java
index c012bd5..4142a30 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java
@@ -14,7 +14,9 @@
 
 package com.google.gerrit.server.project;
 
+import com.google.auto.value.AutoValue;
 import com.google.common.cache.Cache;
+import com.google.common.collect.ImmutableList;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.server.cache.CacheModule;
 import com.google.gerrit.server.util.MostSpecificComparator;
@@ -26,7 +28,7 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -62,7 +64,7 @@
       return;
     }
 
-    EntryKey key = new EntryKey(ref, sections);
+    EntryKey key = EntryKey.create(ref, sections);
     EntryVal val = cache.getIfPresent(key);
     if (val != null) {
       int[] srcIdx = val.order;
@@ -116,35 +118,27 @@
     return true;
   }
 
-  static final class EntryKey {
-    private final String ref;
-    private final String[] patterns;
-    private final int hashCode;
+  @AutoValue
+  static abstract class EntryKey {
+    public abstract String ref();
+    public abstract List<String> patterns();
+    public abstract int cachedHashCode();
 
-    EntryKey(String refName, List<AccessSection> sections) {
+    static EntryKey create(String refName, List<AccessSection> sections) {
       int hc = refName.hashCode();
-      ref = refName;
-      patterns = new String[sections.size()];
-      for (int i = 0; i < patterns.length; i++) {
-        String n = sections.get(i).getName();
-        patterns[i] = n;
+      List<String> patterns = new ArrayList<>(sections.size());
+      for (AccessSection s : sections) {
+        String n = s.getName();
+        patterns.add(n);
         hc = hc * 31 + n.hashCode();
       }
-      hashCode = hc;
+      return new AutoValue_SectionSortCache_EntryKey(
+          refName, ImmutableList.copyOf(patterns), hc);
     }
 
     @Override
     public int hashCode() {
-      return hashCode;
-    }
-
-    @Override
-    public boolean equals(Object other) {
-      if (other instanceof EntryKey) {
-        EntryKey b = (EntryKey) other;
-        return ref.equals(b.ref) && Arrays.equals(patterns, b.patterns);
-      }
-      return false;
+      return cachedHashCode();
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
index 9b5aae3..83364c3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
@@ -90,14 +90,14 @@
 
     try {
       LabelVote lv = LabelVote.parse(v);
-      parsed = new Parsed(lv.getLabel(), "=", lv.getValue());
+      parsed = new Parsed(lv.label(), "=", lv.value());
     } catch (IllegalArgumentException e) {
       // Try next format.
     }
 
     try {
       LabelVote lv = LabelVote.parseWithEquals(v);
-      parsed = new Parsed(lv.getLabel(), "=", lv.getValue());
+      parsed = new Parsed(lv.label(), "=", lv.value());
     } catch (IllegalArgumentException e) {
       // Try next format.
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/LabelVote.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/LabelVote.java
index b69ab64..2e01613 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/LabelVote.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/LabelVote.java
@@ -16,18 +16,18 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 
+import com.google.auto.value.AutoValue;
 import com.google.common.base.Strings;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 
-import java.util.Objects;
-
 /** A single vote on a label, consisting of a label name and a value. */
-public class LabelVote {
+@AutoValue
+public abstract class LabelVote {
   public static LabelVote parse(String text) {
     checkArgument(!Strings.isNullOrEmpty(text), "Empty label vote");
     if (text.charAt(0) == '-') {
-      return new LabelVote(text.substring(1), (short) 0);
+      return create(text.substring(1), (short) 0);
     }
     short sign = 0;
     int i;
@@ -44,9 +44,9 @@
       }
     }
     if (sign == 0) {
-      return new LabelVote(text, (short) 1);
+      return create(text, (short) 1);
     }
-    return new LabelVote(text.substring(0, i),
+    return create(text.substring(0, i),
         (short)(sign * Short.parseShort(text.substring(i + 1))));
   }
 
@@ -54,64 +54,40 @@
     checkArgument(!Strings.isNullOrEmpty(text), "Empty label vote");
     int e = text.lastIndexOf('=');
     checkArgument(e >= 0, "Label vote missing '=': %s", text);
-    return new LabelVote(text.substring(0, e),
+    return create(text.substring(0, e),
         Short.parseShort(text.substring(e + 1), text.length()));
   }
 
-  private final String name;
-  private final short value;
-
-  public LabelVote(String name, short value) {
-    this.name = LabelType.checkNameInternal(name);
-    this.value = value;
+  public static LabelVote create(String label, short value) {
+    return new AutoValue_LabelVote(LabelType.checkNameInternal(label), value);
   }
 
-  public LabelVote(PatchSetApproval psa) {
-    this(psa.getLabel(), psa.getValue());
+  public static LabelVote create(PatchSetApproval psa) {
+    return create(psa.getLabel(), psa.getValue());
   }
 
-  public String getLabel() {
-    return name;
-  }
-
-  public short getValue() {
-    return value;
-  }
+  public abstract String label();
+  public abstract short value();
 
   public String format() {
-    if (value == (short) 0) {
-      return '-' + name;
-    } else if (value < 0) {
-      return name + value;
+    if (value() == (short) 0) {
+      return '-' + label();
+    } else if (value() < 0) {
+      return label() + value();
     } else {
-      return name + '+' + value;
+      return label() + '+' + value();
     }
   }
 
   public String formatWithEquals() {
-    if (value <= (short) 0) {
-      return name + '=' + value;
+    if (value() <= (short) 0) {
+      return label() + '=' + value();
     } else {
-      return name + "=+" + value;
+      return label() + "=+" + value();
     }
   }
 
   @Override
-  public boolean equals(Object o) {
-    if (o instanceof LabelVote) {
-      LabelVote l = (LabelVote) o;
-      return Objects.equals(name, l.name)
-          && value == l.value;
-    }
-    return false;
-  }
-
-  @Override
-  public int hashCode() {
-    return 17 * value  + name.hashCode();
-  }
-
-  @Override
   public String toString() {
     return format();
   }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java
index c39e6a1..78a4c5c 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java
@@ -137,7 +137,7 @@
 
     PatchSetApproval cr = psa(userId, "Code-Review", 2);
     PatchSetApproval v = psa(userId, "Verified", 1);
-    assertEquals(new Result(
+    assertEquals(Result.create(
           list(v),
           list(copy(cr, 1)),
           list()),
@@ -153,7 +153,7 @@
 
     PatchSetApproval cr = psa(userId, "Code-Review", 5);
     PatchSetApproval v = psa(userId, "Verified", 5);
-    assertEquals(new Result(
+    assertEquals(Result.create(
           list(),
           list(copy(cr, 2), copy(v, 1)),
           list()),
@@ -164,7 +164,7 @@
   public void emptyPermissionRangeOmitsResult() throws Exception {
     PatchSetApproval cr = psa(userId, "Code-Review", 1);
     PatchSetApproval v = psa(userId, "Verified", 1);
-    assertEquals(new Result(
+    assertEquals(Result.create(
           list(),
           list(),
           list(cr, v)),
@@ -179,7 +179,7 @@
 
     PatchSetApproval cr = psa(userId, "Code-Review", 0);
     PatchSetApproval v = psa(userId, "Verified", 0);
-    assertEquals(new Result(
+    assertEquals(Result.create(
           list(cr),
           list(),
           list(v)),
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/util/LabelVoteTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/util/LabelVoteTest.java
index 0ed0ba8..4fdbdb2 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/util/LabelVoteTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/util/LabelVoteTest.java
@@ -23,23 +23,23 @@
   public void parse() {
     LabelVote l;
     l = LabelVote.parse("Code-Review-2");
-    assertEquals("Code-Review", l.getLabel());
-    assertEquals((short) -2, l.getValue());
+    assertEquals("Code-Review", l.label());
+    assertEquals((short) -2, l.value());
     l = LabelVote.parse("Code-Review-1");
-    assertEquals("Code-Review", l.getLabel());
-    assertEquals((short) -1, l.getValue());
+    assertEquals("Code-Review", l.label());
+    assertEquals((short) -1, l.value());
     l = LabelVote.parse("-Code-Review");
-    assertEquals("Code-Review", l.getLabel());
-    assertEquals((short) 0, l.getValue());
+    assertEquals("Code-Review", l.label());
+    assertEquals((short) 0, l.value());
     l = LabelVote.parse("Code-Review");
-    assertEquals("Code-Review", l.getLabel());
-    assertEquals((short) 1, l.getValue());
+    assertEquals("Code-Review", l.label());
+    assertEquals((short) 1, l.value());
     l = LabelVote.parse("Code-Review+1");
-    assertEquals("Code-Review", l.getLabel());
-    assertEquals((short) 1, l.getValue());
+    assertEquals("Code-Review", l.label());
+    assertEquals((short) 1, l.value());
     l = LabelVote.parse("Code-Review+2");
-    assertEquals("Code-Review", l.getLabel());
-    assertEquals((short) 2, l.getValue());
+    assertEquals("Code-Review", l.label());
+    assertEquals((short) 2, l.value());
   }
 
   @Test
@@ -55,26 +55,26 @@
   public void parseWithEquals() {
     LabelVote l;
     l = LabelVote.parseWithEquals("Code-Review=-2");
-    assertEquals("Code-Review", l.getLabel());
-    assertEquals((short) -2, l.getValue());
+    assertEquals("Code-Review", l.label());
+    assertEquals((short) -2, l.value());
     l = LabelVote.parseWithEquals("Code-Review=-1");
-    assertEquals("Code-Review", l.getLabel());
-    assertEquals((short) -1, l.getValue());
+    assertEquals("Code-Review", l.label());
+    assertEquals((short) -1, l.value());
     l = LabelVote.parseWithEquals("Code-Review=0");
-    assertEquals("Code-Review", l.getLabel());
-    assertEquals((short) 0, l.getValue());
+    assertEquals("Code-Review", l.label());
+    assertEquals((short) 0, l.value());
     l = LabelVote.parseWithEquals("Code-Review=1");
-    assertEquals("Code-Review", l.getLabel());
-    assertEquals((short) 1, l.getValue());
+    assertEquals("Code-Review", l.label());
+    assertEquals((short) 1, l.value());
     l = LabelVote.parseWithEquals("Code-Review=+1");
-    assertEquals("Code-Review", l.getLabel());
-    assertEquals((short) 1, l.getValue());
+    assertEquals("Code-Review", l.label());
+    assertEquals((short) 1, l.value());
     l = LabelVote.parseWithEquals("Code-Review=2");
-    assertEquals("Code-Review", l.getLabel());
-    assertEquals((short) 2, l.getValue());
+    assertEquals("Code-Review", l.label());
+    assertEquals((short) 2, l.value());
     l = LabelVote.parseWithEquals("Code-Review=+2");
-    assertEquals("Code-Review", l.getLabel());
-    assertEquals((short) 2, l.getValue());
+    assertEquals("Code-Review", l.label());
+    assertEquals((short) 2, l.value());
   }
 
   @Test
diff --git a/gerrit-sshd/BUCK b/gerrit-sshd/BUCK
index fad371a3..cc3dec2 100644
--- a/gerrit-sshd/BUCK
+++ b/gerrit-sshd/BUCK
@@ -17,6 +17,7 @@
     '//lib:guava',
     '//lib:gwtorm',
     '//lib:jsch',
+    '//lib/auto:auto-value',
     '//lib/commons:codec',
     '//lib/commons:collections',
     '//lib/guice:guice',
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index 5d52671..5e33c8f 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -122,8 +122,8 @@
   @Option(name = "--label", aliases = "-l", usage = "custom label(s) to assign", metaVar = "LABEL=VALUE")
   void addLabel(final String token) {
     LabelVote v = LabelVote.parseWithEquals(token);
-    LabelType.checkName(v.getLabel()); // Disallow SUBM.
-    customLabels.put(v.getLabel(), v.getValue());
+    LabelType.checkName(v.label()); // Disallow SUBM.
+    customLabels.put(v.label(), v.value());
   }
 
   @Inject