From d5e602b38e95b2b9543867d03e06d5d552e6fc15 Mon Sep 17 00:00:00 2001 From: konsoletyper Date: Tue, 4 Nov 2014 21:11:27 +0300 Subject: [PATCH] Add PushbackInputStream and DataInputStream --- .../teavm/classlib/java/io/TDataInput.java | 54 ++++ .../classlib/java/io/TDataInputStream.java | 270 ++++++++++++++++++ .../java/io/TPushbackInputStream.java | 176 ++++++++++++ .../java/io/TUTFDataFormatException.java | 34 +++ .../java/io/PushbackInputStreamTest.java | 199 +++++++++++++ 5 files changed, 733 insertions(+) create mode 100644 teavm-classlib/src/main/java/org/teavm/classlib/java/io/TDataInput.java create mode 100644 teavm-classlib/src/main/java/org/teavm/classlib/java/io/TDataInputStream.java create mode 100644 teavm-classlib/src/main/java/org/teavm/classlib/java/io/TPushbackInputStream.java create mode 100644 teavm-classlib/src/main/java/org/teavm/classlib/java/io/TUTFDataFormatException.java create mode 100644 teavm-classlib/src/test/java/org/teavm/classlib/java/io/PushbackInputStreamTest.java diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/io/TDataInput.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/io/TDataInput.java new file mode 100644 index 000000000..8210478e9 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/io/TDataInput.java @@ -0,0 +1,54 @@ +/* + * Copyright 2014 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 org.teavm.classlib.java.lang.TString; + +/** + * + * @author Alexey Andreev + */ +public interface TDataInput { + void readFully(byte[] b) throws TIOException; + + void readFully(byte[] b, int off, int len) throws TIOException; + + int skipBytes(int n) throws TIOException; + + boolean readBoolean() throws TIOException; + + byte readByte() throws TIOException; + + int readUnsignedByte() throws TIOException; + + short readShort() throws TIOException; + + int readUnsignedShort() throws TIOException; + + char readChar() throws TIOException; + + int readInt() throws TIOException; + + long readLong() throws TIOException; + + float readFloat() throws TIOException; + + double readDouble() throws TIOException; + + TString readLine() throws TIOException; + + TString readUTF() throws TIOException; +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/io/TDataInputStream.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/io/TDataInputStream.java new file mode 100644 index 000000000..6a20a5677 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/io/TDataInputStream.java @@ -0,0 +1,270 @@ +/* + * Copyright 2014 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 org.teavm.classlib.java.lang.*; + +/** + * + * @author Alexey Andreev + */ +public class TDataInputStream extends TFilterInputStream implements TDataInput { + byte[] buff; + + public TDataInputStream(TInputStream in) { + super(in); + buff = new byte[8]; + } + + @Override + public final int read(byte[] buffer) throws TIOException { + return in.read(buffer, 0, buffer.length); + } + + @Override + public final int read(byte[] buffer, int offset, int length) throws TIOException { + return in.read(buffer, offset, length); + } + + @Override + public final boolean readBoolean() throws TIOException { + int temp = in.read(); + if (temp < 0) { + throw new TEOFException(); + } + return temp != 0; + } + + @Override + public final byte readByte() throws TIOException { + int temp = in.read(); + if (temp < 0) { + throw new TEOFException(); + } + return (byte)temp; + } + + private int readToBuff(int count) throws TIOException { + int offset = 0; + while(offset < count) { + int bytesRead = in.read(buff, offset, count - offset); + if(bytesRead == -1) return bytesRead; + offset += bytesRead; + } + return offset; + } + + @Override + public final char readChar() throws TIOException { + if (readToBuff(2) < 0){ + throw new TEOFException(); + } + return (char) (((buff[0] & 0xff) << 8) | (buff[1] & 0xff)); + + } + + @Override + public final double readDouble() throws TIOException { + return TDouble.longBitsToDouble(readLong()); + } + + @Override + public final float readFloat() throws TIOException { + return TFloat.intBitsToFloat(readInt()); + } + + @Override + public final void readFully(byte[] buffer) throws TIOException { + readFully(buffer, 0, buffer.length); + } + + @Override + public final void readFully(byte[] buffer, int offset, int length) throws TIOException { + if (length < 0) { + throw new TIndexOutOfBoundsException(); + } + if (length == 0) { + return; + } + if (in == null) { + throw new TNullPointerException(); + } + if (buffer == null) { + throw new TNullPointerException(); + } + if (offset < 0 || offset > buffer.length - length) { + throw new TIndexOutOfBoundsException(); + } + while (length > 0) { + int result = in.read(buffer, offset, length); + if (result < 0) { + throw new TEOFException(); + } + offset += result; + length -= result; + } + } + + @Override + public final int readInt() throws TIOException { + if (readToBuff(4) < 0){ + throw new TEOFException(); + } + return ((buff[0] & 0xff) << 24) | ((buff[1] & 0xff) << 16) | ((buff[2] & 0xff) << 8) | (buff[3] & 0xff); + } + + @Override + @Deprecated + public final TString readLine() throws TIOException { + TStringBuilder line = new TStringBuilder(80); + boolean foundTerminator = false; + while (true) { + int nextByte = in.read(); + switch (nextByte) { + case -1: + if (line.length() == 0 && !foundTerminator) { + return null; + } + return TString.wrap(line.toString()); + case (byte) '\r': + if (foundTerminator) { + ((TPushbackInputStream)in).unread(nextByte); + return TString.wrap(line.toString()); + } + foundTerminator = true; + /* Have to be able to peek ahead one byte */ + if (!(in.getClass() == TPushbackInputStream.class)) { + in = new TPushbackInputStream(in); + } + break; + case (byte) '\n': + return TString.wrap(line.toString()); + default: + if (foundTerminator) { + ((TPushbackInputStream)in).unread(nextByte); + return TString.wrap(line.toString()); + } + line.append((char) nextByte); + } + } + } + + @Override + public final long readLong() throws TIOException { + if (readToBuff(8) < 0){ + throw new TEOFException(); + } + int i1 = ((buff[0] & 0xff) << 24) | ((buff[1] & 0xff) << 16) | + ((buff[2] & 0xff) << 8) | (buff[3] & 0xff); + int i2 = ((buff[4] & 0xff) << 24) | ((buff[5] & 0xff) << 16) | + ((buff[6] & 0xff) << 8) | (buff[7] & 0xff); + return ((i1 & 0xffffffffL) << 32) | (i2 & 0xffffffffL); + } + + @Override + public final short readShort() throws TIOException { + if (readToBuff(2) < 0){ + throw new TEOFException(); + } + return (short) (((buff[0] & 0xff) << 8) | (buff[1] & 0xff)); + } + + @Override + public final int readUnsignedByte() throws TIOException { + int temp = in.read(); + if (temp < 0) { + throw new TEOFException(); + } + return temp; + } + + @Override + public final int readUnsignedShort() throws TIOException { + if (readToBuff(2) < 0){ + throw new TEOFException(); + } + return (char) (((buff[0] & 0xff) << 8) | (buff[1] & 0xff)); + } + + @Override + public final TString readUTF() throws TIOException { + return decodeUTF(readUnsignedShort()); + } + + TString decodeUTF(int utfSize) throws TIOException { + return decodeUTF(utfSize, this); + } + + private static TString decodeUTF(int utfSize, TDataInput in) throws TIOException { + byte[] buf = new byte[utfSize]; + char[] out = new char[utfSize]; + in.readFully(buf, 0, utfSize); + + return convertUTF8WithBuf(buf, out, 0, utfSize); + } + + public static final TString readUTF(TDataInput in) throws TIOException { + return decodeUTF(in.readUnsignedShort(), in); + } + + @Override + public final int skipBytes(int count) throws TIOException { + int skipped = 0; + long skip; + while (skipped < count && (skip = in.skip(count - skipped)) != 0) { + skipped += skip; + } + if (skipped < 0) { + throw new TEOFException(); + } + return skipped; + } + + private static TString convertUTF8WithBuf(byte[] buf, char[] out, int offset, int utfSize) + throws TUTFDataFormatException { + int count = 0; + int s = 0; + int a; + while (count < utfSize) { + char ch = (char)buf[offset + count++]; + if (ch < '\u0080') { + out[s++] = ch; + } else if (((a = out[s]) & 0xe0) == 0xc0) { + if (count >= utfSize) { + throw new TUTFDataFormatException(TString.wrap("End of stream reached")); + } + int b = buf[count++]; + if ((b & 0xC0) != 0x80) { + throw new TUTFDataFormatException(TString.wrap("Malformed UTF-8 sequence")); + } + out[s++] = (char) (((a & 0x1F) << 6) | (b & 0x3F)); + } else if ((a & 0xf0) == 0xe0) { + if (count + 1 >= utfSize) { + throw new TUTFDataFormatException(TString.wrap("Malformed UTF-8 sequence")); + } + int b = buf[count++]; + int c = buf[count++]; + if (((b & 0xC0) != 0x80) || ((c & 0xC0) != 0x80)) { + throw new TUTFDataFormatException(TString.wrap("Malformed UTF-8 sequence")); + } + out[s++] = (char)(((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F)); + } else { + throw new TUTFDataFormatException(TString.wrap("Malformed UTF-8 sequence")); + } + } + return new TString(out, 0, s); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/io/TPushbackInputStream.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/io/TPushbackInputStream.java new file mode 100644 index 000000000..1eca3c513 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/io/TPushbackInputStream.java @@ -0,0 +1,176 @@ +/* + * Copyright 2014 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 org.teavm.classlib.java.lang.TArrayIndexOutOfBoundsException; +import org.teavm.classlib.java.lang.TString; + +/** + * + * @author Alexey Andreev + */ +public class TPushbackInputStream extends TFilterInputStream { + protected byte[] buf; + protected int pos; + + public TPushbackInputStream(TInputStream in) { + super(in); + buf = (in == null) ? null : new byte[1]; + pos = 1; + } + + public TPushbackInputStream(TInputStream in, int size) { + super(in); + if (size <= 0) { + throw new IllegalArgumentException("Size must be positive"); + } + buf = in == null ? null : new byte[size]; + pos = size; + } + + @Override + public int available() throws TIOException { + if (buf == null) { + throw new TIOException(); + } + return buf.length - pos + in.available(); + } + + @Override + public void close() throws TIOException { + if (in != null) { + in.close(); + in = null; + buf = null; + } + } + + @Override + public boolean markSupported() { + return false; + } + + @Override + public int read() throws TIOException { + if (buf == null) { + throw new TIOException(); + } + // Is there a pushback byte available? + if (pos < buf.length) { + return (buf[pos++] & 0xFF); + } + // Assume read() in the InputStream will return low-order byte or -1 + // if end of stream. + return in.read(); + } + + @Override + public int read(byte[] buffer, int offset, int length) throws TIOException { + if (buf == null) { + throw new TIOException(TString.wrap("Stream is closed")); + } + // Force buffer null check first! + if (offset > buffer.length || offset < 0) { + throw new TArrayIndexOutOfBoundsException(TString.wrap("Offset out of bounds: " + offset)); + } + if (length < 0 || length > buffer.length - offset) { + throw new TArrayIndexOutOfBoundsException(TString.wrap("Length out of bounds: " + length)); + } + + int copiedBytes = 0, copyLength = 0, newOffset = offset; + // Are there pushback bytes available? + if (pos < buf.length) { + copyLength = (buf.length - pos >= length) ? length : buf.length - pos; + System.arraycopy(buf, pos, buffer, newOffset, copyLength); + newOffset += copyLength; + copiedBytes += copyLength; + // Use up the bytes in the local buffer + pos += copyLength; + } + // Have we copied enough? + if (copyLength == length) { + return length; + } + int inCopied = in.read(buffer, newOffset, length - copiedBytes); + if (inCopied > 0) { + return inCopied + copiedBytes; + } + if (copiedBytes == 0) { + return inCopied; + } + return copiedBytes; + } + + @Override + public long skip(long count) throws TIOException { + if (in == null) { + throw new TIOException(); + } + if (count <= 0) { + return 0; + } + int numSkipped = 0; + if (pos < buf.length) { + numSkipped += (count < buf.length - pos) ? count : buf.length - pos; + pos += numSkipped; + } + if (numSkipped < count) { + numSkipped += in.skip(count - numSkipped); + } + return numSkipped; + } + + public void unread(byte[] buffer) throws TIOException { + unread(buffer, 0, buffer.length); + } + + public void unread(byte[] buffer, int offset, int length) throws TIOException { + if (length > pos) { + throw new TIOException(TString.wrap("Pushback buffer full")); + } + if (offset > buffer.length || offset < 0) { + throw new TArrayIndexOutOfBoundsException(TString.wrap("Offset out of bounds: " + offset)); + } + if (length < 0 || length > buffer.length - offset) { + throw new TArrayIndexOutOfBoundsException(TString.wrap("Length out of bounds: " + length)); + } + if (buf == null) { + throw new TIOException(TString.wrap("Stream is closed")); + } + System.arraycopy(buffer, offset, buf, pos - length, length); + pos = pos - length; + } + + public void unread(int oneByte) throws TIOException { + if (buf == null) { + throw new TIOException(); + } + if (pos == 0) { + throw new TIOException(); + } + buf[--pos] = (byte) oneByte; + } + + @Override + public void mark(int readlimit) { + return; + } + + @Override + public void reset() throws TIOException { + throw new TIOException(); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/io/TUTFDataFormatException.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/io/TUTFDataFormatException.java new file mode 100644 index 000000000..db05a0e9e --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/io/TUTFDataFormatException.java @@ -0,0 +1,34 @@ +/* + * Copyright 2014 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 org.teavm.classlib.java.lang.TString; + +/** + * + * @author Alexey Andreev + */ +public class TUTFDataFormatException extends TIOException { + private static final long serialVersionUID = -6383472574962319733L; + + public TUTFDataFormatException() { + super(); + } + + public TUTFDataFormatException(TString message) { + super(message); + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/io/PushbackInputStreamTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/io/PushbackInputStreamTest.java new file mode 100644 index 000000000..7af9a2361 --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/io/PushbackInputStreamTest.java @@ -0,0 +1,199 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.junit.Assert.*; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.PushbackInputStream; +import org.junit.Test; + +@SuppressWarnings("resource") +public class PushbackInputStreamTest { + PushbackInputStream pis; + + public PushbackInputStreamTest() { + byte[] array = new byte[1000]; + for (int i = 0; i < array.length; ++i) { + array[i] = (byte)i; + } + pis = new PushbackInputStream(new ByteArrayInputStream(array), array.length); + } + + @Test + public void test_reset() { + PushbackInputStream pb = new PushbackInputStream(new ByteArrayInputStream(new byte[] { 0 }), 2); + try { + pb.reset(); + fail("Should throw IOException"); + } catch (IOException e) { + // expected + } + } + + @Test + public void test_mark() { + PushbackInputStream pb = new PushbackInputStream(new ByteArrayInputStream(new byte[] { 0 }), 2); + pb.mark(Integer.MAX_VALUE); + pb.mark(0); + pb.mark(-1); + pb.mark(Integer.MIN_VALUE); + } + + @Test + public void test_ConstructorLjava_io_InputStream() { + try { + PushbackInputStream str = new PushbackInputStream(null); + str.read(); + fail("Expected IOException"); + } catch (IOException e) { + // Expected + } + + try { + pis = new PushbackInputStream(new ByteArrayInputStream("Hello".getBytes())); + pis.unread("He".getBytes()); + fail("Failed to throw exception on unread when buffer full"); + } catch (IOException e) { + // Expected + } + } + + @Test + public void test_ConstructorLjava_io_InputStreamI() { + try { + pis = new PushbackInputStream(new ByteArrayInputStream("Hello".getBytes()), 5); + pis.unread("Hellos".getBytes()); + } catch (IOException e) { + // Correct + // Pushback buffer should be full + return; + + } + fail("Failed to throw exception on unread when buffer full"); + } + + @Test + public void test_ConstructorLjava_io_InputStreamL() { + try { + PushbackInputStream str = new PushbackInputStream(null, 1); + str.read(); + fail("Expected IOException"); + } catch (IOException e) { + // Expected + } + } + + @Test + public void test_available() { + try { + assertEquals("Available returned incorrect number of bytes", 1000, pis.available()); + } catch (IOException e) { + fail("Exception during available test: " + e.toString()); + } + } + + @Test + public void test_markSupported() { + assertTrue("markSupported returned true", !pis.markSupported()); + } + + @Test + public void test_read() { + try { + assertEquals("Incorrect byte read", 0, pis.read()); + } catch (IOException e) { + fail("Exception during read test : " + e.getMessage()); + } + } + + @Test + public void test_read$BII() { + try { + byte[] buf = new byte[100]; + pis.read(buf, 0, buf.length); + assertEquals(0, buf[0]); + assertEquals(99, buf[99]); + } catch (IOException e) { + fail("Exception during read test : " + e.getMessage()); + } + } + + @Test + public void test_skipJ() throws Exception { + byte[] buf = new byte[50]; + pis.skip(50); + pis.read(buf, 0, buf.length); + assertEquals(50, buf[0]); + assertEquals(99, buf[49]); + pis.unread(buf); + pis.skip(25); + byte[] buf2 = new byte[25]; + pis.read(buf2, 0, buf2.length); + assertEquals(75, buf2[0]); + assertEquals(99, buf2[24]); + } + + @Test + public void test_unread$B() { + try { + byte[] buf = new byte[100]; + pis.read(buf, 0, buf.length); + assertEquals(0, buf[0]); + assertEquals(99, buf[99]); + pis.unread(buf); + pis.read(buf, 0, 50); + assertEquals(0, buf[0]); + assertEquals(49, buf[49]); + } catch (IOException e) { + fail("IOException during unread test : " + e.getMessage()); + } + } + + @Test + public void test_unread$BII() throws IOException { + byte[] buf = new byte[100]; + pis.read(buf, 0, buf.length); + assertEquals(0, buf[0]); + assertEquals(99, buf[99]); + pis.unread(buf, 50, 50); + pis.read(buf, 0, 50); + assertEquals(50, buf[0]); + assertEquals(99, buf[49]); + + // Regression for HARMONY-49 + try { + PushbackInputStream pb = new PushbackInputStream(new ByteArrayInputStream(new byte[] { 0 }), 2); + pb.unread(new byte[1], 0, 5); + fail("Assert 0: should throw IOE"); + } catch (IOException e) { + // expected + } + } + + @Test + public void test_unreadI() { + try { + int x = pis.read(); + assertEquals("Incorrect byte read", 0, x); + pis.unread(x); + assertEquals("Failed to unread", x, pis.read()); + } catch (IOException e) { + fail("IOException during read test : " + e.getMessage()); + } + } +}