Added emulation for InputStream and ByteArrayInputStream
Added emulation for the following Java IO classes based
on the Android libcore implementation:
- InputStream
- ByteArrayInputStream
Added corresponding unit tests.
Change-Id: I041552ebfd075c31d4613b1ce09ab375842fbd7d
Review-Link: https://gwt-review.googlesource.com/#/c/12770/
diff --git a/user/super/com/google/gwt/emul/java/io/ByteArrayInputStream.java b/user/super/com/google/gwt/emul/java/io/ByteArrayInputStream.java
new file mode 100644
index 0000000..4093b38
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/io/ByteArrayInputStream.java
@@ -0,0 +1,190 @@
+// CHECKSTYLE_OFF: Copyrighted to the Android Open Source Project.
+/*
+ * 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.
+ */
+// CHECKSTYLE_ON
+
+package java.io;
+
+/**
+ * A specialized {@link InputStream } for reading the contents of a byte array.
+ *
+ * @see ByteArrayOutputStream
+ */
+public class ByteArrayInputStream extends InputStream {
+ /**
+ * The {@code byte} array containing the bytes to stream over.
+ */
+ protected byte[] buf;
+
+ /**
+ * The current position within the byte array.
+ */
+ protected int pos;
+
+ /**
+ * The current mark position. Initially set to 0 or the <code>offset</code>
+ * parameter within the constructor.
+ */
+ protected int mark;
+
+ /**
+ * The total number of bytes initially available in the byte array
+ * {@code buf}.
+ */
+ protected int count;
+
+ /**
+ * Constructs a new {@code ByteArrayInputStream} on the byte array
+ * {@code buf}.
+ *
+ * @param buf
+ * the byte array to stream over.
+ */
+ public ByteArrayInputStream(byte[] buf) {
+ this.mark = 0;
+ this.buf = buf;
+ this.count = buf.length;
+ }
+
+ /**
+ * Constructs a new {@code ByteArrayInputStream} on the byte array
+ * {@code buf} with the initial position set to {@code offset} and the
+ * number of bytes available set to {@code offset} + {@code length}.
+ *
+ * @param buf
+ * the byte array to stream over.
+ * @param offset
+ * the initial position in {@code buf} to start streaming from.
+ * @param length
+ * the number of bytes available for streaming.
+ */
+ public ByteArrayInputStream(byte[] buf, int offset, int length) {
+ this.buf = buf;
+ pos = offset;
+ mark = offset;
+ count = offset + length > buf.length ? buf.length : offset + length;
+ }
+
+ /**
+ * Returns the number of remaining bytes.
+ *
+ * @return {@code count - pos}
+ */
+ @Override
+ public synchronized int available() {
+ return count - pos;
+ }
+
+ /**
+ * Closes this stream and frees resources associated with this stream.
+ *
+ * @throws IOException
+ * if an I/O error occurs while closing this stream.
+ */
+ @Override
+ public void close() throws IOException {
+ // Do nothing on close, this matches JDK behavior.
+ }
+
+ /**
+ * Sets a mark position in this ByteArrayInputStream. The parameter
+ * {@code readlimit} is ignored. Sending {@code reset()} will reposition the
+ * stream back to the marked position.
+ *
+ * @param readlimit
+ * ignored.
+ * @see #markSupported()
+ * @see #reset()
+ */
+ @Override
+ public synchronized void mark(int readlimit) {
+ mark = pos;
+ }
+
+ /**
+ * Indicates whether this stream supports the {@code mark()} and
+ * {@code reset()} methods. Returns {@code true} since this class supports
+ * these methods.
+ *
+ * @return always {@code true}.
+ * @see #mark(int)
+ * @see #reset()
+ */
+ @Override
+ public boolean markSupported() {
+ return true;
+ }
+
+ /**
+ * Reads a single byte from the source byte array and returns it as an
+ * integer in the range from 0 to 255. Returns -1 if the end of the source
+ * array has been reached.
+ *
+ * @return the byte read or -1 if the end of this stream has been reached.
+ */
+ @Override
+ public synchronized int read() {
+ return pos < count ? buf[pos++] & 0xFF : -1;
+ }
+
+ @Override public synchronized int read(byte[] buffer, int byteOffset, int byteCount) {
+ IOUtils.checkOffsetAndCount(buffer, byteOffset, byteCount);
+
+ // Are there any bytes available?
+ if (this.pos >= this.count) {
+ return -1;
+ }
+ if (byteCount == 0) {
+ return 0;
+ }
+
+ int copylen = this.count - pos < byteCount ? this.count - pos : byteCount;
+ System.arraycopy(this.buf, pos, buffer, byteOffset, copylen);
+ pos += copylen;
+ return copylen;
+ }
+
+ /**
+ * Resets this stream to the last marked location. This implementation
+ * resets the position to either the marked position, the start position
+ * supplied in the constructor or 0 if neither has been provided.
+ *
+ * @see #mark(int)
+ */
+ @Override
+ public synchronized void reset() {
+ pos = mark;
+ }
+
+ /**
+ * Skips {@code byteCount} bytes in this InputStream. Subsequent
+ * calls to {@code read} will not return these bytes unless {@code reset} is
+ * used. This implementation skips {@code byteCount} number of bytes in the
+ * target stream. It does nothing and returns 0 if {@code byteCount} is negative.
+ *
+ * @return the number of bytes actually skipped.
+ */
+ @Override
+ public synchronized long skip(long byteCount) {
+ if (byteCount <= 0) {
+ return 0;
+ }
+ int temp = pos;
+ pos = this.count - pos < byteCount ? this.count : (int) (pos + byteCount);
+ return pos - temp;
+ }
+}
diff --git a/user/super/com/google/gwt/emul/java/io/Closeable.java b/user/super/com/google/gwt/emul/java/io/Closeable.java
new file mode 100644
index 0000000..2ef478f
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/io/Closeable.java
@@ -0,0 +1,36 @@
+// CHECKSTYLE_OFF: Copyrighted to the Android Open Source Project.
+/*
+ * 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.
+ */
+// CHECKSTYLE_ON
+
+package java.io;
+
+/**
+ * An {@code AutoCloseable} whose close method may throw an {@link IOException}.
+ */
+public interface Closeable extends AutoCloseable {
+
+ /**
+ * Closes the object and release any system resources it holds.
+ *
+ * <p>Although only the first call has any effect, it is safe to call close
+ * multiple times on the same object. This is more lenient than the
+ * overridden {@code AutoCloseable.close()}, which may be called at most
+ * once.
+ */
+ void close() throws IOException;
+}
diff --git a/user/super/com/google/gwt/emul/java/io/IOUtils.java b/user/super/com/google/gwt/emul/java/io/IOUtils.java
new file mode 100644
index 0000000..0d8a42a
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/io/IOUtils.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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 java.io;
+
+import static com.google.gwt.core.shared.impl.InternalPreconditions.checkNotNull;
+
+/**
+ * Provides a series of utilities to be reused between IO classes.
+ *
+ * TODO(chehayeb): move these checks to InternalPreconditions.
+ */
+final class IOUtils {
+
+ /**
+ * Validates the offset and the byte count for the given array of bytes.
+ *
+ * @param buffer Array of bytes to be checked.
+ * @param byteOffset Starting offset in the array.
+ * @param byteCount Total number of bytes to be accessed.
+ * @throws NullPointerException if the given reference to the buffer is null.
+ * @throws IndexOutOfBoundsException if {@code byteOffset} is negative, {@code byteCount} is
+ * negative or their sum exceeds the buffer length.
+ */
+ public static void checkOffsetAndCount(byte[] buffer, int byteOffset, int byteCount) {
+ // Ensure we throw a NullPointerException instead of a JavascriptException in case the
+ // given buffer is null.
+ checkNotNull(buffer);
+ checkOffsetAndCount(buffer.length, byteOffset, byteCount);
+ }
+
+ /**
+ * Validates the offset and the byte count for the given array of characters.
+ *
+ * @param buffer Array of characters to be checked.
+ * @param charOffset Starting offset in the array.
+ * @param charCount Total number of characters to be accessed.
+ * @throws NullPointerException if the given reference to the buffer is null.
+ * @throws IndexOutOfBoundsException if {@code charOffset} is negative, {@code charCount} is
+ * negative or their sum exceeds the buffer length.
+ */
+ public static void checkOffsetAndCount(char[] buffer, int charOffset, int charCount) {
+ // Ensure we throw a NullPointerException instead of a JavascriptException in case the
+ // given buffer is null.
+ checkNotNull(buffer);
+ checkOffsetAndCount(buffer.length, charOffset, charCount);
+ }
+
+ /**
+ * Validates the offset and the byte count for the given array length.
+ *
+ * @param length Length of the array to be checked.
+ * @param offset Starting offset in the array.
+ * @param count Total number of elements to be accessed.
+ * @throws IndexOutOfBoundsException if {@code offset} is negative, {@code count} is negative or
+ * their sum exceeds the given {@code length}.
+ */
+ private static void checkOffsetAndCount(int length, int offset, int count) {
+ if ((offset < 0) || (count < 0) || ((offset + count) > length)) {
+ throw new IndexOutOfBoundsException();
+ }
+ }
+
+ private IOUtils() {
+ }
+}
diff --git a/user/super/com/google/gwt/emul/java/io/InputStream.java b/user/super/com/google/gwt/emul/java/io/InputStream.java
new file mode 100644
index 0000000..6b82150
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/io/InputStream.java
@@ -0,0 +1,258 @@
+// CHECKSTYLE_OFF: Copyrighted to the Android Open Source Project.
+/*
+ * 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.
+ */
+// CHECKSTYLE_ON
+
+package java.io;
+
+import static com.google.gwt.core.shared.impl.InternalPreconditions.checkNotNull;
+
+/**
+ * A readable source of bytes.
+ *
+ * <p>Most clients will use input streams that read data from the file system
+ * ({@link FileInputStream}), the network ({@link java.net.Socket#getInputStream()}/{@link
+ * java.net.HttpURLConnection#getInputStream()}), or from an in-memory byte
+ * array ({@link ByteArrayInputStream}).
+ *
+ * <p>Use {@link InputStreamReader} to adapt a byte stream like this one into a
+ * character stream.
+ *
+ * <p>Most clients should wrap their input stream with {@link
+ * BufferedInputStream}. Callers that do only bulk reads may omit buffering.
+ *
+ * <p>Some implementations support marking a position in the input stream and
+ * resetting back to this position later. Implementations that don't return
+ * false from {@link #markSupported()} and throw an {@link IOException} when
+ * {@link #reset()} is called.
+ *
+ * <h3>Subclassing InputStream</h3>
+ * Subclasses that decorate another input stream should consider subclassing
+ * {@link FilterInputStream}, which delegates all calls to the source input
+ * stream.
+ *
+ * <p>All input stream subclasses should override <strong>both</strong> {@link
+ * #read() read()} and {@link #read(byte[],int,int) read(byte[],int,int)}. The
+ * three argument overload is necessary for bulk access to the data. This is
+ * much more efficient than byte-by-byte access.
+ *
+ * @see OutputStream
+ */
+public abstract class InputStream extends Object implements Closeable {
+
+ /**
+ * Size of the temporary buffer used when skipping bytes with {@link skip(long)}.
+ */
+ private static final int MAX_SKIP_BUFFER_SIZE = 4096;
+
+ /**
+ * This constructor does nothing. It is provided for signature
+ * compatibility.
+ */
+ public InputStream() {
+ /* empty */
+ }
+
+ /**
+ * Returns an estimated number of bytes that can be read or skipped without blocking for more
+ * input.
+ *
+ * <p>Note that this method provides such a weak guarantee that it is not very useful in
+ * practice.
+ *
+ * <p>Firstly, the guarantee is "without blocking for more input" rather than "without
+ * blocking": a read may still block waiting for I/O to complete — the guarantee is
+ * merely that it won't have to wait indefinitely for data to be written. The result of this
+ * method should not be used as a license to do I/O on a thread that shouldn't be blocked.
+ *
+ * <p>Secondly, the result is a
+ * conservative estimate and may be significantly smaller than the actual number of bytes
+ * available. In particular, an implementation that always returns 0 would be correct.
+ * In general, callers should only use this method if they'd be satisfied with
+ * treating the result as a boolean yes or no answer to the question "is there definitely
+ * data ready?".
+ *
+ * <p>Thirdly, the fact that a given number of bytes is "available" does not guarantee that a
+ * read or skip will actually read or skip that many bytes: they may read or skip fewer.
+ *
+ * <p>It is particularly important to realize that you <i>must not</i> use this method to
+ * size a container and assume that you can read the entirety of the stream without needing
+ * to resize the container. Such callers should probably write everything they read to a
+ * {@link ByteArrayOutputStream} and convert that to a byte array. Alternatively, if you're
+ * reading from a file, {@link File#length} returns the current length of the file (though
+ * assuming the file's length can't change may be incorrect, reading a file is inherently
+ * racy).
+ *
+ * <p>The default implementation of this method in {@code InputStream} always returns 0.
+ * Subclasses should override this method if they are able to indicate the number of bytes
+ * available.
+ *
+ * @return the estimated number of bytes available
+ * @throws IOException if this stream is closed or an error occurs
+ */
+ public int available() throws IOException {
+ return 0;
+ }
+
+ /**
+ * Closes this stream. Concrete implementations of this class should free
+ * any resources during close. This implementation does nothing.
+ *
+ * @throws IOException
+ * if an error occurs while closing this stream.
+ */
+ public void close() throws IOException {
+ /* empty */
+ }
+
+ /**
+ * Sets a mark position in this InputStream. The parameter {@code readlimit}
+ * indicates how many bytes can be read before the mark is invalidated.
+ * Sending {@code reset()} will reposition the stream back to the marked
+ * position provided {@code readLimit} has not been surpassed.
+ * <p>
+ * This default implementation does nothing and concrete subclasses must
+ * provide their own implementation.
+ *
+ * @param readlimit
+ * the number of bytes that can be read from this stream before
+ * the mark is invalidated.
+ * @see #markSupported()
+ * @see #reset()
+ */
+ public void mark(int readlimit) {
+ /* empty */
+ }
+
+ /**
+ * Indicates whether this stream supports the {@code mark()} and
+ * {@code reset()} methods. The default implementation returns {@code false}.
+ *
+ * @return always {@code false}.
+ * @see #mark(int)
+ * @see #reset()
+ */
+ public boolean markSupported() {
+ return false;
+ }
+
+ /**
+ * Reads a single byte from this stream and returns it as an integer in the
+ * range from 0 to 255. Returns -1 if the end of the stream has been
+ * reached. Blocks until one byte has been read, the end of the source
+ * stream is detected or an exception is thrown.
+ *
+ * @throws IOException
+ * if the stream is closed or another IOException occurs.
+ */
+ public abstract int read() throws IOException;
+
+ /**
+ * Equivalent to {@code read(buffer, 0, buffer.length)}.
+ */
+ public int read(byte[] buffer) throws IOException {
+ // Note that GWT will throw a JavascriptException rather than a NullPointerException if we
+ // skip this check and the buffer array is null. This way we ensure that this implementation
+ // behaves in the same way as the classes that are emulated.
+ checkNotNull(buffer);
+ return read(buffer, 0, buffer.length);
+ }
+
+ /**
+ * Reads up to {@code byteCount} bytes from this stream and stores them in
+ * the byte array {@code buffer} starting at {@code byteOffset}.
+ * Returns the number of bytes actually read or -1 if the end of the stream
+ * has been reached.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code byteOffset < 0 || byteCount < 0 || byteOffset + byteCount > buffer.length}.
+ * @throws IOException
+ * if the stream is closed or another IOException occurs.
+ */
+ public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+ IOUtils.checkOffsetAndCount(buffer, byteOffset, byteCount);
+ for (int i = 0; i < byteCount; ++i) {
+ int c;
+ try {
+ if ((c = read()) == -1) {
+ return i == 0 ? -1 : i;
+ }
+ } catch (IOException e) {
+ if (i != 0) {
+ return i;
+ }
+ throw e;
+ }
+ buffer[byteOffset + i] = (byte) c;
+ }
+ return byteCount;
+ }
+
+ /**
+ * Resets this stream to the last marked location. Throws an
+ * {@code IOException} if the number of bytes read since the mark has been
+ * set is greater than the limit provided to {@code mark}, or if no mark
+ * has been set.
+ * <p>
+ * This implementation always throws an {@code IOException} and concrete
+ * subclasses should provide the proper implementation.
+ *
+ * @throws IOException
+ * if this stream is closed or another IOException occurs.
+ */
+ public synchronized void reset() throws IOException {
+ throw new IOException();
+ }
+
+ /**
+ * Skips at most {@code byteCount} bytes in this stream. The number of actual
+ * bytes skipped may be anywhere between 0 and {@code byteCount}. If
+ * {@code byteCount} is negative, this method does nothing and returns 0, but
+ * some subclasses may throw.
+ *
+ * <p>Note the "at most" in the description of this method: this method may
+ * choose to skip fewer bytes than requested. Callers should <i>always</i>
+ * check the return value.
+ *
+ * <p>This default implementation reads bytes into a temporary buffer. Concrete
+ * subclasses should provide their own implementation.
+ *
+ * @return the number of bytes actually skipped.
+ * @throws IOException if this stream is closed or another IOException
+ * occurs.
+ */
+ public long skip(long byteCount) throws IOException {
+ if (byteCount <= 0) {
+ return 0;
+ }
+ final int bSize = (int) Math.min(MAX_SKIP_BUFFER_SIZE, byteCount);
+ final byte[] b = new byte[bSize];
+ long skipped = 0;
+ while (skipped < byteCount) {
+ final int toRead = (int) Math.min(byteCount - skipped, b.length);
+ final int readCount = read(b, 0, toRead);
+ if (readCount == -1) {
+ break;
+ }
+ skipped += readCount;
+ if (readCount < toRead) {
+ break;
+ }
+ }
+ return skipped;
+ }
+}
diff --git a/user/test/com/google/gwt/emultest/EmulSuite.java b/user/test/com/google/gwt/emultest/EmulSuite.java
index edce91d..0f447bc 100644
--- a/user/test/com/google/gwt/emultest/EmulSuite.java
+++ b/user/test/com/google/gwt/emultest/EmulSuite.java
@@ -15,6 +15,8 @@
*/
package com.google.gwt.emultest;
+import com.google.gwt.emultest.java.io.ByteArrayInputStreamTest;
+import com.google.gwt.emultest.java.io.InputStreamTest;
import com.google.gwt.emultest.java.lang.BooleanTest;
import com.google.gwt.emultest.java.lang.ByteTest;
import com.google.gwt.emultest.java.lang.CharacterTest;
@@ -53,6 +55,9 @@
GWTTestSuite suite = new GWTTestSuite("Tests for com.google.gwt.emul.java");
// $JUnit-BEGIN$
+ //-- java.io
+ suite.addTestSuite(ByteArrayInputStreamTest.class);
+ suite.addTestSuite(InputStreamTest.class);
//-- java.lang
suite.addTestSuite(BooleanTest.class);
suite.addTestSuite(ByteTest.class);
diff --git a/user/test/com/google/gwt/emultest/java/io/ByteArrayInputStreamTest.java b/user/test/com/google/gwt/emultest/java/io/ByteArrayInputStreamTest.java
new file mode 100644
index 0000000..2961e86
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java/io/ByteArrayInputStreamTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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 com.google.gwt.emultest.java.io;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+/**
+ * Unit test for the {@link java.io.ByteArrayInputStream} emulated class.
+ */
+public class ByteArrayInputStreamTest extends InputStreamBaseTest {
+
+ @Override
+ protected InputStream createInputStream(final byte[] expectedBytes) {
+ // note that GWT fails here when trying to use clone().
+ return new ByteArrayInputStream(Arrays.copyOf(expectedBytes, expectedBytes.length));
+ }
+
+ public void testAvailableForEmptyArray() {
+ final ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[0]);
+ final int available = inputStream.available();
+ assertEquals(0, available);
+ }
+
+ public void testAvailableForNonEmptyArray() {
+ final ByteArrayInputStream inputStream = new ByteArrayInputStream(TEST_BYTES);
+ final int available = inputStream.available();
+ assertEquals(TEST_BYTES.length, available);
+ }
+
+ public void testAvailableForEmptyArrayRange() {
+ final ByteArrayInputStream inputStream = new ByteArrayInputStream(TEST_BYTES, 1, 0);
+ final int available = inputStream.available();
+ assertEquals(0, available);
+ }
+
+ public void testAvailableForNonEmptyArrayRange() {
+ final ByteArrayInputStream inputStream = new ByteArrayInputStream(TEST_BYTES, 1, 3);
+ final int available = inputStream.available();
+ assertEquals(3, available);
+ }
+
+ public void testClose() throws IOException {
+ // should do nothing (including not throwing an exception)
+ try (final ByteArrayInputStream inputStream = new ByteArrayInputStream(TEST_BYTES)) {
+ }
+ }
+
+ public void testMarkSupported() {
+ final ByteArrayInputStream inputStream = new ByteArrayInputStream(TEST_BYTES);
+ final boolean markSupported = inputStream.markSupported();
+ assertTrue(markSupported);
+ }
+
+ public void testReadSingleValueFromEmptyArray() {
+ final ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[0]);
+ final int c = inputStream.read();
+ assertEquals(-1, c);
+ }
+
+ public void testReadSingleValuesFromNonEmptyArray() {
+ final ByteArrayInputStream inputStream = new ByteArrayInputStream(TEST_BYTES);
+ for (int i = 0; i < TEST_BYTES.length; i++) {
+ final int c = inputStream.read();
+ assertEquals(TEST_BYTES[i], c);
+
+ final int available = inputStream.available();
+ assertEquals(TEST_BYTES.length - i - 1, available);
+ }
+ // at this point we should have reached the end of the stream.
+ final int c = inputStream.read();
+ assertEquals(-1, c);
+
+ final int available = inputStream.available();
+ assertEquals(0, available);
+ }
+
+ public void testReadSingleValueFromEmptyArrayRange() {
+ final ByteArrayInputStream inputStream = new ByteArrayInputStream(TEST_BYTES, 1, 0);
+ final int c = inputStream.read();
+ assertEquals(-1, c);
+ }
+
+ public void testReadSingleValuesFromNonEmptyArrayRange() {
+ final int startAt = 1;
+ final int count = 3;
+ final ByteArrayInputStream inputStream = new ByteArrayInputStream(TEST_BYTES, startAt, count);
+ for (int i = 0; i < count; i++) {
+ final int c = inputStream.read();
+ assertEquals(TEST_BYTES[i + startAt], c);
+
+ final int available = inputStream.available();
+ assertEquals(count - i - 1, available);
+ }
+ // at this point we should have reached the end of the stream.
+ final int c = inputStream.read();
+ assertEquals(-1, c);
+
+ final int available = inputStream.available();
+ assertEquals(0, available);
+ }
+
+ public void testResetWithoutInvokingMark() {
+ final ByteArrayInputStream inputStream = new ByteArrayInputStream(TEST_BYTES);
+
+ inputStream.reset();
+
+ int c = inputStream.read();
+ assertEquals(TEST_BYTES[0], c);
+ c = inputStream.read();
+ assertEquals(TEST_BYTES[1], c);
+ }
+
+ public void testResetAfterInvokingMark() {
+ final ByteArrayInputStream inputStream = new ByteArrayInputStream(TEST_BYTES);
+
+ int c = inputStream.read();
+ assertEquals(TEST_BYTES[0], c);
+
+ inputStream.mark(0);
+
+ c = inputStream.read();
+ assertEquals(TEST_BYTES[1], c);
+ c = inputStream.read();
+ assertEquals(TEST_BYTES[2], c);
+
+ inputStream.reset();
+
+ c = inputStream.read();
+ assertEquals(TEST_BYTES[1], c);
+ c = inputStream.read();
+ assertEquals(TEST_BYTES[2], c);
+ }
+}
diff --git a/user/test/com/google/gwt/emultest/java/io/InputStreamBaseTest.java b/user/test/com/google/gwt/emultest/java/io/InputStreamBaseTest.java
new file mode 100644
index 0000000..1b499ec
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java/io/InputStreamBaseTest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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 com.google.gwt.emultest.java.io;
+
+import com.google.gwt.junit.client.GWTTestCase;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+/**
+ * Class for reusing tests that are common to {@link java.io.InputStream} and its subclasses.
+ */
+public abstract class InputStreamBaseTest extends GWTTestCase {
+
+ protected static final byte[] TEST_BYTES = new byte[] { 10, 20, 30, 40, 50 };
+
+ /**
+ * Sets module name so that javascript compiler can operate.
+ */
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.emultest.EmulSuite";
+ }
+
+ /**
+ * Factory method for creating a stream object.
+ *
+ * @param expectedBytes expected bytes when reading from the stream.
+ * @return input stream object to be tested.
+ */
+ protected abstract InputStream createInputStream(byte[] expectedBytes);
+
+ public void testReadArrayUsingNullArrayObject() throws IOException {
+ final InputStream inputStream = createInputStream(TEST_BYTES);
+ try {
+ inputStream.read(null, 0, 1);
+ fail("should have thrown NullPointerException");
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ public void testReadArrayUsingNegativeOffsetValue() throws IOException {
+ final InputStream inputStream = createInputStream(TEST_BYTES);
+ final byte[] b = new byte[1];
+ try {
+ inputStream.read(b, -1, 1);
+ fail("should have thrown IndexOutOfBoundsException");
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ }
+
+ public void testReadArrayUsingNegativeLengthValue() throws IOException {
+ final InputStream inputStream = createInputStream(TEST_BYTES);
+ final byte[] b = new byte[1];
+ try {
+ inputStream.read(b, 0, -1);
+ fail("should have thrown IndexOutOfBoundsException");
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ }
+
+ public void testReadArrayUsingAnInvalidRange() throws IOException {
+ final InputStream inputStream = createInputStream(TEST_BYTES);
+ final byte[] b = new byte[1];
+ try {
+ inputStream.read(b, 1, 1);
+ fail("should have thrown IndexOutOfBoundsException");
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ }
+
+ public void testReadArrayUsingZeroLength() throws IOException {
+ final InputStream inputStream = createInputStream(TEST_BYTES);
+ final byte[] b = new byte[1];
+ int c = inputStream.read(b, 0, 0);
+ assertEquals(0, c);
+ c = inputStream.read(b, 1, 0);
+ assertEquals(0, c);
+ }
+
+ public void testReadArrayFromEmptyStream() throws IOException {
+ final InputStream inputStream = createInputStream(new byte[] {});
+ final byte[] b = new byte[1];
+ final int c = inputStream.read(b, 0, 1);
+ assertEquals(-1, c);
+ }
+
+ public void testReadArrayPartial() throws IOException {
+ final InputStream inputStream = createInputStream(TEST_BYTES);
+ final byte[] b = new byte[2];
+ final int result = inputStream.read(b, 1, 1);
+ assertEquals(1, result);
+ assertEquals(b[0], 0);
+ assertEquals(b[1], TEST_BYTES[0]);
+ }
+
+ public void testReadArrayReachEndOfStream() throws IOException {
+ final InputStream inputStream = createInputStream(TEST_BYTES);
+ final byte[] b = new byte[TEST_BYTES.length + 5];
+ final int result = inputStream.read(b, 5, TEST_BYTES.length);
+ assertEquals(TEST_BYTES.length, result);
+
+ final byte[] expected = new byte[b.length];
+ System.arraycopy(TEST_BYTES, 0, expected, 5, TEST_BYTES.length);
+ assertTrue(Arrays.equals(expected, b));
+ }
+
+ public void testReadArrayExceedEndOfStream() throws IOException {
+ final InputStream inputStream = createInputStream(TEST_BYTES);
+ final byte[] b = new byte[5 + TEST_BYTES.length + 5];
+ final int result = inputStream.read(b, 5, TEST_BYTES.length + 5);
+ assertEquals(TEST_BYTES.length, result);
+
+ final byte[] expected = new byte[b.length];
+ System.arraycopy(TEST_BYTES, 0, expected, 5, TEST_BYTES.length);
+ assertTrue(Arrays.equals(expected, b));
+ }
+
+ public void testReadArrayUsingNullArrayObjectNoOffset() throws IOException {
+ final InputStream inputStream = createInputStream(TEST_BYTES);
+ try {
+ inputStream.read(null);
+ fail("should have thrown NullPointerException");
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ public void testReadUsingAnEmptyArrayNoOffset() throws IOException {
+ final InputStream inputStream = createInputStream(TEST_BYTES);
+ final byte[] b = new byte[0];
+ final int c = inputStream.read(b);
+ assertEquals(0, c);
+ }
+
+ public void testReadArrayFromEmptyStreamNoOffset() throws IOException {
+ final InputStream inputStream = createInputStream(new byte[] {});
+ final byte[] b = new byte[1];
+ final int c = inputStream.read(b);
+ assertEquals(-1, c);
+ }
+
+ public void testReadArrayPartialNoOffset() throws IOException {
+ final InputStream inputStream = createInputStream(TEST_BYTES);
+ final byte[] b = new byte[TEST_BYTES.length / 2];
+ final int result = inputStream.read(b);
+ assertEquals(b.length, result);
+ assertTrue(Arrays.equals(Arrays.copyOf(TEST_BYTES, b.length), b));
+ }
+
+ public void testReadArrayReachEndOfStreamNoOffset() throws IOException {
+ final InputStream inputStream = createInputStream(TEST_BYTES);
+ final byte[] b = new byte[TEST_BYTES.length];
+ final int result = inputStream.read(b);
+ assertEquals(TEST_BYTES.length, result);
+ assertTrue(Arrays.equals(TEST_BYTES, b));
+ }
+
+ public void testReadArrayExceedEndOfStreamNoOffset() throws IOException {
+ final InputStream inputStream = createInputStream(TEST_BYTES);
+ final byte[] b = new byte[TEST_BYTES.length * 2];
+ final int result = inputStream.read(b);
+ assertEquals(TEST_BYTES.length, result);
+ assertTrue(Arrays.equals(Arrays.copyOf(TEST_BYTES, b.length), b));
+ }
+
+ public void testSkipZeroBytes() throws IOException {
+ final InputStream inputStream = createInputStream(TEST_BYTES);
+ long result = inputStream.skip(0);
+ assertEquals(0, result);
+ }
+
+ public void testSkipFewBytes() throws IOException {
+ final InputStream inputStream = createInputStream(TEST_BYTES);
+ long result = inputStream.skip(3);
+ assertEquals(3, result);
+ }
+
+ public void testSkipReachEndOfStream() throws IOException {
+ final InputStream inputStream = createInputStream(TEST_BYTES);
+ long result = inputStream.skip(TEST_BYTES.length);
+ assertEquals(TEST_BYTES.length, result);
+ }
+
+ public void testSkipExceedEndOfStream() throws IOException {
+ final InputStream inputStream = createInputStream(TEST_BYTES);
+ long result = inputStream.skip(TEST_BYTES.length + 5);
+ assertEquals(TEST_BYTES.length, result);
+ }
+}
diff --git a/user/test/com/google/gwt/emultest/java/io/InputStreamTest.java b/user/test/com/google/gwt/emultest/java/io/InputStreamTest.java
new file mode 100644
index 0000000..ba491d3
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java/io/InputStreamTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * 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 com.google.gwt.emultest.java.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+/**
+ * Unit test for the {@link java.io.InputStream} emulated class.
+ */
+public class InputStreamTest extends InputStreamBaseTest {
+
+ @Override
+ protected InputStream createInputStream(final byte[] expectedBytes) {
+ return new InputStream() {
+ // note that GWT fails here when trying to use clone().
+ private final byte[] b = Arrays.copyOf(expectedBytes, expectedBytes.length);
+ private int index = 0;
+ @Override public int read() {
+ int c = -1;
+ if (index < b.length) {
+ c = b[index];
+ index++;
+ }
+ return c;
+ }
+ };
+ }
+
+ public void testDefaultBehaviorOfAvailable() throws IOException {
+ final InputStream inputStream = createInputStream(TEST_BYTES);
+ final int available = inputStream.available();
+ assertEquals(0, available);
+ }
+
+ public void testDefaultBehaviorOfClose() throws IOException {
+ final InputStream inputStream = createInputStream(TEST_BYTES);
+ // should do nothing (including not throwing an exception)
+ inputStream.close();
+ }
+
+ public void testDefaultBehaviorOfMark() {
+ final InputStream inputStream = createInputStream(TEST_BYTES);
+ // should do nothing (including not throwing an exception)
+ inputStream.mark(1000);
+ }
+
+ public void testDefaultBehaviorOfMarkSupported() {
+ final InputStream inputStream = createInputStream(TEST_BYTES);
+ final boolean markSupported = inputStream.markSupported();
+ assertFalse(markSupported);
+ }
+
+ public void testDefaultBehaviorOfReset() {
+ final InputStream inputStream = createInputStream(new byte[] {});
+ try {
+ inputStream.reset();
+ fail("should have thrown IOException");
+ } catch (IOException expected) {
+ }
+ }
+}