You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2023/10/25 03:04:10 UTC
[sis] 02/03: Refactor `ChannelImageOutputStream` as an implementation of `ImageOutputStream`. It allows us to test `ChannelData.yield(ChannelData)`.
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit e91f576e12fd2aa70c26b29966d4c5d7e2c99d0d
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Wed Oct 25 04:54:12 2023 +0200
Refactor `ChannelImageOutputStream` as an implementation of `ImageOutputStream`.
It allows us to test `ChannelData.yield(ChannelData)`.
---
.../sis/io/stream/ChannelImageInputStream.java | 21 +-
.../sis/io/stream/ChannelImageOutputStream.java | 186 +++++++++--
.../main/org/apache/sis/io/stream/IOUtilities.java | 75 ++---
.../apache/sis/io/stream/OutputStreamAdapter.java | 11 +-
.../sis/io/stream/ChannelDataOutputTest.java | 340 ++++++++++-----------
.../apache/sis/io/stream/ChannelDataTestCase.java | 53 +++-
.../sis/io/stream/ChannelImageInputStreamTest.java | 97 ++++--
.../io/stream/ChannelImageOutputStreamTest.java | 254 ++++++++-------
8 files changed, 649 insertions(+), 388 deletions(-)
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelImageInputStream.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelImageInputStream.java
index 04eeaead32..9426cb2caa 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelImageInputStream.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelImageInputStream.java
@@ -167,7 +167,8 @@ public class ChannelImageInputStream extends ChannelDataInput implements ImageIn
/**
* Reads up to {@code length} bytes from the stream, and modifies the supplied
* {@code IIOByteBuffer} to indicate the byte array, offset, and length where
- * the data may be found.
+ * the data may be found. This method may reference the internal buffer, thus
+ * avoiding a copy.
*
* @param dest the buffer to be written to.
* @param length the maximum number of bytes to read.
@@ -175,10 +176,22 @@ public class ChannelImageInputStream extends ChannelDataInput implements ImageIn
*/
@Override
public final void readBytes(final IIOByteBuffer dest, int length) throws IOException {
- final byte[] data = new byte[length];
- length = read(data);
+ clearBitOffset();
+ final byte[] data;
+ final int offset;
+ if (buffer.hasArray()) {
+ ensureBufferContains(1);
+ data = buffer.array();
+ offset = buffer.position();
+ length = Math.min(buffer.remaining(), length);
+ buffer.position(offset + length);
+ } else {
+ data = new byte[length];
+ length = read(data);
+ offset = 0;
+ }
dest.setData(data);
- dest.setOffset(0);
+ dest.setOffset(offset);
dest.setLength(length);
}
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelImageOutputStream.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelImageOutputStream.java
index a46152ca96..037b81ee80 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelImageOutputStream.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelImageOutputStream.java
@@ -16,63 +16,191 @@
*/
package org.apache.sis.io.stream;
-import java.io.Closeable;
-import java.io.DataOutput;
import java.io.IOException;
+import java.io.OutputStream;
import java.nio.ByteBuffer;
-import java.nio.channels.WritableByteChannel;
+import java.nio.ByteOrder;
+import java.nio.channels.ByteChannel;
+import javax.imageio.stream.IIOByteBuffer;
import javax.imageio.stream.ImageOutputStream;
/**
- * Adds the missing methods in {@code ChannelDataOutput} for implementing the {@code DataOutput} interface.
- * Current implementation does not yet implements the {@code ImageOutputStream} sub-interface, but a future
- * implementation may do so.
+ * An {@code ImageOutputStream} backed by {@code ChannelDataInput} and {@code ChannelDataOutput}.
+ * Contrarily to most other I/O frameworks in the standard JDK, {@code ImageOutputStream} is read/write.
*
- * <p>We do not implement {@link ImageOutputStream} yet because the latter inherits all read operations from
- * {@code ImageInputStream}, while the {@code org.apache.sis.storage.base} package keeps the concerns
- * separated. Despite that, the name of this {@code ChannelImageOutputStream} anticipates a future version
- * which would implement the image I/O interface.</p>
- *
- * <p>This class is a place-holder for future development.</p>
+ * @todo Not yet used in {@link org.apache.sis.storage.StorageConnector}.
*
* @author Rémi Maréchal (Geomatys)
* @author Martin Desruisseaux (Geomatys)
*/
-public class ChannelImageOutputStream extends ChannelDataOutput implements DataOutput, Closeable {
+@SuppressWarnings("deprecation")
+public class ChannelImageOutputStream extends OutputStream implements ImageOutputStream, Markable {
+ /**
+ * The object to use for reading from the channel.
+ */
+ private final ChannelImageInputStream input;
+
+ /**
+ * The object to use for writing to the channel.
+ */
+ private final ChannelDataOutput output;
+
/**
- * Creates a new output stream for the given channel and using the given buffer.
+ * {@code false} if the stream is reading, or {@code true} if it is writing.
+ *
+ * @see #current()
+ */
+ private boolean writing;
+
+ /**
+ * Creates a new input/output stream for the given channel and using the given buffer.
*
* @param filename a file identifier used only for formatting error message.
- * @param channel the channel where to write data.
- * @param buffer the buffer from where to read the data.
- * @throws IOException if an error occurred while writing into channel.
+ * @param channel the channel where to read and write data.
+ * @param buffer the buffer for data to read and write.
+ * @throws IOException if the stream cannot be created.
*/
- public ChannelImageOutputStream(final String filename, final WritableByteChannel channel, final ByteBuffer buffer)
+ public ChannelImageOutputStream(final String filename, final ByteChannel channel, final ByteBuffer buffer)
throws IOException
{
- super(filename, channel, buffer);
+ input = new ChannelImageInputStream(filename, channel, buffer, true);
+ output = new ChannelDataOutput(filename, channel, buffer);
+ }
+
+ /** Declares that this stream does not cache data itself. */
+ @Override public boolean isCached() {return false;}
+ @Override public boolean isCachedMemory() {return false;}
+ @Override public boolean isCachedFile() {return false;}
+
+ /**
+ * Returns the {@linkplain #input} or {@linkplain #output},
+ * depending on whether this stream is reading or writing.
+ */
+ private ChannelData current() {
+ return writing ? output : input;
+ }
+
+ /**
+ * {@return the object to use for reading from the stream}.
+ * The returned object should not be used anymore after {@link #output()}
+ * has been invoked, until {@code input()} is invoked again.
+ *
+ * @throws IOException if an error occurred while flushing a buffer.
+ */
+ public final ChannelImageInputStream input() throws IOException {
+ if (writing) {
+ output.yield(input);
+ writing = false;
+ }
+ return input;
}
/**
- * Creates a new output source from the given {@code ChannelDataOutput}.
- * This constructor is invoked when we need to change the implementation class
- * from {@code ChannelDataOutput} to {@code ChannelImageOutputStream}.
+ * {@return the object to use for writing to the stream}.
+ * The returned object should not be used anymore after {@link #input()}
+ * has been invoked, until {@code output()} is invoked again.
*
- * @param output the existing instance from which to takes the channel and buffer.
- * @throws IOException if an error occurred while writing into channel.
+ * @throws IOException if an error occurred while flushing a buffer.
*/
- public ChannelImageOutputStream(final ChannelDataOutput output) throws IOException {
- super(output.filename, output.channel, output.buffer);
+ public final ChannelDataOutput output() throws IOException {
+ if (!writing) {
+ input.yield(output);
+ writing = true;
+ }
+ return output;
+ }
+
+ /** Delegates to the reader or writer. */
+ @Override public long length() throws IOException {return current().length();}
+ @Override public void mark() { current().mark();}
+ @Override public void reset() throws IOException { current().reset();}
+ @Override public void reset(long p) throws IOException { current().reset(p);}
+ @Override public void seek(long p) throws IOException { current().seek(p);}
+ @Override public void flushBefore(long p) throws IOException { current().flushBefore(p);}
+ @Override public long getFlushedPosition() {return current().getFlushedPosition();}
+ @Override public long getStreamPosition() {return current().getStreamPosition();}
+ @Override public int getBitOffset() {return current().getBitOffset();}
+ @Override public void setBitOffset(int n) { current().setBitOffset(n);}
+ @Override public ByteOrder getByteOrder() {return input.getByteOrder();}
+ @Override public void setByteOrder(ByteOrder v) { input.setByteOrder(v);}
+ @Override public void readBytes(IIOByteBuffer v, int n) throws IOException { input().readBytes(v, n);}
+ @Override public int read() throws IOException {return input().read();}
+ @Override public int readBit() throws IOException {return input().readBit();}
+ @Override public long readBits(int n) throws IOException {return input().readBits(n);}
+ @Override public boolean readBoolean() throws IOException {return input().readBoolean();}
+ @Override public byte readByte() throws IOException {return input().readByte();}
+ @Override public int readUnsignedByte() throws IOException {return input().readUnsignedByte();}
+ @Override public short readShort() throws IOException {return input().readShort();}
+ @Override public int readUnsignedShort() throws IOException {return input().readUnsignedShort();}
+ @Override public char readChar() throws IOException {return input().readChar();}
+ @Override public int readInt() throws IOException {return input().readInt();}
+ @Override public long readUnsignedInt() throws IOException {return input().readUnsignedInt();}
+ @Override public long readLong() throws IOException {return input().readLong();}
+ @Override public float readFloat() throws IOException {return input().readFloat();}
+ @Override public double readDouble() throws IOException {return input().readDouble();}
+ @Override public String readLine() throws IOException {return input().readLine();}
+ @Override public String readUTF() throws IOException {return input().readUTF();}
+ @Override public int read (byte[] v) throws IOException {return input().read(v);}
+ @Override public int read (byte[] v, int s, int n) throws IOException {return input().read(v, s, n);}
+ @Override public void readFully (byte[] v) throws IOException { input().readFully(v);}
+ @Override public void readFully (byte[] v, int s, int n) throws IOException { input().readFully(v, s, n);}
+ @Override public void readFully (short[] v, int s, int n) throws IOException { input().readFully(v, s, n);}
+ @Override public void readFully (char[] v, int s, int n) throws IOException { input().readFully(v, s, n);}
+ @Override public void readFully (int[] v, int s, int n) throws IOException { input().readFully(v, s, n);}
+ @Override public void readFully (long[] v, int s, int n) throws IOException { input().readFully(v, s, n);}
+ @Override public void readFully (float[] v, int s, int n) throws IOException { input().readFully(v, s, n);}
+ @Override public void readFully (double[] v, int s, int n) throws IOException { input().readFully(v, s, n);}
+ @Override public int skipBytes (int n) throws IOException {return input().skipBytes(n);}
+ @Override public long skipBytes (long n) throws IOException {return input().skipBytes(n);}
+ @Override public void write (int v) throws IOException { output().write(v);}
+ @Override public void writeBit (int v) throws IOException { output().writeBit (v);}
+ @Override public void writeBits (long v, int n) throws IOException { output().writeBits (v, n);}
+ @Override public void writeBoolean(boolean v) throws IOException { output().writeBoolean(v);}
+ @Override public void writeByte (int v) throws IOException { output().writeByte (v);}
+ @Override public void writeShort (int v) throws IOException { output().writeShort (v);}
+ @Override public void writeChar (int v) throws IOException { output().writeChar (v);}
+ @Override public void writeInt (int v) throws IOException { output().writeInt (v);}
+ @Override public void writeLong (long v) throws IOException { output().writeLong (v);}
+ @Override public void writeFloat (float v) throws IOException { output().writeFloat (v);}
+ @Override public void writeDouble (double v) throws IOException { output().writeDouble (v);}
+ @Override public void writeBytes (String v) throws IOException { output().writeBytes (v);}
+ @Override public void writeChars (String v) throws IOException { output().writeChars (v);}
+ @Override public void writeUTF (String v) throws IOException { output().writeUTF (v);}
+ @Override public void write (byte[] v) throws IOException { output().write(v);}
+ @Override public void write (byte[] v, int s, int n) throws IOException { output().write(v, s, n);}
+ @Override public void writeShorts (short[] v, int s, int n) throws IOException { output().writeShorts (v, s, n);}
+ @Override public void writeChars (char[] v, int s, int n) throws IOException { output().writeChars (v, s, n);}
+ @Override public void writeInts (int[] v, int s, int n) throws IOException { output().writeInts (v, s, n);}
+ @Override public void writeLongs (long[] v, int s, int n) throws IOException { output().writeLongs (v, s, n);}
+ @Override public void writeFloats (float[] v, int s, int n) throws IOException { output().writeFloats (v, s, n);}
+ @Override public void writeDoubles(double[] v, int s, int n) throws IOException { output().writeDoubles(v, s, n);}
+
+ /**
+ * Discards the initial position of the stream prior to the current stream position.
+ *
+ * @throws IOException if an I/O error occurred.
+ */
+ @Override
+ public void flush() throws IOException {
+ if (writing) {
+ // Reproduce the behavior of Imava I/O implementation.
+ output.clearBitOffset();
+ }
+ current().flushBefore(getStreamPosition());
}
/**
- * Closes the {@linkplain #channel}.
+ * Closes the channel.
*
* @throws IOException if an error occurred while closing the channel.
*/
@Override
- public final void close() throws IOException {
- channel.close();
+ public void close() throws IOException {
+ try (output.channel) {
+ if (writing) {
+ output.flush();
+ }
+ }
}
}
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/IOUtilities.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/IOUtilities.java
index 2cff111ba8..e2fbe4996a 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/IOUtilities.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/IOUtilities.java
@@ -31,6 +31,7 @@ import java.net.URL;
import java.net.URLDecoder;
import java.net.URISyntaxException;
import java.net.MalformedURLException;
+import java.nio.channels.Channel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.channels.SeekableByteChannel;
@@ -552,25 +553,29 @@ public final class IOUtilities extends Static {
* @return the input stream, or {@code null} if the given stream cannot be converted.
* @throws IOException if an error occurred during input stream creation.
*/
- public static InputStream toInputStream(AutoCloseable stream) throws IOException {
- if (stream != null) {
- if (stream instanceof InputStream) {
- return (InputStream) stream;
- }
- if (stream instanceof OutputStreamAdapter) {
- stream = ((OutputStreamAdapter) stream).output;
- }
- if (stream instanceof ChannelDataOutput) {
- final ChannelDataOutput c = (ChannelDataOutput) stream;
- if (c.channel instanceof ReadableByteChannel) {
- stream = new ChannelImageInputStream(c.filename, (ReadableByteChannel) c.channel, c.buffer, true);
- }
+ public static InputStream toInputStream(final AutoCloseable stream) throws IOException {
+ if (stream instanceof InputStream) {
+ return (InputStream) stream;
+ }
+ final ImageInputStream input;
+ if (stream instanceof ImageInputStream) {
+ input = (ImageInputStream) stream;
+ } else {
+ final ChannelData output;
+ if (stream instanceof ChannelData) {
+ output = (ChannelData) stream;
+ } else if (stream instanceof OutputStreamAdapter) {
+ output = ((OutputStreamAdapter) stream).output;
+ } else {
+ return null;
}
- if (stream instanceof ImageInputStream) {
- return new InputStreamAdapter((ImageInputStream) stream);
+ final Channel channel = output.channel();
+ if (!(channel instanceof ReadableByteChannel)) {
+ return null;
}
+ input = new ChannelImageInputStream(output.filename, (ReadableByteChannel) channel, output.buffer, true);
}
- return null;
+ return new InputStreamAdapter(input);
}
/**
@@ -587,22 +592,23 @@ public final class IOUtilities extends Static {
* @throws IOException if an error occurred during output stream creation.
*/
public static OutputStream toOutputStream(AutoCloseable stream) throws IOException {
- if (stream != null) {
- if (stream instanceof OutputStream) {
- return (OutputStream) stream;
- }
- if (stream instanceof InputStreamAdapter) {
- stream = ((InputStreamAdapter) stream).input;
- }
- if (stream instanceof ChannelDataInput) {
- final ChannelDataInput c = (ChannelDataInput) stream;
- if (c.channel instanceof WritableByteChannel) {
- stream = new ChannelImageOutputStream(c.filename, (WritableByteChannel) c.channel, c.buffer);
- }
- }
- if (stream instanceof ChannelImageOutputStream) {
- return new OutputStreamAdapter((ChannelImageOutputStream) stream);
+ if (stream instanceof OutputStream) {
+ return (OutputStream) stream;
+ }
+ if (stream instanceof InputStreamAdapter) {
+ stream = ((InputStreamAdapter) stream).input;
+ }
+check: if (stream instanceof ChannelData) {
+ final ChannelData data = (ChannelData) stream;
+ final ChannelDataOutput output;
+ if (stream instanceof ChannelDataOutput) {
+ output = (ChannelDataOutput) stream;
+ } else {
+ final Channel channel = data.channel();
+ if (!(channel instanceof WritableByteChannel)) break check;
+ output = new ChannelDataOutput(data.filename, (WritableByteChannel) channel, data.buffer);
}
+ return new OutputStreamAdapter(output);
}
return null;
}
@@ -618,10 +624,9 @@ public final class IOUtilities extends Static {
*/
public static boolean truncate(AutoCloseable stream) throws IOException {
if (stream instanceof OutputStreamAdapter) {
- stream = ((OutputStreamAdapter) stream).output;
- }
- if (stream instanceof ChannelDataOutput) {
- stream = ((ChannelDataOutput) stream).channel;
+ stream = (((OutputStreamAdapter) stream).output).channel();
+ } else if (stream instanceof ChannelData) {
+ stream = ((ChannelData) stream).channel();
}
if (stream instanceof SeekableByteChannel) {
final SeekableByteChannel s = (SeekableByteChannel) stream;
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/OutputStreamAdapter.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/OutputStreamAdapter.java
index 74c08cba00..efa982cc4a 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/OutputStreamAdapter.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/OutputStreamAdapter.java
@@ -32,18 +32,15 @@ final class OutputStreamAdapter extends OutputStream implements Markable {
* The underlying data output stream. In principle, public access to this field breaks encapsulation.
* But since {@code OutputStreamAdapter} does not hold any state and just forwards every method calls
* to that {@code ChannelDataOutput}, using on object or the other does not make a difference.
- *
- * @todo to be replaced by a reference to {@link javax.imageio.stream.ImageOutputStream} if the
- * {@link ChannelImageOutputStream} class implements that interface in a future version.
*/
- final ChannelImageOutputStream output;
+ final ChannelDataOutput output;
/**
* Constructs a new output stream.
*
* @param output the stream to wrap.
*/
- OutputStreamAdapter(final ChannelImageOutputStream output) {
+ OutputStreamAdapter(final ChannelDataOutput output) {
this.output = output;
}
@@ -139,6 +136,8 @@ final class OutputStreamAdapter extends OutputStream implements Markable {
*/
@Override
public void close() throws IOException {
- output.close();
+ try (output.channel) {
+ output.flush();
+ }
}
}
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/io/stream/ChannelDataOutputTest.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/io/stream/ChannelDataOutputTest.java
index a7fe682c7c..763c4ecfdc 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/io/stream/ChannelDataOutputTest.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/io/stream/ChannelDataOutputTest.java
@@ -18,7 +18,6 @@ package org.apache.sis.io.stream;
import java.util.Arrays;
import java.io.ByteArrayOutputStream;
-import java.io.DataOutput;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
@@ -28,7 +27,7 @@ import javax.imageio.stream.ImageOutputStream;
import org.junit.Test;
import org.apache.sis.test.DependsOnMethod;
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
/**
@@ -38,25 +37,24 @@ import static org.junit.Assert.*;
* @author Rémi Maréchal (Geomatys)
* @author Martin Desruisseaux (Geomatys)
*/
-public class ChannelDataOutputTest extends ChannelDataTestCase {
+public final class ChannelDataOutputTest extends ChannelDataTestCase {
/**
- * The {@link DataOutput} implementation to test. This implementation will write data to
- * {@link #testedStreamBackingArray}. The content of that array will be compared to
- * {@link #expectedData} for verifying result correctness.
+ * The implementation to test. This implementation will write data to {@link #testedStreamBackingArray}.
+ * The content of that array will be compared to {@link #expectedData} for verifying result correctness.
*/
- ChannelDataOutput testedStream;
+ private ChannelDataOutput testedStream;
/**
* A stream to use as a reference implementation. Any data written in {@link #testedStream}
* will also be written in {@code referenceStream}, for later comparison.
*/
- ImageOutputStream referenceStream;
+ private ImageOutputStream referenceStream;
/**
* Byte array which is filled by the {@linkplain #testedStream} implementation during write operations.
* The content of this array will be compared to {@linkplain #expectedData}.
*/
- byte[] testedStreamBackingArray;
+ private byte[] testedStreamBackingArray;
/**
* Object which is filled by {@link #referenceStream} implementation during write operations.
@@ -79,12 +77,12 @@ public class ChannelDataOutputTest extends ChannelDataTestCase {
* @param bufferLength length of the {@code ByteBuffer} to use for the tests.
* @throws IOException should never happen since we read and write in memory only.
*/
- void initialize(final String testName, final int streamLength, final int bufferLength) throws IOException {
+ private void initialize(final String testName, final int streamLength, final int bufferLength) throws IOException {
expectedData = new ByteArrayOutputStream(streamLength);
referenceStream = new MemoryCacheImageOutputStream(expectedData);
testedStreamBackingArray = new byte[streamLength];
- testedStream = new ChannelDataOutput(testName,
- new ByteArrayChannel(testedStreamBackingArray, false), ByteBuffer.allocate(bufferLength));
+ var channel = new ByteArrayChannel(testedStreamBackingArray, false);
+ testedStream = new ChannelDataOutput(testName, channel, ByteBuffer.allocate(bufferLength));
}
/**
@@ -119,7 +117,7 @@ public class ChannelDataOutputTest extends ChannelDataTestCase {
* Asserts that the content of {@link #testedStream} is equal to the content of {@link #referenceStream}.
* This method closes the reference stream before to perform the comparison.
*/
- final void assertStreamContentEquals() throws IOException {
+ private void assertStreamContentEquals() throws IOException {
testedStream.flush();
referenceStream.close();
final byte[] expectedArray = expectedData.toByteArray();
@@ -143,7 +141,7 @@ public class ChannelDataOutputTest extends ChannelDataTestCase {
for (int i=0; i<100; i++) {
final int position = random.nextInt(seekRange);
testedStream.seek(position);
- assertEquals("getStreamPosition()", position, testedStream.getStreamPosition());
+ assertEquals(position, testedStream.getStreamPosition(), "getStreamPosition()");
final long v = random.nextLong();
testedStream.writeLong(v);
arrayView.putLong(position, v);
@@ -178,7 +176,7 @@ public class ChannelDataOutputTest extends ChannelDataTestCase {
referenceStream.writeLong(v);
testedStream.writeLong(v);
}
- assertEquals("getStreamPosition()", 24, testedStream.getStreamPosition());
+ assertEquals(24, testedStream.getStreamPosition(), "getStreamPosition()");
testedStream.seek(40); // Move 2 long ahead. Space shall be filled by 0.
referenceStream.writeLong(0);
referenceStream.writeLong(0);
@@ -197,36 +195,26 @@ public class ChannelDataOutputTest extends ChannelDataTestCase {
@Test
public void testArgumentChecks() throws IOException {
initialize("testArgumentChecks", 20, 10);
- try {
- testedStream.setBitOffset(9);
- fail("Shall not accept invalid bitOffset.");
- } catch (IllegalArgumentException e) {
- final String message = e.getMessage();
- assertTrue(message, message.contains("bitOffset"));
- }
- try {
- testedStream.reset();
- fail("Shall not accept reset without mark.");
- } catch (IOException e) {
- // This is the expected exception.
- assertNotNull(e.getMessage());
- }
- /*
- * flushBefore(int).
- */
+ String message;
+
+ // Shall not accept invalid bitOffset.
+ message = assertThrows(IllegalArgumentException.class, () -> testedStream.setBitOffset(9)).getMessage();
+ assertTrue(message.contains("bitOffset"), message);
+
+ // Shall not accept reset without mark.
+ message = assertThrows(IOException.class, () -> testedStream.reset()).getMessage();
+ assertNotNull(message);
+
+ // Shall not flush at a position greater than buffer limit.
final int v = random.nextInt();
referenceStream.writeShort(v);
testedStream.writeShort(v);
- testedStream.flushBefore(0); // Valid.
- try {
- testedStream.flushBefore(3);
- fail("Shall not flush at a position greater than buffer limit.");
- } catch (IndexOutOfBoundsException e) {
- final String message = e.getMessage();
- assertTrue(message, message.contains("position"));
- }
+ testedStream.flushBefore(0); // Valid.
+ message = assertThrows(IndexOutOfBoundsException.class, () -> testedStream.flushBefore(3)).getMessage();
+ assertTrue(message.contains("position"), message);
+
testedStream.flush();
- testedStream.flushBefore(0); // Should be a no-operation.
+ testedStream.flushBefore(0); // Should be a no-operation.
assertStreamContentEquals();
}
@@ -250,142 +238,32 @@ public class ChannelDataOutputTest extends ChannelDataTestCase {
final ImageOutputStream r = this.referenceStream;
final ChannelDataOutput t = this.testedStream;
switch (operation) {
- case 0: {
- final byte v = (byte) random.nextInt(1 << Byte.SIZE);
- r.writeByte(v);
- t.writeByte(v);
- break;
- }
- case 1: {
- final short v = (short) random.nextInt(1 << Short.SIZE);
- r.writeShort(v);
- t.writeShort(v);
- break;
- }
- case 2: {
- final char v = (char) random.nextInt(1 << Character.SIZE);
- r.writeChar(v);
- t.writeChar(v);
- break;
- }
- case 3: {
- final int v = random.nextInt();
- r.writeInt(v);
- t.writeInt(v);
- break;
- }
- case 4: {
- final long v = random.nextLong();
- r.writeLong(v);
- t.writeLong(v);
- break;
- }
- case 5: {
- final float v = random.nextFloat();
- r.writeFloat(v);
- t.writeFloat(v);
- break;
- }
- case 6: {
- final double v = random.nextDouble();
- r.writeDouble(v);
- t.writeDouble(v);
- break;
- }
- case 7: {
- final byte[] tmp = new byte[random.nextInt(ARRAY_MAX_LENGTH / Byte.BYTES)];
- random.nextBytes(tmp);
- r.write(tmp);
- t.write(tmp);
- break;
- }
- case 8: {
- final char[] tmp = new char[random.nextInt(ARRAY_MAX_LENGTH / Character.BYTES)];
- for (int i=0; i<tmp.length; i++) {
- tmp[i] = (char) random.nextInt(1 << Character.SIZE);
- }
- r.writeChars(tmp, 0, tmp.length);
- t.writeChars(tmp);
- break;
- }
- case 9: {
- final short[] tmp = new short[random.nextInt(ARRAY_MAX_LENGTH / Short.BYTES)];
- for (int i=0; i<tmp.length; i++) {
- tmp[i] = (short) random.nextInt(1 << Short.SIZE);
- }
- r.writeShorts(tmp, 0, tmp.length);
- t.writeShorts(tmp);
- break;
- }
- case 10: {
- final int[] tmp = new int[random.nextInt(ARRAY_MAX_LENGTH / Integer.BYTES)];
- for (int i=0; i<tmp.length; i++) {
- tmp[i] = random.nextInt();
- }
- r.writeInts(tmp, 0, tmp.length);
- t.writeInts(tmp);
- break;
- }
- case 11: {
- final long[] tmp = new long[random.nextInt(ARRAY_MAX_LENGTH / Long.BYTES)];
- for (int i=0; i<tmp.length; i++) {
- tmp[i] = random.nextLong();
- }
- r.writeLongs(tmp, 0, tmp.length);
- t.writeLongs(tmp);
- break;
- }
- case 12: {
- final float[] tmp = new float[random.nextInt(ARRAY_MAX_LENGTH / Float.BYTES)];
- for (int i=0; i<tmp.length; i++) {
- tmp[i] = random.nextFloat();
- }
- r.writeFloats(tmp, 0, tmp.length);
- t.writeFloats(tmp);
- break;
- }
- case 13: {
- final double[] tmp = new double[random.nextInt(ARRAY_MAX_LENGTH / Double.BYTES)];
- for (int i=0; i<tmp.length; i++) {
- tmp[i] = random.nextDouble();
- }
- r.writeDoubles(tmp, 0, tmp.length);
- t.writeDoubles(tmp);
- break;
- }
- case 14: {
+ default: throw new AssertionError(operation);
+ case 0: {byte v = (byte) random.nextInt(); r.writeByte (v); t.writeByte (v); break;}
+ case 1: {short v = (short) random.nextInt(); r.writeShort (v); t.writeShort (v); break;}
+ case 2: {char v = (char) random.nextInt(); r.writeChar (v); t.writeChar (v); break;}
+ case 3: {int v = random.nextInt(); r.writeInt (v); t.writeInt (v); break;}
+ case 4: {long v = random.nextLong(); r.writeLong (v); t.writeLong (v); break;}
+ case 5: {float v = random.nextFloat(); r.writeFloat (v); t.writeFloat (v); break;}
+ case 6: {double v = random.nextDouble(); r.writeDouble (v); t.writeDouble (v); break;}
+ case 7: {boolean v = random.nextBoolean(); r.writeBoolean(v); t.writeBoolean(v); break;}
+ case 8: {byte[] v = randomBytes(); r.write (v); t.write (v); break;}
+ case 9: {char[] v = randomChars(); r.writeChars (v, 0, v.length); t.writeChars (v); break;}
+ case 10: {short[] v = randomShorts(); r.writeShorts (v, 0, v.length); t.writeShorts (v); break;}
+ case 11: {int[] v = randomInts(); r.writeInts (v, 0, v.length); t.writeInts (v); break;}
+ case 12: {long[] v = randomLongs(); r.writeLongs (v, 0, v.length); t.writeLongs (v); break;}
+ case 13: {float[] v = randomFloats(); r.writeFloats (v, 0, v.length); t.writeFloats (v); break;}
+ case 14: {double[] v = randomDoubles(); r.writeDoubles(v, 0, v.length); t.writeDoubles(v); break;}
+ case 15: {String v = "Byte sequence"; r.writeBytes(v); t.writeBytes(v); break;}
+ case 16: {String v = "Character sequence"; r.writeChars(v); t.writeChars(v); break;}
+ case 17: {String v = "お元気ですか"; r.writeUTF (v); t.writeUTF (v); break;}
+ case 18: {
final long v = random.nextLong();
final int numBits = random.nextInt(Byte.SIZE);
r.writeBits(v, numBits);
t.writeBits(v, numBits);
break;
}
- case 15: {
- final boolean v = random.nextBoolean();
- r.writeBoolean(v);
- t.writeBoolean(v);
- break;
- }
- case 16: {
- final String s = "Byte sequence";
- r.writeBytes(s);
- t.writeBytes(s);
- break;
- }
- case 17: {
- final String s = "Character sequence";
- r.writeChars(s);
- t.writeChars(s);
- break;
- }
- case 18: {
- final String s = "お元気ですか";
- final byte[] array = s.getBytes("UTF-8");
- assertEquals(s.length() * 3, array.length); // Sanity check.
- r.writeUTF(s);
- t.writeUTF(s);
- break;
- }
case 19: {
long flushedPosition = StrictMath.max(r.getFlushedPosition(), t.getFlushedPosition());
flushedPosition += random.nextInt(1 + (int) (r.getStreamPosition() - flushedPosition));
@@ -398,9 +276,123 @@ public class ChannelDataOutputTest extends ChannelDataTestCase {
t.flush();
break;
}
- default: throw new AssertionError(operation);
}
- assertEquals("getBitOffset()", r.getBitOffset(), t.getBitOffset());
- assertEquals("getStreamPosition()", r.getStreamPosition(), t.getStreamPosition());
+ assertEquals(r.getBitOffset(), t.getBitOffset(), "getBitOffset()");
+ assertEquals(r.getStreamPosition(), t.getStreamPosition(), "getStreamPosition()");
+ }
+
+ /**
+ * Test writing a sequence of bits.
+ *
+ * @throws IOException should never happen since we read and write in memory only.
+ */
+ @Test
+ public void testWriteBits() throws IOException {
+ initialize("testWriteBits", STREAM_LENGTH, randomBufferCapacity());
+ final int length = testedStreamBackingArray.length - ARRAY_MAX_LENGTH; // Keep a margin against buffer underflow.
+ while (testedStream.getStreamPosition() < length) {
+ final long v = random.nextLong();
+ final int numBits = random.nextInt(Byte.SIZE);
+ referenceStream.writeBits(v, numBits);
+ testedStream.writeBits(v, numBits);
+ /*
+ * Randomly force flushing of bits.
+ */
+ if (randomEvent()) {
+ final int f = random.nextInt(256);
+ referenceStream.writeByte(f);
+ testedStream.writeByte(f);
+ }
+ assertEquals(referenceStream.getBitOffset(), testedStream.getBitOffset(), "getBitOffset");
+ assertEquals(referenceStream.getStreamPosition(), testedStream.getStreamPosition(), "getStreamPosition");
+ }
+ assertStreamContentEquals();
+ }
+
+ /**
+ * Tests {@link ChannelImageOutputStream#mark()} and {@code reset()} methods.
+ *
+ * @throws IOException should never happen since we read and write in memory only.
+ */
+ @Test
+ public void testMarkAndReset() throws IOException {
+ initialize("testMarkAndReset", STREAM_LENGTH, 1000); // We need a larger buffer for this test.
+ /*
+ * Fill both streams with random data.
+ * During this process, randomly takes mark.
+ */
+ int nbMarks = 0;
+ for (int i=0; i<STREAM_LENGTH; i++) {
+ if (randomEvent() && i < STREAM_LENGTH - Long.BYTES) {
+ referenceStream.mark();
+ testedStream.mark();
+ nbMarks++;
+ }
+ final int v = random.nextInt(256);
+ referenceStream.writeByte(v);
+ testedStream.writeByte(v);
+ }
+ compareMarks(nbMarks);
+ }
+
+ /**
+ * Invokes {@link ChannelImageOutputStream#reset()} {@code nbMarks} times and verify that the stream position
+ * is the expected one. This method will then write random values at those positions, and finally compare the
+ * stream content.
+ */
+ private void compareMarks(int nbMarks) throws IOException {
+ while (--nbMarks >= 0) {
+ referenceStream.reset();
+ testedStream.reset();
+ assertEquals(referenceStream.getBitOffset(), testedStream.getBitOffset());
+ assertEquals(referenceStream.getStreamPosition(), testedStream.getStreamPosition());
+ final long v = random.nextLong();
+ referenceStream.writeLong(v);
+ testedStream.writeLong(v);
+ }
+ /*
+ * Verify that we have no remaining marks, and finally compare stream content.
+ */
+ String message = assertThrows(IOException.class, () -> testedStream.reset()).getMessage();
+ assertNotNull(message);
+ assertStreamContentEquals();
+ }
+
+ /**
+ * Tests {@link ChannelImageOutputStream#flushBefore(long)}.
+ *
+ * @throws IOException should never happen since we read and write in memory only.
+ */
+ @Test
+ @DependsOnMethod("testMarkAndReset")
+ public void testFlushBefore() throws IOException {
+ final int N = 50; // Number of long values to write.
+ initialize("testFlushBefore", N*Long.BYTES, 200);
+ for (int i=0; i<N; i++) {
+ switch (i) {
+ case 20:
+ case 30:
+ case 40:
+ case 45: {
+ referenceStream.mark();
+ testedStream.mark();
+ break;
+ }
+ case 10: {
+ referenceStream.flushBefore(5 * Long.BYTES);
+ testedStream.flushBefore(5 * Long.BYTES);
+ break;
+ }
+ case 35: {
+ referenceStream.flushBefore(32 * Long.BYTES);
+ testedStream.flushBefore(32 * Long.BYTES);
+ break;
+ }
+ }
+ final long v = random.nextLong();
+ referenceStream.writeLong(v);
+ testedStream.writeLong(v);
+ }
+ compareMarks(2);
}
}
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/io/stream/ChannelDataTestCase.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/io/stream/ChannelDataTestCase.java
index 8c6501259f..b02c10f04c 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/io/stream/ChannelDataTestCase.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/io/stream/ChannelDataTestCase.java
@@ -145,9 +145,56 @@ abstract class ChannelDataTestCase extends TestCase {
*/
final byte[] createRandomArray(final int length) {
final byte[] array = new byte[length];
- for (int i=0; i<length; i++) {
- array[i] = (byte) random.nextInt(256);
- }
+ random.nextBytes(array);
+ return array;
+ }
+
+ /** {@return a new array of bytes or random length}. */
+ final byte[] randomBytes() {
+ final byte[] array = new byte[random.nextInt(ARRAY_MAX_LENGTH / Byte.BYTES)];
+ random.nextBytes(array);
+ return array;
+ }
+
+ /** {@return a new array of characters or random length}. */
+ final char[] randomChars() {
+ final char[] array = new char[random.nextInt(ARRAY_MAX_LENGTH / Character.BYTES)];
+ for (int i=0; i<array.length; i++) array[i] = (char) random.nextInt(1 << Character.SIZE);
+ return array;
+ }
+
+ /** {@return a new array of short integers or random length}. */
+ final short[] randomShorts() {
+ final short[] array = new short[random.nextInt(ARRAY_MAX_LENGTH / Short.BYTES)];
+ for (int i=0; i<array.length; i++) array[i] = (short) random.nextInt(1 << Short.SIZE);
+ return array;
+ }
+
+ /** {@return a new array of integers or random length}. */
+ final int[] randomInts() {
+ final int[] array = new int[random.nextInt(ARRAY_MAX_LENGTH / Integer.BYTES)];
+ for (int i=0; i<array.length; i++) array[i] = random.nextInt();
+ return array;
+ }
+
+ /** {@return a new array of long integers or random length}. */
+ final long[] randomLongs() {
+ final long[] array = new long[random.nextInt(ARRAY_MAX_LENGTH / Long.BYTES)];
+ for (int i=0; i<array.length; i++) array[i] = random.nextLong();
+ return array;
+ }
+
+ /** {@return a new array of single-precision floating point values or random length}. */
+ final float[] randomFloats() {
+ final float[] array = new float[random.nextInt(ARRAY_MAX_LENGTH / Float.BYTES)];
+ for (int i=0; i<array.length; i++) array[i] = random.nextFloat();
+ return array;
+ }
+
+ /** {@return a new array of double-precision floating point values or random length}. */
+ final double[] randomDoubles() {
+ final double[] array = new double[random.nextInt(ARRAY_MAX_LENGTH / Double.BYTES)];
+ for (int i=0; i<array.length; i++) array[i] = random.nextDouble();
return array;
}
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/io/stream/ChannelImageInputStreamTest.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/io/stream/ChannelImageInputStreamTest.java
index b1fae18292..4240b04213 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/io/stream/ChannelImageInputStreamTest.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/io/stream/ChannelImageInputStreamTest.java
@@ -23,13 +23,14 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.channels.Channels;
import javax.imageio.ImageIO;
+import javax.imageio.stream.IIOByteBuffer;
import javax.imageio.stream.ImageInputStream;
// Test dependencies
import org.junit.Test;
import org.apache.sis.test.DependsOn;
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
/**
@@ -65,7 +66,24 @@ public final class ChannelImageInputStreamTest extends ChannelDataTestCase {
*/
@Test
public void testWithRandomData() throws IOException {
- final ByteBuffer buffer = ByteBuffer.allocate(BUFFER_MAX_CAPACITY);
+ test(ByteBuffer.allocate(BUFFER_MAX_CAPACITY));
+ }
+
+ /**
+ * Same test, but using a direct buffer.
+ * Some code paths are different.
+ *
+ * @throws IOException should never happen since we read and write in memory only.
+ */
+ @Test
+ public void testWithDirectBuffer() throws IOException {
+ test(ByteBuffer.allocateDirect(BUFFER_MAX_CAPACITY));
+ }
+
+ /**
+ * Runs the tests with the specified buffer.
+ */
+ private void test(final ByteBuffer buffer) throws IOException {
final ByteOrder byteOrder = random.nextBoolean() ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
final byte[] data = createRandomArray(STREAM_LENGTH);
referenceStream = ImageIO.createImageInputStream(new ByteArrayInputStream(data));
@@ -73,7 +91,7 @@ public final class ChannelImageInputStreamTest extends ChannelDataTestCase {
testedStream = new ChannelImageInputStream("testWithRandomData",
Channels.newChannel(new ByteArrayInputStream(data)), buffer, false);
testedStream.setByteOrder(byteOrder);
- transferRandomData(testedStream, data.length - ARRAY_MAX_LENGTH, 24);
+ transferRandomData(testedStream, data.length - ARRAY_MAX_LENGTH, 25);
testedStream.close();
}
@@ -87,104 +105,121 @@ public final class ChannelImageInputStreamTest extends ChannelDataTestCase {
final ImageInputStream r = referenceStream;
switch (operation) {
default: throw new AssertionError(operation);
- case 0: assertEquals("read()", r.read(), t.read()); break;
- case 1: assertEquals("readBoolean()", r.readBoolean(), t.readBoolean()); break;
- case 2: assertEquals("readChar()", r.readChar(), t.readChar()); break;
- case 3: assertEquals("readByte()", r.readByte(), t.readByte()); break;
- case 4: assertEquals("readShort()", r.readShort(), t.readShort()); break;
- case 5: assertEquals("readUnsignedShort()", r.readUnsignedShort(), t.readUnsignedShort()); break;
- case 6: assertEquals("readInt()", r.readInt(), t.readInt()); break;
- case 7: assertEquals("readUnsignedInt()", r.readUnsignedInt(), t.readUnsignedInt()); break;
- case 8: assertEquals("readLong()", r.readLong(), t.readLong()); break;
- case 9: assertEquals("readFloat()", r.readFloat(), t.readFloat(), 0f); break;
- case 10: assertEquals("readDouble()", r.readDouble(), t.readDouble(), 0d); break;
- case 11: assertEquals("readBit()", r.readBit(), t.readBit()); break;
+ case 0: assertEquals(r.read(), t.read(), "read()"); break;
+ case 1: assertEquals(r.readBoolean(), t.readBoolean(), "readBoolean()"); break;
+ case 2: assertEquals(r.readChar(), t.readChar(), "readChar()"); break;
+ case 3: assertEquals(r.readByte(), t.readByte(), "readByte()"); break;
+ case 4: assertEquals(r.readShort(), t.readShort(), "readShort()"); break;
+ case 5: assertEquals(r.readUnsignedShort(), t.readUnsignedShort(), "readUnsignedShort()"); break;
+ case 6: assertEquals(r.readInt(), t.readInt(), "readInt()"); break;
+ case 7: assertEquals(r.readUnsignedInt(), t.readUnsignedInt(), "readUnsignedInt()"); break;
+ case 8: assertEquals(r.readLong(), t.readLong(), "readLong()"); break;
+ case 9: assertEquals(r.readFloat(), t.readFloat(), "readFloat()"); break;
+ case 10: assertEquals(r.readDouble(), t.readDouble(), "readDouble()"); break;
+ case 11: assertEquals(r.readBit(), t.readBit(), "readBit()"); break;
case 12: {
final int n = random.nextInt(Long.SIZE + 1);
- assertEquals("readBits(" + n + ')', r.readBits(n), t.readBits(n));
+ assertEquals(r.readBits(n), t.readBits(n), () -> "readBits(" + n + ')');
break;
}
case 13: {
final int length = random.nextInt(ARRAY_MAX_LENGTH);
final byte[] actual = new byte[length];
final int n = t.read(actual);
- assertFalse("Reached EOF", n < 0);
+ assertFalse(n < 0, "Reached EOF");
final byte[] expected = new byte[n];
r.readFully(expected);
- assertArrayEquals("read(byte[])", expected, actual);
+ assertArrayEquals(expected, actual, "read(byte[])");
break;
}
case 14: {
final int length = random.nextInt(ARRAY_MAX_LENGTH);
final byte[] expected = new byte[length]; r.readFully(expected);
final byte[] actual = new byte[length]; t.readFully(actual);
- assertArrayEquals("readFully(byte[])", expected, actual);
+ assertArrayEquals(expected, actual, "readFully(byte[])");
break;
}
case 15: {
final int length = random.nextInt(ARRAY_MAX_LENGTH / Character.BYTES);
final char[] expected = new char[length]; r.readFully(expected, 0, length);
final char[] actual = new char[length]; t.readFully(actual, 0, length);
- assertArrayEquals("readFully(char[])", expected, actual);
+ assertArrayEquals(expected, actual, "readFully(char[])");
break;
}
case 16: {
final int length = random.nextInt(ARRAY_MAX_LENGTH / Short.BYTES);
final short[] expected = new short[length]; r.readFully(expected, 0, length);
final short[] actual = new short[length]; t.readFully(actual, 0, length);
- assertArrayEquals("readFully(short[])", expected, actual);
+ assertArrayEquals(expected, actual, "readFully(short[])");
break;
}
case 17: {
final int length = random.nextInt(ARRAY_MAX_LENGTH / Integer.BYTES);
final int[] expected = new int[length]; r.readFully(expected, 0, length);
final int[] actual = new int[length]; t.readFully(actual, 0, length);
- assertArrayEquals("readFully(int[])", expected, actual);
+ assertArrayEquals(expected, actual, "readFully(int[])");
break;
}
case 18: {
final int length = random.nextInt(ARRAY_MAX_LENGTH / Long.BYTES);
final long[] expected = new long[length]; r.readFully(expected, 0, length);
final long[] actual = new long[length]; t.readFully(actual, 0, length);
- assertArrayEquals("readFully(long[])", expected, actual);
+ assertArrayEquals(expected, actual, "readFully(long[])");
break;
}
case 19: {
final int length = random.nextInt(ARRAY_MAX_LENGTH / Float.BYTES);
final float[] expected = new float[length]; r.readFully(expected, 0, length);
final float[] actual = new float[length]; t.readFully(actual, 0, length);
- assertTrue("readFully(float[])", Arrays.equals(expected, actual));
+ assertArrayEquals(expected, actual, "readFully(float[])");
break;
}
case 20: {
final int length = random.nextInt(ARRAY_MAX_LENGTH / Double.BYTES);
final double[] expected = new double[length]; r.readFully(expected, 0, length);
final double[] actual = new double[length]; t.readFully(actual, 0, length);
- assertTrue("readFully(double[])", Arrays.equals(expected, actual));
+ assertArrayEquals(expected, actual, "readFully(double[])");
break;
}
case 21: {
+ final IIOByteBuffer buffer = new IIOByteBuffer(null, 0, 0);
+ t.readBytes(buffer, random.nextInt(ARRAY_MAX_LENGTH));
+ final byte[] actual = data(buffer);
+ r.readBytes(buffer, actual.length);
+ final byte[] expected = data(buffer);
+ assertArrayEquals(expected, actual, "readBytes(IIOByteBuffer)");
+ break;
+ }
+ case 22: {
final long length = random.nextInt(ARRAY_MAX_LENGTH);
final long n = t.skipBytes(length);
- assertFalse("Reached EOF", n < 0);
+ assertFalse(n < 0, "Reached EOF");
r.readFully(new byte[(int) n]);
- assertEquals("skipBytes(int)", r.getStreamPosition(), t.getStreamPosition());
+ assertEquals(r.getStreamPosition(), t.getStreamPosition(), "skipBytes(int)");
break;
}
- case 22: {
+ case 23: {
long flushedPosition = StrictMath.max(r.getFlushedPosition(), t.getFlushedPosition());
flushedPosition += random.nextInt(1 + (int) (r.getStreamPosition() - flushedPosition));
r.flushBefore(flushedPosition);
t.flushBefore(flushedPosition);
break;
}
- case 23: {
+ case 24: {
r.flush();
t.flush();
break;
}
}
- assertEquals("getStreamPosition()", r.getStreamPosition(), t.getStreamPosition());
- assertEquals("getBitOffset()", r.getBitOffset(), t.getBitOffset());
+ assertEquals(r.getStreamPosition(), t.getStreamPosition(), "getStreamPosition()");
+ assertEquals(r.getBitOffset(), t.getBitOffset(), "getBitOffset()");
+ }
+
+ /**
+ * Returns a copy of the data in the given buffer.
+ */
+ private static byte[] data(final IIOByteBuffer buffer) {
+ final int offset = buffer.getOffset();
+ return Arrays.copyOfRange(buffer.getData(), offset, offset + buffer.getLength());
}
}
diff --git a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/io/stream/ChannelImageOutputStreamTest.java b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/io/stream/ChannelImageOutputStreamTest.java
index 818a3feacd..1fd9c83a80 100644
--- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/io/stream/ChannelImageOutputStreamTest.java
+++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/io/stream/ChannelImageOutputStreamTest.java
@@ -16,14 +16,18 @@
*/
package org.apache.sis.io.stream;
+import java.util.Arrays;
import java.io.IOException;
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channel;
+import javax.imageio.stream.ImageOutputStream;
// Test dependencies
import org.junit.Test;
-import org.apache.sis.test.DependsOnMethod;
import org.apache.sis.test.DependsOn;
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
/**
@@ -33,139 +37,177 @@ import static org.junit.Assert.*;
* @author Martin Desruisseaux (Geomatys)
*/
@DependsOn(ChannelDataOutputTest.class)
-public final class ChannelImageOutputStreamTest extends ChannelDataOutputTest {
+public final class ChannelImageOutputStreamTest extends ChannelDataTestCase {
/**
- * Creates a new test case.
+ * The implementation to test. This implementation will write data to {@link #testedStreamBackingArray}.
+ * The content of that array will be compared to {@link #expectedData} for verifying result correctness.
*/
- public ChannelImageOutputStreamTest() {
- }
+ private ChannelImageOutputStream testedStream;
/**
- * Initializes all non-final fields before to execute a test.
+ * A wrapper around {@link #testedStream}, used only because required by the tests in super-class.
*/
- @Override
- void initialize(final String testName, final int streamLength, final int bufferLength) throws IOException {
- super.initialize(testName, streamLength, bufferLength);
- testedStream = new ChannelImageOutputStream(testedStream);
+ private ChannelData testWrapper;
+
+ /**
+ * A stream to use as a reference implementation. Any data written in {@link #testedStream}
+ * will also be written in {@code referenceStream}, for later comparison.
+ */
+ private ImageOutputStream referenceStream;
+
+ /**
+ * Byte array which is filled by the {@linkplain #testedStream} implementation during write operations.
+ * The content of this array will be compared to {@linkplain #expectedData}.
+ */
+ private byte[] testedStreamBackingArray;
+
+ /**
+ * Object which is filled by {@link #referenceStream} implementation during write operations.
+ * <b>Do not write to this stream</b> - this field is kept only for invocation of
+ * {@link ByteArrayOutputStream#toByteArray()}.
+ */
+ private ByteArrayOutputStream expectedData;
+
+ /**
+ * Creates a new test case.
+ */
+ public ChannelImageOutputStreamTest() {
}
/**
- * Test writing a sequence of bits.
+ * Initializes all non-final fields before to execute a test.
*
+ * @param testName the name of the test method to be executed.
+ * @param streamLength length of stream to create.
+ * @param bufferLength length of the {@code ByteBuffer} to use for the tests.
* @throws IOException should never happen since we read and write in memory only.
*/
- @Test
- public void testWriteBits() throws IOException {
- initialize("testWriteBits", STREAM_LENGTH, randomBufferCapacity());
- final int length = testedStreamBackingArray.length - ARRAY_MAX_LENGTH; // Keep a margin against buffer underflow.
- while (testedStream.getStreamPosition() < length) {
- final long v = random.nextLong();
- final int numBits = random.nextInt(Byte.SIZE);
- referenceStream.writeBits(v, numBits);
- testedStream.writeBits(v, numBits);
- /*
- * Randomly force flushing of bits.
- */
- if (randomEvent()) {
- final int f = random.nextInt(256);
- referenceStream.writeByte(f);
- testedStream.writeByte(f);
- }
- assertEquals("getBitOffset", referenceStream.getBitOffset(), testedStream.getBitOffset());
- assertEquals("getStreamPosition", referenceStream.getStreamPosition(), testedStream.getStreamPosition());
- }
- assertStreamContentEquals();
+ private void initialize(final String testName, final int streamLength, final int bufferLength) throws IOException {
+ expectedData = new ByteArrayOutputStream(streamLength);
+ referenceStream = new MemoryCacheImageOutputStream(expectedData);
+ testedStreamBackingArray = new byte[streamLength];
+ var channel = new ByteArrayChannel(testedStreamBackingArray, false);
+ testedStream = new ChannelImageOutputStream(testName, channel, ByteBuffer.allocate(bufferLength));
+ testWrapper = new ChannelData(testName, channel, testedStream.input().buffer) {
+ @Override public Channel channel() {return channel;}
+ @Override public long getStreamPosition() {return testedStream.getStreamPosition();}
+ @Override public void seek(long p) {fail("Should not be invoked.");}
+ @Override void flushNBytes(int n) {fail("Should not be invoked.");}
+ };
}
/**
- * Tests {@link ChannelImageOutputStream#mark()} and {@code reset()} methods.
+ * Fills a stream with random data and compares the result with a reference output stream.
+ * This method tests both read and write operations.
*
* @throws IOException should never happen since we read and write in memory only.
*/
@Test
- public void testMarkAndReset() throws IOException {
- initialize("testMarkAndReset", STREAM_LENGTH, 1000); // We need a larger buffer for this test.
- /*
- * Fill both streams with random data.
- * During this process, randomly takes mark.
- */
- int nbMarks = 0;
- for (int i=0; i<STREAM_LENGTH; i++) {
- if (randomEvent() && i < STREAM_LENGTH - Long.BYTES) {
- referenceStream.mark();
- testedStream.mark();
- nbMarks++;
- }
- final int v = random.nextInt(256);
- referenceStream.writeByte(v);
- testedStream.writeByte(v);
- }
- compareMarks(nbMarks);
+ public void testAllMethods() throws IOException {
+ initialize("testAllMethods", STREAM_LENGTH, randomBufferCapacity());
+ transferRandomData(testWrapper, testedStreamBackingArray.length - ARRAY_MAX_LENGTH, 21); // TODO: should be 22
+ assertStreamContentEquals();
}
/**
- * Invokes {@link ChannelImageOutputStream#reset()} {@code nbMarks} times and verify that the stream position
- * is the expected one. This method will then write random values at those positions, and finally compare the
- * stream content.
+ * Asserts that the content of {@link #testedStream} is equal to the content of {@link #referenceStream}.
+ * This method closes the reference stream before to perform the comparison.
*/
- private void compareMarks(int nbMarks) throws IOException {
- while (--nbMarks >= 0) {
- referenceStream.reset();
- testedStream.reset();
- assertEquals(referenceStream.getBitOffset(), testedStream.getBitOffset());
- assertEquals(referenceStream.getStreamPosition(), testedStream.getStreamPosition());
- final long v = random.nextLong();
- referenceStream.writeLong(v);
- testedStream.writeLong(v);
- }
- /*
- * Verify that we have no remaining marks, and finally compare stream content.
- */
- try {
- testedStream.reset();
- fail("Expected no remaining marks.");
- } catch (IOException e) {
- // This is the expected exception.
- assertNotNull(e.getMessage());
- }
- assertStreamContentEquals();
+ private void assertStreamContentEquals() throws IOException {
+ testedStream.flush();
+ referenceStream.close();
+ final byte[] expectedArray = expectedData.toByteArray();
+ assertArrayEquals(expectedArray, Arrays.copyOf(testedStreamBackingArray, expectedArray.length));
}
/**
- * Tests {@link ChannelImageOutputStream#flushBefore(long)}.
+ * Writes a random unit of data using a method selected randomly.
+ * This method is invoked (indirectly) by {@link #writeInStreams()}.
*
- * @throws IOException should never happen since we read and write in memory only.
+ * @param operation numerical identifier of the operation to test.
*/
- @Test
- @DependsOnMethod("testMarkAndReset")
- public void testFlushBefore() throws IOException {
- final int N = 50; // Number of long values to write.
- initialize("testFlushBefore", N*Long.BYTES, 200);
- for (int i=0; i<N; i++) {
- switch (i) {
- case 20:
- case 30:
- case 40:
- case 45: {
- referenceStream.mark();
- testedStream.mark();
- break;
- }
- case 10: {
- referenceStream.flushBefore(5 * Long.BYTES);
- testedStream.flushBefore(5 * Long.BYTES);
- break;
- }
- case 35: {
- referenceStream.flushBefore(32 * Long.BYTES);
- testedStream.flushBefore(32 * Long.BYTES);
- break;
+ @Override
+ final void transferRandomData(final int operation) throws IOException {
+ final ImageOutputStream r = this.referenceStream;
+ final ChannelImageOutputStream t = this.testedStream;
+ switch (operation) {
+ default: throw new AssertionError(operation);
+ case 0: {byte v = (byte) random.nextInt(); r.writeByte (v); t.writeByte (v); break;}
+ case 1: {short v = (short) random.nextInt(); r.writeShort (v); t.writeShort (v); break;}
+ case 2: {char v = (char) random.nextInt(); r.writeChar (v); t.writeChar (v); break;}
+ case 3: {int v = random.nextInt(); r.writeInt (v); t.writeInt (v); break;}
+ case 4: {long v = random.nextLong(); r.writeLong (v); t.writeLong (v); break;}
+ case 5: {float v = random.nextFloat(); r.writeFloat (v); t.writeFloat (v); break;}
+ case 6: {double v = random.nextDouble(); r.writeDouble (v); t.writeDouble (v); break;}
+ case 7: {boolean v = random.nextBoolean(); r.writeBoolean(v); t.writeBoolean(v); break;}
+ case 8: {byte[] v = randomBytes(); r.write (v); t.write (v); break;}
+ case 9: {char[] v = randomChars(); r.writeChars (v, 0, v.length); t.writeChars (v, 0, v.length); break;}
+ case 10: {short[] v = randomShorts(); r.writeShorts (v, 0, v.length); t.writeShorts (v, 0, v.length); break;}
+ case 11: {int[] v = randomInts(); r.writeInts (v, 0, v.length); t.writeInts (v, 0, v.length); break;}
+ case 12: {long[] v = randomLongs(); r.writeLongs (v, 0, v.length); t.writeLongs (v, 0, v.length); break;}
+ case 13: {float[] v = randomFloats(); r.writeFloats (v, 0, v.length); t.writeFloats (v, 0, v.length); break;}
+ case 14: {double[] v = randomDoubles(); r.writeDoubles(v, 0, v.length); t.writeDoubles(v, 0, v.length); break;}
+ case 15: {String v = "Byte sequence"; r.writeBytes(v); t.writeBytes(v); break;}
+ case 16: {String v = "Character sequence"; r.writeChars(v); t.writeChars(v); break;}
+ case 17: {String v = "お元気ですか"; r.writeUTF (v); t.writeUTF (v); break;}
+ case 18: {
+ final long v = random.nextLong();
+ final int numBits = random.nextInt(Byte.SIZE);
+ r.writeBits(v, numBits);
+ t.writeBits(v, numBits);
+ break;
+ }
+ case 19: {
+ long flushedPosition = StrictMath.max(r.getFlushedPosition(), t.getFlushedPosition());
+ flushedPosition += random.nextInt(1 + (int) (r.getStreamPosition() - flushedPosition));
+ r.flushBefore(flushedPosition);
+ t.flushBefore(flushedPosition);
+ break;
+ }
+ case 20: {
+ r.flush();
+ t.flush();
+ break;
+ }
+ /*
+ * Seek operation, potentially followed by a few read operations.
+ * The seek is necessary for moving to a position where there is something to read.
+ */
+ case 21: {
+ long length = r.length();
+ assertTrue(length >= 0, "length");
+ assertEquals(length, t.length(), "length");
+ long position = Math.max(r.getFlushedPosition(), t.getFlushedPosition());
+ if (position < length) {
+ position += random.nextInt(Math.toIntExact(length - position));
+ r.seek(position);
+ t.seek(position);
+ length -= Double.BYTES; // Make room for the largest element that we will read.
+ for (int i = random.nextInt(5); --i >= 0;) {
+ position = r.getStreamPosition();
+ assertEquals(position, t.getStreamPosition());
+ if (position >= length) break;
+ switch (random.nextInt(12)) {
+ default: throw new AssertionError();
+ case 0: assertEquals(r.read(), t.read(), "read()"); break;
+ case 1: assertEquals(r.readBoolean(), t.readBoolean(), "readBoolean()"); break;
+ case 2: assertEquals(r.readChar(), t.readChar(), "readChar()"); break;
+ case 3: assertEquals(r.readByte(), t.readByte(), "readByte()"); break;
+ case 4: assertEquals(r.readShort(), t.readShort(), "readShort()"); break;
+ case 5: assertEquals(r.readUnsignedShort(), t.readUnsignedShort(), "readUnsignedShort()"); break;
+ case 6: assertEquals(r.readInt(), t.readInt(), "readInt()"); break;
+ case 7: assertEquals(r.readUnsignedInt(), t.readUnsignedInt(), "readUnsignedInt()"); break;
+ case 8: assertEquals(r.readLong(), t.readLong(), "readLong()"); break;
+ case 9: assertEquals(r.readFloat(), t.readFloat(), "readFloat()"); break;
+ case 10: assertEquals(r.readDouble(), t.readDouble(), "readDouble()"); break;
+ case 11: assertEquals(r.readBit(), t.readBit(), "readBit()"); break;
+ }
+ }
}
+ break;
}
- final long v = random.nextLong();
- referenceStream.writeLong(v);
- testedStream.writeLong(v);
}
- compareMarks(2);
+ assertEquals(r.getBitOffset(), t.getBitOffset(), "getBitOffset()");
+ assertEquals(r.getStreamPosition(), t.getStreamPosition(), "getStreamPosition()");
}
}