Add BufferedWriter emulation to GWT
Change-Id: Ia7f7e66e404432d3ee32b4d06df704b09a7f2b4c
diff --git a/user/super/com/google/gwt/emul/java/io/BufferedWriter.java b/user/super/com/google/gwt/emul/java/io/BufferedWriter.java
new file mode 100644
index 0000000..922a482
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/io/BufferedWriter.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2020 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 javaemul.internal.InternalPreconditions.checkArgument;
+import static javaemul.internal.InternalPreconditions.checkNotNull;
+import static javaemul.internal.InternalPreconditions.checkState;
+
+/**
+ * See <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/io/BufferedWriter.html">the official
+ * Java API doc</a> for details.
+ */
+public class BufferedWriter extends Writer {
+ private static int defaultCharBufferSize = 8192;
+
+ private Writer out;
+ private char[] buf;
+ private int pos;
+ private int size;
+
+ public BufferedWriter(Writer out) {
+ this(out, defaultCharBufferSize);
+ }
+
+ public BufferedWriter(Writer out, int size) {
+ super(out);
+ checkArgument(size > 0, "Buffer size <= 0");
+ this.out = out;
+ this.buf = new char[size];
+ this.size = size;
+ this.pos = 0;
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (out == null) {
+ return;
+ }
+ try (Writer w = out) {
+ flushBuffer();
+ } finally {
+ out = null;
+ buf = null;
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ flushBuffer();
+ out.flush();
+ }
+
+ private void ensureOpen() throws IOException {
+ checkState(out != null, "stream closed");
+ }
+
+ private void flushBuffer() throws IOException {
+ ensureOpen();
+ if (pos > 0) {
+ out.write(buf, 0, pos);
+ }
+ pos = 0;
+ }
+
+ public void newLine() throws IOException {
+ write("\n");
+ }
+
+ @Override
+ public void write(char[] buffer, int offset, int count) throws IOException {
+ ensureOpen();
+ IOUtils.checkOffsetAndCount(buffer, offset, count);
+ if (count >= size) {
+ /* If the request length exceeds the size of the output buffer,
+ flush the buffer and then write the data directly. In this
+ way buffered streams will cascade harmlessly. */
+ flushBuffer();
+ out.write(buffer, offset, count);
+ return;
+ }
+
+ int b = offset, t = offset + count;
+ while (b < t) {
+ int d = Math.min(size - pos, t - b);
+ System.arraycopy(buffer, b, buf, pos, d);
+ b += d;
+ pos += d;
+ if (pos >= size) {
+ flushBuffer();
+ }
+ }
+ }
+
+ @Override
+ public void write(int oneChar) throws IOException {
+ ensureOpen();
+ if (pos >= size) {
+ out.write(buf, 0, buf.length);
+ pos = 0;
+ }
+ buf[pos++] = (char) oneChar;
+ }
+
+ @Override
+ public void write(String str, int offset, int count) throws IOException {
+ ensureOpen();
+ // Ensure we throw a NullPointerException instead of a JavascriptException in case the
+ // given string is null.
+ checkNotNull(str);
+ int b = offset, t = offset + count;
+ while (b < t) {
+ int d = Math.min(size - pos, t - b);
+ str.getChars(b, b + d, buf, pos);
+ b += d;
+ pos += d;
+ if (pos >= size) {
+ flushBuffer();
+ }
+ }
+ }
+}
diff --git a/user/test/com/google/gwt/emultest/EmulSuite.java b/user/test/com/google/gwt/emultest/EmulSuite.java
index 0d8bf9f..d28f9ec 100644
--- a/user/test/com/google/gwt/emultest/EmulSuite.java
+++ b/user/test/com/google/gwt/emultest/EmulSuite.java
@@ -16,6 +16,7 @@
package com.google.gwt.emultest;
import com.google.gwt.emultest.java.internal.CoercionsTest;
+import com.google.gwt.emultest.java.io.BufferedWriterTest;
import com.google.gwt.emultest.java.io.ByteArrayInputStreamTest;
import com.google.gwt.emultest.java.io.ByteArrayOutputStreamTest;
import com.google.gwt.emultest.java.io.FilterInputStreamTest;
@@ -70,6 +71,7 @@
CoercionsTest.class,
// -- java.io
+ BufferedWriterTest.class,
ByteArrayInputStreamTest.class,
ByteArrayOutputStreamTest.class,
FilterInputStreamTest.class,
diff --git a/user/test/com/google/gwt/emultest/java/io/BufferedWriterTest.java b/user/test/com/google/gwt/emultest/java/io/BufferedWriterTest.java
new file mode 100644
index 0000000..38b9a28
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java/io/BufferedWriterTest.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2020 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.BufferedWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/** Unit test for the {@link java.io.BufferedWriter} emulated class. */
+public class BufferedWriterTest extends GWTTestCase {
+
+ /** Default buffer size for the {@link java.io.BufferedWriter} object being tested. */
+ private static final int DEFAULT_BUFFER_SIZE = 2;
+
+ /** Array of characters that exceeds the default buffer size. */
+ private static final char[] BIG_CHAR_ARRAY = {'a', 'b', 'c', 'd', 'e', 'f', 'g'};
+
+ /** {@link java.io.BufferedWriter} object being tested. */
+ private BufferedWriter writer;
+
+ /** Underliying writer used by the {@link BufferedWriter} object. */
+ private SinkWriter sink;
+
+ /**
+ * {@link java.io.Writer} we pass to the {@link java.io.BufferedWriter} object being tested. It
+ * allows checking that the characters written by the buffered writter is what we expect.
+ */
+ private static class SinkWriter extends Writer {
+
+ private boolean closed;
+
+ private boolean flushed;
+
+ private ArrayList<Character> chars;
+
+ SinkWriter() {
+ closed = false;
+ flushed = false;
+ chars = new ArrayList<>(1024);
+ }
+
+ @Override
+ public void close() {
+ closed = true;
+ }
+
+ @Override
+ public void flush() {
+ flushed = true;
+ }
+
+ public ArrayList<Character> getChars() {
+ return chars;
+ }
+
+ public boolean isClosed() {
+ return closed;
+ }
+
+ public boolean isFlushed() {
+ return flushed;
+ }
+
+ /**
+ * Converts {@code outputChars} to primitive character array.
+ *
+ * @return primitive char array
+ */
+ public char[] toCharArray() {
+ char[] charArray = new char[chars.size()];
+ for (int i = 0; i < chars.size(); i++) {
+ charArray[i] = chars.get(i);
+ }
+ return charArray;
+ }
+
+ @Override
+ public void write(char[] cbuf, int off, int len) {
+ for (int i = off; i < (off + len); i++) {
+ chars.add(cbuf[i]);
+ }
+ }
+ }
+
+ /** Sets module name so that javascript compiler can operate. */
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.emultest.EmulSuite";
+ }
+
+ @Override
+ protected void gwtSetUp() throws Exception {
+ super.gwtSetUp();
+ sink = new SinkWriter();
+ writer = new BufferedWriter(sink, DEFAULT_BUFFER_SIZE);
+ }
+
+ public void testWriteChar() throws IOException {
+ writer.write('a');
+ assertTrue(sink.getChars().isEmpty());
+
+ writer.write('b');
+ assertTrue(sink.getChars().isEmpty());
+
+ writer.write('c');
+ assertTrue(Arrays.equals(new char[] {'a', 'b'}, sink.toCharArray()));
+ }
+
+ public void testWriteArrayUsingNullArray() throws IOException {
+ final char[] b = null;
+ try {
+ writer.write(b, 0, 2);
+ fail("should have thrown NullPointerException");
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ public void testWriteArrayUsingNegativeOffsetValue() throws IOException {
+ final char[] b = {'a', 'b'};
+ try {
+ writer.write(b, -1, 1);
+ fail("should have thrown IndexOutOfBoundsException");
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ }
+
+ public void testWriteArrayUsingNegativeLengthValue() throws IOException {
+ final char[] b = {'a', 'b'};
+ try {
+ writer.write(b, 0, -1);
+ fail("should have thrown IndexOutOfBoundsException");
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ }
+
+ public void testWriteArrayUsingInvalidRangeValue() throws IOException {
+ final char[] b = {'a', 'b'};
+ try {
+ writer.write(b, 1, 2);
+ fail("should have thrown IndexOutOfBoundsException");
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ }
+
+ public void testWriteArraySmallerThanBufferSize() throws IOException {
+ final char[] b = {'a'};
+ writer.write(b, 0, 1);
+ assertTrue(sink.getChars().isEmpty());
+ }
+
+ public void testWriteArrayEqualThanBufferSize() throws IOException {
+ final int len = DEFAULT_BUFFER_SIZE;
+ writer.write(BIG_CHAR_ARRAY, 1, len);
+ assertTrue(Arrays.equals(Arrays.copyOfRange(BIG_CHAR_ARRAY, 1, len + 1), sink.toCharArray()));
+ }
+
+ public void testWriteArrayLargerThanBufferSize() throws IOException {
+ final int len = DEFAULT_BUFFER_SIZE + 1;
+ writer.write(BIG_CHAR_ARRAY, 1, len);
+ assertTrue(Arrays.equals(Arrays.copyOfRange(BIG_CHAR_ARRAY, 1, len + 1), sink.toCharArray()));
+ }
+
+ public void testWriteArrayMultipleTimes() throws IOException {
+ writer.write(BIG_CHAR_ARRAY, 0, 1);
+ // writer: [ 'a' ], sink: [ ]
+ assertTrue(sink.getChars().isEmpty());
+
+ writer.write(BIG_CHAR_ARRAY, 1, 2);
+ // writer: [ ], sink: [ 'a', 'b', 'c' ]
+ assertTrue(Arrays.equals(Arrays.copyOfRange(BIG_CHAR_ARRAY, 0, 3), sink.toCharArray()));
+
+ writer.write(BIG_CHAR_ARRAY, 3, 3);
+ // writer: [ ], sink: [ 'a', 'b', 'c', 'd', 'e', 'f' ]
+ assertTrue(Arrays.equals(Arrays.copyOfRange(BIG_CHAR_ARRAY, 0, 6), sink.toCharArray()));
+
+ writer.write(BIG_CHAR_ARRAY, 6, 1);
+ // writer: [ 'g' ], sink: [ 'a', 'b', 'c', 'd', 'e', 'f' ]
+ assertTrue(Arrays.equals(Arrays.copyOfRange(BIG_CHAR_ARRAY, 0, 6), sink.toCharArray()));
+ }
+
+ public void testWriteStringUsingNullString() throws IOException {
+ final String s = null;
+ try {
+ writer.write(s, 0, 2);
+ fail("should have thrown NullPointerException");
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ public void testWriteStringUsingNegativeOffsetValue() throws IOException {
+ final String s = "ab";
+ try {
+ writer.write(s, -1, 1);
+ fail("should have thrown IndexOutOfBoundsException");
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ }
+
+ public void testWriteStringUsingNegativeLengthValue() throws IOException {
+ final String s = "ab";
+ writer.write(s, 0, -1);
+ assertTrue(sink.getChars().isEmpty());
+ }
+
+ public void testWriteStringUsingInvalidRangeValue() throws IOException {
+ final String s = "ab";
+ try {
+ writer.write(s, 1, 2);
+ fail("should have thrown IndexOutOfBoundsException");
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ }
+
+ public void testWriteStringSmallerThanBufferSize() throws IOException {
+ final String s = "a";
+ writer.write(s, 0, 1);
+ assertTrue(sink.getChars().isEmpty());
+ }
+
+ public void testWriteStringEqualThanBufferSize() throws IOException {
+ final String s = new String(BIG_CHAR_ARRAY);
+ writer.write(s, 0, 1);
+ assertTrue(sink.getChars().isEmpty());
+ }
+
+ public void testWriteStringLargerThanBufferSize() throws IOException {
+ final String s = new String(BIG_CHAR_ARRAY);
+ final int len = DEFAULT_BUFFER_SIZE + 1;
+ writer.write(s, 1, len);
+ assertTrue(Arrays.equals(Arrays.copyOfRange(BIG_CHAR_ARRAY, 1, len), sink.toCharArray()));
+ }
+
+ public void testWriteStringMultipleTimes() throws IOException {
+ final String s = new String(BIG_CHAR_ARRAY);
+ writer.write(s, 0, 1);
+ // writer: [ 'a' ], sink: [ ]
+ assertTrue(sink.getChars().isEmpty());
+
+ writer.write(s, 1, 2);
+ // writer: [ 'c' ], sink: [ 'a', 'b' ]
+ assertTrue(
+ Arrays.equals(
+ Arrays.copyOfRange(BIG_CHAR_ARRAY, 0, DEFAULT_BUFFER_SIZE), sink.toCharArray()));
+
+ writer.write(s, 3, 3);
+ // writer: [ ], sink: [ 'a', 'b', 'c', 'd', 'e', 'f' ]
+ assertTrue(Arrays.equals(Arrays.copyOfRange(BIG_CHAR_ARRAY, 0, 6), sink.toCharArray()));
+
+ writer.write(s, 6, 1);
+ // writer: [ 'g' ], sink: [ 'a', 'b', 'c', 'd', 'e', 'f' ]
+ assertTrue(Arrays.equals(Arrays.copyOfRange(BIG_CHAR_ARRAY, 0, 6), sink.toCharArray()));
+ }
+
+ public void testFlushWithEmptyBuffer() throws IOException {
+ writer.flush();
+ assertTrue(sink.getChars().isEmpty());
+ assertTrue(sink.isFlushed());
+ }
+
+ public void testFlushWithNonEmptyBuffer() throws IOException {
+ final char[] b = new char[] {'a'};
+ writer.write(b, 0, 1);
+ assertTrue(sink.getChars().isEmpty());
+
+ writer.flush();
+ assertTrue(Arrays.equals(b, sink.toCharArray()));
+ }
+
+ public void testCloseWithEmptyBuffer() throws IOException {
+ writer.close();
+ assertTrue(sink.getChars().isEmpty());
+ assertTrue(sink.isClosed());
+ }
+
+ public void testCloseWithNonEmptyBuffer() throws IOException {
+ final char[] b = new char[] {'a'};
+ writer.write(b, 0, 1);
+ assertTrue(sink.getChars().isEmpty());
+
+ writer.close();
+ assertTrue(Arrays.equals(b, sink.toCharArray()));
+ }
+}