classlib: support more methods in InputStream

This commit is contained in:
Alexey Andreev 2023-07-19 19:22:17 +02:00
parent b1ddf163d7
commit b11ad994fd
3 changed files with 331 additions and 0 deletions

View File

@ -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));
}
}

View File

@ -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<byte[]> 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();
}
}
};
}
}

View File

@ -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;
}
}
}