() {
@Override
public ScheduledExecutorService create() {
ScheduledThreadPoolExecutor service =
diff --git a/google-cloud-core-grpc/src/main/java/com/google/cloud/grpc/LogExceptionRunnable.java b/google-cloud-core-grpc/src/main/java/com/google/cloud/grpc/LogExceptionRunnable.java
new file mode 100644
index 0000000000..40fd6884c1
--- /dev/null
+++ b/google-cloud-core-grpc/src/main/java/com/google/cloud/grpc/LogExceptionRunnable.java
@@ -0,0 +1,58 @@
+/*
+ * 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.grpc;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Throwables;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * This class was copied from grpc-core to prevent dependence on an unstable API that may be subject
+ * to changes
+ * (https://github.com/grpc/grpc-java/blob/d07ecbe037d2705a1c9f4b6345581f860e505b56/core/src/main/java/io/grpc/internal/LogExceptionRunnable.java)
+ *
+ * A simple wrapper for a {@link Runnable} that logs any exception thrown by it, before
+ * re-throwing it.
+ */
+final class LogExceptionRunnable implements Runnable {
+
+ private static final Logger log = Logger.getLogger(LogExceptionRunnable.class.getName());
+
+ private final Runnable task;
+
+ public LogExceptionRunnable(Runnable task) {
+ this.task = checkNotNull(task, "task");
+ }
+
+ @Override
+ public void run() {
+ try {
+ task.run();
+ } catch (Throwable t) {
+ log.log(Level.SEVERE, "Exception while executing runnable " + task, t);
+ Throwables.throwIfUnchecked(t);
+ throw new AssertionError(t);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "LogExceptionRunnable(" + task + ")";
+ }
+}
diff --git a/google-cloud-core-grpc/src/main/java/com/google/cloud/grpc/SharedResourceHolder.java b/google-cloud-core-grpc/src/main/java/com/google/cloud/grpc/SharedResourceHolder.java
new file mode 100644
index 0000000000..c8fc6a886b
--- /dev/null
+++ b/google-cloud-core-grpc/src/main/java/com/google/cloud/grpc/SharedResourceHolder.java
@@ -0,0 +1,184 @@
+/*
+ * 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.grpc;
+
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import java.util.IdentityHashMap;
+import java.util.concurrent.*;
+
+/**
+ * This class was copied from grpc-core to prevent dependence on an unstable API that may be subject
+ * to changes
+ * (https://github.com/grpc/grpc-java/blob/d07ecbe037d2705a1c9f4b6345581f860e505b56/core/src/main/java/io/grpc/internal/SharedResourceHolder.java)
+ *
+ *
A holder for shared resource singletons.
+ *
+ *
Components like client channels and servers need certain resources, e.g. a thread pool, to
+ * run. If the user has not provided such resources, these components will use a default one, which
+ * is shared as a static resource. This class holds these default resources and manages their
+ * life-cycles.
+ *
+ *
A resource is identified by the reference of a {@link Resource} object, which is typically a
+ * singleton, provided to the get() and release() methods. Each Resource object (not its class) maps
+ * to an object cached in the holder.
+ *
+ *
Resources are ref-counted and shut down after a delay when the ref-count reaches zero.
+ */
+final class SharedResourceHolder {
+ static final long DESTROY_DELAY_SECONDS = 1;
+
+ // The sole holder instance.
+ private static final SharedResourceHolder holder =
+ new SharedResourceHolder(
+ new ScheduledExecutorFactory() {
+ @Override
+ public ScheduledExecutorService createScheduledExecutor() {
+ return Executors.newSingleThreadScheduledExecutor(
+ getThreadFactory("grpc-shared-destroyer-%d", true));
+ }
+ });
+
+ private final IdentityHashMap, Instance> instances = new IdentityHashMap<>();
+
+ private final ScheduledExecutorFactory destroyerFactory;
+
+ private ScheduledExecutorService destroyer;
+
+ // Visible to tests that would need to create instances of the holder.
+ SharedResourceHolder(ScheduledExecutorFactory destroyerFactory) {
+ this.destroyerFactory = destroyerFactory;
+ }
+
+ private static ThreadFactory getThreadFactory(String nameFormat, boolean daemon) {
+ return new ThreadFactoryBuilder().setDaemon(daemon).setNameFormat(nameFormat).build();
+ }
+
+ /**
+ * Try to get an existing instance of the given resource. If an instance does not exist, create a
+ * new one with the given factory.
+ *
+ * @param resource the singleton object that identifies the requested static resource
+ */
+ public static T get(Resource resource) {
+ return holder.getInternal(resource);
+ }
+
+ /**
+ * Releases an instance of the given resource.
+ *
+ * The instance must have been obtained from {@link #get(Resource)}. Otherwise will throw
+ * IllegalArgumentException.
+ *
+ *
Caller must not release a reference more than once. It's advisory that you clear the
+ * reference to the instance with the null returned by this method.
+ *
+ * @param resource the singleton Resource object that identifies the released static resource
+ * @param instance the released static resource
+ * @return a null which the caller can use to clear the reference to that instance.
+ */
+ public static T release(final Resource resource, final T instance) {
+ return holder.releaseInternal(resource, instance);
+ }
+
+ /**
+ * Visible to unit tests.
+ *
+ * @see #get(Resource)
+ */
+ @SuppressWarnings("unchecked")
+ synchronized T getInternal(Resource resource) {
+ Instance instance = instances.get(resource);
+ if (instance == null) {
+ instance = new Instance(resource.create());
+ instances.put(resource, instance);
+ }
+ if (instance.destroyTask != null) {
+ instance.destroyTask.cancel(false);
+ instance.destroyTask = null;
+ }
+ instance.refcount++;
+ return (T) instance.payload;
+ }
+
+ /** Visible to unit tests. */
+ synchronized T releaseInternal(final Resource resource, final T instance) {
+ final Instance cached = instances.get(resource);
+ if (cached == null) {
+ throw new IllegalArgumentException("No cached instance found for " + resource);
+ }
+ Preconditions.checkArgument(instance == cached.payload, "Releasing the wrong instance");
+ Preconditions.checkState(cached.refcount > 0, "Refcount has already reached zero");
+ cached.refcount--;
+ if (cached.refcount == 0) {
+ Preconditions.checkState(cached.destroyTask == null, "Destroy task already scheduled");
+ // Schedule a delayed task to destroy the resource.
+ if (destroyer == null) {
+ destroyer = destroyerFactory.createScheduledExecutor();
+ }
+ cached.destroyTask =
+ destroyer.schedule(
+ new LogExceptionRunnable(
+ new Runnable() {
+ @Override
+ public void run() {
+ synchronized (SharedResourceHolder.this) {
+ // Refcount may have gone up since the task was scheduled. Re-check it.
+ if (cached.refcount == 0) {
+ try {
+ resource.close(instance);
+ } finally {
+ instances.remove(resource);
+ if (instances.isEmpty()) {
+ destroyer.shutdown();
+ destroyer = null;
+ }
+ }
+ }
+ }
+ }
+ }),
+ DESTROY_DELAY_SECONDS,
+ TimeUnit.SECONDS);
+ }
+ // Always returning null
+ return null;
+ }
+
+ /** Defines a resource, and the way to create and destroy instances of it. */
+ public interface Resource {
+ /** Create a new instance of the resource. */
+ T create();
+
+ /** Destroy the given instance. */
+ void close(T instance);
+ }
+
+ interface ScheduledExecutorFactory {
+ ScheduledExecutorService createScheduledExecutor();
+ }
+
+ private static class Instance {
+ final Object payload;
+ int refcount;
+ ScheduledFuture> destroyTask;
+
+ Instance(Object payload) {
+ this.payload = payload;
+ }
+ }
+}
diff --git a/google-cloud-core-grpc/src/test/java/com/google/cloud/grpc/SharedResourceHolderTest.java b/google-cloud-core-grpc/src/test/java/com/google/cloud/grpc/SharedResourceHolderTest.java
new file mode 100644
index 0000000000..ba70a0de55
--- /dev/null
+++ b/google-cloud-core-grpc/src/test/java/com/google/cloud/grpc/SharedResourceHolderTest.java
@@ -0,0 +1,292 @@
+/*
+ * 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.grpc;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+
+import java.util.LinkedList;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import org.easymock.EasyMock;
+import org.easymock.IAnswer;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * This class was copied from grpc-core to prevent dependence on an unstable API that may be subject
+ * to changes
+ * (https://github.com/grpc/grpc-java/blob/d07ecbe037d2705a1c9f4b6345581f860e505b56/core/src/test/java/io/grpc/internal/SharedResourceHolderTest.java)
+ *
+ * Unit tests for {@link SharedResourceHolder}.
+ */
+@RunWith(JUnit4.class)
+public class SharedResourceHolderTest {
+
+ private final LinkedList> scheduledDestroyTasks = new LinkedList<>();
+
+ private SharedResourceHolder holder;
+
+ private static class ResourceInstance {
+ volatile boolean closed;
+ }
+
+ private static class ResourceFactory implements SharedResourceHolder.Resource {
+ @Override
+ public ResourceInstance create() {
+ return new ResourceInstance();
+ }
+
+ @Override
+ public void close(ResourceInstance instance) {
+ instance.closed = true;
+ }
+ }
+
+ // Defines two kinds of resources
+ private static final SharedResourceHolder.Resource SHARED_FOO =
+ new ResourceFactory();
+ private static final SharedResourceHolder.Resource SHARED_BAR =
+ new ResourceFactory();
+
+ @Before
+ public void setUp() {
+ holder = new SharedResourceHolder(new MockExecutorFactory());
+ }
+
+ @Test
+ public void destroyResourceWhenRefCountReachesZero() {
+ ResourceInstance foo1 = holder.getInternal(SHARED_FOO);
+ ResourceInstance sharedFoo = foo1;
+ ResourceInstance foo2 = holder.getInternal(SHARED_FOO);
+ assertSame(sharedFoo, foo2);
+
+ ResourceInstance bar1 = holder.getInternal(SHARED_BAR);
+ ResourceInstance sharedBar = bar1;
+
+ foo2 = holder.releaseInternal(SHARED_FOO, foo2);
+ // foo refcount not reached 0, thus shared foo is not closed
+ assertTrue(scheduledDestroyTasks.isEmpty());
+ assertFalse(sharedFoo.closed);
+ assertNull(foo2);
+
+ foo1 = holder.releaseInternal(SHARED_FOO, foo1);
+ assertNull(foo1);
+
+ // foo refcount has reached 0, a destroying task is scheduled
+ assertEquals(1, scheduledDestroyTasks.size());
+ MockScheduledFuture> scheduledDestroyTask = scheduledDestroyTasks.poll();
+ assertEquals(
+ SharedResourceHolder.DESTROY_DELAY_SECONDS,
+ scheduledDestroyTask.getDelay(TimeUnit.SECONDS));
+
+ // Simluate that the destroyer executes the foo destroying task
+ scheduledDestroyTask.runTask();
+ assertTrue(sharedFoo.closed);
+
+ // After the destroying, obtaining a foo will get a different instance
+ ResourceInstance foo3 = holder.getInternal(SHARED_FOO);
+ assertNotSame(sharedFoo, foo3);
+
+ holder.releaseInternal(SHARED_BAR, bar1);
+
+ // bar refcount has reached 0, a destroying task is scheduled
+ assertEquals(1, scheduledDestroyTasks.size());
+ scheduledDestroyTask = scheduledDestroyTasks.poll();
+ assertEquals(
+ SharedResourceHolder.DESTROY_DELAY_SECONDS,
+ scheduledDestroyTask.getDelay(TimeUnit.SECONDS));
+
+ // Simulate that the destroyer executes the bar destroying task
+ scheduledDestroyTask.runTask();
+ assertTrue(sharedBar.closed);
+ }
+
+ @Test
+ public void cancelDestroyTask() {
+ ResourceInstance foo1 = holder.getInternal(SHARED_FOO);
+ ResourceInstance sharedFoo = foo1;
+ holder.releaseInternal(SHARED_FOO, foo1);
+ // A destroying task for foo is scheduled
+ MockScheduledFuture> scheduledDestroyTask = scheduledDestroyTasks.poll();
+ assertFalse(scheduledDestroyTask.cancelled);
+
+ // obtaining a foo before the destroying task is executed will cancel the destroy
+ ResourceInstance foo2 = holder.getInternal(SHARED_FOO);
+ assertTrue(scheduledDestroyTask.cancelled);
+ assertTrue(scheduledDestroyTasks.isEmpty());
+ assertFalse(sharedFoo.closed);
+
+ // And it will be the same foo instance
+ assertSame(sharedFoo, foo2);
+
+ // Release it and the destroying task is scheduled again
+ holder.releaseInternal(SHARED_FOO, foo2);
+ scheduledDestroyTask = scheduledDestroyTasks.poll();
+ assertNotNull(scheduledDestroyTask);
+ assertFalse(scheduledDestroyTask.cancelled);
+ scheduledDestroyTask.runTask();
+ assertTrue(sharedFoo.closed);
+ }
+
+ @Test
+ public void releaseWrongInstance() {
+ ResourceInstance uncached = new ResourceInstance();
+ try {
+ holder.releaseInternal(SHARED_FOO, uncached);
+ fail("Should throw IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ ResourceInstance cached = holder.getInternal(SHARED_FOO);
+ try {
+ holder.releaseInternal(SHARED_FOO, uncached);
+ fail("Should throw IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ holder.releaseInternal(SHARED_FOO, cached);
+ }
+
+ @Test
+ public void overreleaseInstance() {
+ ResourceInstance foo1 = holder.getInternal(SHARED_FOO);
+ holder.releaseInternal(SHARED_FOO, foo1);
+ try {
+ holder.releaseInternal(SHARED_FOO, foo1);
+ fail("Should throw IllegalStateException");
+ } catch (IllegalStateException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void handleInstanceCloseError() {
+ class ExceptionOnCloseResource implements SharedResourceHolder.Resource {
+ @Override
+ public ResourceInstance create() {
+ return new ResourceInstance();
+ }
+
+ @Override
+ public void close(ResourceInstance instance) {
+ throw new RuntimeException();
+ }
+ }
+
+ SharedResourceHolder.Resource resource = new ExceptionOnCloseResource();
+ ResourceInstance instance = holder.getInternal(resource);
+ holder.releaseInternal(resource, instance);
+ MockScheduledFuture> scheduledDestroyTask = scheduledDestroyTasks.poll();
+ try {
+ scheduledDestroyTask.runTask();
+ fail("Should throw RuntimeException");
+ } catch (RuntimeException e) {
+ // expected
+ }
+
+ // Future resource fetches should not get the partially-closed one.
+ assertNotSame(instance, holder.getInternal(resource));
+ }
+
+ private class MockExecutorFactory implements SharedResourceHolder.ScheduledExecutorFactory {
+ @Override
+ public ScheduledExecutorService createScheduledExecutor() {
+ ScheduledExecutorService mockExecutor = createNiceMock(ScheduledExecutorService.class);
+ expect(mockExecutor.schedule(anyObject(Runnable.class), anyLong(), anyObject(TimeUnit.class)))
+ .andAnswer(
+ new IAnswer() {
+ @Override
+ public ScheduledFuture> answer() throws Throwable {
+ Object[] args = EasyMock.getCurrentArguments();
+ Runnable command = (Runnable) args[0];
+ long delay = (Long) args[1];
+ TimeUnit unit = (TimeUnit) args[2];
+ MockScheduledFuture future =
+ new MockScheduledFuture<>(command, delay, unit);
+ scheduledDestroyTasks.add(future);
+ return future;
+ }
+ })
+ .anyTimes();
+ replay(mockExecutor);
+ return mockExecutor;
+ }
+ }
+
+ protected static class MockScheduledFuture implements java.util.concurrent.ScheduledFuture {
+ private boolean cancelled;
+ private boolean finished;
+ final Runnable command;
+ final long delay;
+ final TimeUnit unit;
+
+ MockScheduledFuture(Runnable command, long delay, TimeUnit unit) {
+ this.command = command;
+ this.delay = delay;
+ this.unit = unit;
+ }
+
+ void runTask() {
+ command.run();
+ finished = true;
+ }
+
+ @Override
+ public boolean cancel(boolean interrupt) {
+ if (cancelled || finished) {
+ return false;
+ }
+ cancelled = true;
+ return true;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return cancelled;
+ }
+
+ @Override
+ public long getDelay(TimeUnit targetUnit) {
+ return targetUnit.convert(this.delay, this.unit);
+ }
+
+ @Override
+ public int compareTo(Delayed o) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isDone() {
+ return cancelled || finished;
+ }
+
+ @Override
+ public V get() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V get(long timeout, TimeUnit unit) {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/google-cloud-core-http/pom.xml b/google-cloud-core-http/pom.xml
index e889527679..2d6ea31631 100644
--- a/google-cloud-core-http/pom.xml
+++ b/google-cloud-core-http/pom.xml
@@ -3,7 +3,7 @@
4.0.0
com.google.cloud
google-cloud-core-http
- 2.9.0
+ 2.9.1
jar
Google Cloud Core HTTP
https://github.com/googleapis/java-core
@@ -13,7 +13,7 @@
com.google.cloud
google-cloud-core-parent
- 2.9.0
+ 2.9.1
google-cloud-core-http
diff --git a/google-cloud-core/pom.xml b/google-cloud-core/pom.xml
index af552f9f76..0798324582 100644
--- a/google-cloud-core/pom.xml
+++ b/google-cloud-core/pom.xml
@@ -3,7 +3,7 @@
4.0.0
com.google.cloud
google-cloud-core
- 2.9.0
+ 2.9.1
jar
Google Cloud Core
https://github.com/googleapis/java-core
@@ -13,7 +13,7 @@
com.google.cloud
google-cloud-core-parent
- 2.9.0
+ 2.9.1
google-cloud-core
diff --git a/google-cloud-core/src/main/java/com/google/cloud/Condition.java b/google-cloud-core/src/main/java/com/google/cloud/Condition.java
index 3854883796..c3c4c83fb9 100644
--- a/google-cloud-core/src/main/java/com/google/cloud/Condition.java
+++ b/google-cloud-core/src/main/java/com/google/cloud/Condition.java
@@ -18,6 +18,7 @@
import com.google.api.core.BetaApi;
import com.google.auto.value.AutoValue;
+import javax.annotation.Nullable;
/**
* Class for Identity and Access Management (IAM) policies. IAM policies are used to specify access
@@ -32,9 +33,11 @@
@AutoValue
public abstract class Condition {
/** Get IAM Policy Binding Condition Title */
+ @Nullable
public abstract String getTitle();
/** Get IAM Policy Binding Condition Description */
+ @Nullable
public abstract String getDescription();
/** Get IAM Policy Binding Condition Expression */
@@ -51,10 +54,10 @@ public static Builder newBuilder() {
@AutoValue.Builder
public abstract static class Builder {
/** Set IAM Policy Binding Condition Title */
- public abstract Builder setTitle(String title);
+ public abstract Builder setTitle(@Nullable String title);
/** Set IAM Policy Binding Condition Description */
- public abstract Builder setDescription(String description);
+ public abstract Builder setDescription(@Nullable String description);
/** Set IAM Policy Binding Condition Expression */
public abstract Builder setExpression(String expression);
diff --git a/google-cloud-core/src/main/java/com/google/cloud/ReadChannel.java b/google-cloud-core/src/main/java/com/google/cloud/ReadChannel.java
index 781326fa7a..effc33ee8e 100644
--- a/google-cloud-core/src/main/java/com/google/cloud/ReadChannel.java
+++ b/google-cloud-core/src/main/java/com/google/cloud/ReadChannel.java
@@ -37,6 +37,7 @@ public interface ReadChannel extends ReadableByteChannel, Closeable, Restorable<
@Override
void close();
+ /** Set the offset to read from. */
void seek(long position) throws IOException;
/**
@@ -62,6 +63,23 @@ public interface ReadChannel extends ReadableByteChannel, Closeable, Restorable<
* If used in conjunction with {@link #seek(long)} the total number of returned bytes from this
* channel will be reduced by the number of bytes specified to seek.
*
+ *
The value provided as {@code limit} will define a left-closed,
+ * right-open interval along with either {@code 0} or any value provided to {@link
+ * #seek(long)}, i.e. {@code [}{@link #seek(long)}{@code , }{@link #limit(long)}{@code )}.
+ *
+ *
An example to help illustrate the relationship
+ *
+ * Imagine some data {@code [A, B, C, D, E, F, G, H, I, J]}, 10 bytes total.
+ *
+ *
+ * - {@code limit(5)} would produce {@code [A, B, C, D, E]}
+ *
- {@code seek(8)} would produce {@code [I, J]}
+ *
- {@code seek(2)} {@code limit(5)} would produce {@code [C, D, E]}
+ *
- {@code seek(3)} {@code limit(3)} would produce {@code []}
+ *
+ *
* NOTE:Implementers are not required to return a new instance from this method, however
* they are allowed to. Users of this method should always use the instance returned from this
* method.
diff --git a/google-cloud-core/src/test/java/com/google/cloud/ConditionTest.java b/google-cloud-core/src/test/java/com/google/cloud/ConditionTest.java
new file mode 100644
index 0000000000..67e0e6c2b5
--- /dev/null
+++ b/google-cloud-core/src/test/java/com/google/cloud/ConditionTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public final class ConditionTest {
+
+ @Test
+ public void title_nullable() {
+ Condition condition =
+ Condition.newBuilder().setTitle(null).setDescription("desc").setExpression("expr").build();
+
+ assertThat(condition.getTitle()).isNull();
+ }
+
+ @Test
+ public void description_nullable() {
+ Condition condition =
+ Condition.newBuilder().setTitle("title").setDescription(null).setExpression("expr").build();
+
+ assertThat(condition.getDescription()).isNull();
+ }
+}
diff --git a/pom.xml b/pom.xml
index fc165b0ace..cea71fc54b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
com.google.cloud
google-cloud-core-parent
pom
- 2.9.0
+ 2.9.1
Google Cloud Core Parent
https://github.com/googleapis/java-core
@@ -151,26 +151,26 @@
UTF-8
github
google-cloud-core-parent
- 2.20.1
- 2.2.2
- 2.11.0
- 1.6.22
- 1.13.0
- 2.1.1
+ 2.21.0
+ 2.4.0
+ 2.12.0
+ 1.7.0
+ 1.14.0
+ 2.1.2
1.42.3
- 1.51.0
- 3.21.10
+ 1.51.1
+ 3.21.12
0.31.1
1.3.2
31.1-jre
4.13.2
1.1.3
- 5.0.1
+ 5.1.0
3.0.2
- 1.6.4
+ 1.6.5
3.3
- 2.16
- 2.10
+ 2.18.0
+ 2.10.1
diff --git a/versions.txt b/versions.txt
index 8efc8816bf..377fa07991 100644
--- a/versions.txt
+++ b/versions.txt
@@ -1,4 +1,4 @@
# Format:
# module:released-version:current-version
-google-cloud-core:2.9.0:2.9.0
+google-cloud-core:2.9.1:2.9.1