Added emulation for OutputStream and ByteArrayOutputStream

Added emulation for the following Java IO classes based
on the Android libcore implementation:

 - OutputStream
 - ByteArrayOutputStream

Added corresponding unit tests.

Change-Id: I1a56c1629bdca278d616a4e16651a8d34984628a
Review-Link: https://gwt-review.googlesource.com/#/c/12950/
diff --git a/user/super/com/google/gwt/emul/java/io/ByteArrayOutputStream.java b/user/super/com/google/gwt/emul/java/io/ByteArrayOutputStream.java
new file mode 100644
index 0000000..ca3a166
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/io/ByteArrayOutputStream.java
@@ -0,0 +1,233 @@
+// 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 OutputStream} for class for writing content to an
+ * (internal) byte array. As bytes are written to this stream, the byte array
+ * may be expanded to hold more bytes. When the writing is considered to be
+ * finished, a copy of the byte array can be requested from the class.
+ *
+ * @see ByteArrayInputStream
+ */
+public class ByteArrayOutputStream extends OutputStream {
+    /**
+     * The byte array containing the bytes written.
+     */
+    protected byte[] buf;
+
+    /**
+     * The number of bytes written.
+     */
+    protected int count;
+
+    /**
+     * Constructs a new ByteArrayOutputStream with a default size of 32 bytes.
+     * If more than 32 bytes are written to this instance, the underlying byte
+     * array will expand.
+     */
+    public ByteArrayOutputStream() {
+        buf = new byte[32];
+    }
+
+    /**
+     * Constructs a new {@code ByteArrayOutputStream} with a default size of
+     * {@code size} bytes. If more than {@code size} bytes are written to this
+     * instance, the underlying byte array will expand.
+     *
+     * @param size
+     *            initial size for the underlying byte array, must be
+     *            non-negative.
+     * @throws IllegalArgumentException
+     *             if {@code size} < 0.
+     */
+    public ByteArrayOutputStream(int size) {
+        if (size >= 0) {
+            buf = new byte[size];
+        } else {
+            throw new IllegalArgumentException("size < 0");
+        }
+    }
+
+    /**
+     * Closes this stream. This releases system resources used for this stream.
+     *
+     * @throws IOException
+     *             if an error occurs while attempting to close this stream.
+     */
+    @Override
+    public void close() throws IOException {
+        /**
+         * Although the spec claims "A closed stream cannot perform output
+         * operations and cannot be reopened.", this implementation must do
+         * nothing.
+         */
+        super.close();
+    }
+
+    private void expand(int i) {
+        /* Can the buffer handle @i more bytes, if not expand it */
+        if (count + i <= buf.length) {
+            return;
+        }
+
+        byte[] newbuf = new byte[(count + i) * 2];
+        System.arraycopy(buf, 0, newbuf, 0, count);
+        buf = newbuf;
+    }
+
+    /**
+     * Resets this stream to the beginning of the underlying byte array. All
+     * subsequent writes will overwrite any bytes previously stored in this
+     * stream.
+     */
+    public void reset() {
+        count = 0;
+    }
+
+    /**
+     * Returns the total number of bytes written to this stream so far.
+     *
+     * @return the number of bytes written to this stream.
+     */
+    public int size() {
+        return count;
+    }
+
+    /**
+     * Returns the contents of this ByteArrayOutputStream as a byte array. Any
+     * changes made to the receiver after returning will not be reflected in the
+     * byte array returned to the caller.
+     *
+     * @return this stream's current contents as a byte array.
+     */
+    public byte[] toByteArray() {
+        byte[] newArray = new byte[count];
+        System.arraycopy(buf, 0, newArray, 0, count);
+        return newArray;
+    }
+
+    /**
+     * Returns the contents of this ByteArrayOutputStream as a string. Any
+     * changes made to the receiver after returning will not be reflected in the
+     * string returned to the caller.
+     *
+     * @return this stream's current contents as a string.
+     */
+
+    @Override
+    public String toString() {
+        return new String(buf, 0, count);
+    }
+
+    /**
+     * Returns the contents of this ByteArrayOutputStream as a string. Each byte
+     * {@code b} in this stream is converted to a character {@code c} using the
+     * following function:
+     * {@code c == (char)(((hibyte & 0xff) << 8) | (b & 0xff))}. This method is
+     * deprecated and either {@link #toString()} or {@link #toString(String)}
+     * should be used.
+     *
+     * @param hibyte
+     *            the high byte of each resulting Unicode character.
+     * @return this stream's current contents as a string with the high byte set
+     *         to {@code hibyte}.
+     * @deprecated Use {@link #toString()} instead.
+     */
+    @Deprecated
+    public String toString(int hibyte) {
+        char[] newBuf = new char[size()];
+        for (int i = 0; i < newBuf.length; i++) {
+            newBuf[i] = (char) (((hibyte & 0xff) << 8) | (buf[i] & 0xff));
+        }
+        return new String(newBuf);
+    }
+
+    /**
+     * Returns the contents of this ByteArrayOutputStream as a string converted
+     * according to the encoding declared in {@code charsetName}.
+     *
+     * @param charsetName
+     *            a string representing the encoding to use when translating
+     *            this stream to a string.
+     * @return this stream's current contents as an encoded string.
+     * @throws UnsupportedEncodingException
+     *             if the provided encoding is not supported.
+     */
+    public String toString(String charsetName) throws UnsupportedEncodingException {
+        return new String(buf, 0, count, charsetName);
+    }
+
+    /**
+     * Writes {@code count} bytes from the byte array {@code buffer} starting at
+     * offset {@code index} to this stream.
+     *
+     * @param buffer
+     *            the buffer to be written.
+     * @param offset
+     *            the initial position in {@code buffer} to retrieve bytes.
+     * @param len
+     *            the number of bytes of {@code buffer} to write.
+     * @throws NullPointerException
+     *             if {@code buffer} is {@code null}.
+     * @throws IndexOutOfBoundsException
+     *             if {@code offset < 0} or {@code len < 0}, or if
+     *             {@code offset + len} is greater than the length of
+     *             {@code buffer}.
+     */
+    @Override
+    public void write(byte[] buffer, int offset, int len) {
+        IOUtils.checkOffsetAndCount(buffer, offset, len);
+        if (len == 0) {
+            return;
+        }
+        expand(len);
+        System.arraycopy(buffer, offset, buf, this.count, len);
+        this.count += len;
+    }
+
+    /**
+     * Writes the specified byte {@code oneByte} to the OutputStream. Only the
+     * low order byte of {@code oneByte} is written.
+     *
+     * @param oneByte
+     *            the byte to be written.
+     */
+    @Override
+    public void write(int oneByte) {
+        if (count == buf.length) {
+            expand(1);
+        }
+        buf[count++] = (byte) oneByte;
+    }
+
+    /**
+     * Takes the contents of this stream and writes it to the output stream
+     * {@code out}.
+     *
+     * @param out
+     *            an OutputStream on which to write the contents of this stream.
+     * @throws IOException
+     *             if an error occurs while writing to {@code out}.
+     */
+    public void writeTo(OutputStream out) throws IOException {
+        out.write(buf, 0, count);
+    }
+}
diff --git a/user/super/com/google/gwt/emul/java/io/FilterOutputStream.java b/user/super/com/google/gwt/emul/java/io/FilterOutputStream.java
index 65f6a3b..e185e60 100644
--- a/user/super/com/google/gwt/emul/java/io/FilterOutputStream.java
+++ b/user/super/com/google/gwt/emul/java/io/FilterOutputStream.java
@@ -23,4 +23,7 @@
   public FilterOutputStream(OutputStream out) {
   }
 
