This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-io.git


The following commit(s) were added to refs/heads/master by this push:
     new 8080c7cb2 Add NIO channel support to `AbstractStreamBuilder` (#784)
8080c7cb2 is described below

commit 8080c7cb26b357598c636fc50f52a76995d2949b
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Fri Sep 26 21:12:41 2025 +0200

    Add NIO channel support to `AbstractStreamBuilder` (#784)
    
    * feat: add NIO channel support to `AbstractStreamBuilder`
    
    Some builders derived from `AbstractStreamBuilder` (for example, 
`SevenZFile.Builder` in Commons Compress) need to produce a 
`SeekableByteChannel` as their data source. Until now this required ad-hoc 
`instanceof` switches across different origin types.
    
    This change integrates channel support directly into the origin/builder 
abstraction, leading to a cleaner and more object-oriented design.
    
    ### Key changes
    
    * Add `getReadableByteChannel()` and `getWritableByteChannel()` to 
`AbstractOrigin` and propagate to `AbstractStreamBuilder`.
    * Introduce `ChannelOrigin`, an `AbstractOrigin` implementation backed by 
an existing `ReadableByteChannel`/`WritableByteChannel`.
    * Add `ByteArrayChannel`, a simple in-memory `SeekableByteChannel` 
implementation.
    * Extend unit tests to cover the new methods and types.
    
    * fix: cleanup on Windows
    
    * fix: checkstyle violation
    
    * fix: merge `ByteArrayChannel` and `ByteArraySeekableByteChannel`
    
    * fix: failing test
    
    * fix: remove irrelevant changes
    
    * fix: changelog
    
    * fix: make `data` private
    
    * fix: refine `ByteArraySeekableByteChannel` Javadoc
    
    Clarifies the Javadoc to better reflect the intended usage patterns:
    
    * `wrap(byte[])` is modeled after `ByteArrayInputStream` and is primarily 
for read-oriented use.
    * The public constructors `ByteArraySeekableByteChannel()` and 
`ByteArraySeekableByteChannel(int)` follow the pattern of 
`ByteArrayOutputStream` and are mainly suited for write-oriented use.
    
    * fix: add missing `@Override` annotation
    
    * fix: typo in default size value
    
    * feat: add generic `getChannel(Class<C>)` accessor
    
    Adds a type-safe `getChannel(Class<C>)` method that lets callers request 
any supported `Channel` subtype (e.g. `ReadableByteChannel`, 
`WritableByteChannel`, `SeekableByteChannel`) through a single entry point.
    
    * fix: rename `doGetChannel` to `getChannel`
    
    * fix: Javadoc
    
    * fix: default capacity of `ByteArraySeekableByteChannel`
    
    * fix: rename `getTestData` to `cloneTestData`
    
    * docs: minimum channel type by origin
    
    * fix: make `getChannel` final
    
    * fix: don't clone `testData`
    
    Cloning the test data is unnecessary as all but one test are not supposed 
to modify them.
    
    * fix: test order
---
 src/changes/changes.xml                            |   3 +-
 .../apache/commons/io/build/AbstractOrigin.java    | 469 ++++++++++++++++++++-
 .../commons/io/build/AbstractOriginSupplier.java   |  24 ++
 .../commons/io/build/AbstractStreamBuilder.java    |  19 +
 .../io/channels/ByteArraySeekableByteChannel.java  |  95 +++--
 .../commons/io/build/AbstractOriginTest.java       | 145 ++++++-
 .../build/AbstractRandomAccessFileOriginTest.java  |  11 +
 .../io/build/AbstractStreamBuilderTest.java        |  64 +++
 .../commons/io/build/ByteArrayOriginTest.java      |   8 +-
 .../apache/commons/io/build/ChannelOriginTest.java | 108 +++++
 .../commons/io/build/CharSequenceOriginTest.java   |   7 +
 .../apache/commons/io/build/FileOriginTest.java    |  15 +-
 .../io/build/IORandomAccessFileOriginTest.java     |   6 +-
 .../commons/io/build/InputStreamOriginTest.java    |   9 +-
 .../commons/io/build/OutputStreamOriginTest.java   |   8 +
 .../apache/commons/io/build/PathOriginTest.java    |  14 +-
 .../io/build/RandomAccessFileOriginTest.java       |   5 +-
 .../apache/commons/io/build/ReaderOriginTest.java  |   9 +-
 .../org/apache/commons/io/build/URIOriginTest.java |  14 +-
 .../commons/io/build/WriterStreamOriginTest.java   |   7 +
 .../ByteArraySeekableByteChannelCompressTest.java  |  39 +-
 .../channels/ByteArraySeekableByteChannelTest.java |  83 ++++
 22 files changed, 1085 insertions(+), 77 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index c28573c78..0190734a8 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -63,9 +63,10 @@ The <action> type attribute can be add,update,fix,remove.
       <action dev="ggregory" type="add"                due-to="Gary 
Gregory">Add org.apache.commons.io.output.ProxyOutputStream.writeRepeat(int, 
long).</action>
       <action dev="pkarwasz" type="add"                due-to="Piotr P. 
Karwasz">Add length unit support in FileSystem limits.</action>
       <action dev="pkarwasz" type="add"                due-to="Piotr P. 
Karwasz">Add IOUtils.toByteArray(InputStream, int, int) for safer chunked 
reading with size validation.</action>
-      <action dev="pkarwasz" type="add"                due-to="Piotr P. 
Karwasz">Add org.apache.commons.io.file.PathUtils.getPath(String, 
String).</action>
+      <action dev="ggregory" type="add"                due-to="Gary 
Gregory">Add org.apache.commons.io.file.PathUtils.getPath(String, 
String).</action>
       <action dev="ggregory" type="add"                due-to="Gary 
Gregory">Add 
org.apache.commons.io.channels.ByteArraySeekableByteChannel.</action>
       <action dev="ggregory" type="add"                due-to="Gary 
Gregory">Add IOIterable.asIterable().</action>
+      <action dev="pkarwasz" type="add"                due-to="Piotr P. 
Karwasz">Add NIO channel support to `AbstractStreamBuilder`.</action>
       <action dev="pkarwasz" type="add"                due-to="Piotr P. 
Karwasz">Add CloseShieldChannel to close-shielded NIO Channels #786.</action>
       <!-- UPDATE -->
       <action type="update" dev="ggregory"             due-to="Gary Gregory, 
Dependabot">Bump org.apache.commons:commons-parent from 85 to 88 #774, 
#783.</action>
diff --git a/src/main/java/org/apache/commons/io/build/AbstractOrigin.java 
b/src/main/java/org/apache/commons/io/build/AbstractOrigin.java
index 3c81bfd38..1706076fb 100644
--- a/src/main/java/org/apache/commons/io/build/AbstractOrigin.java
+++ b/src/main/java/org/apache/commons/io/build/AbstractOrigin.java
@@ -19,6 +19,7 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
@@ -29,13 +30,17 @@
 import java.io.Reader;
 import java.io.Writer;
 import java.net.URI;
+import java.nio.channels.Channel;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.channels.WritableByteChannel;
 import java.nio.charset.Charset;
 import java.nio.file.Files;
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.nio.file.StandardOpenOption;
-import java.nio.file.spi.FileSystemProvider;
 import java.util.Arrays;
 import java.util.Objects;
 
@@ -44,7 +49,7 @@
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.io.RandomAccessFileMode;
 import org.apache.commons.io.RandomAccessFiles;
-import org.apache.commons.io.file.spi.FileSystemProviders;
+import org.apache.commons.io.channels.ByteArraySeekableByteChannel;
 import org.apache.commons.io.input.BufferedFileChannelInputStream;
 import org.apache.commons.io.input.CharSequenceInputStream;
 import org.apache.commons.io.input.CharSequenceReader;
