blob: 6fab9672c0e9da26eac30f906d63e5ff0ca58797 [file] [log] [blame]
/*
* Copyright 2008 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.
*/
/**
* Notes: For efficiency we handle String in a specialized way, in fact, a
* java.lang.String is actually implemented as a native JavaScript String. Then
* we just load up the prototype of the JavaScript String object with the
* appropriate instance methods.
*/
package java.lang;
import com.google.gwt.core.client.JavaScriptObject;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.Comparator;
/**
* Intrinsic string class.
*
* TODO(jat): consider whether we want to support the following methods;
*
* <ul>
* <li>deprecated methods dealing with bytes (I assume not since I can't see
* much use for them)
* <ul>
* <li>String(byte[] ascii, int hibyte)
* <li>String(byte[] ascii, int hibyte, int offset, int count)
* <li>getBytes(int srcBegin, int srcEnd, byte[] dst, int dstBegin)
* </ul>
* <li>methods which in JS will essentially do nothing or be the same as other
* methods
* <ul>
* <li>copyValueOf(char[] data)
* <li>copyValueOf(char[] data, int offset, int count)
* </ul>
* <li>methods added in Java 1.6 (the issue is how will it impact users
* building against Java 1.5)
* <ul>
* <li>isEmpty()
* </ul>
* <li>other methods which are not straightforward in JS
* <ul>
* <li>format(String format, Object... args)
* </ul>
* </ul>
*
* Also, in general, we need to improve our support of non-ASCII characters. The
* problem is that correct support requires large tables, and we don't want to
* make users who aren't going to use that pay for it. There are two ways to do
* that:
* <ol>
* <li>construct the tables in such a way that if the corresponding method is
* not called the table will be elided from the output.
* <li>provide a deferred binding target selecting the level of compatibility
* required. Those that only need ASCII (or perhaps a different relatively small
* subset such as Latin1-5) will not pay for large tables, even if they do call
* toLowercase(), for example.
* </ol>
*
* Also, if we ever add multi-locale support, there are a number of other
* methods such as toLowercase(Locale) we will want to consider supporting. This
* is probably rare, but there will be some apps (such as a translation tool)
* which cannot be written without this support.
*
* Another category of incomplete support is that we currently just use the JS
* regex support, which is not exactly the same as Java. We should support Java
* syntax by mapping it into equivalent JS patterns, or emulating them.
*/
public final class String implements Comparable<String>, CharSequence,
Serializable {
static final class HashCache {
/**
* The "old" cache; it will be dumped when front is full.
*/
static JavaScriptObject back = JavaScriptObject.createObject();
/**
* Tracks the number of entries in front.
*/
static int count = 0;
/**
* The "new" cache; it will become back when it becomes full.
*/
static JavaScriptObject front = JavaScriptObject.createObject();
/**
* Pulled this number out of thin air.
*/
static final int MAX_CACHE = 256;
public static native int getHashCode(String str) /*-{
// Accesses must to be prefixed with ':' to prevent conflict with built-in
// JavaScript properties.
var key = ':' + str;
// Check the front store.
var result = @java.lang.String.HashCache::front[key];
if (result != null) {
return result;
}
// Check the back store.
result = @java.lang.String.HashCache::back[key];
if (result == null) {
// Compute the value.
result = @java.lang.String.HashCache::compute(Ljava/lang/String;)(str);
}
// Increment can trigger the swap/flush; call after checking back but
// before writing to front.
@java.lang.String.HashCache::increment()();
return @java.lang.String.HashCache::front[key] = result;
}-*/;
static int compute(String str) {
int hashCode = 0;
int n = str.length();
int nBatch = n - 4;
int i = 0;
// Process batches of 4 characters at a time
while (i < nBatch) {
// Add the next 4 characters to the hash.
// After every 4 characters, we force the result to fit into 32 bits
// by doing a bitwise operation on it.
hashCode = (str.charAt(i + 3)
+ 31 * (str.charAt(i + 2)
+ 31 * (str.charAt(i + 1)
+ 31 * (str.charAt(i)
+ 31 * hashCode)))) | 0;
i += 4;
}
// Now process the leftovers
while (i < n) {
hashCode = hashCode * 31 + str.charAt(i++);
}
// TODO: make a JSNI call in case JDT gets smart about removing this
// Do a final fitting to 32 bits
return hashCode | 0;
}
static void increment() {
if (count == MAX_CACHE) {
back = front;
front = JavaScriptObject.createObject();
count = 0;
}
++count;
}
}
public static final Comparator<String> CASE_INSENSITIVE_ORDER = new Comparator<String>() {
public int compare(String a, String b) {
return a.compareToIgnoreCase(b);
}
};
// names for standard character sets that are supported
private static final String CHARSET_8859_1 = "ISO-8859-1";
private static final String CHARSET_LATIN1 = "ISO-LATIN-1";
private static final String CHARSET_UTF8 = "UTF-8";
public static String copyValueOf(char[] v) {
return valueOf(v);
}
public static String copyValueOf(char[] v, int offset, int count) {
return valueOf(v, offset, count);
}
public static String valueOf(boolean x) {
return "" + x;
}
public static native String valueOf(char x) /*-{
return String.fromCharCode(x);
}-*/;
public static String valueOf(char x[], int offset, int count) {
int end = offset + count;
__checkBounds(x.length, offset, end);
return __valueOf(x, offset, end);
}
public static native String valueOf(char[] x) /*-{
// Trick: fromCharCode is a vararg method, so we can use apply() to pass the
// entire input in one shot.
return String.fromCharCode.apply(null, x);
}-*/;
public static String valueOf(double x) {
return "" + x;
}
public static String valueOf(float x) {
return "" + x;
}
public static String valueOf(int x) {
return "" + x;
}
public static String valueOf(long x) {
return "" + x;
}
public static String valueOf(Object x) {
return "" + x;
}
// CHECKSTYLE_OFF: This class has special needs.
/**
* Checks that bounds are correct.
*
* @param legalCount the end of the legal range
* @param start must be >= 0
* @param end must be <= legalCount and must be >= start
* @throw StringIndexOutOfBoundsException if the range is not legal
* @skip
*/
static void __checkBounds(int legalCount, int start, int end) {
if (start < 0) {
throw new StringIndexOutOfBoundsException(start);
}
if (end < start) {
throw new StringIndexOutOfBoundsException(end - start);
}
if (end > legalCount) {
throw new StringIndexOutOfBoundsException(end);
}
}
/**
* @skip
*/
static String[] __createArray(int numElements) {
return new String[numElements];
}
/**
* This method converts Java-escaped dollar signs "\$" into JavaScript-escaped
* dollar signs "$$", and removes all other lone backslashes, which serve as
* escapes in Java but are passed through literally in JavaScript.
*
* @skip
*/
static String __translateReplaceString(String replaceStr) {
int pos = 0;
while (0 <= (pos = replaceStr.indexOf("\\", pos))) {
if (replaceStr.charAt(pos + 1) == '$') {
replaceStr = replaceStr.substring(0, pos) + "$"
+ replaceStr.substring(++pos);
} else {
replaceStr = replaceStr.substring(0, pos) + replaceStr.substring(++pos);
}
}
return replaceStr;
}
static native String __valueOf(char x[], int start, int end) /*-{
// Trick: fromCharCode is a vararg method, so we can use apply() to pass the
// entire input in one shot.
x = x.slice(start, end);
return String.fromCharCode.apply(null, x);
}-*/;
/**
* @skip
*/
static String _String() {
return "";
}
/**
* @skip
*/
static String _String(byte[] bytes) {
return _String(bytes, 0, bytes.length);
}
/**
* @skip
*/
static String _String(byte[] bytes, int ofs, int len) {
return utf8ToString(bytes, ofs, len);
}
/**
* @skip
*/
static String _String(byte[] bytes, int ofs, int len, String charset)
throws UnsupportedEncodingException {
if (CHARSET_UTF8.equals(charset)) {
return utf8ToString(bytes, ofs, len);
} else if (CHARSET_8859_1.equals(charset) || CHARSET_LATIN1.equals(charset)) {
return latin1ToString(bytes, ofs, len);
} else {
throw new UnsupportedEncodingException("Charset " + charset
+ " not supported");
}
}
/**
* @skip
*/
static String _String(byte[] bytes, String charsetName)
throws UnsupportedEncodingException {
return _String(bytes, 0, bytes.length, charsetName);
}
/**
* @skip
*/
static String _String(char value[]) {
return valueOf(value);
}
/**
* @skip
*/
static String _String(char value[], int offset, int count) {
return valueOf(value, offset, count);
}
/**
* @skip
*/
static String _String(int[] codePoints, int offset, int count) {
char[] chars = new char[count * 2];
int charIdx = 0;
while (count-- > 0) {
charIdx += Character.toChars(codePoints[offset++], chars, charIdx);
}
return valueOf(chars, 0, charIdx);
}
/**
* @skip
*/
static String _String(String other) {
return other;
}
/**
* @skip
*/
static String _String(StringBuffer sb) {
return valueOf(sb);
}
/**
* @skip
*/
static String _String(StringBuilder sb) {
return valueOf(sb);
}
private static native boolean __equals(String me, Object other) /*-{
// Coerce me to a primitive string to force string comparison
return String(me) == other;
}-*/;
// CHECKSTYLE_ON
private static native int compareTo(String thisStr, String otherStr) /*-{
// Coerce to a primitive string to force string comparison
thisStr = String(thisStr);
if (thisStr == otherStr) {
return 0;
}
return thisStr < otherStr ? -1 : 1;
}-*/;
/**
* Encode a single character in UTF8.
*
* @param bytes byte array to store character in
* @param ofs offset into byte array to store first byte
* @param codePoint character to encode
* @return number of bytes consumed by encoding the character
* @throws IllegalArgumentException if codepoint >= 2^26
*/
private static int encodeUtf8(byte[] bytes, int ofs, int codePoint) {
if (codePoint < (1 << 7)) {
bytes[ofs] = (byte) (codePoint & 127);
return 1;
} else if (codePoint < (1 << 11)) {
// 110xxxxx 10xxxxxx
bytes[ofs++] = (byte) (((codePoint >> 6) & 31) | 0xC0);
bytes[ofs] = (byte) ((codePoint & 63) | 0x80);
return 2;
} else if (codePoint < (1 << 16)) {
// 1110xxxx 10xxxxxx 10xxxxxx
bytes[ofs++] = (byte) (((codePoint >> 12) & 15) | 0xE0);
bytes[ofs++] = (byte) (((codePoint >> 6) & 63) | 0x80);
bytes[ofs] = (byte) ((codePoint & 63) | 0x80);
return 3;
} else if (codePoint < (1 << 21)) {
// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
bytes[ofs++] = (byte) (((codePoint >> 18) & 7) | 0xF0);
bytes[ofs++] = (byte) (((codePoint >> 12) & 63) | 0x80);
bytes[ofs++] = (byte) (((codePoint >> 6) & 63) | 0x80);
bytes[ofs] = (byte) ((codePoint & 63) | 0x80);
return 4;
} else if (codePoint < (1 << 26)) {
// 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
bytes[ofs++] = (byte) (((codePoint >> 24) & 3) | 0xF8);
bytes[ofs++] = (byte) (((codePoint >> 18) & 63) | 0x80);
bytes[ofs++] = (byte) (((codePoint >> 12) & 63) | 0x80);
bytes[ofs++] = (byte) (((codePoint >> 6) & 63) | 0x80);
bytes[ofs] = (byte) ((codePoint & 63) | 0x80);
return 5;
}
throw new IllegalArgumentException("Character out of range: " + codePoint);
}
private static native String fromCharCode(char ch) /*-{
return String.fromCharCode(ch);
}-*/;
private static String fromCodePoint(int codePoint) {
if (codePoint >= Character.MIN_SUPPLEMENTARY_CODE_POINT) {
char hiSurrogate = Character.getHighSurrogate(codePoint);
char loSurrogate = Character.getLowSurrogate(codePoint);
return String.fromCharCode(hiSurrogate)
+ String.fromCharCode(loSurrogate);
} else {
return String.fromCharCode((char) codePoint);
}
}
private static byte[] getBytesLatin1(String str) {
int n = str.length();
byte[] bytes = new byte[n];
for (int i = 0; i < n; ++i) {
bytes[i] = (byte) (str.charAt(i) & 255);
}
return bytes;
}
private static byte[] getBytesUtf8(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];
int out = 0;
for (int i = 0; i < n; ) {
int ch = str.codePointAt(i);
i += Character.charCount(ch);
out += encodeUtf8(bytes, out, ch);
}
return bytes;
}
private static String latin1ToString(byte[] bytes, int ofs, int len) {
char[] chars = new char[len];
for (int i = 0; i < len; ++i) {
chars[i] = (char) (bytes[ofs + i] & 255);
}
return valueOf(chars);
}
private static native boolean regionMatches(String thisStr,
boolean ignoreCase, int toffset, String other, int ooffset, int len) /*-{
if (toffset < 0 || ooffset < 0 || len <= 0) {
return false;
}
if (toffset + len > thisStr.length || ooffset + len > other.length) {
return false;
}
var left = thisStr.substr(toffset, len);
var right = other.substr(ooffset, len);
if (ignoreCase) {
left = left.toLowerCase();
right = right.toLowerCase();
}
return left == right;
}-*/;
private static String utf8ToString(byte[] bytes, int ofs, int len) {
// TODO(jat): consider using decodeURIComponent(escape(bytes)) instead
int charCount = 0;
for (int i = 0; i < len; ) {
++charCount;
byte ch = bytes[ofs + i];
if ((ch & 0xC0) == 0x80) {
throw new IllegalArgumentException("Invalid UTF8 sequence");
} else if ((ch & 0x80) == 0) {
++i;
} else if ((ch & 0xE0) == 0xC0) {
i += 2;
} else if ((ch & 0xF0) == 0xE0) {
i += 3;
} else if ((ch & 0xF8) == 0xF0) {
i += 4;
} else {
// no 5+ byte sequences since max codepoint is less than 2^21
throw new IllegalArgumentException("Invalid UTF8 sequence");
}
if (i > len) {
throw new IndexOutOfBoundsException("Invalid UTF8 sequence");
}
}
char[] chars = new char[charCount];
int outIdx = 0;
int count = 0;
for (int i = 0; i < len; ) {
int ch = bytes[ofs + i++];
if ((ch & 0x80) == 0) {
count = 1;
ch &= 127;
} else if ((ch & 0xE0) == 0xC0) {
count = 2;
ch &= 31;
} else if ((ch & 0xF0) == 0xE0) {
count = 3;
ch &= 15;
} else if ((ch & 0xF8) == 0xF0) {
count = 4;
ch &= 7;
} else if ((ch & 0xFC) == 0xF8) {
count = 5;
ch &= 3;
}
while (--count > 0) {
byte b = bytes[ofs + i++];
if ((b & 0xC0) != 0x80) {
throw new IllegalArgumentException("Invalid UTF8 sequence at "
+ (ofs + i - 1) + ", byte=" + Integer.toHexString(b));
}
ch = (ch << 6) | (b & 63);
}
outIdx += Character.toChars(ch, chars, outIdx);
}
return valueOf(chars);
}
public String() {
// magic delegation to _String
_String();
}
public String(byte[] bytes) {
// magic delegation to _String
_String(bytes);
}
public String(byte[] bytes, int ofs, int len) {
// magic delegation to _String
_String(bytes, ofs, len);
}
public String(byte[] bytes, int ofs, int len, String charsetName)
throws UnsupportedEncodingException {
// magic delegation to _String
_String(bytes, ofs, len, charsetName);
}
public String(byte[] bytes, String charsetName)
throws UnsupportedEncodingException {
// magic delegation to _String
_String(bytes, charsetName);
}
public String(char value[]) {
// magic delegation to _String
_String(value);
}
public String(char value[], int offset, int count) {
// magic delegation to _String
_String(value, offset, count);
}
public String(int codePoints[], int offset, int count) {
// magic delegation to _String
_String(codePoints, offset, count);
}
public String(String other) {
// magic delegation to _String
_String(other);
}
public String(StringBuffer sb) {
// magic delegation to _String
_String(sb);
}
public String(StringBuilder sb) {
// magic delegation to _String
_String(sb);
}
public native char charAt(int index) /*-{
return this.charCodeAt(index);
}-*/;
public int codePointAt(int index) {
return Character.codePointAt(this, index, length());
}
public int codePointBefore(int index) {
return Character.codePointBefore(this, index, 0);
}
public int codePointCount(int beginIndex, int endIndex) {
return Character.codePointCount(this, beginIndex, endIndex);
}
public int compareTo(String other) {
return compareTo(this, other);
}
public int compareToIgnoreCase(String other) {
return compareTo(toLowerCase(), other.toLowerCase());
}
public native String concat(String str) /*-{
return this + str;
}-*/;
public boolean contains(CharSequence s) {
return indexOf(s.toString()) != -1;
}
public boolean contentEquals(CharSequence cs) {
return equals(cs.toString());
}
public boolean contentEquals(StringBuffer sb) {
return equals(sb.toString());
}
public native boolean endsWith(String suffix) /*-{
return (this.lastIndexOf(suffix) != -1) && (this.lastIndexOf(suffix) == (this.length - suffix.length));
}-*/;
@Override
public boolean equals(Object other) {
if (!(other instanceof String)) {
return false;
}
return __equals(this, other);
}
public native boolean equalsIgnoreCase(String other) /*-{
if (other == null)
return false;
return (this == other) || (this.toLowerCase() == other.toLowerCase());
}-*/;
public byte[] getBytes() {
// default character set for GWT is UTF-8
return getBytesUtf8(this);
}
public byte[] getBytes(String charSet) throws UnsupportedEncodingException {
if (CHARSET_UTF8.equals(charSet)) {
return getBytesUtf8(this);
}
if (CHARSET_8859_1.equals(charSet) || CHARSET_LATIN1.equals(charSet)) {
return getBytesLatin1(this);
}
throw new UnsupportedEncodingException(charSet + " is not supported");
}
public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) {
for (int srcIdx = srcBegin; srcIdx < srcEnd; ++srcIdx) {
dst[dstBegin++] = charAt(srcIdx);
}
}
@Override
public int hashCode() {
return HashCache.getHashCode(this);
}
public int indexOf(int codePoint) {
return indexOf(fromCodePoint(codePoint));
}
public int indexOf(int codePoint, int startIndex) {
return this.indexOf(String.fromCodePoint(codePoint), startIndex);
}
public native int indexOf(String str) /*-{
return this.indexOf(str);
}-*/;
public native int indexOf(String str, int startIndex) /*-{
return this.indexOf(str, startIndex);
}-*/;
public native String intern() /*-{
return String(this);
}-*/;
public native boolean isEmpty() /*-{
return !this.length;
}-*/;
public int lastIndexOf(int codePoint) {
return lastIndexOf(fromCodePoint(codePoint));
}
public int lastIndexOf(int codePoint, int startIndex) {
return lastIndexOf(fromCodePoint(codePoint), startIndex);
}
public native int lastIndexOf(String str) /*-{
return this.lastIndexOf(str);
}-*/;
public native int lastIndexOf(String str, int start) /*-{
return this.lastIndexOf(str, start);
}-*/;
public native int length() /*-{
return this.length;
}-*/;
/**
* Regular expressions vary from the standard implementation. The
* <code>regex</code> parameter is interpreted by JavaScript as a JavaScript
* regular expression. For consistency, use only the subset of regular
* expression syntax common to both Java and JavaScript.
*
* TODO(jat): properly handle Java regex syntax
*/
public native boolean matches(String regex) /*-{
// We surround the regex with '^' and '$' because it must match
// the entire string.
return new RegExp('^(' + regex + ')$').test(this);
}-*/;
public int offsetByCodePoints(int index, int codePointOffset) {
return Character.offsetByCodePoints(this, index, codePointOffset);
}
public boolean regionMatches(boolean ignoreCase, int toffset, String other,
int ooffset, int len) {
if (other == null) {
throw new NullPointerException();
}
return regionMatches(this, ignoreCase, toffset, other, ooffset, len);
}
public boolean regionMatches(int toffset, String other, int ooffset, int len) {
if (other == null) {
throw new NullPointerException();
}
return regionMatches(this, false, toffset, other, ooffset, len);
}
public native String replace(char from, char to) /*-{
// We previously used \\uXXXX, but Safari 2 doesn't match them properly
// in RegExp
// See http://bugs.webkit.org/show_bug.cgi?id=8043
// http://bugs.webkit.org/show_bug.cgi?id=6257
// http://bugs.webkit.org/show_bug.cgi?id=7253
var regex;
if (from < 256) {
regex = @java.lang.Integer::toHexString(I)(from);
regex = '\\x' + "00".substring(regex.length) + regex;
} else {
// this works because characters above 255 can't be regex special chars
regex = String.fromCharCode(from);
}
return this.replace(RegExp(regex, "g"), String.fromCharCode(to));
}-*/;
public String replace(CharSequence from, CharSequence to) {
// Implementation note: This uses a regex replacement instead of
// a string literal replacement because Safari does not
// follow the spec for "$$" in the replacement string: it
// will insert a literal "$$". IE and Firefox, meanwhile,
// treat "$$" as "$".
// Escape regex special characters from literal replacement string.
String regex = from.toString().replaceAll("([/\\\\\\.\\*\\+\\?\\|\\(\\)\\[\\]\\{\\}$^])", "\\\\$1");
// Escape $ since it is for match backrefs and \ since it is used to escape
// $.
String replacement = to.toString().replaceAll("\\\\", "\\\\\\\\").replaceAll("\\$", "\\\\$");
return replaceAll(regex, replacement);
}
/**
* Regular expressions vary from the standard implementation. The
* <code>regex</code> parameter is interpreted by JavaScript as a JavaScript
* regular expression. For consistency, use only the subset of regular
* expression syntax common to both Java and JavaScript.
*
* TODO(jat): properly handle Java regex syntax
*/
public native String replaceAll(String regex, String replace) /*-{
replace = @java.lang.String::__translateReplaceString(Ljava/lang/String;)(replace);
return this.replace(RegExp(regex, "g"), replace);
}-*/;
/**
* Regular expressions vary from the standard implementation. The
* <code>regex</code> parameter is interpreted by JavaScript as a JavaScript
* regular expression. For consistency, use only the subset of regular
* expression syntax common to both Java and JavaScript.
*
* TODO(jat): properly handle Java regex syntax
*/
public native String replaceFirst(String regex, String replace) /*-{
replace = @java.lang.String::__translateReplaceString(Ljava/lang/String;)(replace);
return this.replace(RegExp(regex), replace);
}-*/;
/**
* Regular expressions vary from the standard implementation. The
* <code>regex</code> parameter is interpreted by JavaScript as a JavaScript
* regular expression. For consistency, use only the subset of regular
* expression syntax common to both Java and JavaScript.
*/
public String[] split(String regex) {
return split(regex, 0);
}
/**
* Regular expressions vary from the standard implementation. The
* <code>regex</code> parameter is interpreted by JavaScript as a JavaScript
* regular expression. For consistency, use only the subset of regular
* expression syntax common to both Java and JavaScript.
*
* TODO(jat): properly handle Java regex syntax
*/
public native String[] split(String regex, int maxMatch) /*-{
// The compiled regular expression created from the string
var compiled = new RegExp(regex, "g");
// the Javascipt array to hold the matches prior to conversion
var out = [];
// how many matches performed so far
var count = 0;
// The current string that is being matched; trimmed as each piece matches
var trail = this;
// used to detect repeated zero length matches
// Must be null to start with because the first match of "" makes no
// progress by intention
var lastTrail = null;
// We do the split manually to avoid Javascript incompatibility
while (true) {
// None of the information in the match returned are useful as we have no
// subgroup handling
var matchObj = compiled.exec(trail);
if (matchObj == null || trail == "" || (count == (maxMatch - 1) && maxMatch > 0)) {
out[count] = trail;
break;
} else {
out[count] = trail.substring(0, matchObj.index);
trail = trail.substring(matchObj.index + matchObj[0].length, trail.length);
// Force the compiled pattern to reset internal state
compiled.lastIndex = 0;
// Only one zero length match per character to ensure termination
if (lastTrail == trail) {
out[count] = trail.substring(0, 1);
trail = trail.substring(1);
}
lastTrail = trail;
count++;
}
}
// all blank delimiters at the end are supposed to disappear if maxMatch == 0;
// however, if the input string is empty, the output should consist of a
// single empty string
if (maxMatch == 0 && this.length > 0) {
var lastNonEmpty = out.length;
while (lastNonEmpty > 0 && out[lastNonEmpty - 1] == "") {
--lastNonEmpty;
}
if (lastNonEmpty < out.length) {
out.splice(lastNonEmpty, out.length - lastNonEmpty);
}
}
var jr = @java.lang.String::__createArray(I)(out.length);
for ( var i = 0; i < out.length; ++i) {
jr[i] = out[i];
}
return jr;
}-*/;
public boolean startsWith(String prefix) {
return indexOf(prefix) == 0;
}
public boolean startsWith(String prefix, int toffset) {
if (toffset < 0 || toffset >= length()) {
return false;
} else {
return indexOf(prefix, toffset) == toffset;
}
}
public CharSequence subSequence(int beginIndex, int endIndex) {
return this.substring(beginIndex, endIndex);
}
public native String substring(int beginIndex) /*-{
return this.substr(beginIndex, this.length - beginIndex);
}-*/;
public native String substring(int beginIndex, int endIndex) /*-{
return this.substr(beginIndex, endIndex - beginIndex);
}-*/;
public char[] toCharArray() {
int n = this.length();
char[] charArr = new char[n];
getChars(0, n, charArr, 0);
return charArr;
}
public native String toLowerCase() /*-{
return this.toLowerCase();
}-*/;
@Override
public String toString() {
/*
* Magic: this method is only used during compiler optimizations; the generated JS will instead alias
* this method to the native String.prototype.toString() function.
*/
return this;
}
public native String toUpperCase() /*-{
return this.toUpperCase();
}-*/;
public native String trim() /*-{
if (this.length == 0 || (this[0] > '\u0020' && this[this.length - 1] > '\u0020')) {
return this;
}
var r1 = this.replace(/^(\s*)/, '');
var r2 = r1.replace(/\s*$/, '');
return r2;
}-*/;
}