Skip to content

Commit 17f2a60

Browse files
committed
feat: add StorageNonBlockingChannelUtils
Move existing Buffers.{emptyTo,fillFrom} over to StorageNonBlockingChannelUtils, prefix their method names with `blocking`. Add new tests for the methods, and move the existing methods from BuffersTest that make sense.
1 parent 6a1ad12 commit 17f2a60

File tree

5 files changed

+361
-112
lines changed

5 files changed

+361
-112
lines changed

google-cloud-storage/src/main/java/com/google/cloud/storage/BlobAppendableUpload.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,16 @@ interface AppendableUploadWriteableByteChannel extends WritableByteChannel {
111111
* operation upon this channel, however, then an invocation of this method will block until the
112112
* first operation is complete.
113113
*
114+
* <p>If your application needs to empty its ByteBuffer before progressing, use our helper
115+
* method {@link StorageNonBlockingChannelUtils#blockingEmptyTo(ByteBuffer,
116+
* WritableByteChannel)} like so:
117+
*
118+
* <pre>{@code
119+
* try (AppendableUploadWriteableByteChannel channel = session.open()) {
120+
* int written = StorageNonBlockingChannelUtils.blockingEmptyTo(byteBuffer, channel);
121+
* }
122+
* }</pre>
123+
*
114124
* @param src The buffer from which bytes are to be retrieved
115125
* @return The number of bytes written, possibly zero
116126
* @throws ClosedChannelException If this channel is closed

google-cloud-storage/src/main/java/com/google/cloud/storage/Buffers.java

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -159,33 +159,11 @@ static int alignSize(int size, int alignmentMultiple) {
159159
}
160160

161161
static int fillFrom(ByteBuffer buf, ReadableByteChannel c) throws IOException {
162-
int total = 0;
163-
while (buf.hasRemaining()) {
164-
int read = c.read(buf);
165-
if (read != -1) {
166-
total += read;
167-
} else if (total == 0) {
168-
return -1;
169-
} else {
170-
break;
171-
}
172-
}
173-
return total;
162+
return StorageNonBlockingChannelUtils.blockingFillFrom(buf, c);
174163
}
175164

176165
static int emptyTo(ByteBuffer buf, WritableByteChannel c) throws IOException {
177-
int total = 0;
178-
while (buf.hasRemaining()) {
179-
int written = c.write(buf);
180-
if (written != -1) {
181-
total += written;
182-
} else if (total == 0) {
183-
return -1;
184-
} else {
185-
break;
186-
}
187-
}
188-
return total;
166+
return StorageNonBlockingChannelUtils.blockingEmptyTo(buf, c);
189167
}
190168