+  @Override
+  public void write(int oneByte) throws IOException {
+  }
 }
diff --git a/user/super/com/google/gwt/emul/java/io/Flushable.java b/user/super/com/google/gwt/emul/java/io/Flushable.java
new file mode 100644
index 0000000..9d236c4
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/io/Flushable.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;
+
+/**
+ * Defines an interface for classes that can (or need to) be flushed, typically
+ * before some output processing is considered to be finished and the object
+ * gets closed.
+ */
+public interface Flushable {
+    /**
+     * Flushes the object by writing out any buffered data to the underlying
+     * output.
+     *
+     * @throws IOException
+     *             if there are any issues writing the data.
+     */
+    void flush() throws IOException;
+}
diff --git a/user/super/com/google/gwt/emul/java/io/OutputStream.java b/user/super/com/google/gwt/emul/java/io/OutputStream.java
index f735723..1331844 100644
--- a/user/super/com/google/gwt/emul/java/io/OutputStream.java
+++ b/user/super/com/google/gwt/emul/java/io/OutputStream.java
@@ -1,22 +1,131 @@
+// CHECKSTYLE_OFF: Copyrighted to the Android Open Source Project.
 /*
- * Copyright 2006 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.
+ *  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;
+
 /**
- * @skip
+ * A writable sink for bytes.
+ *
+ * <p>Most clients will use output streams that write data to the file system
+ * ({@link FileOutputStream}), the network ({@link java.net.Socket#getOutputStream()}/{@link
+ * java.net.HttpURLConnection#getOutputStream()}), or to an in-memory byte array
+ * ({@link ByteArrayOutputStream}).
+ *
+ * <p>Use {@link OutputStreamWriter} to adapt a byte stream like this one into a
+ * character stream.
+ *
+ * <p>Most clients should wrap their output stream with {@link
+ * BufferedOutputStream}. Callers that do only bulk writes may omit buffering.
+ *
+ * <h3>Subclassing OutputStream</h3>
+ * Subclasses that decorate another output stream should consider subclassing
+ * {@link FilterOutputStream}, which delegates all calls to the target output
+ * stream.
+ *
+ * <p>All output stream subclasses should override <strong>both</strong> {@link
+ * #write(int)} and {@link #write(byte[],int,int) write(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 InputStream
+ *
+ * <p>The implementation provided by this class behaves as described in the Java
+ * API documentation except for {@link write(int)} which throws an exception of
+ * type {@link java.lang.UnsupportedOperationException} instead of being
+ * abstract.
  */
