001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016:
017: /**
018: * Notes: For efficiency we handle String in a specialized way, in fact, a
019: * java.lang.String is actually implemented as a native JavaScript String. Then
020: * we just load up the prototype of the JavaScript String object with the
021: * appropriate instance methods.
022: */package java.lang;
023:
024: import com.google.gwt.core.client.JavaScriptObject;
025:
026: import java.io.Serializable;
027:
028: /**
029: * Intrinsic string class.
030: */
031: public final class String implements Comparable<String>, CharSequence,
032: Serializable {
033:
034: /**
035: * Accesses need to be prefixed with ':' to prevent conflict with built-in
036: * JavaScript properties.
037: *
038: * @skip
039: */
040: static JavaScriptObject hashCache;
041:
042: public static String valueOf(boolean x) {
043: return "" + x;
044: };
045:
046: public static native String valueOf(char x) /*-{
047: return String.fromCharCode(x);
048: }-*/;
049:
050: public static String valueOf(char x[], int offset, int count) {
051: int end = offset + count;
052: __checkBounds(x.length, offset, end);
053: return __valueOf(x, offset, end);
054: }
055:
056: public static native String valueOf(char[] x) /*-{
057: // Trick: fromCharCode is a vararg method, so we can use apply() to pass the
058: // entire input in one shot.
059: return String.fromCharCode.apply(null, x);
060: }-*/;
061:
062: public static String valueOf(double x) {
063: return "" + x;
064: }
065:
066: public static String valueOf(float x) {
067: return "" + x;
068: }
069:
070: public static String valueOf(int x) {
071: return "" + x;
072: }
073:
074: public static String valueOf(long x) {
075: return "" + x;
076: }
077:
078: public static String valueOf(Object x) {
079: return "" + x;
080: }
081:
082: // CHECKSTYLE_OFF: This class has special needs.
083:
084: /**
085: * @skip
086: */
087: static String _String() {
088: return "";
089: }
090:
091: /**
092: * @skip
093: */
094: static String _String(char value[]) {
095: return valueOf(value);
096: }
097:
098: /**
099: * @skip
100: */
101: static String _String(char value[], int offset, int count) {
102: return valueOf(value, offset, count);
103: }
104:
105: /**
106: * @skip
107: */
108: static String _String(String other) {
109: return other;
110: }
111:
112: /**
113: * Checks that bounds are correct.
114: *
115: * @param legalCount the end of the legal range
116: * @param start must be >= 0
117: * @param end must be <= legalCount and must be >= start
118: * @throw StringIndexOutOfBoundsException if the range is not legal
119: * @skip
120: */
121: static void __checkBounds(int legalCount, int start, int end) {
122: if (start < 0) {
123: throw new StringIndexOutOfBoundsException(start);
124: }
125: if (end < start) {
126: throw new StringIndexOutOfBoundsException(end - start);
127: }
128: if (end > legalCount) {
129: throw new StringIndexOutOfBoundsException(end);
130: }
131: }
132:
133: /**
134: * @skip
135: */
136: static String[] __createArray(int numElements) {
137: return new String[numElements];
138: }
139:
140: /**
141: * This method converts Java-escaped dollar signs "\$" into JavaScript-escaped
142: * dollar signs "$$", and removes all other lone backslashes, which serve as
143: * escapes in Java but are passed through literally in JavaScript.
144: *
145: * @skip
146: */
147: static String __translateReplaceString(String replaceStr) {
148: int pos = 0;
149: while (0 <= (pos = replaceStr.indexOf("\\", pos))) {
150: if (replaceStr.charAt(pos + 1) == '$') {
151: replaceStr = replaceStr.substring(0, pos) + "$"
152: + replaceStr.substring(++pos);
153: } else {
154: replaceStr = replaceStr.substring(0, pos)
155: + replaceStr.substring(++pos);
156: }
157: }
158: return replaceStr;
159: }
160:
161: private static native boolean __equals(String me, Object other) /*-{
162: // Coerce me to a primitive string to force string comparison
163: return String(me) == other;
164: }-*/;
165:
166: private static native String __valueOf(char x[], int start, int end) /*-{
167: // Trick: fromCharCode is a vararg method, so we can use apply() to pass the
168: // entire input in one shot.
169: x = x.slice(start, end);
170: return String.fromCharCode.apply(null, x);
171: }-*/;
172:
173: // CHECKSTYLE_ON
174:
175: public String() {
176: // magic delegation to _String
177: _String();
178: }
179:
180: public String(char value[]) {
181: // magic delegation to _String
182: _String(value);
183: }
184:
185: public String(char value[], int offset, int count) {
186: // magic delegation to _String
187: _String(value, offset, count);
188: }
189:
190: public String(String other) {
191: // magic delegation to _String
192: _String(other);
193: }
194:
195: public native char charAt(int index) /*-{
196: return this.charCodeAt(index);
197: }-*/;
198:
199: public int compareTo(String other) {
200: if (__equals(this , other)) {
201: return 0;
202: }
203: int this Length = this .length();
204: int otherLength = other.length();
205: int length = Math.min(this Length, otherLength);
206: for (int i = 0; i < length; i++) {
207: char this Char = this .charAt(i);
208: char otherChar = other.charAt(i);
209: if (this Char != otherChar) {
210: return this Char - otherChar;
211: }
212: }
213: return this Length - otherLength;
214: }
215:
216: public native String concat(String str) /*-{
217: return this + str;
218: }-*/;
219:
220: public native boolean endsWith(String suffix) /*-{
221: return (this.lastIndexOf(suffix) != -1)
222: && (this.lastIndexOf(suffix) == (this.length - suffix.length));
223: }-*/;
224:
225: @Override
226: public boolean equals(Object other) {
227: if (!(other instanceof String)) {
228: return false;
229: }
230: return __equals(this , other);
231: }
232:
233: public native boolean equalsIgnoreCase(String other) /*-{
234: if (other == null)
235: return false;
236: return (this == other) || (this.toLowerCase() == other.toLowerCase());
237: }-*/;
238:
239: @Override
240: public native int hashCode() /*-{
241: var hashCache = @java.lang.String::hashCache;
242: if (!hashCache) {
243: hashCache = @java.lang.String::hashCache = {};
244: }
245:
246: // Prefix needed to prevent conflict with built-in JavaScript properties.
247: var key = ':' + this;
248: var hashCode = hashCache[key];
249: // Must check null/undefined because 0 is a legal hashCode
250: if (hashCode == null) {
251: hashCode = 0;
252: var n = this.length;
253: // In our hash code calculation, only 32 characters will actually affect
254: // the final value, so there's no need to sample more than 32 characters.
255: // To get a better hash code, we'd like to evenly distribute these
256: // characters throughout the string. That means that for lengths between
257: // 0 and 63 (inclusive), we increment by 1. For 64-95, 2; 96-127, 3; and
258: // so on. The complicated formula below computes just that. The "| 0"
259: // operation is a fast way to coerce the division result to an integer.
260: var inc = (n < 64) ? 1 : ((n / 32) | 0);
261: for (var i = 0; i < n; i += inc) {
262: hashCode <<= 1;
263: hashCode += this.charCodeAt(i);
264: }
265: hashCode |= 0; // force to 32-bits
266: hashCache[key] = hashCode
267: }
268: return hashCode;
269: }-*/;
270:
271: public native int indexOf(int ch) /*-{
272: return this.indexOf(String.fromCharCode(ch));
273: }-*/;
274:
275: public native int indexOf(int ch, int startIndex) /*-{
276: return this.indexOf(String.fromCharCode(ch), startIndex);
277: }-*/;
278:
279: public native int indexOf(String str) /*-{
280: return this.indexOf(str);
281: }-*/;
282:
283: public native int indexOf(String str, int startIndex) /*-{
284: return this.indexOf(str, startIndex);
285: }-*/;
286:
287: public native int lastIndexOf(int ch) /*-{
288: return this.lastIndexOf(String.fromCharCode(ch));
289: }-*/;
290:
291: public native int lastIndexOf(int ch, int startIndex) /*-{
292: return this.lastIndexOf(String.fromCharCode(ch), startIndex);
293: }-*/;
294:
295: public native int lastIndexOf(String str) /*-{
296: return this.lastIndexOf(str);
297: }-*/;
298:
299: public native int lastIndexOf(String str, int start) /*-{
300: return this.lastIndexOf(str, start);
301: }-*/;
302:
303: public native int length() /*-{
304: return this.length;
305: }-*/;
306:
307: /**
308: * Regular expressions vary from the standard implementation. The
309: * <code>regex</code> parameter is interpreted by JavaScript as a JavaScript
310: * regular expression. For consistency, use only the subset of regular
311: * expression syntax common to both Java and JavaScript.
312: */
313: public native boolean matches(String regex) /*-{
314: var matchObj = new RegExp(regex).exec(this);
315: // if there is no match at all, matchObj will be null
316: // matchObj[0] is the entire matched string
317: return (matchObj == null) ? false : (this == matchObj[0]);
318: }-*/;
319:
320: public native String replace(char from, char to) /*-{
321: var code = @java.lang.Long::toHexString(J)(from);
322: return this.replace(RegExp("\\x" + code, "g"), String.fromCharCode(to));
323: }-*/;
324:
325: /**
326: * Regular expressions vary from the standard implementation. The
327: * <code>regex</code> parameter is interpreted by JavaScript as a JavaScript
328: * regular expression. For consistency, use only the subset of regular
329: * expression syntax common to both Java and JavaScript.
330: */
331: public native String replaceAll(String regex, String replace) /*-{
332: replace = @java.lang.String::__translateReplaceString(Ljava/lang/String;)(replace);
333: return this.replace(RegExp(regex, "g"), replace);
334: }-*/;
335:
336: /**
337: * Regular expressions vary from the standard implementation. The
338: * <code>regex</code> parameter is interpreted by JavaScript as a JavaScript
339: * regular expression. For consistency, use only the subset of regular
340: * expression syntax common to both Java and JavaScript.
341: */
342: public native String replaceFirst(String regex, String replace) /*-{
343: replace = @java.lang.String::__translateReplaceString(Ljava/lang/String;)(replace);
344: return this.replace(RegExp(regex), replace);
345: }-*/;
346:
347: /**
348: * Regular expressions vary from the standard implementation. The
349: * <code>regex</code> parameter is interpreted by JavaScript as a JavaScript
350: * regular expression. For consistency, use only the subset of regular
351: * expression syntax common to both Java and JavaScript.
352: */
353: public String[] split(String regex) {
354: return split(regex, 0);
355: }
356:
357: /**
358: * Regular expressions vary from the standard implementation. The
359: * <code>regex</code> parameter is interpreted by JavaScript as a JavaScript
360: * regular expression. For consistency, use only the subset of regular
361: * expression syntax common to both Java and JavaScript.
362: */
363: public native String[] split(String regex, int maxMatch) /*-{
364: // The compiled regular expression created from the string
365: var compiled = new RegExp(regex, "g");
366: // the Javascipt array to hold the matches prior to conversion
367: var out = [];
368: // how many matches performed so far
369: var count = 0;
370: // The current string that is being matched; trimmed as each piece matches
371: var trail = this;
372: // used to detect repeated zero length matches
373: // Must be null to start with because the first match of "" makes no
374: // progress by intention
375: var lastTrail = null;
376: // We do the split manually to avoid Javascript incompatibility
377: while(true) {
378: // None of the information in the match returned are useful as we have no
379: // subgroup handling
380: var matchObj = compiled.exec(trail);
381: if( matchObj == null || trail == "" ||
382: (count == (maxMatch - 1) && maxMatch > 0)) {
383: out[count] = trail;
384: break;
385: } else {
386: out[count] = trail.substring(0,matchObj.index);
387: trail = trail.substring(matchObj.index + matchObj[0].length, trail.length);
388: // Force the compiled pattern to reset internal state
389: compiled.lastIndex = 0;
390: // Only one zero length match per character to ensure termination
391: if (lastTrail == trail) {
392: out[count] = trail.substring(0,1);
393: trail = trail.substring(1);
394: }
395: lastTrail = trail;
396: count++;
397: }
398: }
399: // all blank delimiters at the end are supposed to disappear if maxMatch ==0
400: if (maxMatch == 0) {
401: for (var i = out.length - 1; i >= 0; i--) {
402: if(out[i] != "") {
403: out.splice(i + 1,out.length - (i + 1));
404: break;
405: }
406: }
407: }
408: var jr = @java.lang.String::__createArray(I)(out.length);
409: var i = 0;
410: for(i = 0; i < out.length; ++i) {
411: jr[i] = out[i];
412: }
413: return jr;
414: }-*/;
415:
416: public boolean startsWith(String prefix) {
417: return indexOf(prefix) == 0;
418: }
419:
420: public boolean startsWith(String prefix, int toffset) {
421: if (toffset < 0 || toffset >= length()) {
422: return false;
423: } else {
424: return indexOf(prefix, toffset) == toffset;
425: }
426: }
427:
428: public CharSequence subSequence(int beginIndex, int endIndex) {
429: return this .substring(beginIndex, endIndex);
430: }
431:
432: public native String substring(int beginIndex) /*-{
433: return this.substr(beginIndex, this.length - beginIndex);
434: }-*/;
435:
436: public native String substring(int beginIndex, int endIndex) /*-{
437: return this.substr(beginIndex, endIndex-beginIndex);
438: }-*/;
439:
440: public char[] toCharArray() {
441: int n = this .length();
442: char[] charArr = new char[n];
443: for (int i = 0; i < n; ++i) {
444: charArr[i] = this .charAt(i);
445: }
446: return charArr;
447: }
448:
449: public native String toLowerCase() /*-{
450: return this.toLowerCase();
451: }-*/;
452:
453: @Override
454: public String toString() {
455: return this ;
456: }
457:
458: public native String toUpperCase() /*-{
459: return this.toUpperCase();
460: }-*/;
461:
462: public native String trim() /*-{
463: if(this.length == 0 || (this[0] > '\u0020' && this[this.length-1] > '\u0020')) {
464: return this;
465: }
466: var r1 = this.replace(/^(\s*)/, '');
467: var r2 = r1.replace(/\s*$/, '');
468: return r2;
469: }-*/;
470:
471: }
|