191169
static long totalRemaining(ByteBuffer[] buffers, int offset, int length) {
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.storage;
18+
19+
import java.io.IOException;
20+
import java.nio.ByteBuffer;
21+
import java.nio.channels.ReadableByteChannel;
22+
import java.nio.channels.WritableByteChannel;
23+
24+
/**
25+
* Set of utility methods for working with non-blocking channels returned by this library.
26+
*
27+
* @since 2.56.0
28+
*/
29+
public final class StorageNonBlockingChannelUtils {
30+
31+
private StorageNonBlockingChannelUtils() {}
32+
33+
/**
34+
* Attempt to fill {@code buf} from {@code c}, blocking the invoking thread if necessary in order
35+
* to do so.
36+
*
37+
* <p>This method will not close {@code c}.
38+
*
39+
* @return The number of bytes read, possibly zero, or <tt>-1</tt> if the channel has reached
40+
* end-of-stream
41+
* @throws IOException any IOException from calling {@link ReadableByteChannel#read(ByteBuffer)}
42+
* @since 2.56.0
43+
*/
44+
public static int blockingFillFrom(ByteBuffer buf, ReadableByteChannel c) throws IOException {
45+
int total = 0;
46+
while (buf.hasRemaining()) {
47+
int read = c.read(buf);
48+
if (read != -1) {
49+
total += read;
50+
} else if (total == 0) {
51+
return -1;
52+
} else {
53+
break;
54+
}
55+
}
56+
return total;
57+
}
58+
59+
/**
60+
* Attempt to empty {@code buf} to {@code c}, blocking the invoking thread if necessary in order
61+
* to do so.
62+
*
63+
* <p>This method will not close {@code c}
64+
*
65+
* @return The number of bytes written, possibly zero
66+
* @throws IOException any IOException from calling {@link WritableByteChannel#write(ByteBuffer)}
67+
* @since 2.56.0
68+
*/
69+
public static int blockingEmptyTo(ByteBuffer buf, WritableByteChannel c) throws IOException {
70+
int total = 0;
71+
while (buf.hasRemaining()) {
72+
int written = c.write(buf);
73+
if (written != 0) {
74+
total += written;
75+
}
76+
}
77+
return total;
78+
}
79+
}

google-cloud-storage/src/test/java/com/google/cloud/storage/BuffersTest.java

Lines changed: 0 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,10 @@
1616

1717
package com.google.cloud.storage;
1818

19-
import static com.google.cloud.storage.TestUtils.assertAll;
20-
import static com.google.cloud.storage.TestUtils.xxd;
2119
import static com.google.common.truth.Truth.assertThat;
2220

23-
import java.io.IOException;
2421
import java.nio.ByteBuffer;
25-
import java.nio.channels.ReadableByteChannel;
2622
import java.security.SecureRandom;
27-
import java.util.concurrent.atomic.AtomicInteger;
2823
import org.junit.Test;
2924

3025
public final class BuffersTest {
@@ -77,87 +72,4 @@ public void allocateAligned_evenlyDivisible_capacityGtAlignment() {
7772
ByteBuffer b1 = Buffers.allocateAligned(8, 4);
7873
assertThat(b1.capacity()).isEqualTo(8);
7974
}
80-
81-
@Test
82-
public void fillFrom_handles_0SizeRead_someBytesRead() throws Exception {
83-
byte[] bytes = new byte[14];
84-
ByteBuffer buf = ByteBuffer.wrap(bytes);
85-
86-
byte[] expected =
87-
new byte[] {
88-
(byte) 'A',
89-
(byte) 'B',
90-
(byte) 'C',
91-
(byte) 'A',
92-
(byte) 'B',
93-
(byte) 'A',
94-
(byte) 'A',
95-
(byte) 'A',
96-
(byte) 'B',
97-
(byte) 'A',
98-
(byte) 'B',
99-
(byte) 'C',
100-
(byte) 0,
101-
(byte) 0
102-
};
103-
104-
int[] acceptSequence = new int[] {3, 2, 1, 0, 0, 1, 2, 3};
105-
AtomicInteger readCount = new AtomicInteger(0);
106-
107-
ReadableByteChannel c =
108-
new ReadableByteChannel() {
109-
@Override
110-
public int read(ByteBuffer dst) throws IOException {
111-
int i = readCount.getAndIncrement();
112-
if (i == acceptSequence.length) {
113-
return -1;
114-
}
115-
int bytesToRead = acceptSequence[i];
116-
if (bytesToRead > 0) {
117-
long copy =
118-
Buffers.copy(DataGenerator.base64Characters().genByteBuffer(bytesToRead), dst);
119-
assertThat(copy).isEqualTo(bytesToRead);
120-
}
121-
122-
return bytesToRead;
123-
}
124-
125-
@Override
126-
public boolean isOpen() {
127-
return true;
128-
}
129-
130-
@Override
131-
public void close() throws IOException {}
132-
};
133-
int filled = Buffers.fillFrom(buf, c);
134-
135-
assertAll(
136-
() -> assertThat(filled).isEqualTo(12),
137-
() -> assertThat(xxd(bytes)).isEqualTo(xxd(expected)));
138-
}
139-
140-
@Test
141-
public void fillFrom_handles_0SizeRead_noBytesRead() throws Exception {
142-
ByteBuffer buf = ByteBuffer.allocate(3);
143-
144-
ReadableByteChannel c =
145-
new ReadableByteChannel() {
146-
@Override
147-
public int read(ByteBuffer dst) throws IOException {
148-
return -1;
149-
}
150-
151-
@Override
152-
public boolean isOpen() {
153-
return true;
154-
}
155-
156-
@Override
157-
public void close() throws IOException {}
158-
};
159-
int filled = Buffers.fillFrom(buf, c);
160-
161-
assertThat(filled).isEqualTo(-1);
162-
}
16375
}

0 commit comments

Comments
 (0)