@@ -53,15 +58,270 @@
 import org.apache.commons.io.output.WriterOutputStream;
 
 /**
- * Abstracts the origin of data for builders like a {@link File}, {@link 
Path}, {@link Reader}, {@link Writer}, {@link InputStream}, {@link 
OutputStream}, and
- * {@link URI}.
+ * Abstract base class that encapsulates the <em>origin</em> of data used by 
Commons IO builders.
  * <p>
- * Some methods may throw {@link UnsupportedOperationException} if that method 
is not implemented in a concrete subclass, see {@link #getFile()} and
- * {@link #getPath()}.
+ * An origin represents where bytes/characters come from or go to, such as a 
{@link File}, {@link Path},
+ * {@link Reader}, {@link Writer}, {@link InputStream}, {@link OutputStream}, 
or {@link URI}. Concrete subclasses
+ * expose only the operations that make sense for the underlying source or 
sink; invoking an unsupported operation
+ * results in {@link UnsupportedOperationException} (see, for example, {@link 
#getFile()} and {@link #getPath()}).
  * </p>
  *
- * @param <T> the type of instances to build.
- * @param <B> the type of builder subclass.
+ * <p>
+ * The table below summarizes which views and conversions are supported for 
each origin type.
+ * Column headers show the target view; cells indicate whether that view is 
available from the origin in that row.
+ * </p>
+ *
+ * <table>
+ *   <caption>Origin support matrix</caption>
+ *   <thead>
+ *     <tr>
+ *       <th>Origin Type</th>
+ *       <th>byte[]</th>
+ *       <th>CS</th>
+ *       <th>File</th>
+ *       <th>Path</th>
+ *       <th>RAF</th>
+ *       <th>IS</th>
+ *       <th>Reader</th>
+ *       <th>RBC</th>
+ *       <th>OS</th>
+ *       <th>Writer</th>
+ *       <th>WBC</th>
+ *       <th>Channel type<sup>2</sup></th>
+ *     </tr>
+ *   </thead>
+ *   <tbody>
+ *     <tr>
+ *       <td>byte[]</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>SBC</td>
+ *     </tr>
+ *     <tr>
+ *       <td>CharSequence (CS)</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✔<sup>1</sup></td>
+ *       <td>✔</td>
+ *       <td>✔<sup>1</sup></td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>SBC</td>
+ *     </tr>
+ *     <tr>
+ *       <td>File</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>FC</td>
+ *     </tr>
+ *     <tr>
+ *       <td>Path</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>FC</td>
+ *     </tr>
+ *     <tr>
+ *       <td>IORandomAccessFile</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>FC</td>
+ *     </tr>
+ *     <tr>
+ *       <td>RandomAccessFile (RAF)</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>FC</td>
+ *     </tr>
+ *     <tr>
+ *       <td>InputStream (IS)</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>RBC</td>
+ *     </tr>
+ *     <tr>
+ *       <td>Reader</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✔<sup>1</sup></td>
+ *       <td>✔</td>
+ *       <td>✔<sup>1</sup></td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>RBC</td>
+ *     </tr>
+ *     <tr>
+ *       <td>ReadableByteChannel (RBC)</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>RBC</td>
+ *     </tr>
+ *     <tr>
+ *       <td>OutputStream (OS)</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>WBC</td>
+ *     </tr>
+ *     <tr>
+ *       <td>Writer</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✔<sup>1</sup></td>
+ *       <td>✔</td>
+ *       <td>✔<sup>1</sup></td>
+ *       <td>WBC</td>
+ *     </tr>
+ *     <tr>
+ *       <td>WritableByteChannel (WBC)</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>WBC</td>
+ *     </tr>
+ *     <tr>
+ *       <td>URI (FileSystem)</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>FC</td>
+ *     </tr>
+ *     <tr>
+ *       <td>URI (http/https))</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✔</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>✖</td>
+ *       <td>RBC</td>
+ *     </tr>
+ *   </tbody>
+ * </table>
+ *
+ * <p><strong>Legend</strong></p>
+ * <ul>
+ *   <li>✔ = Supported</li>
+ *   <li>✖ = Not supported (throws {@link UnsupportedOperationException})</li>
+ *   <li><sup>1</sup> = Characters are converted to bytes using the default 
{@link Charset}.</li>
+ *   <li><sup>2</sup> Minimum channel type provided by the origin:
+ *     <ul>
+ *         <li>RBC = ReadableByteChannel</li>
+ *         <li>WBC = WritableByteChannel</li>
+ *         <li>SBC = SeekableByteChannel</li>
+ *         <li>FC = FileChannel</li>
+ *     </ul>
+ *     The exact channel type may be a subtype of the minimum shown.
+ *   </li>
+ * </ul>
+ *
+ * @param <T> the type produced by the builder.
+ * @param <B> the concrete builder subclass type.
  * @since 2.12.0
  */
 public abstract class AbstractOrigin<T, B extends AbstractOrigin<T, B>> 
extends AbstractSupplier<T, B> {
@@ -137,6 +397,11 @@ public Writer getWriter(final Charset charset, final 
OpenOption... options) thro
             return new OutputStreamWriter(getOutputStream(options), 
Charsets.toCharset(charset));
         }
 
