Add methods to parse group UUIDs from group refs

Change-Id: I3010497078e0f41b71920b28c24ff07c2c5fa10b
Signed-off-by: Edwin Kempin <ekempin@google.com>
diff --git a/java/com/google/gerrit/reviewdb/client/AccountGroup.java b/java/com/google/gerrit/reviewdb/client/AccountGroup.java
index 1c0ea11..2dd5b36 100644
--- a/java/com/google/gerrit/reviewdb/client/AccountGroup.java
+++ b/java/com/google/gerrit/reviewdb/client/AccountGroup.java
@@ -81,12 +81,34 @@
       uuid = newValue;
     }
 
-    /** Parse an AccountGroup.UUID out of a string representation. */
+    /** Parse an {@link AccountGroup.UUID} out of a string representation. */
     public static UUID parse(String str) {
       final UUID r = new UUID();
       r.fromString(str);
       return r;
     }
+
+    /** Parse an {@link AccountGroup.UUID} out of a ref-name. */
+    public static UUID fromRef(String ref) {
+      if (ref == null) {
+        return null;
+      }
+      if (ref.startsWith(RefNames.REFS_GROUPS)) {
+        return fromRefPart(ref.substring(RefNames.REFS_GROUPS.length()));
+      }
+      return null;
+    }
+
+    /**
+     * Parse an {@link AccountGroup.UUID} out of a part of a ref-name.
+     *
+     * @param refPart a ref name with the following syntax: {@code "12/1234..."}. We assume that the
+     *     caller has trimmed any prefix.
+     */
+    public static UUID fromRefPart(String refPart) {
+      String uuid = RefNames.parseShardedUuidFromRefPart(refPart);
+      return uuid != null ? new AccountGroup.UUID(uuid) : null;
+    }
   }
 
   /** @return true if the UUID is for a group managed within Gerrit. */
diff --git a/java/com/google/gerrit/reviewdb/client/RefNames.java b/java/com/google/gerrit/reviewdb/client/RefNames.java
index 4ce2e51..8e9bc34 100644
--- a/java/com/google/gerrit/reviewdb/client/RefNames.java
+++ b/java/com/google/gerrit/reviewdb/client/RefNames.java
@@ -263,6 +263,31 @@
     return id;
   }
 
+  static String parseShardedUuidFromRefPart(String name) {
+    if (name == null) {
+      return null;
+    }
+
+    String[] parts = name.split("/");
+    int n = parts.length;
+    if (n != 2) {
+      return null;
+    }
+
+    // First 2 chars.
+    if (parts[0].length() != 2) {
+      return null;
+    }
+
+    // Full UUID.
+    String uuid = parts[1];
+    if (!uuid.startsWith(parts[0])) {
+      return null;
+    }
+
+    return uuid;
+  }
+
   /**
    * Skips a sharded ref part at the beginning of the name.
    *
diff --git a/javatests/com/google/gerrit/reviewdb/client/AccountGroupTest.java b/javatests/com/google/gerrit/reviewdb/client/AccountGroupTest.java
index 02b6dd8..18a55bf 100644
--- a/javatests/com/google/gerrit/reviewdb/client/AccountGroupTest.java
+++ b/javatests/com/google/gerrit/reviewdb/client/AccountGroupTest.java
@@ -15,6 +15,8 @@
 package com.google.gerrit.reviewdb.client;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.reviewdb.client.AccountGroup.UUID.fromRef;
+import static com.google.gerrit.reviewdb.client.AccountGroup.UUID.fromRefPart;
 
 import java.sql.Timestamp;
 import java.time.Instant;
@@ -24,9 +26,51 @@
 import org.junit.Test;
 
 public class AccountGroupTest {
+  private static final String TEST_UUID = "ccab3195282a8ce4f5014efa391e82d10f884c64";
+  private static final String TEST_SHARDED_UUID = TEST_UUID.substring(0, 2) + "/" + TEST_UUID;
+
   @Test
   public void auditCreationInstant() {
     Instant instant = LocalDateTime.of(2009, Month.JUNE, 8, 19, 31).toInstant(ZoneOffset.UTC);
     assertThat(AccountGroup.auditCreationInstantTs()).isEqualTo(Timestamp.from(instant));
   }
+
+  @Test
+  public void parseRefName() {
+    assertThat(fromRef("refs/groups/" + TEST_SHARDED_UUID)).isEqualTo(uuid(TEST_UUID));
+    assertThat(fromRef("refs/groups/" + TEST_SHARDED_UUID + "-2"))
+        .isEqualTo(uuid(TEST_UUID + "-2"));
+    assertThat(fromRef("refs/groups/7e/7ec4775d")).isEqualTo(uuid("7ec4775d"));
+    assertThat(fromRef("refs/groups/fo/foo")).isEqualTo(uuid("foo"));
+
+    assertThat(fromRef(null)).isNull();
+    assertThat(fromRef("")).isNull();
+
+    // Missing prefix.
+    assertThat(fromRef(TEST_SHARDED_UUID)).isNull();
+
+    // Invalid shards.
+    assertThat(fromRef("refs/groups/c/" + TEST_UUID)).isNull();
+    assertThat(fromRef("refs/groups/cca/" + TEST_UUID)).isNull();
+
+    // Mismatched shard.
+    assertThat(fromRef("refs/groups/ca/" + TEST_UUID)).isNull();
+    assertThat(fromRef("refs/groups/64/" + TEST_UUID)).isNull();
+
+    // Wrong number of segments.
+    assertThat(fromRef("refs/groups/cc")).isNull();
+    assertThat(fromRef("refs/groups/" + TEST_SHARDED_UUID + "/1")).isNull();
+  }
+
+  @Test
+  public void parseRefNameParts() {
+    assertThat(fromRefPart(TEST_SHARDED_UUID)).isEqualTo(uuid(TEST_UUID));
+
+    // Mismatched shard.
+    assertThat(fromRefPart("ab/" + TEST_UUID)).isNull();
+  }
+
+  private AccountGroup.UUID uuid(String uuid) {
+    return new AccountGroup.UUID(uuid);
+  }
 }
diff --git a/javatests/com/google/gerrit/reviewdb/client/RefNamesTest.java b/javatests/com/google/gerrit/reviewdb/client/RefNamesTest.java
index 028fc62..42d3cb7 100644
--- a/javatests/com/google/gerrit/reviewdb/client/RefNamesTest.java
+++ b/javatests/com/google/gerrit/reviewdb/client/RefNamesTest.java
@@ -18,6 +18,7 @@
 import static com.google.gerrit.reviewdb.client.RefNames.parseAfterShardedRefPart;
 import static com.google.gerrit.reviewdb.client.RefNames.parseRefSuffix;
 import static com.google.gerrit.reviewdb.client.RefNames.parseShardedRefPart;
+import static com.google.gerrit.reviewdb.client.RefNames.parseShardedUuidFromRefPart;
 import static com.google.gerrit.reviewdb.client.RefNames.skipShardedRefPart;
 
 import org.junit.Rule;
@@ -25,6 +26,10 @@
 import org.junit.rules.ExpectedException;
 
 public class RefNamesTest {
+  private static final String TEST_GROUP_UUID = "ccab3195282a8ce4f5014efa391e82d10f884c64";
+  private static final String TEST_SHARDED_GROUP_UUID =
+      TEST_GROUP_UUID.substring(0, 2) + "/" + TEST_GROUP_UUID;
+
   @Rule public ExpectedException expectedException = ExpectedException.none();
 
   private final Account.Id accountId = new Account.Id(1011123);
@@ -144,6 +149,33 @@
   }
 
   @Test
+  public void parseShardedUuidFromRefsPart() throws Exception {
+    assertThat(parseShardedUuidFromRefPart(TEST_SHARDED_GROUP_UUID)).isEqualTo(TEST_GROUP_UUID);
+    assertThat(parseShardedUuidFromRefPart(TEST_SHARDED_GROUP_UUID + "-2"))
+        .isEqualTo(TEST_GROUP_UUID + "-2");
+    assertThat(parseShardedUuidFromRefPart("7e/7ec4775d")).isEqualTo("7ec4775d");
+    assertThat(parseShardedUuidFromRefPart("fo/foo")).isEqualTo("foo");
+
+    assertThat(parseShardedUuidFromRefPart(null)).isNull();
+    assertThat(parseShardedUuidFromRefPart("")).isNull();
+
+    // Prefix not stripped.
+    assertThat(parseShardedUuidFromRefPart("refs/groups/" + TEST_SHARDED_GROUP_UUID)).isNull();
+
+    // Invalid shards.
+    assertThat(parseShardedUuidFromRefPart("c/" + TEST_GROUP_UUID)).isNull();
+    assertThat(parseShardedUuidFromRefPart("cca/" + TEST_GROUP_UUID)).isNull();
+
+    // Mismatched shard.
+    assertThat(parseShardedUuidFromRefPart("ca/" + TEST_GROUP_UUID)).isNull();
+    assertThat(parseShardedUuidFromRefPart("64/" + TEST_GROUP_UUID)).isNull();
+
+    // Wrong number of segments.
+    assertThat(parseShardedUuidFromRefPart("cc")).isNull();
+    assertThat(parseShardedUuidFromRefPart(TEST_SHARDED_GROUP_UUID + "/1")).isNull();
+  }
+
+  @Test
   public void skipShardedRefsPart() throws Exception {
     assertThat(skipShardedRefPart("01/1")).isEqualTo("");
     assertThat(skipShardedRefPart("01/1/")).isEqualTo("/");