-public abstract class OutputStream {
+public abstract class OutputStream implements Closeable, Flushable {
+
+    /**
+     * Default constructor.
+     */
+    public OutputStream() {
+    }
+
+    /**
+     * Closes this stream. Implementations of this method should free any
+     * resources used by the stream. This implementation does nothing.
+     *
+     * @throws IOException
+     *             if an error occurs while closing this stream.
+     */
+    public void close() throws IOException {
+        /* empty */
+    }
+
+    /**
+     * Flushes this stream. Implementations of this method should ensure that
+     * any buffered data is written out. This implementation does nothing.
+     *
+     * @throws IOException
+     *             if an error occurs while flushing this stream.
+     */
+    public void flush() throws IOException {
+        /* empty */
+    }
+
+    /**
+     * Equivalent to {@code write(buffer, 0, buffer.length)}.
+     */
+    public void write(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);
+        write(buffer, 0, buffer.length);
+    }
+
+    /**
+     * Writes {@code count} bytes from the byte array {@code buffer} starting at
+     * position {@code offset} to this stream.
+     *
+     * @param buffer
+     *            the buffer to be written.
+     * @param offset
+     *            the start position in {@code buffer} from where to get bytes.
+     * @param count
+     *            the number of bytes from {@code buffer} to write to this
+     *            stream.
+     * @throws IOException
+     *             if an error occurs while writing to this stream.
+     * @throws IndexOutOfBoundsException
+     *             if {@code offset < 0} or {@code count < 0}, or if
+     *             {@code offset + count} is bigger than the length of
+     *             {@code buffer}.
+     */
+    public void write(byte[] buffer, int offset, int count) throws IOException {
+        IOUtils.checkOffsetAndCount(buffer, offset, count);
+        for (int i = offset; i < offset + count; i++) {
+            write(buffer[i]);
+        }
+    }
+
+    /**
+     * Writes a single byte to this stream. Only the least significant byte of
+     * the integer {@code oneByte} is written to the stream.
+     *
+     * @param oneByte
+     *            the byte to be written.
+     * @throws IOException
+     *             if an error occurs while writing to this stream.
+     */
+    public abstract void write(int oneByte) throws IOException;
 }
