From cc04c3446dd122a835799b05a0c1a31316da64ae Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Sun, 26 Nov 2017 17:10:56 +0300 Subject: [PATCH] Add support for several JDK classes and functions --- .../classlib/java/io/TCharArrayReader.java | 145 ++++++++++++++ .../classlib/java/io/TCharArrayWriter.java | 140 ++++++++++++++ .../org/teavm/classlib/java/lang/TClass.java | 6 + .../teavm/classlib/java/lang/TPackage.java | 58 ++++++ .../teavm/classlib/java/lang/TThrowable.java | 15 ++ .../classlib/java/io/CharArrayReaderTest.java | 150 +++++++++++++++ .../classlib/java/io/CharArrayWriterTest.java | 179 ++++++++++++++++++ 7 files changed, 693 insertions(+) create mode 100644 classlib/src/main/java/org/teavm/classlib/java/io/TCharArrayReader.java create mode 100644 classlib/src/main/java/org/teavm/classlib/java/io/TCharArrayWriter.java create mode 100644 classlib/src/main/java/org/teavm/classlib/java/lang/TPackage.java create mode 100644 tests/src/test/java/org/teavm/classlib/java/io/CharArrayReaderTest.java create mode 100644 tests/src/test/java/org/teavm/classlib/java/io/CharArrayWriterTest.java diff --git a/classlib/src/main/java/org/teavm/classlib/java/io/TCharArrayReader.java b/classlib/src/main/java/org/teavm/classlib/java/io/TCharArrayReader.java new file mode 100644 index 000000000..f22756e3a --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/io/TCharArrayReader.java @@ -0,0 +1,145 @@ +/* + * Copyright 2017 Alexey Andreev. + * + * 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 org.teavm.classlib.java.io; + +import java.io.IOException; +import java.io.Reader; + +public class TCharArrayReader extends Reader { + protected char[] buf; + protected int pos; + protected int markedPos = -1; + protected int count; + + public TCharArrayReader(char[] buf) { + this.buf = buf; + this.count = buf.length; + } + + public TCharArrayReader(char[] buf, int offset, int length) { + /* + * The spec of this constructor is broken. In defining the legal values + * of offset and length, it doesn't consider buffer's length. And to be + * compatible with the broken spec, we must also test whether + * (offset + length) overflows. + */ + if (offset < 0 || offset > buf.length || length < 0 || offset + length < 0) { + throw new IllegalArgumentException(); + } + this.buf = buf; + this.pos = offset; + this.markedPos = offset; + + /* This is according to spec */ + int bufferLength = buf.length; + this.count = offset + length < bufferLength ? length : bufferLength; + } + + @Override + public void close() { + if (isOpen()) { + buf = null; + } + } + + private boolean isOpen() { + return buf != null; + } + + private boolean isClosed() { + return buf == null; + } + + @Override + public void mark(int readLimit) throws IOException { + if (isClosed()) { + throw new IOException(); + } + markedPos = pos; + } + + @Override + public boolean markSupported() { + return true; + } + + @Override + public int read() throws IOException { + if (isClosed()) { + throw new IOException(); + } + if (pos == count) { + return -1; + } + return buf[pos++]; + } + + @Override + public int read(char[] buffer, int offset, int len) throws IOException { + if (offset < 0 || offset > buffer.length) { + throw new ArrayIndexOutOfBoundsException("Offset out of bounds:" + offset); + } + if (len < 0 || len > buffer.length - offset) { + throw new ArrayIndexOutOfBoundsException("Length out of bounds: " + len); + } + if (isClosed()) { + throw new IOException(); + } + if (pos < this.count) { + int bytesRead = pos + len > this.count ? this.count - pos : len; + System.arraycopy(this.buf, pos, buffer, offset, bytesRead); + pos += bytesRead; + return bytesRead; + } + return -1; + } + + @Override + public boolean ready() throws IOException { + if (isClosed()) { + throw new IOException(); + } + return pos != count; + } + + @Override + public void reset() throws IOException { + if (isClosed()) { + throw new IOException(); + } + pos = markedPos != -1 ? markedPos : 0; + } + + @Override + public long skip(long n) throws IOException { + if (isClosed()) { + throw new IOException(); + } + if (n <= 0) { + return 0; + } + long skipped; + if (n < this.count - pos) { + pos = pos + (int) n; + skipped = n; + } else { + skipped = this.count - pos; + pos = this.count; + } + return skipped; + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/io/TCharArrayWriter.java b/classlib/src/main/java/org/teavm/classlib/java/io/TCharArrayWriter.java new file mode 100644 index 000000000..b367b10b8 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/io/TCharArrayWriter.java @@ -0,0 +1,140 @@ +/* + * Copyright 2017 Alexey Andreev. + * + * 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 org.teavm.classlib.java.io; + +import java.io.IOException; +import java.io.Writer; + +public class TCharArrayWriter extends Writer { + protected char[] buf; + protected int count; + + public TCharArrayWriter() { + buf = new char[32]; + lock = buf; + } + + public TCharArrayWriter(int initialSize) { + if (initialSize < 0) { + throw new IllegalArgumentException(); + } + buf = new char[initialSize]; + lock = buf; + } + + @Override + public void close() { + /* empty */ + } + + private void expand(int i) { + /* Can the buffer handle @i more chars, if not expand it */ + if (count + i <= buf.length) { + return; + } + + int newLen = Math.max(2 * buf.length, count + i); + char[] newbuf = new char[newLen]; + System.arraycopy(buf, 0, newbuf, 0, count); + buf = newbuf; + } + + @Override + public void flush() { + /* empty */ + } + + public void reset() { + count = 0; + } + + public int size() { + return count; + } + + public char[] toCharArray() { + char[] result = new char[count]; + System.arraycopy(buf, 0, result, 0, count); + return result; + } + + @Override + public String toString() { + return new String(buf, 0, count); + } + + @Override + public void write(char[] c, int offset, int len) { + // avoid int overflow + if (offset < 0 || offset > c.length || len < 0 || len > c.length - offset) { + throw new IndexOutOfBoundsException(); + } + expand(len); + System.arraycopy(c, offset, this.buf, this.count, len); + this.count += len; + } + + @Override + public void write(int oneChar) { + expand(1); + buf[count++] = (char) oneChar; + } + + @Override + public void write(String str, int offset, int len) { + if (str == null) { + throw new NullPointerException(); + } + // avoid int overflow + if (offset < 0 || offset > str.length() || len < 0 || len > str.length() - offset) { + throw new StringIndexOutOfBoundsException(); + } + expand(len); + str.getChars(offset, offset + len, buf, this.count); + this.count += len; + } + + public void writeTo(Writer out) throws IOException { + out.write(buf, 0, count); + } + + @Override + public TCharArrayWriter append(char c) { + write(c); + return this; + } + + @Override + public TCharArrayWriter append(CharSequence csq) { + if (null == csq) { + append("NULL", 0, 4); + } else { + append(csq, 0, csq.length()); + } + return this; + } + + @Override + public TCharArrayWriter append(CharSequence csq, int start, int end) { + if (null == csq) { + csq = "NULL"; + } + String output = csq.subSequence(start, end).toString(); + write(output, 0, output.length()); + return this; + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java index f441fc46f..f54701a9d 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java @@ -625,4 +625,10 @@ public class TClass extends TObject implements TAnnotatedElement { return getClassLoader().getResourceAsStream(name); } + + public TPackage getPackage() { + String name = (String) (Object) getName(); + name = name.substring(0, name.lastIndexOf('.') + 1); + return TPackage.getPackage(name); + } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TPackage.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TPackage.java new file mode 100644 index 000000000..13e3dbd04 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TPackage.java @@ -0,0 +1,58 @@ +/* + * Copyright 2017 Alexey Andreev. + * + * 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 org.teavm.classlib.java.lang; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.HashMap; +import java.util.Map; + +public class TPackage implements AnnotatedElement { + private static Map packages = new HashMap<>(); + private String name; + + TPackage(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static TPackage getPackage(String name) { + TPackage pkg = packages.get(name); + if (pkg == null) { + pkg = new TPackage(name); + packages.put(name, pkg); + } + return pkg; + } + + @Override + public T getAnnotation(Class annotationClass) { + return null; + } + + @Override + public Annotation[] getAnnotations() { + return new Annotation[0]; + } + + @Override + public Annotation[] getDeclaredAnnotations() { + return new Annotation[0]; + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TThrowable.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TThrowable.java index 29e7195e4..47999d9d8 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TThrowable.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TThrowable.java @@ -16,6 +16,7 @@ package org.teavm.classlib.java.lang; import org.teavm.classlib.java.io.TPrintStream; +import org.teavm.classlib.java.io.TPrintWriter; import org.teavm.classlib.java.util.TArrays; import org.teavm.interop.DelegateTo; import org.teavm.interop.Remove; @@ -166,6 +167,20 @@ public class TThrowable extends RuntimeException { } } + public void printStackTrace(TPrintWriter stream) { + stream.println(TString.wrap(getClass().getName() + ": " + getMessage())); + if (stackTrace != null) { + for (TStackTraceElement element : stackTrace) { + stream.print(TString.wrap(" at ")); + stream.println(element); + } + } + if (cause != null && cause != this) { + stream.print(TString.wrap("Caused by: ")); + cause.printStackTrace(stream); + } + } + @Rename("getStackTrace") public TStackTraceElement[] getStackTrace0() { return stackTrace != null ? stackTrace.clone() : new TStackTraceElement[0]; diff --git a/tests/src/test/java/org/teavm/classlib/java/io/CharArrayReaderTest.java b/tests/src/test/java/org/teavm/classlib/java/io/CharArrayReaderTest.java new file mode 100644 index 000000000..0f1f1b395 --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/io/CharArrayReaderTest.java @@ -0,0 +1,150 @@ +/* + * Copyright 2017 Alexey Andreev. + * + * 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 org.teavm.classlib.java.io; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.io.CharArrayReader; +import java.io.IOException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.junit.TeaVMTestRunner; + +@RunWith(TeaVMTestRunner.class) +public class CharArrayReaderTest { + char[] hw = { 'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd' }; + CharArrayReader cr; + + @Test + public void constructor$C() throws IOException { + cr = new CharArrayReader(hw); + assertTrue("Failed to create reader", cr.ready()); + } + + @Test + public void constructor$CII() throws IOException { + cr = new CharArrayReader(hw, 5, 5); + assertTrue("Failed to create reader", cr.ready()); + + int c = cr.read(); + assertTrue("Created incorrect reader--returned '" + (char) c + "' instead of 'W'", c == 'W'); + } + + @Test + public void close() { + cr = new CharArrayReader(hw); + cr.close(); + try { + cr.read(); + fail("Failed to throw exception on read from closed stream"); + } catch (IOException e) { + // Expected + } + + // No-op + cr.close(); + } + + @Test + public void markI() throws IOException { + cr = new CharArrayReader(hw); + cr.skip(5L); + cr.mark(100); + cr.read(); + cr.reset(); + assertEquals("Failed to mark correct position", 'W', cr.read()); + } + + @Test + public void markSupported() { + cr = new CharArrayReader(hw); + assertTrue("markSupported returned false", cr.markSupported()); + } + + @Test + public void read() throws IOException { + cr = new CharArrayReader(hw); + assertEquals("Read returned incorrect char", 'H', cr.read()); + cr = new CharArrayReader(new char[] { '\u8765' }); + assertTrue("Incorrect double byte char", cr.read() == '\u8765'); + } + + @Test + public void read$CII() throws IOException { + char[] c = new char[11]; + cr = new CharArrayReader(hw); + cr.read(c, 1, 10); + assertTrue("Read returned incorrect chars", new String(c, 1, 10).equals(new String(hw, 0, 10))); + } + + @Test + public void ready() throws IOException { + cr = new CharArrayReader(hw); + assertTrue("ready returned false", cr.ready()); + cr.skip(1000); + assertTrue("ready returned true", !cr.ready()); + cr.close(); + + try { + cr.ready(); + fail("No exception 1"); + } catch (IOException e) { + // expected + } + try { + cr = new CharArrayReader(hw); + cr.close(); + cr.ready(); + fail("No exception 2"); + } catch (IOException e) { + // expected + } + } + + @Test + public void reset() throws IOException { + cr = new CharArrayReader(hw); + cr.skip(5L); + cr.mark(100); + cr.read(); + cr.reset(); + assertEquals("Reset failed to return to marker position", 'W', cr.read()); + + // Regression for HARMONY-4357 + String str = "offsetHello world!"; + char[] data = new char[str.length()]; + str.getChars(0, str.length(), data, 0); + int offsetLength = 6; + int length = data.length - offsetLength; + + CharArrayReader reader = new CharArrayReader(data, offsetLength, length); + reader.reset(); + for (int i = 0; i < length; i++) { + assertEquals(data[offsetLength + i], (char) reader.read()); + } + } + + @Test + public void skipJ() throws IOException { + cr = new CharArrayReader(hw); + long skipped = cr.skip(5L); + + assertEquals("Failed to skip correct number of chars", 5L, skipped); + assertEquals("Skip skipped wrong chars", 'W', cr.read()); + } +} diff --git a/tests/src/test/java/org/teavm/classlib/java/io/CharArrayWriterTest.java b/tests/src/test/java/org/teavm/classlib/java/io/CharArrayWriterTest.java new file mode 100644 index 000000000..a35c2e38c --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/io/CharArrayWriterTest.java @@ -0,0 +1,179 @@ +/* + * Copyright 2017 Alexey Andreev. + * + * 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 org.teavm.classlib.java.io; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import java.io.CharArrayReader; +import java.io.CharArrayWriter; +import java.io.IOException; +import java.io.StringWriter; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.junit.TeaVMTestRunner; + +@RunWith(TeaVMTestRunner.class) +public class CharArrayWriterTest { + char[] hw = { 'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd' }; + CharArrayWriter cw = new CharArrayWriter(); + CharArrayReader cr; + + @Test + public void constructor() { + cw = new CharArrayWriter(90); + assertEquals("Created incorrect writer", 0, cw.size()); + } + + @Test + public void constructorI() { + cw = new CharArrayWriter(); + assertEquals("Created incorrect writer", 0, cw.size()); + } + + @Test + public void close() { + cw.close(); + } + + @Test + public void flush() { + cw.flush(); + } + + @Test + public void reset() throws IOException { + cw.write("HelloWorld", 5, 5); + cw.reset(); + cw.write("HelloWorld", 0, 5); + cr = new CharArrayReader(cw.toCharArray()); + char[] c = new char[100]; + cr.read(c, 0, 5); + assertEquals("Reset failed to reset buffer", "Hello", new String(c, 0, 5)); + } + + @Test + public void size() { + assertEquals("Returned incorrect size", 0, cw.size()); + cw.write(hw, 5, 5); + assertEquals("Returned incorrect size", 5, cw.size()); + } + + @Test + public void toCharArray() throws IOException { + cw.write("HelloWorld", 0, 10); + cr = new CharArrayReader(cw.toCharArray()); + char[] c = new char[100]; + cr.read(c, 0, 10); + assertEquals("toCharArray failed to return correct array", "HelloWorld", new String(c, 0, 10)); + } + + @Test + public void test_toString() { + cw.write("HelloWorld", 5, 5); + cr = new CharArrayReader(cw.toCharArray()); + assertEquals("Returned incorrect string", "World", cw.toString()); + } + + @Test + public void write$CII() throws IOException { + cw.write(hw, 5, 5); + cr = new CharArrayReader(cw.toCharArray()); + char[] c = new char[100]; + cr.read(c, 0, 5); + assertEquals("Writer failed to write correct chars", "World", new String(c, 0, 5)); + } + + @Test + public void write$CII_2() { + // Regression for HARMONY-387 + CharArrayWriter obj = new CharArrayWriter(); + try { + obj.write(new char[] { '0' }, 0, -1); + fail("IndexOutOfBoundsException expected"); + } catch (IndexOutOfBoundsException t) { + assertEquals( + "IndexOutOfBoundsException rather than a subclass expected", + IndexOutOfBoundsException.class, t.getClass()); + } + } + + @Test + public void writeI() throws IOException { + cw.write('T'); + cr = new CharArrayReader(cw.toCharArray()); + assertEquals("Writer failed to write char", 'T', cr.read()); + } + + @Test + public void writeLjava_lang_StringII() throws IOException { + cw.write("HelloWorld", 5, 5); + cr = new CharArrayReader(cw.toCharArray()); + char[] c = new char[100]; + cr.read(c, 0, 5); + assertEquals("Writer failed to write correct chars", "World", new String(c, 0, 5)); + } + + @Test + public void writeLjava_lang_StringII_2() throws StringIndexOutOfBoundsException { + // Regression for HARMONY-387 + CharArrayWriter obj = new CharArrayWriter(); + try { + obj.write((String) null, -1, 0); + fail("NullPointerException expected"); + } catch (NullPointerException t) { + // Expected + } + } + + @Test + public void writeToLjava_io_Writer() throws IOException { + cw.write("HelloWorld", 0, 10); + StringWriter sw = new StringWriter(); + cw.writeTo(sw); + assertEquals("Writer failed to write correct chars", "HelloWorld", sw.toString()); + } + + @Test + public void appendChar() { + char testChar = ' '; + CharArrayWriter writer = new CharArrayWriter(10); + writer.append(testChar); + writer.flush(); + assertEquals(String.valueOf(testChar), writer.toString()); + writer.close(); + } + + @Test + public void appendCharSequence() { + String testString = "My Test String"; + CharArrayWriter writer = new CharArrayWriter(10); + writer.append(testString); + writer.flush(); + assertEquals(testString, writer.toString()); + writer.close(); + } + + @Test + public void test_appendCharSequenceIntInt() { + String testString = "My Test String"; + CharArrayWriter writer = new CharArrayWriter(10); + writer.append(testString, 1, 3); + writer.flush(); + assertEquals(testString.substring(1, 3), writer.toString()); + writer.close(); + } +}