Add Reader and StringReader emulation.

Change-Id: Ib382e5d1605e56a37b6dca6703016591f5a06c63
Review-Link: https://gwt-review.googlesource.com/#/c/20680/
diff --git a/user/super/com/google/gwt/emul/java/io/Reader.java b/user/super/com/google/gwt/emul/java/io/Reader.java
new file mode 100644
index 0000000..5413799
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/io/Reader.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2018 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;
+
+/**
+ * Reads a stream of characters.
+ */
+public abstract class Reader {
+  /**
+   * The maximum buffer size to incrementally read in {@link #skip}.
+   */
+  private static final int MAX_SKIP_BUFFER_SIZE = 1024;
+
+  /**
+   * Closes the reader, and releases any associated resources.
+   */
+  public abstract void close() throws IOException;
+
+  /**
+   * Marks the present position in the stream. Until {@code readAheadLimit} more
+   * characters have been read, the current point in the stream will be stored
+   * as the mark. Calls to {@link #reset} will reposition the point in the
+   * stream to the mark.
+   *
+   * @throws IOException If the stream does not support mark().
+   */
+  public void mark(int readAheadLimit) throws IOException {
+    throw new IOException("Not supported");
+  }
+
+  /**
+   * Returns whether {@link #mark} is implemented.
+   */
+  public boolean markSupported() {
+    return false;
+  }
+
+  /**
+   * Reads a single character, or -1 if we are at the end of the stream.
+   */
+  public int read() throws IOException {
+    char chr[] = new char[1];
+    return (read(chr) == -1) ? -1 : chr[0];
+  }
+
+  /**
+   * Attempts to fill {@code buf} with characters up to the size of the array.
+   */
+  public int read(char[] buf) {
+    return read(buf, 0, buf.length);
+  }
+
+  /**
+   * Attempts to fill {@code buf} with up to {@code len} characters. Characters
+   * will be stored in {@code buf} starting at index {@code off}.
+   */
+  public abstract int read(char[] buf, int off, int len);
+
+  /**
+   * Returns whether the stream is ready for reading characters.
+   */
+  public boolean ready() throws IOException {
+    return false;
+  }
+
+  /**
+   * Attempts to reset the stream to the previous mark.
+   */
+  public void reset() throws IOException {
+    throw new IOException("Not supported");
+  }
+
+  /**
+   * Skips {@code n} characters, returning the number of characters that were actually skipped.
+   */
+  public long skip(long n) throws IOException {
+    long remaining = n;
+    int bufferSize = Math.min((int) n, MAX_SKIP_BUFFER_SIZE);
+    char[] skipBuffer = new char[bufferSize];
+    while (remaining > 0) {
+      long numRead = read(skipBuffer, 0, (int) remaining);
+      if (numRead < 0) {
+        break;
+      }
+      remaining -= numRead;
+    }
+    return n - remaining;
+  }
+}
diff --git a/user/super/com/google/gwt/emul/java/io/StringReader.java b/user/super/com/google/gwt/emul/java/io/StringReader.java
new file mode 100644
index 0000000..08d3241
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/io/StringReader.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2018 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;
+
+/**
+ * Reads characters from a string.
+ */
+public class StringReader extends Reader {
+  private final String text;
+  private int position;
+
+  /**
+   * Constructs a reader which will read from the given string.
+   */
+  public StringReader(String text) {
+    this.text = text;
+  }
+
+  @Override
+  public void close() throws IOException { }
+
+  /**
+   * Reads up to the specified number of characters from the string.
+   */
+  @Override
+  public int read(char[] buf, int off, int readLength) {
+    if (position >= text.length()) {
+      return -1;
+    }
+    int length = Math.min(text.length() - position, readLength);
+    text.getChars(position, position + length, buf, off);
+    position += length;
+    return length;
+  }
+}
diff --git a/user/test/com/google/gwt/emultest/java/io/StringReaderTest.java b/user/test/com/google/gwt/emultest/java/io/StringReaderTest.java
new file mode 100644
index 0000000..c9d79de
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java/io/StringReaderTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2018 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.StringReader;
+import java.util.Arrays;
+
+/**
+ * Unit test for the {@link java.io.StringReader} emulated class.
+ */
+public class StringReaderTest extends GWTTestCase {
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.emultest.EmulSuite";
+  }
+
+  public void testEmptyString() throws Exception {
+    StringReader reader = new StringReader("");
+    assertEquals(-1, reader.read());
+    char[] buf = new char[5];
+    assertEquals(-1, reader.read(buf));
+    assertEquals(-1, reader.read(buf, 2, 0));
+  }
+
+  public void testString() throws Exception {
+    StringReader reader = new StringReader("The q\u00DCuick brown fox jumped over the lazy dog");
+    assertEquals('T', reader.read());
+
+    char[] buf = new char[10];
+    assertEquals(6, reader.read(buf, 3, 6));
+    assertEquals("\u0000\u0000\u0000he q\u00DCi\u0000", String.valueOf(buf));
+
+    assertEquals('k', reader.read());
+    assertEquals(' ', reader.read());
+
+    assertEquals(2, reader.read(buf, 0, 2));
+    // First 2 characters now filled.
+    assertEquals("br\u0000he q\u00DCi\u0000", String.valueOf(buf));
+
+    assertEquals(10, reader.read(buf));
+    assertEquals("own fox ju", String.valueOf(buf));
+
+    char[] four = new char[4];
+    assertEquals(4, reader.read(four));
+    assertEquals("mped", String.valueOf(four));
+
+    char[] emptyBuf = new char[0];
+    assertEquals(0, reader.read(emptyBuf));
+
+    assertEquals(10, reader.read(buf));
+    assertEquals(" over the ", String.valueOf(buf));
+
+    char[] eight = new char[8];
+    assertEquals(7, reader.read(eight));
+    assertEquals("lazy dog\u0000", String.valueOf(eight));
+
+    assertEquals(-1, reader.read());
+    assertEquals(-1, reader.read(eight));
+    assertEquals(-1, reader.read(eight, 0, 0));
+    assertEquals(-1, reader.read(eight, 0, 1));
+  }
+
+  public void testSkip_stringOverBufferSize() throws Exception {
+    StringReader reader = new StringReader(repeat('x', 1025));
+
+    assertEquals(1025, reader.skip(2000));
+
+    assertEquals(-1, reader.read());
+  }
+
+  public void testSkip_longString() throws Exception {
+    int n = 10000;
+    char[] arr = new char[n];
+    for (char i = 0; i < n; i++) {
+      arr[i] = i;
+    }
+
+    StringReader reader = new StringReader(String.valueOf(arr));
+    assertEquals(5000, reader.skip(5000));
+    assertEquals(5000, reader.read());
+    assertEquals(5001, reader.read());
+
+    assertEquals(4000, reader.skip(4000));
+    assertEquals(9002, reader.read());
+
+    assertEquals(997, reader.skip(4000));
+    assertEquals(-1, reader.read());
+  }
+
+  private static String repeat(char c, int count) {
+    char[] arr = new char[count];
+    Arrays.fill(arr, c);
+    return String.valueOf(arr);
+  }
+}