diff --git a/user/test/com/google/gwt/emultest/EmulSuite.java b/user/test/com/google/gwt/emultest/EmulSuite.java
index 0f447bc..43a4db2 100644
--- a/user/test/com/google/gwt/emultest/EmulSuite.java
+++ b/user/test/com/google/gwt/emultest/EmulSuite.java
@@ -16,7 +16,9 @@
 package com.google.gwt.emultest;
 
 import com.google.gwt.emultest.java.io.ByteArrayInputStreamTest;
+import com.google.gwt.emultest.java.io.ByteArrayOutputStreamTest;
 import com.google.gwt.emultest.java.io.InputStreamTest;
+import com.google.gwt.emultest.java.io.OutputStreamTest;
 import com.google.gwt.emultest.java.lang.BooleanTest;
 import com.google.gwt.emultest.java.lang.ByteTest;
 import com.google.gwt.emultest.java.lang.CharacterTest;
@@ -57,7 +59,9 @@
     // $JUnit-BEGIN$
     //-- java.io
     suite.addTestSuite(ByteArrayInputStreamTest.class);
+    suite.addTestSuite(ByteArrayOutputStreamTest.class);
     suite.addTestSuite(InputStreamTest.class);
+    suite.addTestSuite(OutputStreamTest.class);
     //-- java.lang
     suite.addTestSuite(BooleanTest.class);
     suite.addTestSuite(ByteTest.class);
