All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.emul.java.lang.String Maven / Gradle / Ivy

/*
 * 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.
 */

package java.lang;

import static javaemul.internal.InternalPreconditions.checkCriticalStringBounds;
import static javaemul.internal.InternalPreconditions.checkNotNull;
import static javaemul.internal.InternalPreconditions.checkStringElementIndex;

import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Comparator;
import java.util.Locale;
import java.util.StringJoiner;

import javaemul.internal.ArrayHelper;
import javaemul.internal.EmulatedCharset;
import javaemul.internal.HashCodes;
import javaemul.internal.JsUtils;
import javaemul.internal.NativeRegExp;
import javaemul.internal.annotations.DoNotInline;

import jsinterop.annotations.JsMethod;
import jsinterop.annotations.JsNonNull;
import jsinterop.annotations.JsPackage;
import jsinterop.annotations.JsProperty;
import jsinterop.annotations.JsType;

/**
 * Intrinsic string class.
 */
// Needed to have constructors not fail compilation internally at Google
@SuppressWarnings({ "ReturnValueIgnored", "unusable-by-js" })
public final class String implements Comparable, CharSequence,
    Serializable {
  /* TODO(jat): consider whether we want to support the following methods;
   *
   * 
    *
  • deprecated methods dealing with bytes (I assume not since I can't see * much use for them) *
      *
    • String(byte[] ascii, int hibyte) *
    • String(byte[] ascii, int hibyte, int offset, int count) *
    • getBytes(int srcBegin, int srcEnd, byte[] dst, int dstBegin) *
    *
  • methods which in JS will essentially do nothing or be the same as other * methods *
      *
    • copyValueOf(char[] data) *
    • copyValueOf(char[] data, int offset, int count) *
    *
  • methods added in Java 1.6 (the issue is how will it impact users * building against Java 1.5) *
      *
    • isEmpty() *
    *
  • other methods which are not straightforward in JS *
      *
    • format(String format, Object... args) *
    *
* *

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: *

    *
  1. construct the tables in such a way that if the corresponding method is * not called the table will be elided from the output. *
  2. 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. *
* * 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. * * IMPORTANT NOTE: if newer JREs add new interfaces to String, please update * {@link Devirtualizer} and {@link JavaResourceBase} */ public static final Comparator CASE_INSENSITIVE_ORDER = new Comparator() { @Override public int compare(String a, String b) { return a.compareToIgnoreCase(b); } }; 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 join(CharSequence delimiter, CharSequence... elements) { StringJoiner joiner = new StringJoiner(delimiter); for (CharSequence e : elements) { joiner.add(e); } return joiner.toString(); } public static String join(CharSequence delimiter, Iterable elements) { StringJoiner joiner = new StringJoiner(delimiter); for (CharSequence e : elements) { joiner.add(e); } return joiner.toString(); } public static String valueOf(boolean x) { return "" + x; } public static String valueOf(char x) { return NativeString.fromCharCode(x); } public static String valueOf(char x[], int offset, int count) { int end = offset + count; checkCriticalStringBounds(offset, end, x.length); // Work around function.prototype.apply call stack size limits: // https://code.google.com/p/v8/issues/detail?id=2896 // Performance: http://jsperf.com/string-fromcharcode-test/13 int batchSize = ArrayHelper.ARRAY_PROCESS_BATCH_SIZE; String s = ""; for (int batchStart = offset; batchStart < end;) { int batchEnd = Math.min(batchStart + batchSize, end); s += fromCharCode(ArrayHelper.unsafeClone(x, batchStart, batchEnd)); batchStart = batchEnd; } return s; } private static String fromCharCode(Object[] array) { return getFromCharCodeFunction().apply(null, array); } @JsType(isNative = true, name = "Function", namespace = JsPackage.GLOBAL) private static class NativeFunction { public native String apply(String thisContext, Object[] argsArray); } @JsProperty(name = "String.fromCharCode", namespace = "") private static native NativeFunction getFromCharCodeFunction(); public static String valueOf(char[] x) { return valueOf(x, 0, x.length); } 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; } // valueOf needs to be treated special: // J2cl uses it for String concat and thus it can not use string concatenation itself. public static @JsNonNull String valueOf(Object x) { return x == null ? "null" : x.toString(); } /** * 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 */ private 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; } private static Charset getCharset(String charsetName) throws UnsupportedEncodingException { try { return Charset.forName(charsetName); } catch (UnsupportedCharsetException e) { throw new UnsupportedEncodingException(charsetName); } } static String fromCodePoint(int codePoint) { if (codePoint >= Character.MIN_SUPPLEMENTARY_CODE_POINT) { char hiSurrogate = Character.getHighSurrogate(codePoint); char loSurrogate = Character.getLowSurrogate(codePoint); return String.valueOf(hiSurrogate) + String.valueOf(loSurrogate); } else { return String.valueOf((char) codePoint); } } public String() { /* * Call to $create(args) must be here so that the method is referenced and not * pruned before new String(args) is replaced by $create(args) by * RewriteConstructorCallsForUnboxedTypes. */ $create(); } public String(byte[] bytes) { /* * Call to $create(args) must be here so that the method is referenced and not * pruned before new String(args) is replaced by $create(args) by * RewriteConstructorCallsForUnboxedTypes. */ $create(bytes); } public String(byte[] bytes, int ofs, int len) { /* * Call to $create(args) must be here so that the method is referenced and not * pruned before new String(args) is replaced by $create(args) by * RewriteConstructorCallsForUnboxedTypes. */ $create(bytes, ofs, len); } public String(byte[] bytes, int ofs, int len, String charsetName) throws UnsupportedEncodingException { /* * Call to $create(args) must be here so that the method is referenced and not * pruned before new String(args) is replaced by $create(args) by * RewriteConstructorCallsForUnboxedTypes. */ $create(bytes, ofs, len, charsetName); } public String(byte[] bytes, int ofs, int len, Charset charset) { /* * Call to $create(args) must be here so that the method is referenced and not * pruned before new String(args) is replaced by $create(args) by * RewriteConstructorCallsForUnboxedTypes. */ $create(bytes, ofs, len, charset); } public String(byte[] bytes, String charsetName) throws UnsupportedEncodingException { /* * Call to $create(args) must be here so that the method is referenced and not * pruned before new String(args) is replaced by $create(args) by * RewriteConstructorCallsForUnboxedTypes. */ $create(bytes, charsetName); } public String(byte[] bytes, Charset charset) { /* * Call to $create(args) must be here so that the method is referenced and not * pruned before new String(args) is replaced by $create(args) by * RewriteConstructorCallsForUnboxedTypes. */ $create(bytes, charset); } public String(char value[]) { /* * Call to $create(args) must be here so that the method is referenced and not * pruned before new String(args) is replaced by $create(args) by * RewriteConstructorCallsForUnboxedTypes. */ $create(value); } public String(char value[], int offset, int count) { /* * Call to $create(args) must be here so that the method is referenced and not * pruned before new String(args) is replaced by $create(args) by * RewriteConstructorCallsForUnboxedTypes. */ $create(value, offset, count); } public String(int codePoints[], int offset, int count) { /* * Call to $create(args) must be here so that the method is referenced and not * pruned before new String(args) is replaced by $create(args) by * RewriteConstructorCallsForUnboxedTypes. */ $create(codePoints, offset, count); } public String(String other) { /* * Call to $create(args) must be here so that the method is referenced and not * pruned before new String(args) is replaced by $create(args) by * RewriteConstructorCallsForUnboxedTypes. */ $create(other); } public String(StringBuffer sb) { /* * Call to $create(args) must be here so that the method is referenced and not * pruned before new String(args) is replaced by $create(args) by * RewriteConstructorCallsForUnboxedTypes. */ $create(sb); } public String(StringBuilder sb) { /* * Call to $create(args) must be here so that the method is referenced and not * pruned before new String(args) is replaced by $create(args) by * RewriteConstructorCallsForUnboxedTypes. */ $create(sb); } private NativeString asNativeString() { return JsUtils.uncheckedCast(this); } @Override public char charAt(int index) { checkStringElementIndex(index, length()); return asNativeString().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); } @Override public int compareTo(String other) { // Trick compiler into thinking that these are double so what we could do arithmetic comparison // which is supported on underlying JavaScript strings. double a = JsUtils.unsafeCastToDouble(checkNotNull(this)); double b = JsUtils.unsafeCastToDouble(checkNotNull(other)); return a == b ? 0 : (a < b ? -1 : 1); } public int compareToIgnoreCase(String other) { return toLowerCase().compareTo(other.toLowerCase()); } public String concat(String str) { return checkNotNull(this) + checkNotNull(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 boolean endsWith(String suffix) { // If IE8 supported negative start index, we could have just used "-suffixlength". int suffixlength = suffix.length(); return asNativeString().substr(length() - suffixlength, suffixlength).equals(suffix); } // Marked with @DoNotInline because we don't have static eval for "==" yet. @DoNotInline @Override public boolean equals(Object other) { // Java equality is translated into triple equality which is a quick to compare strings for // equality without any instanceOf checks. return checkNotNull(this) == other; } public boolean equalsIgnoreCase(String other) { checkNotNull(this); if (other == null) { return false; } if (equals(other)) { return true; } return length() == other.length() && toLowerCase().equals(other.toLowerCase()); } public byte[] getBytes() { // default character set for GWT is UTF-8 return getBytes(EmulatedCharset.UTF_8); } public byte[] getBytes(String charsetName) throws UnsupportedEncodingException { return getBytes(getCharset(charsetName)); } public byte[] getBytes(Charset charset) { return ((EmulatedCharset) charset).getBytes(this); } public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) { checkCriticalStringBounds(srcBegin, srcEnd, length()); checkCriticalStringBounds(dstBegin, dstBegin + (srcEnd - srcBegin), dst.length); getChars0(srcBegin, srcEnd, dst, dstBegin); } private void getChars0(int srcBegin, int srcEnd, char[] dst, int dstBegin) { while (srcBegin < srcEnd) { dst[dstBegin++] = charAt(srcBegin++); } } @Override public int hashCode() { return HashCodes.getStringHashCode(this); } public int indexOf(int codePoint) { return indexOf(fromCodePoint(codePoint)); } public int indexOf(int codePoint, int startIndex) { return indexOf(fromCodePoint(codePoint), startIndex); } public int indexOf(String str) { return asNativeString().indexOf(str); } public int indexOf(String str, int startIndex) { return asNativeString().indexOf(str, startIndex); } public String intern() { return checkNotNull(this); } public boolean isEmpty() { return length() == 0; } public int lastIndexOf(int codePoint) { return lastIndexOf(fromCodePoint(codePoint)); } public int lastIndexOf(int codePoint, int startIndex) { return lastIndexOf(fromCodePoint(codePoint), startIndex); } public int lastIndexOf(String str) { return asNativeString().lastIndexOf(str); } public int lastIndexOf(String str, int start) { return asNativeString().lastIndexOf(str, start); } @Override public int length() { return asNativeString().length; } /** * Regular expressions vary from the standard implementation. The * regex 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 boolean matches(String regex) { // We surround the regex with '^' and '$' because it must match the entire string. return new NativeRegExp("^(" + 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) { checkNotNull(other); if (toffset < 0 || ooffset < 0) { return false; } if (toffset + len > length() || ooffset + len > other.length()) { return false; } if (len <= 0) { return true; } String left = asNativeString().substr(toffset, len); String right = other.asNativeString().substr(ooffset, len); return ignoreCase ? left.equalsIgnoreCase(right) : left.equals(right); } public boolean regionMatches(int toffset, String other, int ooffset, int len) { return regionMatches(false, toffset, other, ooffset, len); } public String replace(char from, char to) { // Translate 'from' into unicode escape sequence (\\u and a four-digit hexadecimal number). // Escape sequence replacement is used instead of a string literal replacement // in order to escape regexp special characters (e.g. '.'). String hex = Integer.toHexString(from); String regex = "\\u" + "0000".substring(hex.length()) + hex; String replace = NativeString.fromCharCode(to); return nativeReplaceAll(regex, replace); } 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 * regex 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 String replaceAll(String regex, String replace) { replace = translateReplaceString(replace); return nativeReplaceAll(regex, replace); } String nativeReplaceAll(String regex, String replace) { return asNativeString().replace(new NativeRegExp(regex, "g"), replace); } /** * Regular expressions vary from the standard implementation. The * regex 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 String replaceFirst(String regex, String replace) { replace = translateReplaceString(replace); NativeRegExp jsRegEx = new NativeRegExp(regex); return asNativeString().replace(jsRegEx, replace); } /** * Regular expressions vary from the standard implementation. The * regex 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 * regex 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 String[] split(String regex, int maxMatch) { // The compiled regular expression created from the string NativeRegExp compiled = new NativeRegExp(regex, "g"); // the Javascipt array to hold the matches prior to conversion String[] out = new String[0]; // how many matches performed so far int count = 0; // The current string that is being matched; trimmed as each piece matches String 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 String 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 NativeRegExp.Match matchObj = compiled.exec(trail); if (matchObj == null || trail == "" || (count == (maxMatch - 1) && maxMatch > 0)) { out[count] = trail; break; } else { int matchIndex = matchObj.getIndex(); out[count] = trail.substring(0, matchIndex); trail = trail.substring(matchIndex + matchObj.asArray()[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) { int lastNonEmpty = out.length; while (lastNonEmpty > 0 && out[lastNonEmpty - 1] == "") { --lastNonEmpty; } if (lastNonEmpty < out.length) { ArrayHelper.setLength(out, lastNonEmpty); } } return out; } public boolean startsWith(String prefix) { return startsWith(prefix, 0); } public boolean startsWith(String prefix, int toffset) { return toffset >= 0 && asNativeString().substr(toffset, prefix.length()).equals(prefix); } @Override public CharSequence subSequence(int beginIndex, int endIndex) { return substring(beginIndex, endIndex); } public String substring(int beginIndex) { return asNativeString().substr(beginIndex); } public String substring(int beginIndex, int endIndex) { return asNativeString().substr(beginIndex, endIndex - beginIndex); } public char[] toCharArray() { int n = length(); char[] charArr = new char[n]; getChars0(0, n, charArr, 0); return charArr; } /** * Transforms the String to lower-case in a locale insensitive way. *

* Unlike JRE, we don't do locale specific transformation by default. That is backward compatible * for GWT and in most of the cases that is what the developer actually wants. If you want to make * a transformation based on native locale of the browser, you can do * {@code toLowerCase(Locale.getDefault())} instead. */ public String toLowerCase() { return asNativeString().toLowerCase(); } /** * If provided {@code locale} is {@link Locale#getDefault()}, uses javascript's * {@code toLocaleLowerCase} to do a locale specific transformation. Otherwise, it will fallback * to {@code toLowerCase} which performs the right thing for the limited set of Locale's * predefined in GWT Locale emulation. */ public String toLowerCase(Locale locale) { return locale == Locale.getDefault() ? asNativeString().toLocaleLowerCase() : asNativeString().toLowerCase(); } // See the notes in lowerCase pair. public String toUpperCase() { return asNativeString().toUpperCase(); } // See the notes in lowerCase pair. public String toUpperCase(Locale locale) { return locale == Locale.getDefault() ? asNativeString().toLocaleUpperCase() : asNativeString().toUpperCase(); } @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 checkNotNull(this); } public String trim() { int length = length(); int start = 0; while (start < length && charAt(start) <= ' ') { start++; } int end = length; while (end > start && charAt(end - 1) <= ' ') { end--; } return start > 0 || end < length ? substring(start, end) : this; } @JsType(isNative = true, name = "String", namespace = "") private static class NativeString { public static native String fromCharCode(char x); public int length; public native char charCodeAt(int index); public native int indexOf(String str); public native int indexOf(String str, int startIndex); public native int lastIndexOf(String str); public native int lastIndexOf(String str, int start); public native String replace(NativeRegExp regex, String replace); public native String substr(int beginIndex); public native String substr(int beginIndex, int len); public native String toLocaleLowerCase(); public native String toLocaleUpperCase(); public native String toLowerCase(); public native String toUpperCase(); } // CHECKSTYLE_OFF: Utility Methods for unboxed String. protected static String $create() { return ""; } protected static String $create(byte[] bytes) { return $create(bytes, 0, bytes.length); } protected static String $create(byte[] bytes, int ofs, int len) { return $create(bytes, ofs, len, EmulatedCharset.UTF_8); } protected static String $create(byte[] bytes, int ofs, int len, String charsetName) throws UnsupportedEncodingException { return $create(bytes, ofs, len, String.getCharset(charsetName)); } protected static String $create(byte[] bytes, int ofs, int len, Charset charset) { return String.valueOf(((EmulatedCharset) charset).decodeString(bytes, ofs, len)); } protected static String $create(byte[] bytes, String charsetName) throws UnsupportedEncodingException { return $create(bytes, 0, bytes.length, charsetName); } protected static String $create(byte[] bytes, Charset charset) { return $create(bytes, 0, bytes.length, charset); } protected static String $create(char value[]) { return String.valueOf(value); } protected static String $create(char value[], int offset, int count) { return String.valueOf(value, offset, count); } protected static String $create(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 String.valueOf(chars, 0, charIdx); } protected static String $create(String other) { return checkNotNull(other); } protected static String $create(StringBuffer sb) { return sb.toString(); } protected static String $create(StringBuilder sb) { return sb.toString(); } @JsMethod protected static boolean $isInstance(Object instance) { return "string".equals(JsUtils.typeOf(instance)); } // CHECKSTYLE_ON: end utility methods }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy