diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/StringBuilderTests.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/StringBuilderTests.java index ea6e07815..293eaeebe 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/StringBuilderTests.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/StringBuilderTests.java @@ -34,4 +34,12 @@ public class StringBuilderTests { sb.append(2147483647); assertEquals("2147483647", sb.toString()); } + + @Test + public void appendsCodePoint() { + StringBuilder sb = new StringBuilder(); + sb.appendCodePoint(969356); + assertEquals(56178, sb.charAt(0)); + assertEquals(56972, sb.charAt(1)); + } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/StringTests.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/StringTests.java index 17bab7bed..30a593447 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/StringTests.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/StringTests.java @@ -77,4 +77,91 @@ public class StringTests { public void startsWithWorks() { assertTrue("123".startsWith("12")); } + + @Test + public void regionsMatched() { + assertTrue("12345".regionMatches(2, "23456", 1, 2)); + } + + @Test + public void endsWithWorkds() { + assertTrue("12345".endsWith("45")); + } + + @Test + public void exposesSupplementaryCodePoint() { + String str = new String(new char[] { (char)56178, (char)56972 }); + assertEquals(969356, str.codePointAt(0)); + assertEquals(56972, str.codePointAt(1)); + } + + @Test + public void exposesWrongSurrogates() { + String str = new String(new char[] { (char)56972, (char)56178 }); + assertEquals(56972, str.codePointAt(0)); + assertEquals(56178, str.codePointAt(1)); + } + + @Test + public void exposesSupplementaryCodePointBefore() { + String str = new String(new char[] { (char)56178, (char)56972 }); + assertEquals(969356, str.codePointBefore(2)); + assertEquals(56178, str.codePointBefore(1)); + } + + @Test + public void countsCodePoints() { + String str = new String(new char[] { (char)56178, (char)56972, 'a', 'b' }); + assertEquals(3, str.codePointCount(0, 4)); + assertEquals(1, str.codePointCount(0, 2)); + assertEquals(2, str.codePointCount(2, 4)); + assertEquals(3, str.codePointCount(1, 4)); + } + + @Test + public void givesOffsetByCodePoint() { + String str = new String(new char[] { (char)56178, (char)56972, 'a', 'b' }); + assertEquals(2, str.offsetByCodePoints(0, 1)); + assertEquals(2, str.offsetByCodePoints(1, 1)); + assertEquals(4, str.offsetByCodePoints(0, 3)); + assertEquals(4, str.offsetByCodePoints(1, 3)); + } + + @Test + public void findsCodePoint() { + String str = new String(new char[] { 'a', 'b', (char)56178, (char)56972, 'c', + (char)56178, (char)56972, 'c', 'd' }); + assertEquals(2, str.indexOf(969356)); + assertEquals(4, str.indexOf('c')); + } + + @Test + public void findsCodePointBackward() { + String str = new String(new char[] { 'a', 'b', (char)56178, (char)56972, 'c', + (char)56178, (char)56972, 'c', 'd' }); + assertEquals(5, str.lastIndexOf(969356)); + assertEquals(7, str.lastIndexOf('c')); + } + + @Test + public void findsString() { + assertEquals(1, "abcdbcd".indexOf("bc")); + assertEquals(-1, "abcdbcd".indexOf("bb")); + } + + @Test + public void findsStringBackward() { + assertEquals(4, "abcdbcd".lastIndexOf("bc")); + assertEquals(-1, "abcdbcd".lastIndexOf("bb")); + } + + @Test + public void concatenatesStrings() { + assertEquals("abcd", "ab".concat("cd")); + } + + @Test + public void replacesCharacter() { + assertEquals("abbdbbd", "abcdbcd".replace('c', 'b')); + } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TAbstractStringBuilder.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TAbstractStringBuilder.java index 04906cbf7..476ce5506 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TAbstractStringBuilder.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TAbstractStringBuilder.java @@ -73,6 +73,17 @@ class TAbstractStringBuilder extends TObject implements TSerializable, TCharSequ return this; } + protected TAbstractStringBuilder appendCodePoint(int codePoint) { + if (codePoint < TString.SUPPLEMENTARY_PLANE) { + return append((char)codePoint); + } + ensureCapacity(length + 2); + codePoint -= TString.SUPPLEMENTARY_PLANE; + buffer[length++] = TString.highSurrogate(codePoint); + buffer[length++] = TString.lowSurrogate(codePoint); + return this; + } + private void ensureCapacity(int capacity) { if (buffer.length >= capacity) { return; diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TString.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TString.java index bb6534eae..feb967cb0 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TString.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TString.java @@ -7,7 +7,14 @@ import org.teavm.javascript.ni.GeneratedBy; * * @author Alexey Andreev */ -public class TString extends TObject implements TSerializable, TComparable { +public class TString extends TObject implements TSerializable, TComparable, + TCharSequence { + static int SURROGATE_BIT_MASK = 0xFC00; + static int SURROGATE_BIT_INV_MASK = 0x03FF; + static int HIGH_SURROGATE_BITS = 0xF800; + static int LOW_SURROGATE_BITS = 0xF800; + static int MEANINGFUL_SURROGATE_BITS = 10; + static int SUPPLEMENTARY_PLANE = 0x10000; private char[] characters; private transient int hashCode; @@ -37,13 +44,57 @@ public class TString extends TObject implements TSerializable, TComparable= characters.length) { - throw new StringIndexOutOfBoundsException(null); + throw new TStringIndexOutOfBoundsException(null); } return characters[index]; } + public int codePointAt(int index) { + if (index == characters.length - 1 || (characters[index] & SURROGATE_BIT_MASK) != HIGH_SURROGATE_BITS || + (characters[index + 1] & SURROGATE_BIT_MASK) != LOW_SURROGATE_BITS) { + return characters[index]; + } + return ((characters[index] & SURROGATE_BIT_INV_MASK) << MEANINGFUL_SURROGATE_BITS) | + (characters[index + 1] & SURROGATE_BIT_INV_MASK) + SUPPLEMENTARY_PLANE; + } + + public int codePointBefore(int index) { + if (index == 1 || (characters[index] & SURROGATE_BIT_MASK) != LOW_SURROGATE_BITS || + (characters[index - 1] & SURROGATE_BIT_MASK) != HIGH_SURROGATE_BITS) { + return characters[index - 1]; + } + return ((characters[index - 1] & SURROGATE_BIT_INV_MASK) << MEANINGFUL_SURROGATE_BITS) | + (characters[index] & SURROGATE_BIT_INV_MASK) + SUPPLEMENTARY_PLANE; + } + + public int codePointCount(int beginIndex, int endIndex) { + int count = endIndex; + --endIndex; + for (int i = beginIndex; i < endIndex; ++i) { + if ((characters[i] & SURROGATE_BIT_MASK) == HIGH_SURROGATE_BITS && + (characters[i + 1] & SURROGATE_BIT_MASK) == LOW_SURROGATE_BITS) { + --count; + } + } + return count; + } + + public int offsetByCodePoints(int index, int codePointOffset) { + for (int i = 0; i < codePointOffset; ++i) { + if (index < characters.length - 1 && (characters[index] & SURROGATE_BIT_MASK) == HIGH_SURROGATE_BITS && + (characters[index + 1] & SURROGATE_BIT_MASK) == LOW_SURROGATE_BITS) { + index += 2; + } else { + index++; + } + } + return index; + } + + @Override public int length() { return characters.length; } @@ -106,9 +157,177 @@ public class TString extends TObject implements TSerializable, TComparable length() || ooffset + len > other.length()) { + return false; + } + for (int i = 0; i < len; ++i) { + if (charAt(toffset++) != other.charAt(ooffset++)) { + return false; + } + } + return true; + } + + public boolean endsWith(TString suffix) { + if (this == suffix) { + return true; + } + if (suffix.length() > length()) { + return false; + } + int j = 0; + for (int i = length() - suffix.length(); i < length(); ++i) { + if (charAt(i) != suffix.charAt(j++)) { + return false; + } + } + return true; + } + + static char highSurrogate(int codePoint) { + return (char)(TString.HIGH_SURROGATE_BITS | (codePoint >> TString.MEANINGFUL_SURROGATE_BITS) & + TString.SURROGATE_BIT_INV_MASK); + } + + static char lowSurrogate(int codePoint) { + return (char)(TString.HIGH_SURROGATE_BITS | codePoint & TString.SURROGATE_BIT_INV_MASK); + } + + public int indexOf(int ch, int fromIndex) { + if (ch < SUPPLEMENTARY_PLANE) { + char bmpChar = (char)ch; + for (int i = fromIndex; i < characters.length; ++i) { + if (characters[i] == bmpChar) { + return i; + } + } + return -1; + } else { + char hi = highSurrogate(ch); + char lo = lowSurrogate(ch); + for (int i = fromIndex; i < characters.length - 1; ++i) { + if (characters[i] == hi && characters[i + 1] == lo) { + return i; + } + } + return -1; + } + } + + public int indexOf(int ch) { + return indexOf(ch, 0); + } + + public int lastIndexOf(int ch, int fromIndex) { + if (ch < SUPPLEMENTARY_PLANE) { + char bmpChar = (char)ch; + for (int i = fromIndex; i >= 0; --i) { + if (characters[i] == bmpChar) { + return i; + } + } + return -1; + } else { + char hi = highSurrogate(ch); + char lo = lowSurrogate(ch); + for (int i = fromIndex; i >= 1; --i) { + if (characters[i] == lo && characters[i - 1] == hi) { + return i; + } + } + return -1; + } + } + + public int lastIndexOf(int ch) { + return lastIndexOf(ch, length() - 1); + } + + public int indexOf(TString str, int fromIndex) { + int toIndex = length() - str.length(); + outer: + for (int i = fromIndex; i < toIndex; ++i) { + for (int j = 0; j < str.length(); ++j) { + if (charAt(i + j) != str.charAt(j)) { + continue outer; + } + } + return i; + } + return -1; + } + + public int indexOf(TString str) { + return indexOf(str, 0); + } + + public int lastIndexOf(TString str, int fromIndex) { + fromIndex = Math.min(fromIndex, length() - str.length()); + outer: + for (int i = fromIndex; i >= 0; --i) { + for (int j = 0; j < str.length(); ++j) { + if (charAt(i + j) != str.charAt(j)) { + continue outer; + } + } + return i; + } + return -1; + } + + public int lastIndexOf(TString str) { + return lastIndexOf(str, length()); + } + + public TString substring(int beginIndex, int endIndex) { + if (beginIndex > endIndex) { + throw new TIndexOutOfBoundsException(); + } + return new TString(characters, beginIndex, endIndex - beginIndex); + } + + public TString substring(int beginIndex) { + return substring(beginIndex, length()); + } + + @Override + public TCharSequence subSequence(int beginIndex, int endIndex) { + return substring(beginIndex, endIndex); + } + + public TString concat(TString str) { + if (str.isEmpty()) { + return this; + } + char[] buffer = new char[length() + str.length()]; + int index = 0; + for (int i = 0; i < length(); ++i) { + buffer[index++] = charAt(i); + } + for (int i = 0; i < str.length(); ++i) { + buffer[index++] = str.charAt(i); + } + return new TString(buffer); + } + + public TString replace(char oldChar, char newChar) { + if (oldChar == newChar) { + return this; + } + char[] buffer = new char[length()]; + for (int i = 0; i < length(); ++i) { + buffer[i] = charAt(i) == oldChar ? newChar : charAt(i); + } + return new TString(buffer); + } + public static TString valueOf(int index) { return new TStringBuilder().append(index).toString0(); } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TStringBuilder.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TStringBuilder.java index f79940052..a1aff92df 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TStringBuilder.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TStringBuilder.java @@ -22,4 +22,10 @@ public class TStringBuilder extends TAbstractStringBuilder { super.append(c); return this; } + + @Override + public TStringBuilder appendCodePoint(int codePoint) { + super.appendCodePoint(codePoint); + return this; + } }