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 <= 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");
+ }
+ }
+
}