diff --git a/user/test/com/google/gwt/emultest/java/io/ByteArrayOutputStreamTest.java b/user/test/com/google/gwt/emultest/java/io/ByteArrayOutputStreamTest.java
new file mode 100644
index 0000000..c7cced3
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java/io/ByteArrayOutputStreamTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+/**
+ * Unit test for the {@link java.io.ByteArrayOutputStream} emulated class.
+ */
+public class ByteArrayOutputStreamTest extends OutputStreamBaseTest {
+
+  private static ByteArrayOutputStream outputStream;
+
+  @Override
+  protected OutputStream createDefaultOutputStream() {
+    outputStream = new ByteArrayOutputStream();
+    return outputStream;
+  }
+
+  @Override
+  protected byte[] getBytesWritten() {
+    return outputStream !=  null ? outputStream.toByteArray() : null;
+  }
+
+  public void testClose() throws IOException {
+    final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+    // should do nothing (including not throwing an exception).
+    outputStream.close();
+  }
+
+  public void testFlush() throws IOException {
+    final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+    // should do nothing (including not throwing an exception).
+    outputStream.flush();
+  }
+
+  public void testReset() throws IOException {
+    final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(2);
+
+    outputStream.write(TEST_ARRAY);
+
+    byte[] actualBytes = outputStream.toByteArray();
+    assertTrue(Arrays.equals(TEST_ARRAY, actualBytes));
+ 
+    outputStream.reset();
+
+    final byte[] expectedBytes = new byte[] { 101, 102 };
+    outputStream.write(expectedBytes);
+
+    actualBytes = outputStream.toByteArray();
+    assertTrue(Arrays.equals(expectedBytes, actualBytes));
+  }
+
+  public void testSize() throws IOException {
+    final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1);
+    assertEquals(0, outputStream.size());
+
+    outputStream.write(128);
+    assertEquals(1, outputStream.size());
+
+    outputStream.write(TEST_ARRAY);
+    assertEquals(1 + TEST_ARRAY.length, outputStream.size());
+
+    outputStream.write(TEST_ARRAY, 1, 2);
+    assertEquals(3 + TEST_ARRAY.length, outputStream.size());
+
+    outputStream.reset();
+    assertEquals(0, outputStream.size());
+
+    outputStream.write(128);
+    assertEquals(1, outputStream.size());
+  }
+
+  public void testToStringUsingEmptyStream() throws IOException {
+    final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1);
+    final String actualString = outputStream.toString();
+    assertTrue(actualString.isEmpty());
+  }
+
+  public void testToStringUsingNonEmptyStream() throws IOException {
+    final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1);
+    final byte[] values = new byte[] {
+        (byte) 0x48, (byte) 0x65, (byte) 0x6c, (byte) 0x6c, (byte) 0x6f
+    };
+    outputStream.write(values);
+    final String actualString = outputStream.toString();
+    assertEquals("Hello", actualString);
+  }
+
+  public void testToStringWithHighByteAndEmptyStream() throws IOException {
+    final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1);
+    final String actualString = outputStream.toString(0x01);
+    assertTrue(actualString.isEmpty());
+  }
+
+  public void testToStringWithHighByteAndNonEmptyStream() throws IOException {
+    final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1);
+    final byte[] values = new byte[] {
+        (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04
+    };
+    outputStream.write(values);
+    final String actualString = outputStream.toString(0x01);
+    final String expectedString = new String(new char[] {
+        (char) 0x0100, (char) 0x0101, (char) 0x0102, (char) 0x0103, (char) 0x0104
+    });
+    assertEquals(expectedString, actualString);
+  }
+
+  public void testToStringWithCharsetNameAndEmptyStream() throws IOException {
+    final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1);
+    final String actualString = outputStream.toString("UTF-8");
+    assertTrue(actualString.isEmpty());
+  }
+
+  public void testToStringWithCharsetNameAndNonEmptyStream() throws IOException {
+    final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1);
+    final String expectedString = "Hello";
+
+    outputStream.write(expectedString.getBytes("UTF-8"));
+    final String actualString = outputStream.toString("UTF-8");
+    assertEquals(expectedString, actualString);
+  }
+
+  public void testWriteSingleValues() throws IOException {
+    final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(2);
+    assertEquals(0, outputStream.size());
+
+    for (int i = 0; i < 3; i++) {
+      outputStream.write(TEST_ARRAY[i]);
+      assertEquals(i + 1, outputStream.size());
+    }
+    final byte[] expectedBytes = Arrays.copyOf(TEST_ARRAY, 3);
+    final byte[] actualBytes = outputStream.toByteArray();
+    assertTrue(Arrays.equals(expectedBytes, actualBytes));
+  }
+}
diff --git a/user/test/com/google/gwt/emultest/java/io/OutputStreamBaseTest.java b/user/test/com/google/gwt/emultest/java/io/OutputStreamBaseTest.java
new file mode 100644
index 0000000..e7b02a9
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java/io/OutputStreamBaseTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.OutputStream;
+import java.util.Arrays;
+
+/**
+ * Class for reusing tests that are commong to {@link java.io.OutputStream} and its subclasses.
+ */
+public abstract class OutputStreamBaseTest extends GWTTestCase {
+
+  protected static final byte[] TEST_ARRAY = new byte[] { 10, 20, 30, 40, 50 };
+
+  /**
+   * Factory method for creating a stream object.
+   *
+   * @return output stream object to be tested.
+   */
+  protected abstract OutputStream createDefaultOutputStream();
+
+  /**
+   * Retrieves the array of bytes written by the seam.
+   *
+   * @return bytes written by the stream.
+   */
+  protected abstract byte[] getBytesWritten();
+
+  /**
+   * Sets module name so that javascript compiler can operate.
+   */
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.emultest.EmulSuite";
+  }
+
+  public void testWriteArrayUsingNullArrayObject() throws IOException {
+    final OutputStream outputStream = createDefaultOutputStream();
+    try {
+      outputStream.write(null, 0, 1);
+      fail("should have thrown NullPointerException");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  public void testWriteArrayUsingNegativeOffsetValue() throws IOException {
+    final OutputStream outputStream = createDefaultOutputStream();
+    try {
+      outputStream.write(TEST_ARRAY, -1, 1);
+      fail("should have thrown IndexOutOfBoundsException");
+    } catch (IndexOutOfBoundsException expected) {
+    }
+  }
+
+  public void testWriteArrayUsingNegativeLengthValue() throws IOException {
+    final OutputStream outputStream = createDefaultOutputStream();
+    try {
+      outputStream.write(TEST_ARRAY, 0, -1);
+      fail("should have thrown IndexOutOfBoundsException");
+    } catch (IndexOutOfBoundsException expected) {
+    }
+  }
+
+  public void testWriteArrayUsingAnInvalidRange() throws IOException {
+    final OutputStream outputStream = createDefaultOutputStream();
+    try {
+      outputStream.write(TEST_ARRAY, 1, TEST_ARRAY.length);
+      fail("should have thrown IndexOutOfBoundsException");
+    } catch (IndexOutOfBoundsException expected) {
+    }
+  }
+
+  public void testWriteArrayZeroLength() throws IOException {
+    final OutputStream outputStream = createDefaultOutputStream();
+    outputStream.write(TEST_ARRAY, 0, 0);
+    assertEquals(0, getBytesWritten().length);
+  }
+
+  public void testWriteArrayZeroOffset() throws IOException {
+    final OutputStream outputStream = createDefaultOutputStream();
+    outputStream.write(TEST_ARRAY, 0, TEST_ARRAY.length);
+    assertTrue(Arrays.equals(TEST_ARRAY, getBytesWritten()));
+  }
+
+  public void testWriteArrayFirstBytesOnly() throws IOException {
+    final OutputStream outputStream = createDefaultOutputStream();
+    outputStream.write(TEST_ARRAY, 0, TEST_ARRAY.length - 2);
+
+    final byte[] expected = Arrays.copyOf(TEST_ARRAY, TEST_ARRAY.length - 2);
+    assertTrue(Arrays.equals(expected, getBytesWritten()));
+  }
+
+  public void testWriteArrayLastBytesOnly() throws IOException {
+    final OutputStream outputStream = createDefaultOutputStream();
+    outputStream.write(TEST_ARRAY, 2, TEST_ARRAY.length - 2);
+
+    final byte[] expected = Arrays.copyOfRange(TEST_ARRAY, 2, TEST_ARRAY.length);
+    assertTrue(Arrays.equals(expected, getBytesWritten()));
+  }
+
+  public void testWriteArrayMiddleBytesOnly() throws IOException {
+    final OutputStream outputStream = createDefaultOutputStream();
+    outputStream.write(TEST_ARRAY, 2, TEST_ARRAY.length - 4);
+
+    final byte[] expected = Arrays.copyOfRange(TEST_ARRAY, 2, TEST_ARRAY.length - 2);
+    assertTrue(Arrays.equals(expected, getBytesWritten()));
+  }
+
+  public void testWriteArrayUsingNullArrayObjectAndNoOffset() throws IOException {
+    final OutputStream outputStream = createDefaultOutputStream();
+    try {
+      outputStream.write(null);
+      fail("should have thrown NullPointerException");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  public void testWriteArrayZeroBytesNoOffset() throws IOException {
+    final OutputStream outputStream = createDefaultOutputStream();
+    outputStream.write(new byte[0]);
+    assertEquals(0, getBytesWritten().length);
+  }
+
+  public void testWriteArrayNoOffset() throws IOException {
+    final OutputStream outputStream = createDefaultOutputStream();
+    outputStream.write(TEST_ARRAY);
+    assertTrue(Arrays.equals(TEST_ARRAY, getBytesWritten()));
+  }
+}
diff --git a/user/test/com/google/gwt/emultest/java/io/OutputStreamTest.java b/user/test/com/google/gwt/emultest/java/io/OutputStreamTest.java
new file mode 100644
index 0000000..c5f8370
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java/io/OutputStreamTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.OutputStream;
+import java.util.LinkedList;
+
+/**
+ * Unit test for the {@link java.io.OutputStream} emulated class.
+ */
+public class OutputStreamTest extends OutputStreamBaseTest {
+
+  private static LinkedList<Byte> outputBytes;
+
+  @Override
+  protected void gwtSetUp() throws Exception {
+    super.gwtSetUp();
+    outputBytes = new LinkedList<>();
+  }
+
+  @Override
+  protected OutputStream createDefaultOutputStream() {
+    return new OutputStream() {
+      @Override public void write(int b) {
+        outputBytes.add((byte) b);
+      }
+    };
+  }
+
+  @Override
+  protected byte[] getBytesWritten() {
+    byte[] bytesWritten = new byte[outputBytes.size()];
+    int i = 0;
+    for (Byte nextByte : outputBytes) {
+      bytesWritten[i++] = nextByte;
+    }
+    return bytesWritten;
+  }
+
+  public void testDefaultBehaviorOfClose() throws IOException {
+    final OutputStream outputStream = createDefaultOutputStream();
+    // should do nothing (including not throwing an exception).
+    outputStream.close();
+  }
+
+  public void testDefaultBehaviorOfFlush() throws IOException {
+    final OutputStream outputStream = createDefaultOutputStream();
+    // should do nothing (including not throwing an exception).
+    outputStream.flush();
+  }
+}