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/07 20:23:42 UTC

[sis] 03/06: `ChannelDataOutput` implements `DataOutput` for allowing `instanceof` checks against a public interface.

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 a5cb0d05680cd62201d58669e8460b1b6d1d6595
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sun Sep 17 16:32:14 2023 +0200

    `ChannelDataOutput` implements `DataOutput` for allowing `instanceof` checks against a public interface.
---
 .../apache/sis/io/stream/ChannelDataOutput.java    | 114 +++++++++++++++++++--
 .../sis/io/stream/ChannelImageOutputStream.java    |  85 +--------------
 .../org/apache/sis/storage/StorageConnector.java   |  14 +--
 .../sis/io/stream/ChannelDataOutputTest.java       |  91 +++++-----------
 .../io/stream/ChannelImageOutputStreamTest.java    |  18 +---
 5 files changed, 142 insertions(+), 180 deletions(-)

diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelDataOutput.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelDataOutput.java
index 31e17058bb..ccf2e6701e 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelDataOutput.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/ChannelDataOutput.java
@@ -17,10 +17,12 @@
 package org.apache.sis.io.stream;
 
 import java.util.Arrays;
+import java.io.DataOutput;
 import java.io.Flushable;
 import java.io.IOException;
 import java.nio.Buffer;
 import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.nio.CharBuffer;
 import java.nio.DoubleBuffer;
 import java.nio.FloatBuffer;
@@ -29,6 +31,7 @@ import java.nio.LongBuffer;
 import java.nio.ShortBuffer;
 import java.nio.channels.SeekableByteChannel;
 import java.nio.channels.WritableByteChannel;
+import java.nio.charset.StandardCharsets;
 import org.apache.sis.util.internal.Numerics;
 import org.apache.sis.storage.internal.Resources;
 
@@ -50,18 +53,15 @@ import static org.apache.sis.util.ArgumentChecks.ensureBetween;
  * <p>Since this class is only a helper tool, it does not "own" the channel and consequently does not provide
  * {@code close()} method. It is users responsibility to close the channel after usage.</p>
  *
