[
https://issues.apache.org/jira/browse/GROOVY-11902?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18071018#comment-18071018
]
ASF GitHub Bot commented on GROOVY-11902:
-----------------------------------------
Copilot commented on code in PR #2433:
URL: https://github.com/apache/groovy/pull/2433#discussion_r3035498166
##########
subprojects/groovy-nio/src/main/java/org/apache/groovy/nio/extensions/NioExtensions.java:
##########
@@ -2036,4 +2041,230 @@ public static <T> T withCloseable(Closeable self,
@ClosureParams(value = SimpleT
return IOGroovyMethods.withCloseable(self, action);
}
+ //
-------------------------------------------------------------------------
+ // Asynchronous file I/O extensions
+ //
-------------------------------------------------------------------------
+
+ /**
+ * Asynchronously read the content of the Path and return it as a String
+ * using the platform's default charset.
+ * <p>
+ * Uses {@link AsynchronousFileChannel} internally, making the I/O
+ * non-blocking. The returned {@link CompletableFuture} completes
+ * when the entire file has been read.
+ *
+ * <pre class="groovyTestCase">
+ * import java.nio.file.Files
+ * def path = Files.createTempFile('test', '.txt')
+ * path.text = 'Hello async'
+ * assert path.textAsync.get() == 'Hello async'
+ * Files.delete(path)
+ * </pre>
+ *
+ * @param self the file whose content we want to read
+ * @return a CompletableFuture that completes with the file content as a
String
+ * @since 6.0.0
+ */
+ public static CompletableFuture<String> getTextAsync(Path self) {
+ return getTextAsync(self, Charset.defaultCharset().name());
+ }
+
+ /**
+ * Asynchronously read the content of the Path and return it as a String
+ * using the specified charset.
+ *
+ * <pre class="groovyTestCase">
+ * import java.nio.file.Files
+ * def path = Files.createTempFile('test', '.txt')
+ * path.text = 'Hello async'
+ * assert path.getTextAsync('UTF-8').get() == 'Hello async'
+ * Files.delete(path)
+ * </pre>
+ *
+ * @param self the file whose content we want to read
+ * @param charset the charset used to decode the file content
+ * @return a CompletableFuture that completes with the file content as a
String
+ * @since 6.0.0
+ */
+ public static CompletableFuture<String> getTextAsync(Path self, String
charset) {
+ return getBytesAsync(self).thenApply(bytes -> new String(bytes,
Charset.forName(charset)));
+ }
+
+ /**
+ * Asynchronously read the content of the Path and return it as a byte
array.
+ * <p>
+ * Uses {@link AsynchronousFileChannel} internally. The entire file content
+ * is read into a byte array.
+ *
+ * <pre class="groovyTestCase">
+ * import java.nio.file.Files
+ * def path = Files.createTempFile('test', '.txt')
+ * path.bytes = [72, 105] as byte[]
+ * assert path.bytesAsync.get() == [72, 105] as byte[]
+ * Files.delete(path)
+ * </pre>
+ *
+ * @param self the file whose content we want to read
+ * @return a CompletableFuture that completes with the file content as a
byte array
+ * @since 6.0.0
+ */
+ public static CompletableFuture<byte[]> getBytesAsync(Path self) {
+ CompletableFuture<byte[]> result = new CompletableFuture<>();
+ AsynchronousFileChannel channel = null;
+ try {
+ channel = AsynchronousFileChannel.open(self,
StandardOpenOption.READ);
+ long size = channel.size();
+ if (size > Integer.MAX_VALUE) {
+ channel.close();
+ result.completeExceptionally(new IOException("File too large
to read into a single byte array: " + size + " bytes"));
+ return result;
+ }
+ ByteBuffer buf = ByteBuffer.allocate((int) size);
+ readFully(channel, buf, 0, result);
+ } catch (IOException | RuntimeException e) {
+ if (channel != null) {
+ try {
+ channel.close();
+ } catch (IOException ignored) {
+ }
+ }
+ result.completeExceptionally(e);
+ }
+ return result;
+ }
+
+ private static void readFully(AsynchronousFileChannel channel, ByteBuffer
buf,
+ long position, CompletableFuture<byte[]>
result) {
+ channel.read(buf, position, null, new CompletionHandler<Integer,
Void>() {
+ @Override
+ public void completed(Integer bytesRead, Void attachment) {
+ if (bytesRead == -1 || !buf.hasRemaining()) {
+ try {
+ channel.close();
+ } catch (IOException e) {
+ result.completeExceptionally(e);
+ return;
+ }
+ buf.flip();
+ byte[] bytes = new byte[buf.remaining()];
+ buf.get(bytes);
+ result.complete(bytes);
+ } else {
+ readFully(channel, buf, position + bytesRead, result);
+ }
+ }
+
+ @Override
+ public void failed(Throwable exc, Void attachment) {
+ try {
+ channel.close();
+ } catch (IOException ignored) {
+ }
+ result.completeExceptionally(exc);
+ }
+ });
+ }
+
+ /**
+ * Asynchronously write the String content to the Path using the
+ * platform's default charset.
+ *
+ * <pre class="groovyTestCase">
+ * import java.nio.file.Files
+ * def path = Files.createTempFile('test', '.txt')
+ * path.writeAsync('Hello async').get()
+ * assert path.text == 'Hello async'
+ * Files.delete(path)
+ * </pre>
+ *
+ * @param self the file to write to
+ * @param text the text to write
+ * @return a CompletableFuture that completes when the write is finished
+ * @since 6.0.0
+ */
+ public static CompletableFuture<Void> writeAsync(Path self, String text) {
+ return writeAsync(self, text, Charset.defaultCharset().name());
+ }
+
+ /**
+ * Asynchronously write the String content to the Path using the
+ * specified charset.
+ *
+ * <pre class="groovyTestCase">
+ * import java.nio.file.Files
+ * def path = Files.createTempFile('test', '.txt')
+ * path.writeAsync('Hello async', 'UTF-8').get()
+ * assert path.text == 'Hello async'
+ * Files.delete(path)
+ * </pre>
+ *
+ * @param self the file to write to
+ * @param text the text to write
+ * @param charset the charset used to encode the text
+ * @return a CompletableFuture that completes when the write is finished
+ * @since 6.0.0
+ */
+ public static CompletableFuture<Void> writeAsync(Path self, String text,
String charset) {
+ return writeBytesAsync(self, text.getBytes(Charset.forName(charset)));
+ }
+
+ /**
+ * Asynchronously write the byte array to the Path, creating the file
+ * if it doesn't exist and truncating it if it does.
+ *
+ * <pre class="groovyTestCase">
+ * import java.nio.file.Files
+ * def path = Files.createTempFile('test', '.txt')
+ * path.writeBytesAsync([72, 105] as byte[]).get()
+ * assert path.bytes == [72, 105] as byte[]
+ * Files.delete(path)
+ * </pre>
+ *
+ * @param self the file to write to
+ * @param bytes the byte array to write
+ * @return a CompletableFuture that completes when the write is finished
+ * @since 6.0.0
+ */
+ public static CompletableFuture<Void> writeBytesAsync(Path self, byte[]
bytes) {
+ CompletableFuture<Void> result = new CompletableFuture<>();
+ try {
+ AsynchronousFileChannel channel =
AsynchronousFileChannel.open(self,
+ StandardOpenOption.WRITE, StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING);
+ ByteBuffer buf = ByteBuffer.wrap(bytes);
+ writeFully(channel, buf, 0, result);
+ } catch (IOException e) {
+ result.completeExceptionally(e);
+ }
+ return result;
+ }
+
+ private static void writeFully(AsynchronousFileChannel channel, ByteBuffer
buf,
+ long position, CompletableFuture<Void>
result) {
+ channel.write(buf, position, null, new CompletionHandler<Integer,
Void>() {
+ @Override
+ public void completed(Integer bytesWritten, Void attachment) {
+ if (buf.hasRemaining()) {
+ writeFully(channel, buf, position + bytesWritten, result);
+ } else {
+ try {
+ channel.close();
+ } catch (IOException e) {
Review Comment:
`writeFully` recurses with `position + bytesWritten`. If `bytesWritten` is
`0` while the buffer still has remaining data (possible for async writes), this
can recurse forever without progress. Handle the `bytesWritten == 0` case
explicitly to avoid infinite recursion / stack overflow.
##########
subprojects/groovy-nio/src/test/groovy/org/apache/groovy/nio/extensions/NioExtensionsTest.groovy:
##########
@@ -1296,4 +1296,99 @@ class NioExtensionsTest extends Specification {
Files.exists(destPath)
!Files.exists(path)
}
+
+ // --
> minor groovy-nio improvements
> -----------------------------
>
> Key: GROOVY-11902
> URL: https://issues.apache.org/jira/browse/GROOVY-11902
> Project: Groovy
> Issue Type: Improvement
> Reporter: Paul King
> Assignee: Paul King
> Priority: Major
>
> ⏺ Asynchronous file I/O extension methods for java.nio.file.Path
>
>
> Adds six new extension methods to Path via groovy-nio that use
> AsynchronousFileChannel for non-blocking file I/O, returning
> CompletableFuture results:
> Reading:
>
>
> - path.getTextAsync() → CompletableFuture<String> (default charset)
> - path.getTextAsync(charset) → CompletableFuture<String>
>
>
> - path.getBytesAsync() → CompletableFuture<byte[]>
> Writing:
>
>
> - path.writeAsync(text) → CompletableFuture<Void> (default charset)
> - path.writeAsync(text, charset) → CompletableFuture<Void>
>
>
> - path.writeBytesAsync(bytes) → CompletableFuture<Void>
> Groovy property syntax works naturally: path.textAsync, path.bytesAsync.
> Usage:
> {code:groovy}
> // blocking
>
>
> def text = path.textAsync.get()
> // composition
>
>
> path.textAsync.thenApply { it.toUpperCase() }
> // with async/await (potential future Groovy 6 feature)
> def text = await path.textAsync
> {code}
--
This message was sent by Atlassian Jira
(v8.20.10#820010)