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