diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java index ec542a46..2c421c44 100644 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java @@ -45,39 +45,39 @@ import java.nio.ByteBuffer; * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java#2 $ */ public final class DecoderStream extends FilterInputStream { - protected final ByteBuffer buffer; - protected final Decoder decoder; + private final ByteBuffer buffer; + private final Decoder decoder; /** * Creates a new decoder stream and chains it to the - * input stream specified by the {@code pStream} argument. + * input stream specified by the {@code stream} argument. * The stream will use a default decode buffer size. * - * @param pStream the underlying input stream. - * @param pDecoder the decoder that will be used to decode the underlying stream + * @param stream the underlying input stream. + * @param decoder the decoder that will be used to decode the underlying stream * * @see java.io.FilterInputStream#in */ - public DecoderStream(final InputStream pStream, final Decoder pDecoder) { + public DecoderStream(final InputStream stream, final Decoder decoder) { // TODO: Let the decoder decide preferred buffer size - this(pStream, pDecoder, 1024); + this(stream, decoder, 1024); } /** * Creates a new decoder stream and chains it to the - * input stream specified by the {@code pStream} argument. + * input stream specified by the {@code stream} argument. * - * @param pStream the underlying input stream. - * @param pDecoder the decoder that will be used to decode the underlying stream - * @param pBufferSize the size of the decode buffer + * @param stream the underlying input stream. + * @param decoder the decoder that will be used to decode the underlying stream + * @param bufferSize the size of the decode buffer * * @see java.io.FilterInputStream#in */ - public DecoderStream(final InputStream pStream, final Decoder pDecoder, final int pBufferSize) { - super(pStream); + public DecoderStream(final InputStream stream, final Decoder decoder, final int bufferSize) { + super(stream); - decoder = pDecoder; - buffer = ByteBuffer.allocate(pBufferSize); + this.decoder = decoder; + buffer = ByteBuffer.allocate(bufferSize); buffer.flip(); } @@ -95,15 +95,15 @@ public final class DecoderStream extends FilterInputStream { return buffer.get() & 0xff; } - public int read(final byte pBytes[], final int pOffset, final int pLength) throws IOException { - if (pBytes == null) { + public int read(final byte[] bytes, final int offset, final int length) throws IOException { + if (bytes == null) { throw new NullPointerException(); } - else if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) || - ((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) { - throw new IndexOutOfBoundsException("bytes.length=" + pBytes.length + " offset=" + pOffset + " length=" + pLength); + else if ((offset < 0) || (offset > bytes.length) || (length < 0) || + ((offset + length) > bytes.length) || ((offset + length) < 0)) { + throw new IndexOutOfBoundsException("bytes.length=" + bytes.length + " offset=" + offset + " length=" + length); } - else if (pLength == 0) { + else if (length == 0) { return 0; } @@ -114,11 +114,11 @@ public final class DecoderStream extends FilterInputStream { } } - // Read until we have read pLength bytes, or have reached EOF + // Read until we have read length bytes, or have reached EOF int count = 0; - int off = pOffset; + int off = offset; - while (pLength > count) { + while (length > count) { if (!buffer.hasRemaining()) { if (fill() < 0) { break; @@ -126,8 +126,8 @@ public final class DecoderStream extends FilterInputStream { } // Copy as many bytes as possible - int dstLen = Math.min(pLength - count, buffer.remaining()); - buffer.get(pBytes, off, dstLen); + int dstLen = Math.min(length - count, buffer.remaining()); + buffer.get(bytes, off, dstLen); // Update offset (rest) off += dstLen; @@ -139,7 +139,7 @@ public final class DecoderStream extends FilterInputStream { return count; } - public long skip(final long pLength) throws IOException { + public long skip(final long length) throws IOException { // End of file? if (!buffer.hasRemaining()) { if (fill() < 0) { @@ -147,10 +147,10 @@ public final class DecoderStream extends FilterInputStream { } } - // Skip until we have skipped pLength bytes, or have reached EOF + // Skip until we have skipped length bytes, or have reached EOF long total = 0; - while (total < pLength) { + while (total < length) { if (!buffer.hasRemaining()) { if (fill() < 0) { break; @@ -158,7 +158,7 @@ public final class DecoderStream extends FilterInputStream { } // NOTE: Skipped can never be more than avail, which is an int, so the cast is safe - int skipped = (int) Math.min(pLength - total, buffer.remaining()); + int skipped = (int) Math.min(length - total, buffer.remaining()); buffer.position(buffer.position() + skipped); total += skipped; } @@ -174,7 +174,7 @@ public final class DecoderStream extends FilterInputStream { * * @throws IOException if an I/O error occurs */ - protected int fill() throws IOException { + private int fill() throws IOException { buffer.clear(); int read = decoder.decode(in, buffer); diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java index dafceb2e..19afa032 100644 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java @@ -45,41 +45,39 @@ import java.nio.ByteBuffer; * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java#2 $ */ public final class EncoderStream extends FilterOutputStream { - // TODO: This class need a test case ASAP!!! - protected final Encoder encoder; + private final Encoder encoder; private final boolean flushOnWrite; - protected final ByteBuffer buffer; + private final ByteBuffer buffer; /** * Creates an output stream filter built on top of the specified * underlying output stream. * - * @param pStream the underlying output stream - * @param pEncoder the encoder to use + * @param stream the underlying output stream + * @param encoder the encoder to use */ - public EncoderStream(final OutputStream pStream, final Encoder pEncoder) { - this(pStream, pEncoder, false); + public EncoderStream(final OutputStream stream, final Encoder encoder) { + this(stream, encoder, false); } /** * Creates an output stream filter built on top of the specified * underlying output stream. * - * @param pStream the underlying output stream - * @param pEncoder the encoder to use - * @param pFlushOnWrite if {@code true}, calls to the byte-array + * @param stream the underlying output stream + * @param encoder the encoder to use + * @param flushOnWrite if {@code true}, calls to the byte-array * {@code write} methods will automatically flush the buffer. */ - public EncoderStream(final OutputStream pStream, final Encoder pEncoder, final boolean pFlushOnWrite) { - super(pStream); + public EncoderStream(final OutputStream stream, final Encoder encoder, final boolean flushOnWrite) { + super(stream); - encoder = pEncoder; - flushOnWrite = pFlushOnWrite; + this.encoder = encoder; + this.flushOnWrite = flushOnWrite; buffer = ByteBuffer.allocate(1024); - buffer.flip(); } public void close() throws IOException { @@ -104,33 +102,33 @@ public final class EncoderStream extends FilterOutputStream { } } - public final void write(final byte[] pBytes) throws IOException { - write(pBytes, 0, pBytes.length); + public void write(final byte[] bytes) throws IOException { + write(bytes, 0, bytes.length); } // TODO: Verify that this works for the general case (it probably won't)... // TODO: We might need a way to explicitly flush the encoder, or specify // that the encoder can't buffer. In that case, the encoder should probably - // tell the EncoderStream how large buffer it prefers... - public void write(final byte[] pBytes, final int pOffset, final int pLength) throws IOException { - if (!flushOnWrite && pLength < buffer.remaining()) { + // tell the EncoderStream how large buffer it prefers... + public void write(final byte[] values, final int offset, final int length) throws IOException { + if (!flushOnWrite && length < buffer.remaining()) { // Buffer data - buffer.put(pBytes, pOffset, pLength); + buffer.put(values, offset, length); } else { // Encode data already in the buffer encodeBuffer(); // Encode rest without buffering - encoder.encode(out, ByteBuffer.wrap(pBytes, pOffset, pLength)); + encoder.encode(out, ByteBuffer.wrap(values, offset, length)); } } - public void write(final int pByte) throws IOException { + public void write(final int value) throws IOException { if (!buffer.hasRemaining()) { encodeBuffer(); // Resets bufferPos to 0 } - buffer.put((byte) pByte); + buffer.put((byte) value); } } diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/enc/DecoderStreamTest.java b/common/common-io/src/test/java/com/twelvemonkeys/io/enc/DecoderStreamTest.java new file mode 100644 index 00000000..8982b485 --- /dev/null +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/enc/DecoderStreamTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2022, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.io.enc; + +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Random; + +import static org.junit.Assert.*; + +public class DecoderStreamTest { + + private final Random rng = new Random(5467809876546L); + + private byte[] createData(final int length) { + byte[] data = new byte[length]; + rng.nextBytes(data); + return data; + } + + @Test + public void testDecodeSingleBytes() throws IOException { + byte[] data = createData(1327); + + InputStream source = new ByteArrayInputStream(data); + try (InputStream stream = new DecoderStream(source, new NullDecoder())) { + for (byte datum : data) { + int read = stream.read(); + assertNotEquals(-1, read); + assertEquals(datum, (byte) read); + } + + assertEquals(-1, stream.read()); + } + } + + @Test + public void testDecodeArray() throws IOException { + int length = 793; + byte[] data = createData(length * 10); + + InputStream source = new ByteArrayInputStream(data); + byte[] result = new byte[477]; + + try (InputStream stream = new DecoderStream(source, new NullDecoder())) { + int dataOffset = 0; + while (dataOffset < data.length) { + int count = stream.read(result); + + assertFalse(count <= 0); + assertArrayEquals(Arrays.copyOfRange(data, dataOffset, dataOffset + count), Arrays.copyOfRange(result, 0, count)); + + dataOffset += count; + } + + assertEquals(-1, stream.read()); + } + } + + @Test + public void testDecodeArrayOffset() throws IOException { + int length = 793; + byte[] data = createData(length * 10); + + InputStream source = new ByteArrayInputStream(data); + byte[] result = new byte[477]; + + try (InputStream stream = new DecoderStream(source, new NullDecoder())) { + int dataOffset = 0; + while (dataOffset < data.length) { + int resultOffset = dataOffset % result.length; + int count = stream.read(result, resultOffset, result.length - resultOffset); + + assertFalse(count <= 0); + assertArrayEquals(Arrays.copyOfRange(data, dataOffset + resultOffset, dataOffset + count), Arrays.copyOfRange(result, resultOffset, count)); + + dataOffset += count; + } + + assertEquals(-1, stream.read()); + } + } + + private static final class NullDecoder implements Decoder { + @Override + public int decode(InputStream stream, ByteBuffer buffer) throws IOException { + int read = stream.read(buffer.array(), buffer.arrayOffset(), buffer.remaining()); + + if (read > 0) { + // Set position, should be equivalent to using buffer.put(stream.read()) until EOF or buffer full + buffer.position(read); + } + + return read; + } + } +} \ No newline at end of file diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/enc/EncoderStreamTest.java b/common/common-io/src/test/java/com/twelvemonkeys/io/enc/EncoderStreamTest.java new file mode 100644 index 00000000..a4c8cdff --- /dev/null +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/enc/EncoderStreamTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2022, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.io.enc; + +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Random; + +import static org.junit.Assert.assertArrayEquals; + +public class EncoderStreamTest { + + private final Random rng = new Random(5467809876546L); + + private byte[] createData(final int length) { + byte[] data = new byte[length]; + rng.nextBytes(data); + return data; + } + + @Test + public void testEncodeSingleBytes() throws IOException { + byte[] data = createData(1327); + + ByteArrayOutputStream result = new ByteArrayOutputStream(); + try (OutputStream stream = new EncoderStream(result, new NullEncoder())) { + for (byte datum : data) { + stream.write(datum); + } + } + + assertArrayEquals(data, result.toByteArray()); + } + + @Test + public void testEncodeArray() throws IOException { + byte[] data = createData(1793); + + ByteArrayOutputStream result = new ByteArrayOutputStream(); + try (OutputStream stream = new EncoderStream(result, new NullEncoder())) { + for (int i = 0; i < 10; i++) { + stream.write(data); + } + } + + byte[] encoded = result.toByteArray(); + + for (int i = 0; i < 10; i++) { + assertArrayEquals(data, Arrays.copyOfRange(encoded, i * data.length, (i + 1) * data.length)); + } + } + + @Test + public void testEncodeArrayOffset() throws IOException { + byte[] data = createData(87); + + ByteArrayOutputStream result = new ByteArrayOutputStream(); + try (OutputStream stream = new EncoderStream(result, new NullEncoder())) { + for (int i = 0; i < 10; i++) { + stream.write(data, 13, 59); + } + } + + byte[] original = Arrays.copyOfRange(data, 13, 13 + 59); + byte[] encoded = result.toByteArray(); + + for (int i = 0; i < 10; i++) { + assertArrayEquals(original, Arrays.copyOfRange(encoded, i * original.length, (i + 1) * original.length)); + } + } + + private static final class NullEncoder implements Encoder { + @Override + public void encode(OutputStream stream, ByteBuffer buffer) throws IOException { + stream.write(buffer.array(), buffer.arrayOffset(), buffer.remaining()); + } + } +} \ No newline at end of file