Add OutputStreamWriter emulation in GWT
Change-Id: Idc42124cfc6421b9e58432a36b1bf4246b91c979
diff --git a/user/super/com/google/gwt/emul/java/io/IOUtils.java b/user/super/com/google/gwt/emul/java/io/IOUtils.java
index b8d3049..118cba2 100644
--- a/user/super/com/google/gwt/emul/java/io/IOUtils.java
+++ b/user/super/com/google/gwt/emul/java/io/IOUtils.java
@@ -59,6 +59,23 @@
}
/**
+ * Validates the offset and the byte count for the given string.
+ *
+ * @param str String to be checked.
+ * @param offset Starting offset in the string.
+ * @param count Total number of characters to be accessed.
+ * @throws NullPointerException if the given reference to the string is null.
+ * @throws IndexOutOfBoundsException if {@code offset} is negative, {@code count} is
+ * negative or their sum exceeds the string length.
+ */
+ public static void checkOffsetAndCount(String str, int offset, int count) {
+ // Ensure we throw a NullPointerException instead of a JavascriptException in case the
+ // given string is null.
+ checkNotNull(str);
+ checkOffsetAndCount(str.length(), offset, count);
+ }
+
+ /**
* Validates the offset and the byte count for the given array length.
*
* @param length Length of the array to be checked.
diff --git a/user/super/com/google/gwt/emul/java/io/OutputStreamWriter.java b/user/super/com/google/gwt/emul/java/io/OutputStreamWriter.java
new file mode 100644
index 0000000..e03612d
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/io/OutputStreamWriter.java
@@ -0,0 +1,62 @@
+/*
+ * 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.checkNotNull;
+
+import java.nio.charset.Charset;
+import javaemul.internal.EmulatedCharset;
+
+/**
+ * See <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/io/OutputStreamWriter.html">the
+ * official Java API doc</a> for details.
+ */
+public class OutputStreamWriter extends Writer {
+
+ private final OutputStream out;
+
+ private final Charset charset;
+
+ public OutputStreamWriter(OutputStream out, String charsetName) {
+ this(out, Charset.forName(charsetName));
+ }
+
+ public OutputStreamWriter(OutputStream out, Charset charset) {
+ this.out = checkNotNull(out);
+ this.charset = checkNotNull(charset);
+ }
+
+ @Override
+ public void close() throws IOException {
+ out.close();
+ }
+
+ @Override
+ public void flush() throws IOException {
+ out.flush();
+ }
+
+ public String getEncoding() {
+ return charset.name();
+ }
+
+ @Override
+ public void write(char[] buffer, int offset, int count) throws IOException {
+ IOUtils.checkOffsetAndCount(buffer, offset, count);
+ byte[] byteBuffer = ((EmulatedCharset) charset).getBytes(buffer, offset, count);
+ out.write(byteBuffer, 0, byteBuffer.length);
+ }
+}
diff --git a/user/super/com/google/gwt/emul/java/io/Writer.java b/user/super/com/google/gwt/emul/java/io/Writer.java
new file mode 100644
index 0000000..4bba793
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/io/Writer.java
@@ -0,0 +1,84 @@
+/*
+ * 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.checkNotNull;
+import static javaemul.internal.InternalPreconditions.checkPositionIndexes;
+
+import java.util.Objects;
+
+/**
+ * See <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/io/Writer.html">the official Java API
+ * doc</a> for details.
+ */
+public abstract class Writer implements Appendable, Closeable, Flushable {
+
+ protected Writer() {}
+
+ protected Writer(Object lock) {}
+
+ public abstract void close() throws IOException;
+
+ public abstract void flush() throws IOException;
+
+ public void write(char[] buf) throws IOException {
+ // Ensure we throw a NullPointerException instead of a JavascriptException in case the
+ // given buffer is null.
+ checkNotNull(buf);
+ write(buf, 0, buf.length);
+ }
+
+ public abstract void write(char[] buf, int offset, int count) throws IOException;
+
+ public void write(int oneChar) throws IOException {
+ char[] oneCharArray = new char[1];
+ oneCharArray[0] = (char) oneChar;
+ write(oneCharArray);
+ }
+
+ public void write(String str) throws IOException {
+ // Ensure we throw a NullPointerException instead of a JavascriptException in case the
+ // given string is null.
+ checkNotNull(str);
+ write(str, 0, str.length());
+ }
+
+ public void write(String str, int offset, int count) throws IOException {
+ IOUtils.checkOffsetAndCount(str, offset, count);
+ char[] buf = new char[count];
+ str.getChars(offset, offset + count, buf, 0);
+ write(buf, 0, buf.length);
+ }
+
+ public Writer append(char c) throws IOException {
+ write(c);
+ return this;
+ }
+
+ public Writer append(CharSequence csq) throws IOException {
+ write(Objects.toString(csq));
+ return this;
+ }
+
+ public Writer append(CharSequence csq, int start, int end) throws IOException {
+ if (csq == null) {
+ csq = "null";
+ }
+ checkPositionIndexes(start, end, csq.length());
+ write(csq.subSequence(start, end).toString());
+ return this;
+ }
+}
diff --git a/user/super/com/google/gwt/emul/javaemul/internal/EmulatedCharset.java b/user/super/com/google/gwt/emul/javaemul/internal/EmulatedCharset.java
index ddde0fa..4cdf924 100644
--- a/user/super/com/google/gwt/emul/javaemul/internal/EmulatedCharset.java
+++ b/user/super/com/google/gwt/emul/javaemul/internal/EmulatedCharset.java
@@ -34,6 +34,16 @@
}
@Override
+ public byte[] getBytes(char[] buffer, int offset, int count) {
+ int n = offset + count;
+ byte[] bytes = new byte[count];
+ for (int i = offset; i < n; ++i) {
+ bytes[i] = (byte) (buffer[i] & 255);
+ }
+ return bytes;
+ }
+
+ @Override
public byte[] getBytes(String str) {
int n = str.length();
byte[] bytes = new byte[n];
@@ -118,26 +128,23 @@
}
@Override
+ public byte[] getBytes(char[] buffer, int offset, int count) {
+ int n = offset + count;
+ byte[] bytes = new byte[0];
+ int out = 0;
+ for (int i = offset; i < n; ) {
+ int ch = Character.codePointAt(buffer, i, n);
+ i += Character.charCount(ch);
+ out += encodeUtf8(bytes, out, ch);
+ }
+ return bytes;
+ }
+
+ @Override
public byte[] getBytes(String str) {
// TODO(jat): consider using unescape(encodeURIComponent(bytes)) instead
int n = str.length();
- int byteCount = 0;
- for (int i = 0; i < n;) {
- int ch = str.codePointAt(i);
- i += Character.charCount(ch);
- if (ch < (1 << 7)) {
- byteCount++;
- } else if (ch < (1 << 11)) {
- byteCount += 2;
- } else if (ch < (1 << 16)) {
- byteCount += 3;
- } else if (ch < (1 << 21)) {
- byteCount += 4;
- } else if (ch < (1 << 26)) {
- byteCount += 5;
- }
- }
- byte[] bytes = new byte[byteCount];
+ byte[] bytes = new byte[0];
int out = 0;
for (int i = 0; i < n;) {
int ch = str.codePointAt(i);
@@ -197,5 +204,7 @@
public abstract byte[] getBytes(String string);
+ public abstract byte[] getBytes(char[] buffer, int offset, int count);
+
public abstract char[] decodeString(byte[] bytes, int ofs, int len);
}
diff --git a/user/test/com/google/gwt/emultest/EmulSuite.java b/user/test/com/google/gwt/emultest/EmulSuite.java
index 575fd5b..0d8bf9f 100644
--- a/user/test/com/google/gwt/emultest/EmulSuite.java
+++ b/user/test/com/google/gwt/emultest/EmulSuite.java
@@ -22,6 +22,8 @@
import com.google.gwt.emultest.java.io.FilterOutputStreamTest;
import com.google.gwt.emultest.java.io.InputStreamTest;
import com.google.gwt.emultest.java.io.OutputStreamTest;
+import com.google.gwt.emultest.java.io.OutputStreamWriterTest;
+import com.google.gwt.emultest.java.io.WriterTest;
import com.google.gwt.emultest.java.lang.BooleanTest;
import com.google.gwt.emultest.java.lang.ByteTest;
import com.google.gwt.emultest.java.lang.CharacterTest;
@@ -74,6 +76,8 @@
FilterOutputStreamTest.class,
InputStreamTest.class,
OutputStreamTest.class,
+ OutputStreamWriterTest.class,
+ WriterTest.class,
// -- java.lang
BooleanTest.class,
diff --git a/user/test/com/google/gwt/emultest/java/io/OutputStreamWriterTest.java b/user/test/com/google/gwt/emultest/java/io/OutputStreamWriterTest.java
new file mode 100644
index 0000000..49ae2b2
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java/io/OutputStreamWriterTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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 static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.gwt.junit.client.GWTTestCase;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+
+/**
+ * Unit test for the {@link java.io.OutputStreamWriter} emulated class.
+ */
+public class OutputStreamWriterTest extends GWTTestCase {
+
+ private final Charset encodingUTF8Charset = UTF_8;
+
+ /** String containing unicode characters. */
+ private static final String UNICODE_STRING = "ËÛëŶǾȜϞ";
+
+ /** Array of characters that contains ASCII characters. */
+ private static final char[] ASCII_CHAR_ARRAY = {'a', 'b', 'c', '"', '&', '<', '>'};
+
+ /** {@link java.io.OutputStreamWriter} object being tested. */
+ private OutputStreamWriter writer;
+
+ /** Underlying output stream used by the {@link OutputStreamWriter} object. */
+ private ByteArrayOutputStream baos;
+
+ /**
+ * 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();
+ baos = new ByteArrayOutputStream();
+ writer = new OutputStreamWriter(baos, encodingUTF8Charset);
+ }
+
+ public void testNullCharset() throws UnsupportedEncodingException {
+ Charset nullCharset = null;
+ try {
+ new OutputStreamWriter(baos, nullCharset);
+ fail("should have thrown NullPointerException");
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ public void testNullOutputStream() throws UnsupportedEncodingException {
+ try {
+ new OutputStreamWriter(/* out = */ null, encodingUTF8Charset);
+ fail("should have thrown NullPointerException");
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ public void testWriteUnicodeChar() throws IOException {
+ writer.write(UNICODE_STRING, 0, UNICODE_STRING.length());
+ assertTrue(Arrays.equals(UNICODE_STRING.getBytes(encodingUTF8Charset), baos.toByteArray()));
+ }
+
+ public void testWriteASCIIChar() throws IOException {
+ writer.write(ASCII_CHAR_ARRAY, 0, ASCII_CHAR_ARRAY.length);
+ assertTrue(
+ Arrays.equals(
+ new String(ASCII_CHAR_ARRAY).getBytes(encodingUTF8Charset), baos.toByteArray()));
+ }
+
+ 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) {
+ }
+ }
+}
diff --git a/user/test/com/google/gwt/emultest/java/io/WriterTest.java b/user/test/com/google/gwt/emultest/java/io/WriterTest.java
new file mode 100644
index 0000000..7df1001
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java/io/WriterTest.java
@@ -0,0 +1,250 @@
+/*
+ * 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.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit test for the {@link java.io.Writer} emulated class.
+ */
+public class WriterTest extends GWTTestCase {
+
+ private TestWriter writer;
+
+ /**
+ * Instatiable version of {@link java.io.Writer} for testing purposes.
+ */
+ private static class TestWriter extends Writer {
+
+ private List<Character> outputChars = new ArrayList<>(1024);
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public void flush() {
+ }
+
+ @Override
+ public void write(char[] cbuf, int off, int len) {
+ for (int i = off; i < (off + len); i++) {
+ outputChars.add(cbuf[i]);
+ }
+ }
+
+ /**
+ * Converts {@code outputChars} to primitive character array.
+ *
+ * @return primitive char array
+ */
+ public char[] toCharArray() {
+ if (outputChars.isEmpty()) {
+ return null;
+ }
+ char[] charArray = new char[outputChars.size()];
+ for (int i = 0; i < outputChars.size(); i++) {
+ charArray[i] = outputChars.get(i);
+ }
+ return charArray;
+ }
+ }
+
+ /**
+ * 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();
+ writer = new TestWriter();
+ }
+
+ public void testAppendChar() throws IOException {
+ Writer w = writer.append('a');
+ assertEquals(writer, w);
+
+ assertTrue(Arrays.equals(new char[] { 'a' }, writer.toCharArray()));
+
+ w = writer.append('b');
+ assertEquals(writer, w);
+ assertTrue(Arrays.equals(new char[] { 'a', 'b' }, writer.toCharArray()));
+
+ w = writer.append('c');
+ assertEquals(writer, w);
+ assertTrue(Arrays.equals(new char[] { 'a', 'b', 'c' }, writer.toCharArray()));
+ }
+
+ public void testAppendNullCharSequence() throws IOException {
+ final Writer w = writer.append(null);
+ assertEquals(writer, w);
+ assertTrue(Arrays.equals("null".toCharArray(), writer.toCharArray()));
+ }
+
+ public void testAppendEmptyCharSequence() throws IOException {
+ final CharSequence csq = "";
+ final Writer w = writer.append(csq);
+ assertEquals(writer, w);
+ assertNull(writer.toCharArray());
+ }
+
+ public void testAppendNonEmptyCharSequence() throws IOException {
+ final CharSequence csq = "hola";
+ final Writer w = writer.append(csq);
+ assertEquals(writer, w);
+ assertTrue(Arrays.equals("hola".toCharArray(), writer.toCharArray()));
+ }
+
+ public void testAppendSubCharSequenceUsingNegativeStartValue() throws IOException {
+ final CharSequence csq = "hola";
+ try {
+ writer.append(csq, -1, 2);
+ fail("should have thrown IndexOutOfBoundsException");
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ }
+
+ public void testAppendSubCharSequenceUsingNegativeEndValue() throws IOException {
+ final CharSequence csq = "hola";
+ try {
+ writer.append(csq, 0, -1);
+ fail("should have thrown IndexOutOfBoundsException");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testAppendSubCharSequenceStartIsGreaterThanEnd() throws IOException {
+ final CharSequence csq = "hola";
+ try {
+ writer.append(csq, 2, 1);
+ fail("should have thrown IndexOutOfBoundsException");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testAppendNullSubCharSequence() throws IOException {
+ final Writer w = writer.append(null, 1, "null".length() - 1);
+ assertEquals(writer, w);
+ assertTrue(Arrays.equals("ul".toCharArray(), writer.toCharArray()));
+ }
+
+ public void testAppendEmptySubCharSequence() throws IOException {
+ final CharSequence csq = "";
+ final Writer w = writer.append(csq, 0, 0);
+ assertEquals(writer, w);
+ assertNull(writer.toCharArray());
+ }
+
+ public void testAppendNonEmptySubCharSequence() throws IOException {
+ final CharSequence csq = "hola";
+ final Writer w = writer.append(csq, 1, "hola".length() - 1);
+ assertEquals(writer, w);
+ assertTrue(Arrays.equals("ol".toCharArray(), writer.toCharArray()));
+ }
+
+ public void testWriteChar() throws IOException {
+ writer.write('a');
+ assertTrue(Arrays.equals(new char[] { 'a' }, writer.toCharArray()));
+
+ writer.write('b');
+ assertTrue(Arrays.equals(new char[] { 'a', 'b' }, writer.toCharArray()));
+
+ writer.write('c');
+ assertTrue(Arrays.equals(new char[] { 'a', 'b', 'c' }, writer.toCharArray()));
+ }
+
+ public void testWriteEmptyCharArray() throws IOException {
+ final char[] charArray = new char[] { };
+ writer.write(charArray);
+ assertNull(writer.toCharArray());
+ }
+
+ public void testWriteNonEmptyCharArray() throws IOException {
+ final char[] charArray = "hola".toCharArray();
+ writer.write(charArray);
+ assertTrue(Arrays.equals(charArray, writer.toCharArray()));
+ }
+
+ public void testWriteEmptyString() throws IOException {
+ final String str = "";
+ writer.write(str);
+ assertNull(writer.toCharArray());
+ }
+
+ public void testWriteNullString() throws IOException {
+ try {
+ final String str = null;
+ writer.write(str);
+ fail("should have thrown NullPointerException");
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ public void testWriteNonEmptyString() throws IOException {
+ final String str = "hola";
+ writer.write(str);
+ assertTrue(Arrays.equals(str.toCharArray(), writer.toCharArray()));
+ }
+
+ public void testWriteSubStringUsingNegativeStartValue() throws IOException {
+ final String str = "hola";
+ try {
+ writer.append(str, -1, 2);
+ fail("should have thrown IndexOutOfBoundsException");
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ }
+
+ public void testWriteSubStringUsingNegativeEndValue() throws IOException {
+ final String str = "hola";
+ try {
+ writer.append(str, 0, -1);
+ fail("should have thrown IndexOutOfBoundsException");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testWriteSubStringStartIsGreaterThanEnd() throws IOException {
+ final String str = "hola";
+ try {
+ writer.append(str, 2, 1);
+ fail("should have thrown IndexOutOfBoundsException");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testWriteEmptySubstring() throws IOException {
+ final String str = "";
+ writer.write(str, 0, 0);
+ assertNull(writer.toCharArray());
+ }
+
+ public void testWriteNonEmptySubstring() throws IOException {
+ final String str = "hola";
+ writer.write(str, 1, 2);
+ assertTrue(Arrays.equals("ol".toCharArray(), writer.toCharArray()));
+ }
+}