+        @Override
+        protected Channel getChannel(OpenOption... options) throws IOException 
{
+            return getRandomAccessFile(options).getChannel();
+        }
+
         @Override
         public long size() throws IOException {
             return origin.length();
@@ -179,6 +444,17 @@ public Reader getReader(final Charset charset) throws 
IOException {
             return new InputStreamReader(getInputStream(), 
Charsets.toCharset(charset));
         }
 
+        @Override
+        protected Channel getChannel(OpenOption... options) throws IOException 
{
+            for (final OpenOption option : options) {
+                if (option == StandardOpenOption.WRITE) {
+                    throw new UnsupportedOperationException(
+                            "Only READ is supported for byte[] origins: " + 
Arrays.toString(options));
+                }
+            }
+            return ByteArraySeekableByteChannel.wrap(getByteArray());
+        }
+
         @Override
         public long size() throws IOException {
             return origin.length;
@@ -241,6 +517,17 @@ public Reader getReader(final Charset charset) throws 
IOException {
             return new CharSequenceReader(get());
         }
 
+        @Override
+        protected Channel getChannel(OpenOption... options) throws IOException 
{
+            for (final OpenOption option : options) {
+                if (option == StandardOpenOption.WRITE) {
+                    throw new UnsupportedOperationException(
+                            "Only READ is supported for CharSequence origins: 
" + Arrays.toString(options));
+                }
+            }
+            return ByteArraySeekableByteChannel.wrap(getByteArray());
+        }
+
         @Override
         public long size() throws IOException {
             return origin.length();
@@ -283,6 +570,10 @@ public Path getPath() {
             return get().toPath();
         }
 
+        @Override
+        protected Channel getChannel(OpenOption... options) throws IOException 
{
+            return Files.newByteChannel(getPath(), options);
+        }
     }
 
     /**
@@ -324,6 +615,19 @@ public Reader getReader(final Charset charset) throws 
IOException {
             return new InputStreamReader(getInputStream(), 
Charsets.toCharset(charset));
         }
 
+        @Override
+        protected Channel getChannel(OpenOption... options) throws IOException 
{
+            return Channels.newChannel(getInputStream(options));
+        }
+
+        @Override
+        public long size() throws IOException {
+            if (origin instanceof FileInputStream) {
+                final FileInputStream fileInputStream = (FileInputStream) 
origin;
+                return fileInputStream.getChannel().size();
+            }
+            throw unsupportedOperation("size");
+        }
     }
 
     /**
@@ -394,6 +698,11 @@ public OutputStream getOutputStream(final OpenOption... 
options) {
         public Writer getWriter(final Charset charset, final OpenOption... 
options) throws IOException {
             return new OutputStreamWriter(origin, Charsets.toCharset(charset));
         }
+
+        @Override
+        protected Channel getChannel(OpenOption... options) throws IOException 
{
+            return Channels.newChannel(getOutputStream(options));
+        }
     }
 
     /**
@@ -429,6 +738,10 @@ public Path getPath() {
             return get();
         }
 
+        @Override
+        protected Channel getChannel(OpenOption... options) throws IOException 
{
+            return Files.newByteChannel(getPath(), options);
+        }
     }
 
     /**
@@ -511,6 +824,11 @@ public Reader getReader(final Charset charset) throws 
IOException {
             // No conversion
             return get();
         }
+
+        @Override
+        protected Channel getChannel(OpenOption... options) throws IOException 
{
+            return Channels.newChannel(getInputStream());
+        }
     }
 
     /**
@@ -539,10 +857,6 @@ public File getFile() {
         public InputStream getInputStream(final OpenOption... options) throws 
IOException {
             final URI uri = get();
             final String scheme = uri.getScheme();
-            final FileSystemProvider fileSystemProvider = 
FileSystemProviders.installed().getFileSystemProvider(scheme);
-            if (fileSystemProvider != null) {
-                return Files.newInputStream(fileSystemProvider.getPath(uri), 
options);
-            }
             if (SCHEME_HTTP.equalsIgnoreCase(scheme) || 
SCHEME_HTTPS.equalsIgnoreCase(scheme)) {
                 return uri.toURL().openStream();
             }
@@ -553,6 +867,16 @@ public InputStream getInputStream(final OpenOption... 
options) throws IOExceptio
         public Path getPath() {
             return Paths.get(get());
         }
+
+        @Override
+        protected Channel getChannel(OpenOption... options) throws IOException 
{
+            final URI uri = get();
+            final String scheme = uri.getScheme();
+            if (SCHEME_HTTP.equalsIgnoreCase(scheme) || 
SCHEME_HTTPS.equalsIgnoreCase(scheme)) {
+                return Channels.newChannel(uri.toURL().openStream());
+            }
+            return Files.newByteChannel(getPath(), options);
+        }
     }
 
     /**
@@ -598,6 +922,73 @@ public Writer getWriter(final Charset charset, final 
OpenOption... options) thro
             // No conversion
             return get();
         }
+
+        @Override
+        protected Channel getChannel(OpenOption... options) throws IOException 
{
+            return Channels.newChannel(getOutputStream());
+        }
+    }
+
+    /**
+     * A {@link Channel} origin.
+     *
+     * @since 2.21.0
+     */
+    public static class ChannelOrigin extends AbstractOrigin<Channel, 
ChannelOrigin> {
+
+        /**
+         * Constructs a new instance for the given origin.
+         *
+         * @param origin The origin, not null.
+         */
+        public ChannelOrigin(final Channel origin) {
+            super(origin);
+        }
+
+        @Override
+        public byte[] getByteArray() throws IOException {
+            return IOUtils.toByteArray(getInputStream());
+        }
+
+        @Override
+        public InputStream getInputStream(final OpenOption... options) throws 
IOException {
+            return 
Channels.newInputStream(getChannel(ReadableByteChannel.class, options));
+        }
+
+        @Override
+        public Reader getReader(Charset charset) throws IOException {
+            return Channels.newReader(
+                    getChannel(ReadableByteChannel.class),
+                    Charsets.toCharset(charset).newDecoder(),
+                    -1);
+        }
+
+        @Override
+        public OutputStream getOutputStream(final OpenOption... options) 
throws IOException {
+            return 
Channels.newOutputStream(getChannel(WritableByteChannel.class, options));
+        }
+
+        @Override
+        public Writer getWriter(Charset charset, OpenOption... options) throws 
IOException {
+            return Channels.newWriter(
+                    getChannel(WritableByteChannel.class, options),
+                    Charsets.toCharset(charset).newEncoder(),
+                    -1);
+        }
+
+        @Override
+        protected Channel getChannel(OpenOption... options) throws IOException 
{
+            // No conversion
+            return get();
+        }
+
+        @Override
+        public long size() throws IOException {
+            if (origin instanceof SeekableByteChannel) {
+                return ((SeekableByteChannel) origin).size();
+            }
+            throw unsupportedOperation("size");
+        }
     }
 
     /**
@@ -675,8 +1066,7 @@ public CharSequence getCharSequence(final Charset charset) 
throws IOException {
      * @throws UnsupportedOperationException if this method is not implemented 
in a concrete subclass.
      */
     public File getFile() {
-        throw new UnsupportedOperationException(
-                String.format("%s#getFile() for %s origin %s", 
getSimpleClassName(), origin.getClass().getSimpleName(), origin));
+        throw unsupportedOperation("getFile");
     }
 
     /**
@@ -710,8 +1100,7 @@ public OutputStream getOutputStream(final OpenOption... 
options) throws IOExcept
      * @throws UnsupportedOperationException if this method is not implemented 
in a concrete subclass.
      */
     public Path getPath() {
-        throw new UnsupportedOperationException(
-                String.format("%s#getPath() for %s origin %s", 
getSimpleClassName(), origin.getClass().getSimpleName(), origin));
+        throw unsupportedOperation("getPath");
     }
 
     /**
@@ -755,6 +1144,39 @@ public Writer getWriter(final Charset charset, final 
OpenOption... options) thro
         return Files.newBufferedWriter(getPath(), Charsets.toCharset(charset), 
options);
     }
 
+    /**
+     * Gets this origin as a Channel of the given type, if possible.
+     *
+     * @param channelType The type of channel to return.
+     * @param options Options specifying how a file-based origin is opened, 
ignored otherwise.
+     * @return A new Channel on the origin of the given type.
+     * @param <C> The type of channel to return.
+     * @throws IOException                   If an I/O error occurs.
+     * @throws UnsupportedOperationException If this origin cannot be 
converted to a channel of the given type.
+     * @since 2.21.0
+     */
+    public final <C extends Channel> C getChannel(Class<C> channelType, 
OpenOption... options) throws IOException {
+        Objects.requireNonNull(channelType, "channelType");
+        final Channel channel = getChannel(options);
+        if (channelType.isInstance(channel)) {
+            return channelType.cast(channel);
+        }
+        throw unsupportedChannelType(channelType);
+    }
+
+    /**
+     * Gets this origin as a Channel, if possible.
+     *
+     * @param options Options specifying how a file-based origin is opened, 
ignored otherwise.
+     * @return A new Channel on the origin.
+     * @throws IOException                   If an I/O error occurs.
+     * @throws UnsupportedOperationException If this origin cannot be 
converted to a channel.
+     * @since 2.21.0
+     */
+    protected Channel getChannel(OpenOption... options) throws IOException {
+        throw unsupportedOperation("getChannel");
+    }
+
     /**
      * Gets the size of the origin, if possible.
      *
@@ -770,4 +1192,19 @@ public long size() throws IOException {
     public String toString() {
         return getSimpleClassName() + "[" + origin.toString() + "]";
     }
+
+    UnsupportedOperationException unsupportedOperation(String method) {
+        return new UnsupportedOperationException(String.format(
+                "%s#%s() for %s origin %s",
+                getSimpleClassName(), method, 
origin.getClass().getSimpleName(), origin));
+    }
+
+    UnsupportedOperationException unsupportedChannelType(Class<? extends 
Channel> channelType) {
+        return new UnsupportedOperationException(String.format(
+                "%s#getChannel(%s) for %s origin %s",
+                getSimpleClassName(),
+                channelType.getSimpleName(),
+                origin.getClass().getSimpleName(),
+                origin));
+    }
 }
diff --git 
a/src/main/java/org/apache/commons/io/build/AbstractOriginSupplier.java 
b/src/main/java/org/apache/commons/io/build/AbstractOriginSupplier.java
index 8f2354d95..6f14fac3b 100644
--- a/src/main/java/org/apache/commons/io/build/AbstractOriginSupplier.java
+++ b/src/main/java/org/apache/commons/io/build/AbstractOriginSupplier.java
@@ -24,11 +24,13 @@
 import java.io.Reader;
 import java.io.Writer;
 import java.net.URI;
+import java.nio.channels.Channel;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 
 import org.apache.commons.io.IORandomAccessFile;
 import org.apache.commons.io.build.AbstractOrigin.ByteArrayOrigin;
+import org.apache.commons.io.build.AbstractOrigin.ChannelOrigin;
 import org.apache.commons.io.build.AbstractOrigin.CharSequenceOrigin;
 import org.apache.commons.io.build.AbstractOrigin.FileOrigin;
 import org.apache.commons.io.build.AbstractOrigin.IORandomAccessFileOrigin;
@@ -182,6 +184,17 @@ protected static WriterOrigin newWriterOrigin(final Writer 
origin) {
         return new WriterOrigin(origin);
     }
 
+    /**
+     * Constructs a new channel origin for a channel.
+     *
+     * @param origin the channel.
+     * @return a new channel origin.
+     * @since 2.21.0
+     */
+    protected static ChannelOrigin newChannelOrigin(final Channel origin) {
+        return new ChannelOrigin(origin);
+    }
+
     /**
      * The underlying origin.
      */
@@ -368,4 +381,15 @@ public B setURI(final URI origin) {
     public B setWriter(final Writer origin) {
         return setOrigin(newWriterOrigin(origin));
     }
+
+    /**
+     * Sets a new origin.
+     *
+     * @param origin the new origin.
+     * @return {@code this} instance.
+     * @since 2.21.0
+     */
+    public B setChannel(final Channel origin) {
+        return setOrigin(newChannelOrigin(origin));
+    }
 }
diff --git 
a/src/main/java/org/apache/commons/io/build/AbstractStreamBuilder.java 
b/src/main/java/org/apache/commons/io/build/AbstractStreamBuilder.java
index 3feac4a65..fe37c5eed 100644
--- a/src/main/java/org/apache/commons/io/build/AbstractStreamBuilder.java
+++ b/src/main/java/org/apache/commons/io/build/AbstractStreamBuilder.java
@@ -24,6 +24,8 @@
 import java.io.RandomAccessFile;
 import java.io.Reader;
 import java.io.Writer;
+import java.nio.channels.Channel;
+import java.nio.channels.ReadableByteChannel;
 import java.nio.charset.Charset;
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
@@ -258,6 +260,23 @@ public Writer getWriter() throws IOException {
         return checkOrigin().getWriter(getCharset(), getOpenOptions());
     }
 
+    /**
+     * Gets a Channel from the origin with OpenOption[].
+     *
+     * @param channelType The channel type, not null.
+     * @return A channel of the specified type.
+     * @param <C>         The channel type.
+     * @throws IllegalStateException         if the {@code origin} is {@code 
null}.
+     * @throws UnsupportedOperationException if the origin cannot be converted 
to a {@link ReadableByteChannel}.
+     * @throws IOException                   if an I/O error occurs.
+     * @see AbstractOrigin#getChannel
+     * @see #getOpenOptions()
+     * @since 2.21.0
+     */
+    public <C extends Channel> C getChannel(Class<C> channelType) throws 
IOException {
+        return checkOrigin().getChannel(channelType, getOpenOptions());
+    }
+
     /**
      * Sets the buffer size. Invalid input (bufferSize &lt;= 0) resets the 
value to its default.
      * <p>
diff --git 
a/src/main/java/org/apache/commons/io/channels/ByteArraySeekableByteChannel.java
 
b/src/main/java/org/apache/commons/io/channels/ByteArraySeekableByteChannel.java
index 51e8c1824..d1aff8515 100644
--- 
a/src/main/java/org/apache/commons/io/channels/ByteArraySeekableByteChannel.java
+++ 
b/src/main/java/org/apache/commons/io/channels/ByteArraySeekableByteChannel.java
@@ -19,12 +19,14 @@
 
 package org.apache.commons.io.channels;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.channels.ClosedChannelException;
 import java.nio.channels.SeekableByteChannel;
 import java.util.Arrays;
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.Objects;
 import java.util.concurrent.locks.ReentrantLock;
 
 import org.apache.commons.io.IOUtils;
@@ -37,41 +39,73 @@
  * accessed via {@link ByteArraySeekableByteChannel#array()}.
  * </p>
  *
- * @since 2.19.0
+ * @since 2.21.0
  */
 public class ByteArraySeekableByteChannel implements SeekableByteChannel {
 
     private static final int RESIZE_LIMIT = Integer.MAX_VALUE >> 1;
+
+    /**
+     * Constructs a new channel backed directly by the given byte array.
+     *
+     * <p>The channel initially contains the full contents of the array, with 
its
+     * size set to {@code bytes.length} and its position set to {@code 0}.</p>
+     *
+     * <p>Reads and writes operate on the shared array.
+     * If a write operation extends beyond the current capacity, the channel 
will
+     * automatically allocate a larger backing array and copy the existing 
contents.</p>
+     *
+     * @param bytes The byte array to wrap, must not be {@code null}
+     * @return A new channel that uses the given array as its initial backing 
store
+     * @throws NullPointerException If {@code bytes} is {@code null}
+     * @see #array()
+     * @see ByteArrayInputStream#ByteArrayInputStream(byte[])
+     */
+    public static ByteArraySeekableByteChannel wrap(byte[] bytes) {
+        Objects.requireNonNull(bytes, "bytes");
+        return new ByteArraySeekableByteChannel(bytes);
+    }
+
     private byte[] data;
-    private final AtomicBoolean closed = new AtomicBoolean();
+    private volatile boolean closed;
     private int position;
     private int size;
     private final ReentrantLock lock = new ReentrantLock();
 
     /**
-     * Constructs a new instance using a default empty buffer.
+     * Constructs a new instance, with a default internal buffer capacity.
+     * <p>
+     * The initial size and position of the channel are 0.
+     * </p>
+     *
+     * @see ByteArrayOutputStream#ByteArrayOutputStream()
      */
     public ByteArraySeekableByteChannel() {
-        this(0);
+        this(IOUtils.DEFAULT_BUFFER_SIZE);
     }
 
     /**
-     * Constructs a new instance from a byte array.
+     * Constructs a new instance, with an internal buffer of the given 
capacity, in bytes.
+     * <p>
+     * The initial size and position of the channel are 0.
+     * </p>
      *
-     * @param data input data or pre-allocated array.
+     * @param size Capacity of the internal buffer to allocate, in bytes.
+     * @see ByteArrayOutputStream#ByteArrayOutputStream(int)
      */
-    public ByteArraySeekableByteChannel(final byte[] data) {
-        this.data = data;
-        this.size = data.length;
+    public ByteArraySeekableByteChannel(final int size) {
+        if (size < 0) {
+            throw new IllegalArgumentException("Size must be non-negative");
+        }
+        this.data = new byte[size];
+        this.position = 0;
+        this.size = 0;
     }
 
-    /**
-     * Constructs a new instance from a size of storage to be allocated.
-     *
-     * @param size size of internal buffer to allocate, in bytes.
-     */
-    public ByteArraySeekableByteChannel(final int size) {
-        this(new byte[size]);
+    private ByteArraySeekableByteChannel(byte[] data) {
+        this.data = data;
+        this.position = 0;
+        this.size = data.length;
     }
 
     /**
@@ -86,6 +120,18 @@ public byte[] array() {
         return data;
     }
 
+    /**
+     * Gets a copy of the data stored in this channel.
+     * <p>
+     * The returned array is a copy of the internal buffer, sized to the 
actual data stored in this channel.
+     * </p>
+     *
+     * @return a new byte array containing the data stored in this channel.
+     */
+    public byte[] toByteArray() {
+        return Arrays.copyOf(data, size);
+    }
+
     private void checkOpen() throws ClosedChannelException {
         if (!isOpen()) {
             throw new ClosedChannelException();
@@ -101,7 +147,7 @@ private int checkRange(final long newSize, final String 
method) {
 
     @Override
     public void close() {
-        closed.set(true);
+        closed = true;
     }
 
     /**
@@ -115,7 +161,7 @@ public long getSize() {
 
     @Override
     public boolean isOpen() {
-        return !closed.get();
+        return !closed;
     }
 
     @Override
@@ -165,7 +211,7 @@ public int read(final ByteBuffer buf) throws IOException {
 
     private void resize(final int newLength) {
         int len = data.length;
-        if (len <= 0) {
+        if (len == 0) {
             len = 1;
         }
         if (newLength < RESIZE_LIMIT) {
@@ -212,13 +258,12 @@ public int write(final ByteBuffer b) throws IOException {
         checkOpen();
         lock.lock();
         try {
-            int wanted = b.remaining();
-            final int possibleWithoutResize = size - position;
+            final int wanted = b.remaining();
+            final int possibleWithoutResize = Math.max(0, size - position);
             if (wanted > possibleWithoutResize) {
                 final int newSize = position + wanted;
-                if (newSize < 0) { // overflow
-                    resize(IOUtils.SOFT_MAX_ARRAY_LENGTH);
-                    wanted = IOUtils.SOFT_MAX_ARRAY_LENGTH - position;
+                if (newSize < 0 || newSize > IOUtils.SOFT_MAX_ARRAY_LENGTH) { 
// overflow
+                    throw new OutOfMemoryError("required array size " + 
Integer.toUnsignedString(newSize) + " too large");
                 } else {
                     resize(newSize);
                 }
diff --git a/src/test/java/org/apache/commons/io/build/AbstractOriginTest.java 
b/src/test/java/org/apache/commons/io/build/AbstractOriginTest.java
index e55da41d3..4f7d0e97f 100644
--- a/src/test/java/org/apache/commons/io/build/AbstractOriginTest.java
+++ b/src/test/java/org/apache/commons/io/build/AbstractOriginTest.java
@@ -23,6 +23,7 @@
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.io.Closeable;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -30,18 +31,25 @@
 import java.io.RandomAccessFile;
 import java.io.Reader;
 import java.io.Writer;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.channels.WritableByteChannel;
 import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.nio.file.StandardOpenOption;
 import java.util.Objects;
 
 import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
 import org.apache.commons.io.build.AbstractOrigin.RandomAccessFileOrigin;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.EnumSource;
 
@@ -55,18 +63,34 @@ public abstract class AbstractOriginTest<T, B extends 
AbstractOrigin<T, B>> {
 
     protected static final String FILE_RES_RO = 
"/org/apache/commons/io/test-file-20byteslength.bin";
     protected static final String FILE_NAME_RO = "src/test/resources" + 
FILE_RES_RO;
-    protected static final String FILE_NAME_RW = "target/" + 
AbstractOriginTest.class.getSimpleName() + ".txt";
+    protected static final String FILE_NAME_RW = 
AbstractOriginTest.class.getSimpleName() + ".txt";
     private static final int RO_LENGTH = 20;
 
     protected AbstractOrigin<T, B> originRo;
     protected AbstractOrigin<T, B> originRw;
 
+    @TempDir
+    protected Path tempPath;
+
     @BeforeEach
-    public void beforeEach() throws IOException {
+    void beforeEach() throws IOException {
         setOriginRo(newOriginRo());
+        resetOriginRw();
         setOriginRw(newOriginRw());
     }
 
+    @AfterEach
+    void cleanup() {
+        final T originRo = getOriginRo().get();
+        if (originRo instanceof Closeable) {
+            IOUtils.closeQuietly((Closeable) originRo);
+        }
+        final T originRw = getOriginRw().get();
+        if (originRw instanceof Closeable) {
+            IOUtils.closeQuietly((Closeable) originRw);
+        }
+    }
+
     protected AbstractOrigin<T, B> getOriginRo() {
         return Objects.requireNonNull(originRo, "originRo");
     }
@@ -92,9 +116,21 @@ protected void setOriginRw(final AbstractOrigin<T, B> 
origin) {
         this.originRw = origin;
     }
 
+    protected void resetOriginRw() throws IOException {
+        // No-op
+    }
+
+    byte[] getFixtureByteArray() throws IOException {
+        return IOUtils.resourceToByteArray(FILE_RES_RO);
+    }
+
+    String getFixtureString() throws IOException {
+        return IOUtils.resourceToString(FILE_RES_RO, StandardCharsets.UTF_8);
+    }
+
     @Test
     void testGetByteArray() throws IOException {
-        assertArrayEquals(Files.readAllBytes(Paths.get(FILE_NAME_RO)), 
getOriginRo().getByteArray());
+        assertArrayEquals(getFixtureByteArray(), getOriginRo().getByteArray());
     }
 
     @Test
@@ -114,7 +150,9 @@ void testGetByteArrayAt_1_1() throws IOException {
 
     @Test
     void testGetCharSequence() throws IOException {
-        assertNotNull(getOriginRo().getCharSequence(Charset.defaultCharset()));
+        final CharSequence charSequence = 
getOriginRo().getCharSequence(StandardCharsets.UTF_8);
+        assertNotNull(charSequence);
+        assertEquals(getFixtureString(), charSequence.toString());
     }
 
     @Test
@@ -135,6 +173,7 @@ private void testGetFile(final File file, final long 
expectedLen) throws IOExcep
     void testGetInputStream() throws IOException {
         try (InputStream inputStream = getOriginRo().getInputStream()) {
             assertNotNull(inputStream);
+            assertArrayEquals(getFixtureByteArray(), 
IOUtils.toByteArray(inputStream));
         }
     }
 
@@ -224,9 +263,15 @@ void testGetReader() throws IOException {
         try (Reader reader = 
getOriginRo().getReader(Charset.defaultCharset())) {
             assertNotNull(reader);
         }
+        setOriginRo(newOriginRo());
         try (Reader reader = getOriginRo().getReader(null)) {
             assertNotNull(reader);
         }
+        setOriginRo(newOriginRo());
+        try (Reader reader = getOriginRo().getReader(StandardCharsets.UTF_8)) {
+            assertNotNull(reader);
+            assertEquals(getFixtureString(), IOUtils.toString(reader));
+        }
     }
 
     @Test
@@ -240,8 +285,96 @@ void testGetWriter() throws IOException {
         }
     }
 
+    @Test
+    void testGetReadableByteChannel() throws IOException {
+        try (ReadableByteChannel channel =
+                getOriginRo().getChannel(ReadableByteChannel.class, 
StandardOpenOption.READ)) {
+            final SeekableByteChannel seekable =
+                    channel instanceof SeekableByteChannel ? 
(SeekableByteChannel) channel : null;
+            assertNotNull(channel);
+            assertTrue(channel.isOpen());
+            if (seekable != null) {
+                assertEquals(0, seekable.position());
+                assertEquals(RO_LENGTH, seekable.size());
+            }
+            checkRead(channel);
+            if (seekable != null) {
+                assertEquals(RO_LENGTH, seekable.position());
+            }
+        }
+    }
+
+    private void checkRead(ReadableByteChannel channel) throws IOException {
+        final ByteBuffer buffer = ByteBuffer.allocate(RO_LENGTH);
+        int read = channel.read(buffer);
+        assertEquals(RO_LENGTH, read);
+        assertArrayEquals(getFixtureByteArray(), buffer.array());
+        // Channel is at EOF
+        buffer.clear();
+        read = channel.read(buffer);
+        assertEquals(-1, read);
+    }
+
+    @Test
+    void testGetWritableByteChannel() throws IOException {
+        final boolean supportsRead;
+        try (WritableByteChannel channel =
+                getOriginRw().getChannel(WritableByteChannel.class, 
StandardOpenOption.WRITE)) {
+            supportsRead = channel instanceof ReadableByteChannel;
+            final SeekableByteChannel seekable =
+                    channel instanceof SeekableByteChannel ? 
(SeekableByteChannel) channel : null;
+            assertNotNull(channel);
+            assertTrue(channel.isOpen());
+            if (seekable != null) {
+                assertEquals(0, seekable.position());
+                assertEquals(0, seekable.size());
+            }
+            checkWrite(channel);
+            if (seekable != null) {
+                assertEquals(RO_LENGTH, seekable.position());
+                assertEquals(RO_LENGTH, seekable.size());
+            }
+        }
+        if (supportsRead) {
+            setOriginRw(newOriginRw());
+            try (ReadableByteChannel channel =
+                    getOriginRw().getChannel(ReadableByteChannel.class, 
StandardOpenOption.READ)) {
+                assertNotNull(channel);
+                assertTrue(channel.isOpen());
+                checkRead(channel);
+            }
+        }
+        setOriginRw(newOriginRw());
+        try (WritableByteChannel channel =
+                getOriginRw().getChannel(WritableByteChannel.class, 
StandardOpenOption.WRITE)) {
+            final SeekableByteChannel seekable =
+                    channel instanceof SeekableByteChannel ? 
(SeekableByteChannel) channel : null;
+            assertNotNull(channel);
+            assertTrue(channel.isOpen());
+            if (seekable != null) {
+                seekable.position(RO_LENGTH);
+                assertEquals(RO_LENGTH, seekable.position());
+                assertEquals(RO_LENGTH, seekable.size());
+                // Truncate
+                final int newSize = RO_LENGTH / 2;
+                seekable.truncate(newSize);
+                assertEquals(newSize, seekable.position());
+                assertEquals(newSize, seekable.size());
+                // Rewind
+                seekable.position(0);
+                assertEquals(0, seekable.position());
+            }
+        }
+    }
+
+    private void checkWrite(WritableByteChannel channel) throws IOException {
+        final ByteBuffer buffer = ByteBuffer.wrap(getFixtureByteArray());
+        final int written = channel.write(buffer);
+        assertEquals(RO_LENGTH, written);
+    }
+
     @Test
     void testSize() throws IOException {
-        assertEquals(Files.size(Paths.get(FILE_NAME_RO)), 
getOriginRo().getByteArray().length);
+        assertEquals(RO_LENGTH, getOriginRo().getByteArray().length);
     }
 }
diff --git 
a/src/test/java/org/apache/commons/io/build/AbstractRandomAccessFileOriginTest.java
 
b/src/test/java/org/apache/commons/io/build/AbstractRandomAccessFileOriginTest.java
index 369ce295d..28644f3c8 100644
--- 
a/src/test/java/org/apache/commons/io/build/AbstractRandomAccessFileOriginTest.java
+++ 
b/src/test/java/org/apache/commons/io/build/AbstractRandomAccessFileOriginTest.java
@@ -17,12 +17,17 @@
 
 package org.apache.commons.io.build;
 
+import java.io.IOException;
 import java.io.RandomAccessFile;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
 
 import org.apache.commons.io.IORandomAccessFile;
 import 
org.apache.commons.io.build.AbstractOrigin.AbstractRandomAccessFileOrigin;
 import org.apache.commons.io.build.AbstractOrigin.IORandomAccessFileOrigin;
 import org.apache.commons.io.build.AbstractOrigin.RandomAccessFileOrigin;
+import org.apache.commons.lang3.ArrayUtils;
 
 /**
  * Tests {@link RandomAccessFileOrigin} and {@link IORandomAccessFileOrigin}.
@@ -35,4 +40,10 @@
 public abstract class AbstractRandomAccessFileOriginTest<T extends 
RandomAccessFile, B extends AbstractRandomAccessFileOrigin<T, B>>
         extends AbstractOriginTest<T, B> {
 
+    @Override
+    protected void resetOriginRw() throws IOException {
+        // Reset the file
+        final Path rwPath = tempPath.resolve(FILE_NAME_RW);
+        Files.write(rwPath, ArrayUtils.EMPTY_BYTE_ARRAY, 
StandardOpenOption.CREATE);
+    }
 }
diff --git 
a/src/test/java/org/apache/commons/io/build/AbstractStreamBuilderTest.java 
b/src/test/java/org/apache/commons/io/build/AbstractStreamBuilderTest.java
index f94957f53..fc3bbafca 100644
--- a/src/test/java/org/apache/commons/io/build/AbstractStreamBuilderTest.java
+++ b/src/test/java/org/apache/commons/io/build/AbstractStreamBuilderTest.java
@@ -17,13 +17,30 @@
 
 package org.apache.commons.io.build;
 
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.io.FileInputStream;
+import java.io.RandomAccessFile;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.Arrays;
+import java.util.Objects;
+import java.util.stream.Stream;
 
+import org.apache.commons.io.function.IOConsumer;
+import org.apache.commons.lang3.ArrayUtils;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
 
 /**
  * Tests {@link AbstractStreamBuilder}.
@@ -65,4 +82,51 @@ void testBufferSizeChecker() {
         // resize
         assertResult(builder().setBufferSizeMax(2).setBufferSizeChecker(i -> 
100).setBufferSize(3).get(), 100);
     }
+
+    private static Stream<IOConsumer<Builder>> fileBasedConfigurers() throws 
URISyntaxException {
+        final URI uri = Objects.requireNonNull(
+                        
AbstractStreamBuilderTest.class.getResource(AbstractOriginTest.FILE_RES_RO))
+                .toURI();
+        final Path path = Paths.get(AbstractOriginTest.FILE_NAME_RO);
+        return Stream.of(
+                b -> b.setByteArray(ArrayUtils.EMPTY_BYTE_ARRAY),
+                b -> b.setFile(AbstractOriginTest.FILE_NAME_RO),
+                b -> b.setFile(path.toFile()),
+                b -> b.setPath(AbstractOriginTest.FILE_NAME_RO),
+                b -> b.setPath(path),
+                b -> b.setRandomAccessFile(new 
RandomAccessFile(AbstractOriginTest.FILE_NAME_RO, "r")),
+                // We can convert FileInputStream to ReadableByteChannel, but 
not the reverse.
+                // Therefore, we don't use Files.newInputStream.
+                b -> b.setInputStream(new 
FileInputStream(AbstractOriginTest.FILE_NAME_RO)),
+                b -> b.setChannel(Files.newByteChannel(path)),
+                b -> b.setURI(uri));
+    }
+
+    /**
+     * Tests various ways to obtain a {@link java.io.InputStream}.
+     *
+     * @param configurer Lambda to configure the builder.
+     */
+    @ParameterizedTest
+    @MethodSource("fileBasedConfigurers")
+    void testGetInputStream(IOConsumer<Builder> configurer) throws Exception {
+        final Builder builder = builder();
+            configurer.accept(builder);
+        assertNotNull(builder.getInputStream());
+    }
+
+    /**
+     * Tests various ways to obtain a {@link SeekableByteChannel}.
+     *
+     * @param configurer Lambda to configure the builder.
+     */
+    @ParameterizedTest
+    @MethodSource("fileBasedConfigurers")
+    void getGetSeekableByteChannel(IOConsumer<Builder> configurer) throws 
Exception {
+        final Builder builder = builder();
+        configurer.accept(builder);
+        try (ReadableByteChannel channel = assertDoesNotThrow(() -> 
builder.getChannel(SeekableByteChannel.class))) {
+            assertTrue(channel.isOpen());
+        }
+    }
 }
diff --git a/src/test/java/org/apache/commons/io/build/ByteArrayOriginTest.java 
b/src/test/java/org/apache/commons/io/build/ByteArrayOriginTest.java
index 41a693148..b4fd3bf0b 100644
--- a/src/test/java/org/apache/commons/io/build/ByteArrayOriginTest.java
+++ b/src/test/java/org/apache/commons/io/build/ByteArrayOriginTest.java
@@ -82,11 +82,17 @@ void testGetRandomAccessFile(final OpenOption openOption) {
         assertThrows(UnsupportedOperationException.class, () -> 
super.testGetRandomAccessFile(openOption));
     }
 
+    @Override
+    @Test
+    void testGetWritableByteChannel() throws IOException {
+        // Cannot convert a byte[] to a WritableByteChannel.
+        assertThrows(UnsupportedOperationException.class, 
super::testGetWritableByteChannel);
+    }
+
     @Override
     @Test
     void testGetWriter() {
         // Cannot convert a byte[] to a Writer.
         assertThrows(UnsupportedOperationException.class, 
super::testGetWriter);
     }
-
 }
diff --git a/src/test/java/org/apache/commons/io/build/ChannelOriginTest.java 
b/src/test/java/org/apache/commons/io/build/ChannelOriginTest.java
new file mode 100644
index 000000000..156ece294
--- /dev/null
+++ b/src/test/java/org/apache/commons/io/build/ChannelOriginTest.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *      https://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 org.apache.commons.io.build;
+
+import static java.nio.file.StandardOpenOption.READ;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+
+import java.io.IOException;
+import java.nio.channels.Channel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+
+import org.apache.commons.io.build.AbstractOrigin.ChannelOrigin;
+import org.apache.commons.lang3.ArrayUtils;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+class ChannelOriginTest extends AbstractOriginTest<Channel, ChannelOrigin> {
+    @Override
+    protected ChannelOrigin newOriginRo() throws IOException {
+        return new ChannelOrigin(Files.newByteChannel(Paths.get(FILE_NAME_RO), 
Collections.singleton(READ)));
+    }
+
+    @Override
+    protected ChannelOrigin newOriginRw() throws IOException {
+        return new ChannelOrigin(Files.newByteChannel(
+                tempPath.resolve(FILE_NAME_RW),
+                new HashSet<>(Arrays.asList(StandardOpenOption.READ, 
StandardOpenOption.WRITE))));
+    }
+
+    @Override
+    protected void resetOriginRw() throws IOException {
+        // Reset the file
+        final Path rwPath = tempPath.resolve(FILE_NAME_RW);
+        Files.write(rwPath, ArrayUtils.EMPTY_BYTE_ARRAY, 
StandardOpenOption.CREATE);
+    }
+
+    @Override
+    @Test
+    void testGetFile() {
+        // A FileByteChannel cannot be converted into a File.
+        assertThrows(UnsupportedOperationException.class, super::testGetFile);
+    }
+
+    @Override
+    @Test
+    void testGetPath() {
+        // A FileByteChannel cannot be converted into a Path.
+        assertThrows(UnsupportedOperationException.class, super::testGetPath);
+    }
+
+    @Override
+    @Test
+    void testGetRandomAccessFile() {
+        // A FileByteChannel cannot be converted into a RandomAccessFile.
+        assertThrows(UnsupportedOperationException.class, 
super::testGetRandomAccessFile);
+    }
+
+    @Override
+    @ParameterizedTest
+    @EnumSource(StandardOpenOption.class)
+    void testGetRandomAccessFile(OpenOption openOption) {
+        // A FileByteChannel cannot be converted into a RandomAccessFile.
+        assertThrows(UnsupportedOperationException.class, () -> 
super.testGetRandomAccessFile(openOption));
+    }
+
+    @Test
+    void testUnsupportedOperations_ReadableByteChannel() {
+        final ReadableByteChannel channel = mock(ReadableByteChannel.class);
+        final ChannelOrigin origin = new ChannelOrigin(channel);
+        assertThrows(UnsupportedOperationException.class, 
origin::getOutputStream);
+        assertThrows(UnsupportedOperationException.class, () -> 
origin.getWriter(null));
+        assertThrows(UnsupportedOperationException.class, () -> 
origin.getChannel(WritableByteChannel.class));
+    }
+
+    @Test
+    void testUnsupportedOperations_WritableByteChannel() {
+        final Channel channel = mock(WritableByteChannel.class);
+        final ChannelOrigin origin = new ChannelOrigin(channel);
+        assertThrows(UnsupportedOperationException.class, 
origin::getInputStream);
+        assertThrows(UnsupportedOperationException.class, () -> 
origin.getReader(null));
+        assertThrows(UnsupportedOperationException.class, () -> 
origin.getChannel(ReadableByteChannel.class));
+    }
+}
diff --git 
a/src/test/java/org/apache/commons/io/build/CharSequenceOriginTest.java 
b/src/test/java/org/apache/commons/io/build/CharSequenceOriginTest.java
index df29f453a..0fc5b9c5c 100644
--- a/src/test/java/org/apache/commons/io/build/CharSequenceOriginTest.java
+++ b/src/test/java/org/apache/commons/io/build/CharSequenceOriginTest.java
@@ -109,6 +109,13 @@ void testGetReaderIgnoreCharsetNull() throws IOException {
         }
     }
 
+    @Override
+    @Test
+    void testGetWritableByteChannel() throws IOException {
+        // Cannot convert a CharSequence to a WritableByteChannel.
+        assertThrows(UnsupportedOperationException.class, 
super::testGetWritableByteChannel);
+    }
+
     @Override
     @Test
     void testGetWriter() {
diff --git a/src/test/java/org/apache/commons/io/build/FileOriginTest.java 
b/src/test/java/org/apache/commons/io/build/FileOriginTest.java
index 4f0cb2514..548d78885 100644
--- a/src/test/java/org/apache/commons/io/build/FileOriginTest.java
+++ b/src/test/java/org/apache/commons/io/build/FileOriginTest.java
@@ -17,8 +17,13 @@
 package org.apache.commons.io.build;
 
 import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
 
 import org.apache.commons.io.build.AbstractOrigin.FileOrigin;
+import org.apache.commons.lang3.ArrayUtils;
 
 /**
  * Tests {@link FileOrigin}.
@@ -35,8 +40,14 @@ protected FileOrigin newOriginRo() {
     }
 
     @Override
-    protected FileOrigin newOriginRw() {
-        return new FileOrigin(new File(FILE_NAME_RW));
+    protected FileOrigin newOriginRw() throws IOException {
+        return new FileOrigin(tempPath.resolve(FILE_NAME_RW).toFile());
     }
 
+    @Override
+    protected void resetOriginRw() throws IOException {
+        // Reset the file
+        final Path rwPath = tempPath.resolve(FILE_NAME_RW);
+        Files.write(rwPath, ArrayUtils.EMPTY_BYTE_ARRAY, 
StandardOpenOption.CREATE);
+    }
 }
diff --git 
a/src/test/java/org/apache/commons/io/build/IORandomAccessFileOriginTest.java 
b/src/test/java/org/apache/commons/io/build/IORandomAccessFileOriginTest.java
index 8d83bba1c..b51118c65 100644
--- 
a/src/test/java/org/apache/commons/io/build/IORandomAccessFileOriginTest.java
+++ 
b/src/test/java/org/apache/commons/io/build/IORandomAccessFileOriginTest.java
@@ -17,6 +17,7 @@
 package org.apache.commons.io.build;
 
 import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.io.RandomAccessFile;
 
 import org.apache.commons.io.IORandomAccessFile;
@@ -38,8 +39,7 @@ protected IORandomAccessFileOrigin newOriginRo() throws 
FileNotFoundException {
 
     @SuppressWarnings("resource")
     @Override
-    protected IORandomAccessFileOrigin newOriginRw() throws 
FileNotFoundException {
-        return new 
IORandomAccessFileOrigin(RandomAccessFileMode.READ_WRITE.io(FILE_NAME_RW));
+    protected IORandomAccessFileOrigin newOriginRw() throws IOException {
+        return new 
IORandomAccessFileOrigin(RandomAccessFileMode.READ_WRITE.io(tempPath.resolve(FILE_NAME_RW).toFile().getPath()));
     }
-
 }
diff --git 
a/src/test/java/org/apache/commons/io/build/InputStreamOriginTest.java 
b/src/test/java/org/apache/commons/io/build/InputStreamOriginTest.java
index ac83acbec..7329fdb57 100644
--- a/src/test/java/org/apache/commons/io/build/InputStreamOriginTest.java
+++ b/src/test/java/org/apache/commons/io/build/InputStreamOriginTest.java
@@ -20,6 +20,7 @@
 
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.OpenOption;
 import java.nio.file.StandardOpenOption;
@@ -87,11 +88,17 @@ void testGetRandomAccessFile(final OpenOption openOption) {
         assertThrows(UnsupportedOperationException.class, () -> 
super.testGetRandomAccessFile(openOption));
     }
 
+    @Override
+    @Test
+    void testGetWritableByteChannel() throws IOException {
+        // Cannot convert a InputStream to a WritableByteChannel.
+        assertThrows(UnsupportedOperationException.class, 
super::testGetWritableByteChannel);
+    }
+
     @Override
     @Test
     void testGetWriter() {
         // Cannot convert a InputStream to a Writer.
         assertThrows(UnsupportedOperationException.class, 
super::testGetWriter);
     }
-
 }
diff --git 
a/src/test/java/org/apache/commons/io/build/OutputStreamOriginTest.java 
b/src/test/java/org/apache/commons/io/build/OutputStreamOriginTest.java
index 5e66e244d..b002a7e68 100644
--- a/src/test/java/org/apache/commons/io/build/OutputStreamOriginTest.java
+++ b/src/test/java/org/apache/commons/io/build/OutputStreamOriginTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
+import java.io.IOException;
 import java.io.OutputStream;
 import java.nio.file.OpenOption;
 import java.nio.file.StandardOpenOption;
@@ -125,6 +126,13 @@ void testGetReader() {
         assertThrows(UnsupportedOperationException.class, 
super::testGetReader);
     }
 
+    @Override
+    @Test
+    void testGetReadableByteChannel() throws IOException {
+        // Cannot convert a OutputStream to a ReadableByteChannel.
+        assertThrows(UnsupportedOperationException.class, 
super::testGetReadableByteChannel);
+    }
+
     @Override
     @Test
     void testSize() {
diff --git a/src/test/java/org/apache/commons/io/build/PathOriginTest.java 
b/src/test/java/org/apache/commons/io/build/PathOriginTest.java
index 81c4fc8d9..bc29df52b 100644
--- a/src/test/java/org/apache/commons/io/build/PathOriginTest.java
+++ b/src/test/java/org/apache/commons/io/build/PathOriginTest.java
@@ -16,10 +16,14 @@
  */
 package org.apache.commons.io.build;
 
+import java.io.IOException;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
 
 import org.apache.commons.io.build.AbstractOrigin.PathOrigin;
+import org.apache.commons.lang3.ArrayUtils;
 
 /**
  * Tests {@link PathOrigin}.
@@ -36,8 +40,14 @@ protected PathOrigin newOriginRo() {
     }
 
     @Override
-    protected PathOrigin newOriginRw() {
-        return new PathOrigin(Paths.get(FILE_NAME_RW));
+    protected PathOrigin newOriginRw() throws IOException {
+        return new PathOrigin(tempPath.resolve(FILE_NAME_RW));
     }
 
+    @Override
+    protected void resetOriginRw() throws IOException {
+        // Reset the file
+        final Path rwPath = tempPath.resolve(FILE_NAME_RW);
+        Files.write(rwPath, ArrayUtils.EMPTY_BYTE_ARRAY, 
StandardOpenOption.CREATE);
+    }
 }
diff --git 
a/src/test/java/org/apache/commons/io/build/RandomAccessFileOriginTest.java 
b/src/test/java/org/apache/commons/io/build/RandomAccessFileOriginTest.java
index 7cebf778d..d7306c12c 100644
--- a/src/test/java/org/apache/commons/io/build/RandomAccessFileOriginTest.java
+++ b/src/test/java/org/apache/commons/io/build/RandomAccessFileOriginTest.java
@@ -20,6 +20,7 @@
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.io.RandomAccessFile;
 
 import org.apache.commons.io.RandomAccessFileMode;
@@ -41,8 +42,8 @@ protected RandomAccessFileOrigin newOriginRo() throws 
FileNotFoundException {
 
     @SuppressWarnings("resource")
     @Override
-    protected RandomAccessFileOrigin newOriginRw() throws 
FileNotFoundException {
-        return new 
RandomAccessFileOrigin(RandomAccessFileMode.READ_WRITE.create(FILE_NAME_RW));
+    protected RandomAccessFileOrigin newOriginRw() throws IOException {
+        return new 
RandomAccessFileOrigin(RandomAccessFileMode.READ_WRITE.create(tempPath.resolve(FILE_NAME_RW)));
     }
 
     @Override
diff --git a/src/test/java/org/apache/commons/io/build/ReaderOriginTest.java 
b/src/test/java/org/apache/commons/io/build/ReaderOriginTest.java
index 6897d2418..3c1e3032f 100644
--- a/src/test/java/org/apache/commons/io/build/ReaderOriginTest.java
+++ b/src/test/java/org/apache/commons/io/build/ReaderOriginTest.java
@@ -20,6 +20,7 @@
 
 import java.io.FileNotFoundException;
 import java.io.FileReader;
+import java.io.IOException;
 import java.io.Reader;
 import java.nio.file.OpenOption;
 import java.nio.file.StandardOpenOption;
@@ -85,11 +86,17 @@ void testGetRandomAccessFile(final OpenOption openOption) {
         assertThrows(UnsupportedOperationException.class, () -> 
super.testGetRandomAccessFile(openOption));
     }
 
+    @Override
+    @Test
+    void testGetWritableByteChannel() throws IOException {
+        // Cannot convert a InputStream to a WritableByteChannel.
+        assertThrows(UnsupportedOperationException.class, 
super::testGetWritableByteChannel);
+    }
+
     @Override
     @Test
     void testGetWriter() {
         // Cannot convert a Reader to a Writer.
         assertThrows(UnsupportedOperationException.class, 
super::testGetWriter);
     }
-
 }
diff --git a/src/test/java/org/apache/commons/io/build/URIOriginTest.java 
b/src/test/java/org/apache/commons/io/build/URIOriginTest.java
index 303cfe111..6fd2043eb 100644
--- a/src/test/java/org/apache/commons/io/build/URIOriginTest.java
+++ b/src/test/java/org/apache/commons/io/build/URIOriginTest.java
@@ -18,11 +18,16 @@
 
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 
+import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
 
 import org.apache.commons.io.build.AbstractOrigin.URIOrigin;
+import org.apache.commons.lang3.ArrayUtils;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
@@ -43,7 +48,14 @@ protected URIOrigin newOriginRo() {
 
     @Override
     protected URIOrigin newOriginRw() {
-        return new URIOrigin(Paths.get(FILE_NAME_RW).toUri());
+        return new URIOrigin(tempPath.resolve(FILE_NAME_RW).toUri());
+    }
+
+    @Override
+    protected void resetOriginRw() throws IOException {
+        // Reset the file
+        final Path rwPath = tempPath.resolve(FILE_NAME_RW);
+        Files.write(rwPath, ArrayUtils.EMPTY_BYTE_ARRAY, 
StandardOpenOption.CREATE);
     }
 
     @ParameterizedTest
diff --git 
a/src/test/java/org/apache/commons/io/build/WriterStreamOriginTest.java 
b/src/test/java/org/apache/commons/io/build/WriterStreamOriginTest.java
index f0aa52512..7b9908a11 100644
--- a/src/test/java/org/apache/commons/io/build/WriterStreamOriginTest.java
+++ b/src/test/java/org/apache/commons/io/build/WriterStreamOriginTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
+import java.io.IOException;
 import java.io.StringWriter;
 import java.io.Writer;
 import java.nio.file.OpenOption;
@@ -125,6 +126,12 @@ void testGetReader() {
         assertThrows(UnsupportedOperationException.class, 
super::testGetReader);
     }
 
+    @Override
+    void testGetReadableByteChannel() throws IOException {
+        // Cannot convert a Writer to a ReadableByteChannel.
+        assertThrows(UnsupportedOperationException.class, 
super::testGetReadableByteChannel);
+    }
+
     @Override
     @Test
     void testSize() {
diff --git 
a/src/test/java/org/apache/commons/io/channels/ByteArraySeekableByteChannelCompressTest.java
 
b/src/test/java/org/apache/commons/io/channels/ByteArraySeekableByteChannelCompressTest.java
index 186ed1e8d..66c8e60b9 100644
--- 
a/src/test/java/org/apache/commons/io/channels/ByteArraySeekableByteChannelCompressTest.java
+++ 
b/src/test/java/org/apache/commons/io/channels/ByteArraySeekableByteChannelCompressTest.java
@@ -31,6 +31,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
@@ -40,7 +41,13 @@
  */
 class ByteArraySeekableByteChannelCompressTest {
 
-    private final byte[] testData = "Some 
data".getBytes(StandardCharsets.UTF_8);
+    private static final byte[] testData = "Some 
data".getBytes(StandardCharsets.UTF_8);
+
+    @AfterEach
+    void afterEach() {
+        // Reading tests don't modify the data
+        assertArrayEquals("Some data".getBytes(StandardCharsets.UTF_8), 
testData);
+    }
 
     /*
      * <q>If the stream is already closed then invoking this method has no 
effect.</q>
@@ -62,7 +69,7 @@ void testCloseIsIdempotent() throws Exception {
     @ParameterizedTest
     @ValueSource(ints = { 0, 1, 2, 3, 4, 5, 6 })
     void testReadingFromAPositionAfterEndReturnsEOF(final int size) throws 
Exception {
-        try (SeekableByteChannel c = new ByteArraySeekableByteChannel(size)) {
+        try (SeekableByteChannel c = ByteArraySeekableByteChannel.wrap(new 
byte[size])) {
             final int position = 2;
             c.position(position);
             assertEquals(position, c.position());
@@ -74,7 +81,7 @@ void testReadingFromAPositionAfterEndReturnsEOF(final int 
size) throws Exception
 
     @Test
     void testShouldReadContentsProperly() throws IOException {
-        try (ByteArraySeekableByteChannel c = new 
ByteArraySeekableByteChannel(testData)) {
+        try (ByteArraySeekableByteChannel c = 
ByteArraySeekableByteChannel.wrap(testData)) {
             final ByteBuffer readBuffer = ByteBuffer.allocate(testData.length);
             final int readCount = c.read(readBuffer);
             assertEquals(testData.length, readCount);
@@ -85,7 +92,7 @@ void testShouldReadContentsProperly() throws IOException {
 
     @Test
     void testShouldReadContentsWhenBiggerBufferSupplied() throws IOException {
-        try (ByteArraySeekableByteChannel c = new 
ByteArraySeekableByteChannel(testData)) {
+        try (ByteArraySeekableByteChannel c = 
ByteArraySeekableByteChannel.wrap(testData)) {
             final ByteBuffer readBuffer = ByteBuffer.allocate(testData.length 
+ 1);
             final int readCount = c.read(readBuffer);
             assertEquals(testData.length, readCount);
@@ -96,7 +103,7 @@ void testShouldReadContentsWhenBiggerBufferSupplied() throws 
IOException {
 
     @Test
     void testShouldReadDataFromSetPosition() throws IOException {
-        try (ByteArraySeekableByteChannel c = new 
ByteArraySeekableByteChannel(testData)) {
+        try (ByteArraySeekableByteChannel c = 
ByteArraySeekableByteChannel.wrap(testData)) {
             final ByteBuffer readBuffer = ByteBuffer.allocate(4);
             c.position(5L);
             final int readCount = c.read(readBuffer);
@@ -108,7 +115,7 @@ void testShouldReadDataFromSetPosition() throws IOException 
{
 
     @Test
     void testShouldSetProperPosition() throws IOException {
-        try (ByteArraySeekableByteChannel c = new 
ByteArraySeekableByteChannel(testData)) {
+        try (ByteArraySeekableByteChannel c = 
ByteArraySeekableByteChannel.wrap(testData)) {
             final long posAtFour = c.position(4L).position();
             final long posAtTheEnd = c.position(testData.length).position();
             final long posPastTheEnd = c.position(testData.length + 
1L).position();
@@ -120,7 +127,7 @@ void testShouldSetProperPosition() throws IOException {
 
     @Test
     void testShouldSetProperPositionOnTruncate() throws IOException {
-        try (ByteArraySeekableByteChannel c = new 
ByteArraySeekableByteChannel(testData)) {
+        try (ByteArraySeekableByteChannel c = 
ByteArraySeekableByteChannel.wrap(testData)) {
             c.position(testData.length);
             c.truncate(4L);
             assertEquals(4L, c.position());
@@ -130,7 +137,7 @@ void testShouldSetProperPositionOnTruncate() throws 
IOException {
 
     @Test
     void testShouldSignalEOFWhenPositionAtTheEnd() throws IOException {
-        try (ByteArraySeekableByteChannel c = new 
ByteArraySeekableByteChannel(testData)) {
+        try (ByteArraySeekableByteChannel c = 
ByteArraySeekableByteChannel.wrap(testData)) {
             final ByteBuffer readBuffer = ByteBuffer.allocate(testData.length);
             c.position(testData.length + 1);
             final int readCount = c.read(readBuffer);
@@ -170,7 +177,7 @@ void 
testShouldThrowExceptionWhenTruncatingToIncorrectSize() {
 
     @Test
     void testShouldTruncateContentsProperly() throws ClosedChannelException {
-        try (ByteArraySeekableByteChannel c = new 
ByteArraySeekableByteChannel(testData)) {
+        try (ByteArraySeekableByteChannel c = 
ByteArraySeekableByteChannel.wrap(testData)) {
             c.truncate(4);
             final byte[] bytes = Arrays.copyOf(c.array(), (int) c.size());
             assertEquals("Some", new String(bytes, StandardCharsets.UTF_8));
@@ -193,7 +200,7 @@ void testShouldWriteDataProperly() throws IOException {
 
     @Test
     void testShouldWriteDataProperlyAfterPositionSet() throws IOException {
-        try (ByteArraySeekableByteChannel c = new 
ByteArraySeekableByteChannel(testData)) {
+        try (ByteArraySeekableByteChannel c = 
ByteArraySeekableByteChannel.wrap(testData.clone())) {
             final ByteBuffer inData = ByteBuffer.wrap(testData);
             final ByteBuffer expectedData = 
ByteBuffer.allocate(testData.length + 5).put(testData, 0, 5).put(testData);
             c.position(5L);
@@ -242,7 +249,7 @@ void 
testThrowsIOExceptionWhenPositionIsSetToANegativeValue() throws Exception {
      */
     @Test
     void testTruncateDoesntChangeSmallPosition() throws Exception {
-        try (SeekableByteChannel c = new 
ByteArraySeekableByteChannel(testData)) {
+        try (SeekableByteChannel c = 
ByteArraySeekableByteChannel.wrap(testData)) {
             c.position(1);
             c.truncate(testData.length - 1);
             assertEquals(testData.length - 1, c.size());
@@ -255,7 +262,7 @@ void testTruncateDoesntChangeSmallPosition() throws 
Exception {
      */
     @Test
     void 
testTruncateMovesPositionWhenNewSizeIsBiggerThanSizeAndPositionIsEvenBigger() 
throws Exception {
-        try (SeekableByteChannel c = new 
ByteArraySeekableByteChannel(testData)) {
+        try (SeekableByteChannel c = 
ByteArraySeekableByteChannel.wrap(testData)) {
             c.position(2 * testData.length);
             c.truncate(testData.length + 1);
             assertEquals(testData.length, c.size());
@@ -269,7 +276,7 @@ void 
testTruncateMovesPositionWhenNewSizeIsBiggerThanSizeAndPositionIsEvenBigger
      */
     @Test
     void testTruncateMovesPositionWhenNotResizingButPositionBiggerThanSize() 
throws Exception {
-        try (SeekableByteChannel c = new 
ByteArraySeekableByteChannel(testData)) {
+        try (SeekableByteChannel c = 
ByteArraySeekableByteChannel.wrap(testData)) {
             c.position(2 * testData.length);
             c.truncate(testData.length);
             assertEquals(testData.length, c.size());
@@ -282,7 +289,7 @@ void 
testTruncateMovesPositionWhenNotResizingButPositionBiggerThanSize() throws
      */
     @Test
     void testTruncateMovesPositionWhenShrinkingBeyondPosition() throws 
Exception {
-        try (SeekableByteChannel c = new 
ByteArraySeekableByteChannel(testData)) {
+        try (SeekableByteChannel c = 
ByteArraySeekableByteChannel.wrap(testData)) {
             c.position(4);
             c.truncate(3);
             assertEquals(3, c.size());
@@ -295,7 +302,7 @@ void testTruncateMovesPositionWhenShrinkingBeyondPosition() 
throws Exception {
      */
     @Test
     void testTruncateToBiggerSizeDoesntChangeAnything() throws Exception {
-        try (SeekableByteChannel c = new 
ByteArraySeekableByteChannel(testData)) {
+        try (SeekableByteChannel c = 
ByteArraySeekableByteChannel.wrap(testData)) {
             assertEquals(testData.length, c.size());
             c.truncate(testData.length + 1);
             assertEquals(testData.length, c.size());
@@ -310,7 +317,7 @@ void testTruncateToBiggerSizeDoesntChangeAnything() throws 
Exception {
      */
     @Test
     void testTruncateToCurrentSizeDoesntChangeAnything() throws Exception {
-        try (SeekableByteChannel c = new 
ByteArraySeekableByteChannel(testData)) {
+        try (SeekableByteChannel c = 
ByteArraySeekableByteChannel.wrap(testData)) {
             assertEquals(testData.length, c.size());
             c.truncate(testData.length);
             assertEquals(testData.length, c.size());
diff --git 
a/src/test/java/org/apache/commons/io/channels/ByteArraySeekableByteChannelTest.java
 
b/src/test/java/org/apache/commons/io/channels/ByteArraySeekableByteChannelTest.java
index 96cd377d2..e5e1442be 100644
--- 
a/src/test/java/org/apache/commons/io/channels/ByteArraySeekableByteChannelTest.java
+++ 
b/src/test/java/org/apache/commons/io/channels/ByteArraySeekableByteChannelTest.java
@@ -17,8 +17,24 @@
 
 package org.apache.commons.io.channels;
 
+import static org.apache.commons.lang3.ArrayUtils.EMPTY_BYTE_ARRAY;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
 import java.io.IOException;
+import java.nio.ByteBuffer;
 import java.nio.channels.SeekableByteChannel;
+import java.nio.charset.StandardCharsets;
+import java.util.stream.Stream;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.function.IOSupplier;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 
 /**
  * A sanity test to make sure {@link AbstractSeekableByteChannelTest} works 
for files.
@@ -30,4 +46,71 @@ protected SeekableByteChannel createChannel() throws 
IOException {
         return new ByteArraySeekableByteChannel();
     }
 
+    private static final byte[] testData = "Some 
data".getBytes(StandardCharsets.UTF_8);
+
+    static Stream<Arguments> testConstructor() {
+        return Stream.of(
+                Arguments.of(
+                        (IOSupplier<ByteArraySeekableByteChannel>) 
ByteArraySeekableByteChannel::new,
+                        EMPTY_BYTE_ARRAY,
+                        IOUtils.DEFAULT_BUFFER_SIZE),
+                Arguments.of(
+                        (IOSupplier<ByteArraySeekableByteChannel>) () -> new 
ByteArraySeekableByteChannel(8),
+                        EMPTY_BYTE_ARRAY,
+                        8),
+                Arguments.of(
+                        (IOSupplier<ByteArraySeekableByteChannel>) () -> new 
ByteArraySeekableByteChannel(16),
+                        EMPTY_BYTE_ARRAY,
+                        16),
+                Arguments.of(
+                        (IOSupplier<ByteArraySeekableByteChannel>)
+                                () -> 
ByteArraySeekableByteChannel.wrap(EMPTY_BYTE_ARRAY),
+                        EMPTY_BYTE_ARRAY,
+                        0),
+                Arguments.of(
+                        (IOSupplier<ByteArraySeekableByteChannel>)
+                                () -> 
ByteArraySeekableByteChannel.wrap(testData),
+                        testData,
+                        testData.length));
+    }
+
+    @ParameterizedTest
+    @MethodSource
+    void testConstructor(IOSupplier<ByteArraySeekableByteChannel> supplier, 
byte[] expected, int capacity) throws IOException {
+        try (ByteArraySeekableByteChannel channel = supplier.get()) {
+            assertEquals(0, channel.position());
+            assertEquals(expected.length, channel.size());
+            assertEquals(capacity, channel.array().length);
+            assertArrayEquals(expected, channel.toByteArray());
+        }
+    }
+
+    @Test
+    void testConstructorInvalid() {
+        assertThrows(IllegalArgumentException.class, () -> new 
ByteArraySeekableByteChannel(-1));
+        assertThrows(NullPointerException.class, () -> 
ByteArraySeekableByteChannel.wrap(null));
+    }
+
+    static Stream<Arguments> testShouldResizeWhenWritingMoreDataThanCapacity() 
{
+        return Stream.of(
+                // Resize from 0
+                Arguments.of(EMPTY_BYTE_ARRAY, 1),
+                // Resize less than double
+                Arguments.of(new byte[8], 1),
+                // Resize more that double
+                Arguments.of(new byte[8], 20));
+    }
+
+    @ParameterizedTest
+    @MethodSource
+    void testShouldResizeWhenWritingMoreDataThanCapacity(byte[] data, int 
wanted) throws IOException {
+        try (ByteArraySeekableByteChannel c = 
ByteArraySeekableByteChannel.wrap(data)) {
+            c.position(data.length);
+            final ByteBuffer inData = ByteBuffer.wrap(new byte[wanted]);
+            final int writeCount = c.write(inData);
+            assertEquals(wanted, writeCount);
+            assertTrue(c.array().length >= data.length + wanted, "Capacity not 
increased sufficiently");
+        }
+    }
+
 }

Reply via email to