Repository: googleapis/java-storage
Pull request #1380 (2022-05-09 23:26:48)

chore: add Crc32cValue type
Provides compiler validation of trying to concatenate two crc32c values



Patch:
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Crc32cValue.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Crc32cValue.java
new file mode 100644
index 00000000..1ab3c6bd
--- /dev/null
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Crc32cValue.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * 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.cloud.storage;
+
+abstract class Crc32cValue<Res extends Crc32cValue<Res>> {
+
+  private Crc32cValue() {}
+
+  public abstract int getValue();
+
+  /**
+   * Concatenate {@code other} to {@code this} value.
+   *
+   * <p>The concat operation satisfies the Left <a target="_blank" rel="noopener noreferrer"
+   * href="https://en.wikipedia.org/wiki/Distributive_property">Distributive property</a>.
+   *
+   * <p>This means, given the following instances:
+   *
+   * <pre>{@code
+   * var A = Crc32cValue.of(a);
+   * var B = Crc32cValue.of(b, 4);
+   * var C = Crc32cValue.of(c, 4);
+   * var D = Crc32cValue.of(d, 4);
+   * }</pre>
+   *
+   * Each of the following lines will all produce the same value:
+   *
+   * <pre>{@code
+   * var ABCD1 = A.concat(B).concat(C).concat(D);
+   * var ABCD2 = A.concat(B.concat(C.concat(D)));
+   * var ABCD3 = A.concat(B.concat(C)).concat(D);
+   * }</pre>
+   */
+  public abstract Res concat(Crc32cLengthKnown other);
+
+  static Crc32cLengthUnknown of(int value) {
+    return new Crc32cLengthUnknown(value);
+  }
+
+  static Crc32cLengthKnown of(int value, long length) {
+    return new Crc32cLengthKnown(value, length);
+  }
+
+  static final class Crc32cLengthUnknown extends Crc32cValue<Crc32cLengthUnknown> {
+    private final int value;
+
+    public Crc32cLengthUnknown(int value) {
+      this.value = value;
+    }
+
+    public int getValue() {
+      return value;
+    }
+
+    @Override
+    public Crc32cLengthUnknown concat(Crc32cLengthKnown other) {
+      int combined = Crc32cUtility.crc32cCombineGoogle(value, other.value, other.length);
+      return new Crc32cLengthUnknown(combined);
+    }
+
+    @Override
+    public String toString() {
+      return String.format("crc32c{0x%08x}", value);
+    }
+
+    public Crc32cLengthKnown withLength(long length) {
+      return new Crc32cLengthKnown(value, length);
+    }
+  }
+
+  static final class Crc32cLengthKnown extends Crc32cValue<Crc32cLengthKnown> {
+    private final int value;
+    private final long length;
+
+    private Crc32cLengthKnown(int value, long length) {
+      this.value = value;
+      this.length = length;
+    }
+
+    @Override
+    public int getValue() {
+      return value;
+    }
+
+    public long getLength() {
+      return length;
+    }
+
+    @Override
+    public Crc32cLengthKnown concat(Crc32cLengthKnown other) {
+      int combined = Crc32cUtility.crc32cCombineGoogle(value, other.value, other.length);
+      return new Crc32cLengthKnown(combined, length + other.length);
+    }
+
+    @Override
+    public String toString() {
+      return String.format("crc32c{0x%08x (length = %d)}", value, length);
+    }
+  }
+}


Test patch:
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/Crc32cValueTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/Crc32cValueTest.java
new file mode 100644
index 00000000..600b1337
--- /dev/null
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/Crc32cValueTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * 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.cloud.storage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.cloud.storage.Crc32cValue.Crc32cLengthKnown;
+import com.google.cloud.storage.Crc32cValue.Crc32cLengthUnknown;
+import com.google.common.hash.HashFunction;
+import com.google.common.hash.Hashing;
+import net.jqwik.api.Example;
+
+final class Crc32cValueTest {
+
+  @Example
+  public void ensureConcatenationOfTwoValuesOnlyWorksWhenTheLengthIsKnownForRightHandSide() {
+    Crc32cValue.of(1).concat(Crc32cValue.of(2, 1L));
+  }
+
+  @Example
+  public void ensureConcatSatisfiesTheLeftDistributedProperty() {
+    HashFunction f = Hashing.crc32c();
+
+    int expected =
+        f.hashBytes(
+                new byte[] {
+                  0x00, 0x01, 0x02, 0x03,
+                  0x04, 0x05, 0x06, 0x07,
+                  0x08, 0x09, 0x0a, 0x0b,
+                  0x0c, 0x0d, 0x0e, 0x0f
+                })
+            .asInt();
+    int a = f.hashBytes(new byte[] {0x00, 0x01, 0x02, 0x03}).asInt();
+    int b = f.hashBytes(new byte[] {0x04, 0x05, 0x06, 0x07}).asInt();
+    int c = f.hashBytes(new byte[] {0x08, 0x09, 0x0a, 0x0b}).asInt();
+    int d = f.hashBytes(new byte[] {0x0c, 0x0d, 0x0e, 0x0f}).asInt();
+
+    Crc32cLengthUnknown A = Crc32cValue.of(a);
+    Crc32cLengthKnown B = Crc32cValue.of(b, 4);
+    Crc32cLengthKnown C = Crc32cValue.of(c, 4);
+    Crc32cLengthKnown D = Crc32cValue.of(d, 4);
+
+    Crc32cLengthKnown BC = B.concat(C);
+    Crc32cLengthKnown BCD = BC.concat(D);
+    Crc32cLengthUnknown ABCD = A.concat(BCD);
+
+    Crc32cValue<?> chain = A.concat(B).concat(C).concat(D);
+    Crc32cValue<?> nesting = A.concat(B.concat(C.concat(D)));
+    Crc32cValue<?> mixed = A.concat(B.concat(C)).concat(D);
+
+    assertThat(ABCD.getValue()).isEqualTo(expected);
+    assertThat(chain.getValue()).isEqualTo(expected);
+    assertThat(nesting.getValue()).isEqualTo(expected);
+    assertThat(mixed.getValue()).isEqualTo(expected);
+  }
+}