From b11ad994fd401043ff3d93d61c2d4e77aca5dc73 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Wed, 19 Jul 2023 19:22:17 +0200 Subject: [PATCH] classlib: support more methods in InputStream --- .../java/io/TByteArrayInputStream.java | 12 ++ .../teavm/classlib/java/io/TInputStream.java | 177 ++++++++++++++++++ .../classlib/java/io/InputStreamTest.java | 142 ++++++++++++++ 3 files changed, 331 insertions(+) create mode 100644 tests/src/test/java/org/teavm/classlib/java/io/InputStreamTest.java diff --git a/classlib/src/main/java/org/teavm/classlib/java/io/TByteArrayInputStream.java b/classlib/src/main/java/org/teavm/classlib/java/io/TByteArrayInputStream.java index cc07b9078..ca2a33ccc 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/io/TByteArrayInputStream.java +++ b/classlib/src/main/java/org/teavm/classlib/java/io/TByteArrayInputStream.java @@ -15,6 +15,8 @@ */ package org.teavm.classlib.java.io; +import java.io.IOException; +import java.util.Arrays; import org.teavm.classlib.java.lang.TMath; public class TByteArrayInputStream extends TInputStream { @@ -78,4 +80,14 @@ public class TByteArrayInputStream extends TInputStream { @Override public void close() { } + + @Override + public byte[] readAllBytes() throws IOException { + return Arrays.copyOfRange(buf, pos, count); + } + + @Override + public byte[] readNBytes(int len) throws IOException { + return Arrays.copyOfRange(buf, pos, Math.min(count, pos + len)); + } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/io/TInputStream.java b/classlib/src/main/java/org/teavm/classlib/java/io/TInputStream.java index d7dce0fe4..f5b606dfe 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/io/TInputStream.java +++ b/classlib/src/main/java/org/teavm/classlib/java/io/TInputStream.java @@ -16,10 +16,16 @@ package org.teavm.classlib.java.io; import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import org.teavm.classlib.java.lang.TInteger; import org.teavm.classlib.java.lang.TObject; public abstract class TInputStream extends TObject implements TCloseable { + private static final int BUFFER_SIZE = 2048; + public TInputStream() { } @@ -80,4 +86,175 @@ public abstract class TInputStream extends TObject implements TCloseable { public boolean markSupported() { return false; } + + public byte[] readAllBytes() throws IOException { + return readNBytes(Integer.MAX_VALUE); + } + + public byte[] readNBytes(int len) throws IOException { + if (len < 0) { + throw new IndexOutOfBoundsException(); + } + if (len == 0) { + return new byte[0]; + } + + List buffers = null; + var buffer = new byte[BUFFER_SIZE]; + var positionInBuffer = 0; + var totalBytesRead = 0; + while (true) { + var bytesToRead = Math.min(buffer.length - positionInBuffer, len - totalBytesRead); + var bytesRead = read(buffer, positionInBuffer, bytesToRead); + if (bytesRead < 0) { + break; + } + positionInBuffer += bytesRead; + totalBytesRead += bytesRead; + if (totalBytesRead == len) { + break; + } + if (positionInBuffer * 2 > buffer.length) { + if (buffers == null) { + buffers = new ArrayList<>(); + } + buffers.add(Arrays.copyOf(buffer, positionInBuffer)); + positionInBuffer = 0; + } + } + if (buffers == null) { + return positionInBuffer == buffer.length ? buffer : Arrays.copyOf(buffer, positionInBuffer); + } + + var result = new byte[totalBytesRead]; + var ptr = 0; + for (var part : buffers) { + System.arraycopy(part, 0, result, ptr, part.length); + ptr += part.length; + } + if (positionInBuffer > 0) { + System.arraycopy(buffer, 0, result, ptr, positionInBuffer); + } + return result; + } + + public int readNBytes(byte[] b, int off, int len) throws IOException { + if (off < 0 || len < 0 || off + len >= b.length) { + throw new IndexOutOfBoundsException(); + } + if (len == 0) { + return 0; + } + + var initialLen = len; + + while (true) { + var bytesRead = read(b, off, len); + if (bytesRead < 0) { + break; + } + off += bytesRead; + len -= bytesRead; + } + + return initialLen - len; + } + + public void skipNBytes(long n) throws IOException { + if (n < 0) { + throw new IndexOutOfBoundsException(); + } + while (n > 0) { + var bytesSkipped = skip(n); + if (bytesSkipped < 0) { + throw new IOException(); + } + n -= bytesSkipped; + if (n == 0) { + break; + } + if (n < 0) { + throw new IOException(); + } + } + } + + public long transferTo(OutputStream out) throws IOException { + var buffer = new byte[BUFFER_SIZE]; + var bytesTransferred = 0L; + while (true) { + var bytesRead = read(buffer); + if (bytesRead < 0) { + break; + } + out.write(buffer, 0, bytesRead); + bytesTransferred += bytesRead; + } + return bytesTransferred; + } + + public static TInputStream nullInputStream() { + return new TInputStream() { + private boolean closed; + + @Override + public void close() { + closed = true; + } + + @Override + public int read() throws IOException { + checkOpen(); + return -1; + } + + @Override + public int read(byte[] b) throws IOException { + checkOpen(); + return -1; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + checkOpen(); + return -1; + } + + @Override + public byte[] readAllBytes() throws IOException { + checkOpen(); + return new byte[0]; + } + + @Override + public byte[] readNBytes(int len) throws IOException { + checkOpen(); + return new byte[0]; + } + + @Override + public long skip(long n) throws IOException { + checkOpen(); + if (n > 0) { + throw new IOException(); + } else if (n < 0) { + throw new IndexOutOfBoundsException(); + } + return 0; + } + + @Override + public void skipNBytes(long n) throws IOException { + if (n > 0) { + throw new IOException(); + } + } + + private void checkOpen() throws IOException { + if (closed) { + throw new IOException(); + } + } + }; + } } diff --git a/tests/src/test/java/org/teavm/classlib/java/io/InputStreamTest.java b/tests/src/test/java/org/teavm/classlib/java/io/InputStreamTest.java new file mode 100644 index 000000000..4637d728d --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/io/InputStreamTest.java @@ -0,0 +1,142 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.io; + +import static org.testng.Assert.assertEquals; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.junit.TeaVMTestRunner; +import org.teavm.junit.WholeClassCompilation; + +@RunWith(TeaVMTestRunner.class) +@WholeClassCompilation +public class InputStreamTest { + @Test + @SuppressWarnings("resource") + public void readAll() throws IOException { + var is = new ChunkedInputStream(17, 1000); + var bytes = is.readAllBytes(); + assertEquals(17_000, bytes.length); + for (var i = 0; i < bytes.length; ++i) { + assertEquals(i % 17, bytes[i], "wrong element at position " + i); + } + } + + @Test + @SuppressWarnings("resource") + public void readAllWithLargeChunks() throws IOException { + var is = new ChunkedInputStream(3000, 2); + var bytes = is.readAllBytes(); + assertEquals(6000, bytes.length); + for (var i = 0; i < bytes.length; ++i) { + assertEquals((byte) (i % 3000), bytes[i], "wrong element at position " + i); + } + } + + @Test + @SuppressWarnings("resource") + public void readN() throws IOException { + var is = new ChunkedInputStream(17, 1000); + var bytes = is.readNBytes(5000); + assertEquals(5000, bytes.length); + for (var i = 0; i < bytes.length; ++i) { + assertEquals(i % 17, bytes[i], "wrong element at position " + i); + } + } + + @Test + @SuppressWarnings("resource") + public void transfer() throws IOException { + var is = new ChunkedInputStream(17, 1000); + var os = new ByteArrayOutputStream(); + is.transferTo(os); + var bytes = os.toByteArray(); + assertEquals(17_000, bytes.length); + for (var i = 0; i < bytes.length; ++i) { + assertEquals(i % 17, bytes[i], "wrong element at position " + i); + } + } + + @Test + @SuppressWarnings("resource") + public void skipN() throws IOException { + var is = new ChunkedInputStream(17, 1000); + is.skipNBytes(19); + assertEquals(2, is.read()); + assertEquals(17_000 - 20, is.readAllBytes().length); + } + + static class ChunkedInputStream extends InputStream { + private int chunkSize; + private int chunkRepeats; + private int lastValue; + + ChunkedInputStream(int chunkSize, int chunkRepeats) { + this.chunkSize = chunkSize; + this.chunkRepeats = chunkRepeats; + } + + @Override + public int read() throws IOException { + if (chunkRepeats == 0) { + return -1; + } + var result = lastValue++; + if (lastValue == chunkSize) { + lastValue = 0; + chunkRepeats--; + } + return result; + } + + @Override + public long skip(long n) throws IOException { + if (chunkRepeats == 0) { + return -1; + } + var value = lastValue; + n = Math.min(n, chunkSize - value); + value += (int) n; + if (value == chunkSize) { + value = 0; + chunkRepeats--; + } + lastValue = value; + return n; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (chunkRepeats == 0) { + return -1; + } + var value = lastValue; + len = Math.min(len, chunkSize - value); + for (var i = 0; i < len; ++i) { + b[off++] = (byte) value++; + } + if (value == chunkSize) { + value = 0; + chunkRepeats--; + } + lastValue = value; + return len; + } + } +}