- * <h2>Relationship with {@code DataOutput}</h2>
- * This class API is compatibly with the {@link java.io.DataOutput} interface, so subclasses can implement that
- * interface if they wish. This class does not implement {@code DataOutput} itself because it is not needed for
- * SIS purposes.
- * However, the {@link ChannelImageOutputStream} class implements the {@code DataOutput} interface, together with
- * the {@link javax.imageio.stream.ImageOutputStream} one, mostly for situations when inter-operability with
- * {@link javax.imageio} is needed.
+ * <h2>Relationship with {@code ImageOutputStream}</h2>
+ * This class API is compatibly with the {@link javax.imageio.stream.ImageOutputStream} interface, so subclasses
+ * can implement that interface if they wish. This class does not implement {@code ImageOutputStream} because it
+ * is not needed for SIS purposes.
  *
  * @author  Rémi Maréchal (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  */
-public class ChannelDataOutput extends ChannelData implements Flushable {
+public class ChannelDataOutput extends ChannelData implements DataOutput, Flushable {
     /**
      * The channel where data are written.
      * This is supplied at construction time.
@@ -151,6 +151,8 @@ public class ChannelDataOutput extends ChannelData implements Flushable {
      *
      * @param  bit  the bit to write (rightmost bit).
      * @throws IOException if an error occurred while creating the data output.
+     *
+     * @see #writeBoolean(boolean)
      */
     public final void writeBit(final int bit) throws IOException {
         writeBits(bit, 1);
@@ -207,6 +209,38 @@ public class ChannelDataOutput extends ChannelData implements Flushable {
         }
     }
 
+    /**
+     * Writes boolean value (8 bits) into the steam. This method delegates to {@linkplain #writeByte(int)}.
+     * If boolean {@code v} is {@code true} the byte value 1 is written whereas if boolean is {@code false}
+     * zero is written.
+     *
+     * <p>For writing a single bit, see {@link #writeBit(int)} instead.
+     *
+     * @param  v  boolean to be written.
+     * @throws IOException if some I/O exception occurs during writing.
+     *
+     * @see #writeBit(int)
+     */
+    @Override
+    public final void writeBoolean(final boolean v) throws IOException {
+        writeByte(v ? 1 : 0);
+    }
+
+    /**
+     * Writes a single byte to the stream at the current position.
+     * The 24 high-order bits of {@code v} are ignored.
+     *
+     * @param  v  an integer whose lower 8 bits are to be written.
+     * @throws IOException if some I/O exception occurs during writing.
+     *
+     * @deprecated Prefer {@link #writeByte(int)} for readability.
+     */
+    @Override
+    @Deprecated
+    public final void write(final int v) throws IOException {
+        writeByte(v);
+    }
+
     /**
      * Writes the 8 low-order bits of {@code v} to the stream.
      * The 24 high-order bits of {@code v} are ignored.
@@ -216,6 +250,7 @@ public class ChannelDataOutput extends ChannelData implements Flushable {
      * @param  value  byte to be written.
      * @throws IOException if some I/O exception occurs during writing.
      */
+    @Override
     public final void writeByte(final int value) throws IOException {
         ensureBufferAccepts(Byte.BYTES);
         buffer.put((byte) value);
@@ -230,6 +265,7 @@ public class ChannelDataOutput extends ChannelData implements Flushable {
      * @param  value  short integer to be written.
      * @throws IOException if some I/O exception occurs during writing.
      */
+    @Override
     public final void writeShort(final int value) throws IOException {
         ensureBufferAccepts(Short.BYTES);
         buffer.putShort((short) value);
@@ -243,6 +279,7 @@ public class ChannelDataOutput extends ChannelData implements Flushable {
      * @param  value  character to be written.
      * @throws IOException if some I/O exception occurs during writing.
      */
+    @Override
     public final void writeChar(final int value) throws IOException {
         ensureBufferAccepts(Character.BYTES);
         buffer.putChar((char) value);
@@ -256,6 +293,7 @@ public class ChannelDataOutput extends ChannelData implements Flushable {
      * @param  value  integer to be written.
      * @throws IOException if some I/O exception occurs during writing.
      */
+    @Override
     public final void writeInt(final int value) throws IOException {
         ensureBufferAccepts(Integer.BYTES);
         buffer.putInt(value);
@@ -269,6 +307,7 @@ public class ChannelDataOutput extends ChannelData implements Flushable {
      * @param  value  long integer to be written.
      * @throws IOException if some I/O exception occurs during writing.
      */
+    @Override
     public final void writeLong(final long value) throws IOException {
         ensureBufferAccepts(Long.BYTES);
         buffer.putLong(value);
@@ -282,6 +321,7 @@ public class ChannelDataOutput extends ChannelData implements Flushable {
      * @param  value floating point value to be written.
      * @throws IOException if some I/O exception occurs during writing.
      */
+    @Override
     public final void writeFloat(final float value) throws IOException {
         ensureBufferAccepts(Float.BYTES);
         buffer.putFloat(value);
@@ -295,6 +335,7 @@ public class ChannelDataOutput extends ChannelData implements Flushable {
      * @param  value  double precision floating point value to be written.
      * @throws IOException if some I/O exception occurs during writing.
      */
+    @Override
     public final void writeDouble(final double value) throws IOException {
         ensureBufferAccepts(Double.BYTES);
         buffer.putDouble(value);
@@ -311,6 +352,7 @@ public class ChannelDataOutput extends ChannelData implements Flushable {
      * @param  src  an array of bytes to be written into stream.
      * @throws IOException if an error occurred while writing the stream.
      */
+    @Override
     public final void write(final byte[] src) throws IOException {
         write(src, 0, src.length);
     }
@@ -413,6 +455,7 @@ public class ChannelDataOutput extends ChannelData implements Flushable {
      * @param  length  the number of bytes to write.
      * @throws IOException if an error occurred while writing the stream.
      */
+    @Override
     public final void write(final byte[] src, int offset, int length) throws IOException {
         if (length != 0) {
             do {
@@ -585,6 +628,61 @@ public class ChannelDataOutput extends ChannelData implements Flushable {
         }.writeFully(Double.BYTES, offset, length);
     }
 
+    /**
+     * Writes the lower-order byte of each character. The high-order eight bits of each character
+     * in the string are ignored - this method does <strong>not</strong> applies any encoding.
+     *
+     * <p>This method is provided because required by the {@link DataOutput} interface,
+     * but its usage should generally be avoided.</p>
+     *
+     * @param  ascii  the string to be written.
+     * @throws IOException if an error occurred while writing the stream.
+     */
+    @Override
+    public void writeBytes(final String ascii) throws IOException {
+        final byte[] data = new byte[ascii.length()];
+        for (int i=0; i<data.length; i++) {
+            data[i] = (byte) ascii.charAt(i);
+        }
+        write(data);
+    }
+
+    /**
+     * Writes all characters from the given text into the stream.
+     *
+     * @param  text  the characters to be written into the stream.
+     * @throws IOException if an error occurred while writing the stream.
+     */
+    @Override
+    public final void writeChars(final String text) throws IOException {
+        writeChars(text.toCharArray());
+    }
+
+    /**
+     * Writes two bytes of length information to the output stream, followed by the modified UTF-8 representation
+     * of every character in the {@code str} string. Each character is converted to a group of one, two, or three
+     * bytes, depending on the character code point value.
+     *
+     * @param  s  the string to be written.
+     * @throws IOException if an error occurred while writing the stream.
+     */
+    @Override
+    public void writeUTF(final String s) throws IOException {
+        byte[] data = s.getBytes(StandardCharsets.UTF_8);
+        if (data.length > Short.MAX_VALUE) {
+            throw new IllegalArgumentException(Resources.format(
+                    Resources.Keys.ExcessiveStringSize_3, filename, Short.MAX_VALUE, data.length));
+        }
+        final ByteOrder oldOrder = buffer.order();
+        buffer.order(ByteOrder.BIG_ENDIAN);
+        try {
+            writeShort(data.length);
+            write(data);
+        } finally {
+            buffer.order(oldOrder);
+        }
+    }
+
     /**
      * Repeats the same byte many times.
      * This method can be used for filling a region of the output stream.
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 60061cb5e7..a46152ca96 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
@@ -20,10 +20,8 @@ import java.io.Closeable;
 import java.io.DataOutput;
 import java.io.IOException;
 import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
 import java.nio.channels.WritableByteChannel;
 import javax.imageio.stream.ImageOutputStream;
-import org.apache.sis.storage.internal.Resources;
 
 
 /**
@@ -36,8 +34,7 @@ import org.apache.sis.storage.internal.Resources;
  * separated. Despite that, the name of this {@code ChannelImageOutputStream} anticipates a future version
  * which would implement the image I/O interface.</p>
  *
- * <p>{@code DataOutput} methods are defined in this separated class rather than in the parent class
- * because some methods are Java 1.0 legacy and should be avoided (e.g. {@link #writeBytes(String)}).</p>
+ * <p>This class is a place-holder for future development.</p>
  *
  * @author  Rémi Maréchal (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
@@ -69,86 +66,6 @@ public class ChannelImageOutputStream extends ChannelDataOutput implements DataO
         super(output.filename, output.channel, output.buffer);
     }
 
-    /**
-     * Writes a single byte to the stream at the current position.
-     * The 24 high-order bits of {@code v} are ignored.
-     *
-     * @param  v  an integer whose lower 8 bits are to be written.
-     * @throws IOException if some I/O exception occurs during writing.
-     */
-    @Override
-    public final void write(final int v) throws IOException {
-        writeByte(v);
-    }
-
-    /**
-     * Writes boolean value (8 bits) into the steam. This method delegates to {@linkplain #writeByte(int)}.
-     * If boolean {@code v} is {@code true} the byte value 1 is written whereas if boolean is {@code false}
-     * zero is written.
-     *
-     * @param  v  boolean to be written.
-     * @throws IOException if some I/O exception occurs during writing.
-     */
-    @Override
-    public final void writeBoolean(final boolean v) throws IOException {
-        writeByte(v ? 1 : 0);
-    }
-
-    /**
-     * Writes the lower-order byte of each character. The high-order eight bits of each character
-     * in the string are ignored - this method does <strong>not</strong> applies any encoding.
-     *
-     * <p>This method is provided because required by the {@link DataOutput} interface, but its
-     * usage should generally be avoided.</p>
-     *
-     * @param  s  the string to be written.
-     * @throws IOException if an error occurred while writing the stream.
-     */
-    @Override
-    public void writeBytes(final String s) throws IOException {
-        final byte[] data = new byte[s.length()];
-        for (int i=0; i<data.length; i++) {
-            data[i] = (byte) s.charAt(i);
-        }
-        write(data);
-    }
-
-    /**
-     * Writes all characters from the source into the stream.
-     *
-     * @param  s  a String consider as an array of characters to be written into stream.
-     * @throws IOException if an error occurred while writing the stream.
-     */
-    @Override
-    public final void writeChars(final String s) throws IOException {
-        writeChars(s.toCharArray());
-    }
-
-    /**
-     * Writes two bytes of length information to the output stream, followed by the modified UTF-8 representation
-     * of every character in the {@code str} string. Each character is converted to a group of one, two, or three
-     * bytes, depending on the character code point value.
-     *
-     * @param  s  the string to be written.
-     * @throws IOException if an error occurred while writing the stream.
-     */
-    @Override
-    public void writeUTF(final String s) throws IOException {
-        byte[] data = s.getBytes("UTF-8");
-        if (data.length > Short.MAX_VALUE) {
-            throw new IllegalArgumentException(Resources.format(
-                    Resources.Keys.ExcessiveStringSize_3, filename, Short.MAX_VALUE, data.length));
-        }
-        final ByteOrder oldOrder = buffer.order();
-        buffer.order(ByteOrder.BIG_ENDIAN);
-        try {
-            writeShort(data.length);
-            write(data);
-        } finally {
-            buffer.order(oldOrder);
-        }
-    }
-
     /**
      * Closes the {@linkplain #channel}.
      *
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/StorageConnector.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/StorageConnector.java
index 0be3ca9ad4..eb2f6d4628 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/StorageConnector.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/StorageConnector.java
@@ -67,7 +67,6 @@ import org.apache.sis.io.stream.ChannelFactory;
 import org.apache.sis.io.stream.ChannelDataInput;
 import org.apache.sis.io.stream.ChannelDataOutput;
 import org.apache.sis.io.stream.ChannelImageInputStream;
-import org.apache.sis.io.stream.ChannelImageOutputStream;
 import org.apache.sis.io.stream.InputStreamAdapter;
 import org.apache.sis.io.stream.RewindableLineReader;
 import org.apache.sis.io.stream.InternalOptionKey;
@@ -1475,18 +1474,13 @@ public class StorageConnector implements Serializable {
         if (reset(c)) {
             out = (ChannelDataOutput) c.view;
         } else {
-            out = createChannelDataOutput();                        // May be null.
+            out = createChannelDataOutput();        // May be null. This method should not have been invoked before.
         }
         final DataOutput asDataOutput;
         if (out != null) {
-            c = getView(ChannelDataOutput.class);                   // May have been added by createChannelDataOutput(…).
-            if (out instanceof DataOutput) {
-                asDataOutput = (DataOutput) out;
-            } else {
-                asDataOutput = new ChannelImageOutputStream(out);   // Upgrade existing instance.
-                c.view = asDataOutput;
-            }
-            views.put(DataOutput.class, c);                         // Share the same Coupled instance.
+            asDataOutput = out;
+            c = getView(ChannelDataOutput.class);   // Refresh because may have been added by createChannelDataInput().
+            views.put(DataOutput.class, c);         // Share the same `Coupled` instance.
         } else {
             reset();
             try {
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 f6ec9c2e12..a7fe682c7c 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,9 +18,7 @@ package org.apache.sis.io.stream;
 
 import java.util.Arrays;
 import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
 import java.io.DataOutput;
-import java.io.DataOutputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.channels.ByteChannel;
@@ -52,7 +50,7 @@ public class ChannelDataOutputTest extends ChannelDataTestCase {
      * A stream to use as a reference implementation. Any data written in {@link #testedStream}
      * will also be written in {@code referenceStream}, for later comparison.
      */
-    DataOutput referenceStream;
+    ImageOutputStream referenceStream;
 
     /**
      * Byte array which is filled by the {@linkplain #testedStream} implementation during write operations.
@@ -65,7 +63,7 @@ public class ChannelDataOutputTest extends ChannelDataTestCase {
      * <b>Do not write to this stream</b> - this field is kept only for invocation of
      * {@link ByteArrayOutputStream#toByteArray()}.
      */
-    ByteArrayOutputStream expectedData;
+    private ByteArrayOutputStream expectedData;
 
     /**
      * Creates a new test case.
@@ -83,7 +81,7 @@ public class ChannelDataOutputTest extends ChannelDataTestCase {
      */
     void initialize(final String testName, final int streamLength, final int bufferLength) throws IOException {
         expectedData             = new ByteArrayOutputStream(streamLength);
-        referenceStream          = new DataOutputStream(expectedData);
+        referenceStream          = new MemoryCacheImageOutputStream(expectedData);
         testedStreamBackingArray = new byte[streamLength];
         testedStream             = new ChannelDataOutput(testName,
                 new ByteArrayChannel(testedStreamBackingArray, false), ByteBuffer.allocate(bufferLength));
@@ -123,7 +121,7 @@ public class ChannelDataOutputTest extends ChannelDataTestCase {
      */
     final void assertStreamContentEquals() throws IOException {
         testedStream.flush();
-        ((Closeable) referenceStream).close();
+        referenceStream.close();
         final byte[] expectedArray = expectedData.toByteArray();
         assertArrayEquals(expectedArray, Arrays.copyOf(testedStreamBackingArray, expectedArray.length));
     }
@@ -138,7 +136,7 @@ public class ChannelDataOutputTest extends ChannelDataTestCase {
     public void testWriteAndSeek() throws IOException {
         initialize("testWriteAndSeek", STREAM_LENGTH, randomBufferCapacity());
         writeInStreams();
-        ((Closeable) referenceStream).close();
+        referenceStream.close();
         final byte[] expectedArray = expectedData.toByteArray();
         final int seekRange = expectedArray.length - Long.BYTES;
         final ByteBuffer arrayView = ByteBuffer.wrap(expectedArray);
@@ -238,17 +236,18 @@ public class ChannelDataOutputTest extends ChannelDataTestCase {
      * @throws IOException should never happen since we read and write in memory only.
      */
     private void writeInStreams() throws IOException {
-        transferRandomData(testedStream, testedStreamBackingArray.length - ARRAY_MAX_LENGTH,
-                (testedStream instanceof DataOutput) ? 21 : 14);
+        transferRandomData(testedStream, testedStreamBackingArray.length - ARRAY_MAX_LENGTH, 21);
     }
 
     /**
      * Writes a random unit of data using a method selected randomly.
      * This method is invoked (indirectly) by {@link #writeInStreams()}.
+     *
+     * @param  operation  numerical identifier of the operation to test.
      */
     @Override
     final void transferRandomData(final int operation) throws IOException {
-        final DataOutput r = this.referenceStream;
+        final ImageOutputStream r = this.referenceStream;
         final ChannelDataOutput t = this.testedStream;
         switch (operation) {
             case 0: {
@@ -304,13 +303,8 @@ public class ChannelDataOutputTest extends ChannelDataTestCase {
                 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);
-                    if (!(r instanceof ImageOutputStream)) {
-                        r.writeChar(tmp[i]);
-                    }
-                }
-                if (r instanceof ImageOutputStream) {
-                    ((ImageOutputStream) r).writeChars(tmp, 0, tmp.length);
                 }
+                r.writeChars(tmp, 0, tmp.length);
                 t.writeChars(tmp);
                 break;
             }
@@ -318,13 +312,8 @@ public class ChannelDataOutputTest extends ChannelDataTestCase {
                 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);
-                    if (!(r instanceof ImageOutputStream)) {
-                        r.writeShort(tmp[i]);
-                    }
-                }
-                if (r instanceof ImageOutputStream) {
-                    ((ImageOutputStream) r).writeShorts(tmp, 0, tmp.length);
                 }
+                r.writeShorts(tmp, 0, tmp.length);
                 t.writeShorts(tmp);
                 break;
             }
@@ -332,13 +321,8 @@ public class ChannelDataOutputTest extends ChannelDataTestCase {
                 final int[] tmp = new int[random.nextInt(ARRAY_MAX_LENGTH / Integer.BYTES)];
                 for (int i=0; i<tmp.length; i++) {
                     tmp[i] = random.nextInt();
-                    if (!(r instanceof ImageOutputStream)) {
-                        r.writeInt(tmp[i]);
-                    }
-                }
-                if (r instanceof ImageOutputStream) {
-                    ((ImageOutputStream) r).writeInts(tmp, 0, tmp.length);
                 }
+                r.writeInts(tmp, 0, tmp.length);
                 t.writeInts(tmp);
                 break;
             }
@@ -346,13 +330,8 @@ public class ChannelDataOutputTest extends ChannelDataTestCase {
                 final long[] tmp = new long[random.nextInt(ARRAY_MAX_LENGTH / Long.BYTES)];
                 for (int i=0; i<tmp.length; i++) {
                     tmp[i] = random.nextLong();
-                    if (!(r instanceof ImageOutputStream)) {
-                        r.writeLong(tmp[i]);
-                    }
-                }
-                if (r instanceof ImageOutputStream) {
-                    ((ImageOutputStream) r).writeLongs(tmp, 0, tmp.length);
                 }
+                r.writeLongs(tmp, 0, tmp.length);
                 t.writeLongs(tmp);
                 break;
             }
@@ -360,13 +339,8 @@ public class ChannelDataOutputTest extends ChannelDataTestCase {
                 final float[] tmp = new float[random.nextInt(ARRAY_MAX_LENGTH / Float.BYTES)];
                 for (int i=0; i<tmp.length; i++) {
                     tmp[i] = random.nextFloat();
-                    if (!(r instanceof ImageOutputStream)) {
-                        r.writeFloat(tmp[i]);
-                    }
-                }
-                if (r instanceof ImageOutputStream) {
-                    ((ImageOutputStream) r).writeFloats(tmp, 0, tmp.length);
                 }
+                r.writeFloats(tmp, 0, tmp.length);
                 t.writeFloats(tmp);
                 break;
             }
@@ -374,42 +348,34 @@ public class ChannelDataOutputTest extends ChannelDataTestCase {
                 final double[] tmp = new double[random.nextInt(ARRAY_MAX_LENGTH / Double.BYTES)];
                 for (int i=0; i<tmp.length; i++) {
                     tmp[i] = random.nextDouble();
-                    if (!(r instanceof ImageOutputStream)) {
-                        r.writeDouble(tmp[i]);
-                    }
-                }
-                if (r instanceof ImageOutputStream) {
-                    ((ImageOutputStream) r).writeDoubles(tmp, 0, tmp.length);
                 }
+                r.writeDoubles(tmp, 0, tmp.length);
                 t.writeDoubles(tmp);
                 break;
             }
-            /*
-             * Cases below this point are executed only by ChannelImageOutputStreamTest.
-             */
             case 14: {
                 final long v = random.nextLong();
                 final int numBits = random.nextInt(Byte.SIZE);
-                ((ImageOutputStream) r).writeBits(v, numBits);
+                r.writeBits(v, numBits);
                 t.writeBits(v, numBits);
                 break;
             }
             case 15: {
                 final boolean v = random.nextBoolean();
                 r.writeBoolean(v);
-                ((DataOutput) t).writeBoolean(v);
+                t.writeBoolean(v);
                 break;
             }
             case 16: {
                 final String s = "Byte sequence";
                 r.writeBytes(s);
-                ((DataOutput) t).writeBytes(s);
+                t.writeBytes(s);
                 break;
             }
             case 17: {
                 final String s = "Character sequence";
                 r.writeChars(s);
-                ((DataOutput) t).writeChars(s);
+                t.writeChars(s);
                 break;
             }
             case 18: {
@@ -417,27 +383,24 @@ public class ChannelDataOutputTest extends ChannelDataTestCase {
                 final byte[] array = s.getBytes("UTF-8");
                 assertEquals(s.length() * 3, array.length); // Sanity check.
                 r.writeUTF(s);
-                ((DataOutput) t).writeUTF(s);
+                t.writeUTF(s);
                 break;
             }
             case 19: {
-                final ImageOutputStream ir = (ImageOutputStream) r;
-                long flushedPosition = StrictMath.max(ir.getFlushedPosition(), t.getFlushedPosition());
-                flushedPosition += random.nextInt(1 + (int) (ir.getStreamPosition() - flushedPosition));
-                ir.flushBefore(flushedPosition);
-                t .flushBefore(flushedPosition);
+                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: {
-                ((ImageOutputStream) r).flush();
+                r.flush();
                 t.flush();
                 break;
             }
             default: throw new AssertionError(operation);
         }
-        if (r instanceof ImageOutputStream) {
-            assertEquals("getBitOffset()",      ((ImageOutputStream) r).getBitOffset(),      t.getBitOffset());
-            assertEquals("getStreamPosition()", ((ImageOutputStream) r).getStreamPosition(), t.getStreamPosition());
-        }
+        assertEquals("getBitOffset()",      r.getBitOffset(),      t.getBitOffset());
+        assertEquals("getStreamPosition()", r.getStreamPosition(), t.getStreamPosition());
     }
 }
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 5d0d024761..9fe710f87d 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
@@ -17,9 +17,6 @@
 package org.apache.sis.io.stream;
 
 import java.io.IOException;
-import java.io.ByteArrayOutputStream;
-import java.nio.ByteBuffer;
-import javax.imageio.stream.ImageOutputStream;
 
 // Test dependencies
 import org.junit.Test;
@@ -47,12 +44,9 @@ public final class ChannelImageOutputStreamTest extends ChannelDataOutputTest {
      * Initializes all non-final fields before to execute a test.
      */
     @Override
-    void initialize(final String fileName, final int streamLength, final int bufferLength) throws IOException {
-        expectedData             = new ByteArrayOutputStream(streamLength);
-        referenceStream          = new MemoryCacheImageOutputStream(expectedData);
-        testedStreamBackingArray = new byte[streamLength];
-        testedStream             = new ChannelImageOutputStream(fileName,
-                new ByteArrayChannel(testedStreamBackingArray, false), ByteBuffer.allocate(bufferLength));
+    void initialize(final String testName, final int streamLength, final int bufferLength) throws IOException {
+        super.initialize(testName, streamLength, bufferLength);
+        testedStream = new ChannelImageOutputStream(testedStream);
     }
 
     /**
@@ -63,8 +57,7 @@ public final class ChannelImageOutputStreamTest extends ChannelDataOutputTest {
     @Test
     public void testWriteBits() throws IOException {
         initialize("testWriteBits", STREAM_LENGTH, randomBufferCapacity());
-        final ImageOutputStream referenceStream = (ImageOutputStream) this.referenceStream;
-        final int length = testedStreamBackingArray.length - ARRAY_MAX_LENGTH; // Keep a margin against buffer underflow.
+        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);
@@ -92,7 +85,6 @@ public final class ChannelImageOutputStreamTest extends ChannelDataOutputTest {
     @Test
     public void testMarkAndReset() throws IOException {
         initialize("testMarkAndReset", STREAM_LENGTH, 1000); // We need a larger buffer for this test.
-        final ImageOutputStream referenceStream = (ImageOutputStream) this.referenceStream;
         /*
          * Fill both streams with random data.
          * During this process, randomly takes mark.
@@ -117,7 +109,6 @@ public final class ChannelImageOutputStreamTest extends ChannelDataOutputTest {
      * stream content.
      */
     private void compareMarks(int nbMarks) throws IOException {
-        final ImageOutputStream referenceStream = (ImageOutputStream) this.referenceStream;
         while (--nbMarks >= 0) {
             referenceStream.reset();
             testedStream.reset();
@@ -150,7 +141,6 @@ public final class ChannelImageOutputStreamTest extends ChannelDataOutputTest {
     public void testFlushBefore() throws IOException {
         final int N = 50; // Number of long values to write.
         initialize("testFlushBefore", N*Long.BYTES, 200);
-        final ImageOutputStream referenceStream = (ImageOutputStream) this.referenceStream;
         for (int i=0; i<N; i++) {
             switch (i) {
                 case 20: