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())); + } +}