Repository: googleapis/java-storage
Pull request #2198 (2023-09-08 21:49:54)

chore: Update the Java code generator (gapic-generator-java) to 2.25.0
- [ ] Regenerate this pull request now.

PiperOrigin-RevId: 563824709

Source-Link: https://github.com/googleapis/googleapis/commit/8fe4127b6bd66764c7a854d555d05ff4914b6716

Source-Link: https://github.com/googleapis/googleapis-gen/commit/c8c0112151b19d890bb49b747b0ab28b816d47b1
Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiYzhjMDExMjE1MWIxOWQ4OTBiYjQ5Yjc0N2IwYWIyOGI4MTZkNDdiMSJ9


Patch:
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/SyncingFileChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/SyncingFileChannel.java
new file mode 100644
index 00000000..74c76eca
--- /dev/null
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/SyncingFileChannel.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2023 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 com.google.cloud.storage.UnbufferedWritableByteChannelSession.UnbufferedWritableByteChannel;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+
+/** A FileChannel decorator that will fsync after every {@link #write(ByteBuffer)} */
+final class SyncingFileChannel implements UnbufferedWritableByteChannel {
+
+  private final FileChannel fc;
+
+  SyncingFileChannel(FileChannel fc) {
+    this.fc = fc;
+  }
+
+  @Override
+  public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
+    long written = fc.write(srcs, offset, length);
+    // metadata in this case are things like mtime, atime etc. Those are not important to our needs
+    // simply force the file contents to by synced.
+    fc.force(/*includeMetaData = */ false);
+    return written;
+  }
+
+  @Override
+  public boolean isOpen() {
+    return fc.isOpen();
+  }
+
+  @Override
+  public void close() throws IOException {
+    fc.close();
+  }
+}
diff --git a/pom.xml b/pom.xml
index 409694dc..b40c1cc7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -54,7 +54,7 @@
     <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
     <github.global.server>github</github.global.server>
     <site.installationModule>google-cloud-storage-parent</site.installationModule>
-    <google.cloud.shared-dependencies.version>3.14.0</google.cloud.shared-dependencies.version>
+    <google.cloud.shared-dependencies.version>3.15.0</google.cloud.shared-dependencies.version>
   </properties>
 
   <dependencyManagement>
diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/BucketName.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/BucketName.java
index 078819ae..d2643c96 100644
--- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/BucketName.java
+++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/BucketName.java
@@ -137,7 +137,7 @@ public class BucketName implements ResourceName {
     if (o == this) {
       return true;
     }
-    if (o != null || getClass() == o.getClass()) {
+    if (o != null && getClass() == o.getClass()) {
       BucketName that = ((BucketName) o);
       return Objects.equals(this.project, that.project) && Objects.equals(this.bucket, that.bucket);
     }
diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CryptoKeyName.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CryptoKeyName.java
index f80912dc..a9d2ffd7 100644
--- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CryptoKeyName.java
+++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CryptoKeyName.java
@@ -175,7 +175,7 @@ public class CryptoKeyName implements ResourceName {
     if (o == this) {
       return true;
     }
-    if (o != null || getClass() == o.getClass()) {
+    if (o != null && getClass() == o.getClass()) {
       CryptoKeyName that = ((CryptoKeyName) o);
       return Objects.equals(this.project, that.project)
           && Objects.equals(this.location, that.location)
diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/NotificationConfigName.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/NotificationConfigName.java
index ecb00bec..3a0c0a36 100644
--- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/NotificationConfigName.java
+++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/NotificationConfigName.java
@@ -159,7 +159,7 @@ public class NotificationConfigName implements ResourceName {
     if (o == this) {
       return true;
     }
-    if (o != null || getClass() == o.getClass()) {
+    if (o != null && getClass() == o.getClass()) {
       NotificationConfigName that = ((NotificationConfigName) o);
       return Objects.equals(this.project, that.project)
           && Objects.equals(this.bucket, that.bucket)
diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ProjectName.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ProjectName.java
index 154bd94b..b8d7aa7b 100644
--- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ProjectName.java
+++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ProjectName.java
@@ -127,7 +127,7 @@ public class ProjectName implements ResourceName {
     if (o == this) {
       return true;
     }
-    if (o != null || getClass() == o.getClass()) {
+    if (o != null && getClass() == o.getClass()) {
       ProjectName that = ((ProjectName) o);
       return Objects.equals(this.project, that.project);
     }


Test patch:
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITSyncingFileChannelTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITSyncingFileChannelTest.java
new file mode 100644
index 00000000..5fcd6429
--- /dev/null
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITSyncingFileChannelTest.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2023 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.cloud.storage.RewindableContentPropertyTest.byteBuffers;
+import static com.google.cloud.storage.TestUtils.xxd;
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.file.Files.readAllBytes;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.protobuf.ByteString;
+import java.io.IOException;
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+import net.jqwik.api.Arbitraries;
+import net.jqwik.api.Arbitrary;
+import net.jqwik.api.ForAll;
+import net.jqwik.api.Property;
+import net.jqwik.api.Provide;
+
+public final class ITSyncingFileChannelTest {
+
+  /**
+   * Run a series of generated scenarios where each write is performed against a {@link
+   * SyncingFileChannel} after {@link SyncingFileChannel#write(ByteBuffer)} returns verify the full
+   * contents of the file match the expected cumulative value.
+   */
+  @Property
+  void shouldHandleAnySizeWriteGt0(@ForAll("WriteScenario") WriteScenario writeScenario)
+      throws IOException {
+    // use try-with-resource to approximate @TearDown and cleanup the file
+    try (WriteScenario ws = writeScenario) {
+      Path path = ws.getPath();
+      try (FileChannel fc = FileChannel.open(path, ws.getOpenOptions());
+          SyncingFileChannel syncing = new SyncingFileChannel(fc)) {
+        assertThat(syncing.isOpen()).isTrue();
+        ByteBuffer[] writes = ws.writes();
+        for (int i = 0; i < writes.length; i++) {
+          ByteBuffer buf = writes[i];
+          syncing.write(buf);
+          assertThat(xxd(readAllBytes(path))).isEqualTo(ws.expected(i));
+        }
+      }
+      assertThat(xxd(readAllBytes(path))).isEqualTo(ws.all());
+    }
+  }
+
+  @Provide("WriteScenario")
+  static Arbitrary<WriteScenario> writeScenario() {
+    return Arbitraries.lazyOf(
+            () ->
+                Arbitraries.oneOf(
+                    byteBuffers(1, 10),
+                    byteBuffers(10, 100),
+                    byteBuffers(100, 1_000),
+                    byteBuffers(1_000, 10_000),
+                    byteBuffers(10_000, 100_000),
+                    byteBuffers(100_000, 1_000_000)))
+        .map(
+            buffers ->
+                Arrays.stream(buffers).filter(Buffer::hasRemaining).toArray(ByteBuffer[]::new))
+        .filter(
+            buffers -> {
+              long totalAvailable = Arrays.stream(buffers).mapToLong(ByteBuffer::remaining).sum();
+              return totalAvailable > 0;
+            })
+        .map(WriteScenario::of);
+  }
+
+  static final class WriteScenario implements AutoCloseable {
+    private static final Path TMP_DIR = Paths.get(System.getProperty("java.io.tmpdir"));
+    private static final Collector<CharSequence, ?, String> DEBUG_JOINER =
+        Collectors.joining(",\n\t", "[\n\t", "\n]");
+
+    private final Path path;
+    private final ByteBuffer[] writes;
+    private final ByteString[] expectedCumulativeContents;
+    private final EnumSet<StandardOpenOption> openOptions;
+
+    private WriteScenario(Path path, ByteBuffer[] writes, ByteString[] expectedCumulativeContents) {
+      this.path = path;
+      this.writes = writes;
+      this.expectedCumulativeContents = expectedCumulativeContents;
+      this.openOptions = EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE);
+    }
+
+    public Path getPath() {
+      return path;
+    }
+
+    public EnumSet<StandardOpenOption> getOpenOptions() {
+      return openOptions;
+    }
+
+    ByteBuffer[] writes() {
+      return Arrays.stream(writes).map(ByteBuffer::duplicate).toArray(ByteBuffer[]::new);
+    }
+
+    String expected(int idx) {
+      Preconditions.checkArgument(
+          0 <= idx && idx < expectedCumulativeContents.length,
+          "index out of bounds: (0 <= %s && %s < %s)",
+          idx,
+          idx,
+          expectedCumulativeContents.length);
+      return xxd(false, expectedCumulativeContents[idx].asReadOnlyByteBuffer());
+    }
+
+    String all() {
+      return xxd(
+          false,
+          expectedCumulativeContents[expectedCumulativeContents.length - 1].asReadOnlyByteBuffer());
+    }
+
+    @Override
+    public void close() throws IOException {
+      Files.deleteIfExists(path);
+    }
+
+    @Override
+    public String toString() {
+      return MoreObjects.toStringHelper(this)
+          .add("\npath", path)
+          .add(
+              "\nwrites",
+              Arrays.stream(writes)
+                  .map(b -> String.format("%s \n %s", b.toString(), xxd(false, b.duplicate())))
+                  .collect(DEBUG_JOINER))
+          .add(
+              "\nexpectedCumulativeContents",
+              Arrays.stream(expectedCumulativeContents)
+                  .map(ByteString::toString)
+                  .collect(DEBUG_JOINER))
+          .toString();
+    }
+
+    public static WriteScenario of(ByteBuffer[] byteBuffers) {
+      try {
+        Path path = Files.createTempFile(TMP_DIR, WriteScenario.class.getName() + "-", ".bin");
+
+        List<ByteString> byteStrings = new ArrayList<>();
+        for (int i = 0; i < byteBuffers.length; i++) {
+          ByteString bs = ByteString.empty();
+          for (int j = 0; j <= i; j++) {
+            ByteBuffer byteBuffer = byteBuffers[j].duplicate();
+            bs = bs.concat(ByteStringStrategy.noCopy().apply(byteBuffer));
+          }
+          byteStrings.add(bs);
+        }
+
+        return new WriteScenario(path, byteBuffers, byteStrings.toArray(new ByteString[0]));
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    }
+  }
+}
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableContentPropertyTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableContentPropertyTest.java
index 48d29bc8..79453af5 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableContentPropertyTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableContentPropertyTest.java
@@ -143,7 +143,7 @@ final class RewindableContentPropertyTest {
   }
 
   @NonNull
-  private static Arbitrary<ByteBuffer[]> byteBuffers(int perBufferMinSize, int perBufferMaxSize) {
+  static Arbitrary<ByteBuffer[]> byteBuffers(int perBufferMinSize, int perBufferMaxSize) {
     return byteBuffer(perBufferMinSize, perBufferMaxSize)
         .array(ByteBuffer[].class)
         .ofMinSize(1)
@@ -155,7 +155,7 @@ final class RewindableContentPropertyTest {
    * limit
    */
   @NonNull
-  private static Arbitrary<ByteBuffer> byteBuffer(int minSize, int maxSize) {
+  static Arbitrary<ByteBuffer> byteBuffer(int minSize, int maxSize) {
     return Arbitraries.integers()
         .between(minSize, maxSize)
         .withDistribution(RandomDistribution.uniform())