001: /*
002: * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: * Free SoftwareFoundation, Inc.
023: * 59 Temple Place, Suite 330
024: * Boston, MA 02111-1307 USA
025: *
026: * @author Scott Ferguson
027: */
028:
029: package com.caucho.es;
030:
031: import com.caucho.util.CharBuffer;
032: import com.caucho.util.IntArray;
033:
034: /**
035: * JavaScript object
036: */
037: class NativeString extends Native {
038: static final int NEW = 1;
039: static final int TO_STRING = NEW + 1;
040: static final int FROM_CHAR_CODE = TO_STRING + 1;
041: static final int VALUE_OF = FROM_CHAR_CODE + 1;
042: static final int CHAR_AT = VALUE_OF + 1;
043: static final int CHAR_CODE_AT = CHAR_AT + 1;
044: static final int INDEX_OF = CHAR_CODE_AT + 1;
045: static final int LAST_INDEX_OF = INDEX_OF + 1;
046: static final int SPLIT = LAST_INDEX_OF + 1;
047: static final int SUBSTRING = SPLIT + 1;
048: static final int TO_UPPER_CASE = SUBSTRING + 1;
049: static final int TO_LOWER_CASE = TO_UPPER_CASE + 1;
050:
051: // js1.2
052: static final int CONCAT = TO_LOWER_CASE + 1;
053: static final int MATCH = CONCAT + 1;
054: static final int REPLACE = MATCH + 1;
055: static final int SEARCH = REPLACE + 1;
056: static final int SLICE = SEARCH + 1;
057: static final int SUBSTR = SLICE + 1;
058:
059: // caucho
060: static final int PRINTF = SUBSTR + 1;
061: static final int CONTAINS = PRINTF + 1;
062: static final int STARTS_WITH = CONTAINS + 1;
063: static final int ENDS_WITH = STARTS_WITH + 1;
064: static final int GET_BYTES = ENDS_WITH + 1;
065:
066: static final int REPLACE_WS = 0;
067: static final int REPLACE_DIGIT = REPLACE_WS + 1;
068: static final int REPLACE_ID = REPLACE_DIGIT + 1;
069:
070: private NativeString(String name, int n, int len) {
071: super (name, len);
072:
073: this .n = n;
074: }
075:
076: /**
077: * Creates the native String object
078: */
079: static ESObject create(Global resin) {
080: NativeString nativeString = new NativeString("String", NEW, 1);
081: ESObject stringProto = new ESWrapper("String", resin.objProto,
082: ESString.NULL);
083: NativeWrapper string;
084: string = new NativeWrapper(resin, nativeString, stringProto,
085: ESThunk.STRING_THUNK);
086: resin.stringProto = stringProto;
087:
088: stringProto.put("length", ESNumber.create(0), DONT_ENUM
089: | DONT_DELETE | READ_ONLY);
090:
091: put(stringProto, "valueOf", VALUE_OF, 0);
092: put(stringProto, "toString", TO_STRING, 0);
093: put(stringProto, "charAt", CHAR_AT, 1);
094: put(stringProto, "charCodeAt", CHAR_CODE_AT, 1);
095: put(stringProto, "indexOf", INDEX_OF, 2);
096: put(stringProto, "lastIndexOf", LAST_INDEX_OF, 2);
097: put(stringProto, "split", SPLIT, 1);
098: put(stringProto, "substring", SUBSTRING, 2);
099: put(stringProto, "toUpperCase", TO_UPPER_CASE, 0);
100: put(stringProto, "toLowerCase", TO_LOWER_CASE, 0);
101:
102: put(string, "fromCharCode", FROM_CHAR_CODE, 0);
103:
104: // js1.2
105: put(stringProto, "concat", CONCAT, 1);
106: put(stringProto, "match", MATCH, 1);
107: put(stringProto, "replace", REPLACE, 2);
108: put(stringProto, "search", SEARCH, 1);
109: put(stringProto, "slice", SLICE, 2);
110: put(stringProto, "substr", SUBSTR, 2);
111:
112: // caucho extensions
113: put(string, "printf", PRINTF, 1);
114: put(stringProto, "contains", CONTAINS, 1);
115: put(stringProto, "startsWith", STARTS_WITH, 1);
116: put(stringProto, "endsWith", ENDS_WITH, 1);
117: put(stringProto, "getBytes", GET_BYTES, 1);
118:
119: stringProto.setClean();
120: string.setClean();
121:
122: return string;
123: }
124:
125: private static void put(ESObject obj, String name, int n, int len) {
126: ESId id = ESId.intern(name);
127:
128: obj.put(id, new NativeString(name, n, len), DONT_ENUM);
129: }
130:
131: public ESBase call(Call eval, int length) throws Throwable {
132: switch (n) {
133: case NEW:
134: if (length == 0)
135: return ESString.create("");
136: else
137: return eval.getArg(0).toStr();
138:
139: case FROM_CHAR_CODE:
140: return fromCharCode(eval, length);
141:
142: case VALUE_OF:
143: case TO_STRING:
144: try {
145: return (ESBase) ((ESWrapper) eval.getArg(-1)).value;
146: } catch (ClassCastException e) {
147: if (eval.getArg(-1) instanceof ESString)
148: return eval.getArg(-1);
149:
150: if (eval.getArg(-1) instanceof ESThunk)
151: return (ESBase) ((ESWrapper) ((ESThunk) eval
152: .getArg(-1)).getObject()).value;
153:
154: throw new ESException("toString expects string object");
155: }
156:
157: case CHAR_AT:
158: return charAt(eval, length);
159:
160: case CHAR_CODE_AT:
161: return charCodeAt(eval, length);
162:
163: case INDEX_OF:
164: return indexOf(eval, length);
165:
166: case LAST_INDEX_OF:
167: return lastIndexOf(eval, length);
168:
169: case SPLIT:
170: return split(eval, length);
171:
172: case SUBSTRING:
173: return substring(eval, length);
174:
175: case TO_UPPER_CASE:
176: return eval.getArg(-1).toStr().toUpperCase();
177:
178: case TO_LOWER_CASE:
179: return eval.getArg(-1).toStr().toLowerCase();
180:
181: case CONCAT:
182: return concat(eval, length);
183:
184: case MATCH:
185: return match(eval, length);
186:
187: case REPLACE:
188: return replace(eval, length);
189:
190: case SEARCH:
191: return search(eval, length);
192:
193: case SLICE:
194: return slice(eval, length);
195:
196: case SUBSTR:
197: return substr(eval, length);
198:
199: case PRINTF:
200: return printf(eval, length);
201:
202: case CONTAINS:
203: if (length < 1)
204: return ESBoolean.FALSE;
205: else
206: return eval.getArg(-1).toStr().contains(eval.getArg(0));
207:
208: case STARTS_WITH:
209: if (length < 1)
210: return ESBoolean.FALSE;
211: else
212: return eval.getArg(-1).toStr().startsWith(
213: eval.getArg(0));
214:
215: case ENDS_WITH:
216: if (length < 1)
217: return ESBoolean.FALSE;
218: else
219: return eval.getArg(-1).toStr().endsWith(eval.getArg(0));
220:
221: case GET_BYTES:
222: if (length < 1)
223: return eval.wrap(eval.getArgString(-1, length)
224: .getBytes());
225: else
226: return eval.wrap(eval.getArgString(-1, length)
227: .getBytes(eval.getArgString(0, length)));
228:
229: default:
230: throw new ESException("Unknown object function");
231: }
232: }
233:
234: public ESBase construct(Call eval, int length) throws Throwable {
235: if (n != NEW)
236: throw new ESException("Unknown object function");
237:
238: return (ESObject) create(eval, length);
239: }
240:
241: private ESBase create(Call eval, int length) throws Throwable {
242: ESBase value;
243: if (length == 0)
244: value = ESString.create("");
245: else
246: value = eval.getArg(0).toStr();
247:
248: return value.toObject();
249: }
250:
251: private ESBase fromCharCode(Call eval, int length) throws Throwable {
252: StringBuffer sbuf = new StringBuffer();
253:
254: for (int i = 0; i < length; i++) {
255: int value = eval.getArg(i).toInt32() & 0xffff;
256:
257: sbuf.append((char) value);
258: }
259:
260: return ESString.create(sbuf.toString());
261: }
262:
263: private ESBase charAt(Call eval, int length) throws Throwable {
264: ESString string = eval.getArg(-1).toStr();
265:
266: if (length == 0)
267: return ESString.create("");
268:
269: int value = (int) eval.getArg(0).toNum();
270:
271: if (value < 0 || value >= string.length())
272: return ESString.create("");
273: else
274: return ESString.create("" + (char) string.charAt(value));
275: }
276:
277: private ESBase charCodeAt(Call eval, int length) throws Throwable {
278: ESString string = eval.getArg(-1).toStr();
279:
280: if (length == 0)
281: return ESNumber.NaN;
282:
283: int value = (int) eval.getArg(0).toNum();
284:
285: if (value < 0 || value >= string.length())
286: return ESNumber.NaN;
287: else
288: return ESNumber.create(string.charAt(value));
289: }
290:
291: private ESBase indexOf(Call eval, int length) throws Throwable {
292: ESString string = eval.getArg(-1).toStr();
293:
294: if (length == 0)
295: return ESNumber.create(-1);
296:
297: int pos = 0;
298: if (length > 1)
299: pos = (int) eval.getArg(1).toNum();
300:
301: ESString test = eval.getArg(0).toStr();
302:
303: return ESNumber.create(string.indexOf(test, pos));
304: }
305:
306: private ESBase lastIndexOf(Call eval, int length) throws Throwable {
307: ESString string = eval.getArg(-1).toStr();
308:
309: if (length == 0)
310: return ESNumber.create(-1);
311:
312: int pos = string.length() + 1;
313: if (length > 1)
314: pos = (int) eval.getArg(1).toNum();
315:
316: ESString test = eval.getArg(0).toStr();
317:
318: return ESNumber.create(string.lastIndexOf(test, pos));
319: }
320:
321: private String escapeRegexp(String arg) {
322: CharBuffer cb = new CharBuffer();
323:
324: for (int i = 0; i < arg.length(); i++) {
325: int ch;
326: switch ((ch = arg.charAt(i))) {
327: case '\\':
328: case '-':
329: case '[':
330: case ']':
331: case '(':
332: case ')':
333: case '$':
334: case '^':
335: case '|':
336: case '?':
337: case '*':
338: case '{':
339: case '}':
340: case '.':
341: cb.append('\\');
342: cb.append((char) ch);
343: break;
344:
345: default:
346: cb.append((char) ch);
347: }
348: }
349:
350: return cb.toString();
351: }
352:
353: private ESBase split(Call eval, int length) throws Throwable {
354: Global resin = Global.getGlobalProto();
355: ESString string = eval.getArg(-1).toStr();
356:
357: ESArray array = resin.createArray();
358:
359: if (length == 0) {
360: array.setProperty(0, string);
361: return array;
362: } else if (eval.getArg(0) instanceof ESRegexp) {
363: throw new UnsupportedOperationException();
364:
365: // splitter = (ESRegexp) eval.getArg(0);
366: } else {
367: String arg = eval.getArg(0).toString();
368:
369: String[] values = string.toString().split(arg);
370:
371: for (int i = 0; i < values.length; i++) {
372: array.setProperty(i, ESString.create(values[i]));
373: }
374:
375: /*
376: if (arg.length() == 1 && arg.charAt(0) == ' ') {
377: splitter = new ESRegexp("\\s", "g");
378: } else
379: splitter = new ESRegexp(escapeRegexp(arg), "g");
380: */
381: }
382:
383: return array;
384: }
385:
386: private ESBase substring(Call eval, int length) throws Throwable {
387: ESString string = eval.getArg(-1).toStr();
388:
389: if (length == 0)
390: return string;
391:
392: int start = (int) eval.getArg(0).toNum();
393: int end = string.length();
394:
395: if (length > 1)
396: end = (int) eval.getArg(1).toNum();
397:
398: if (start < 0)
399: start = 0;
400: if (end > string.length())
401: end = string.length();
402: if (start > end)
403: return string.substring(end, start);
404: else
405: return string.substring(start, end);
406: }
407:
408: private ESBase concat(Call eval, int length) throws Throwable {
409: ESString string = eval.getArg(-1).toStr();
410:
411: if (length == 0)
412: return string;
413:
414: CharBuffer cb = new CharBuffer();
415: cb.append(string.toString());
416:
417: for (int i = 0; i < length; i++) {
418: ESString next = eval.getArg(i).toStr();
419:
420: cb.append(next.toString());
421: }
422:
423: return ESString.create(cb);
424: }
425:
426: private ESBase match(Call eval, int length) throws Throwable {
427: /*
428: if (length == 0)
429: return esNull;
430:
431: Global resin = Global.getGlobalProto();
432: ESString string = eval.getArg(-1).toStr();
433:
434: ESBase arg = eval.getArg(0);
435: ESRegexp regexp;
436:
437: if (arg instanceof ESRegexp)
438: regexp = (ESRegexp) arg;
439: else if (length > 1)
440: regexp = new ESRegexp(arg.toStr(), eval.getArg(1).toStr());
441: else
442: regexp = new ESRegexp(arg.toStr(), ESString.NULL);
443:
444: IntArray results = new IntArray();
445:
446: resin.getRegexp().setRegexp(regexp);
447: regexp.setLastIndex(0);
448: if (! regexp.exec(string))
449: return esNull;
450:
451: ESArray array = resin.createArray();
452:
453: if (! regexp.regexp.isGlobal()) {
454: for (int i = 0; i < regexp.regexp.length(); i++) {
455: array.setProperty(i, string.substring(regexp.regexp.getBegin(i),
456: regexp.regexp.getEnd(i)));
457: }
458:
459: return array;
460: }
461:
462: int i = 0;
463: do {
464: array.setProperty(i, string.substring(regexp.regexp.getBegin(0),
465: regexp.regexp.getEnd(0)));
466: i++;
467: } while (regexp.exec(string));
468:
469: return array;
470: */
471: return esNull;
472: }
473:
474: private void replaceFun(CharBuffer result, String pattern,
475: ESRegexp regexp, ESBase fun) throws Throwable {
476: /*
477: Call call = Global.getGlobalProto().getCall();
478:
479: call.top = 1;
480: call.setThis(regexp);
481: for (int i = 0; i < regexp.regexp.length(); i++) {
482: int begin = regexp.regexp.getBegin(i);
483: int end = regexp.regexp.getEnd(i);
484: call.setArg(i, ESString.create(pattern.substring(begin, end)));
485: }
486:
487: ESBase value = fun.call(call, regexp.regexp.length());
488:
489: Global.getGlobalProto().freeCall(call);
490:
491: String string = value.toStr().toString();
492:
493: result.append(string);
494: */
495: }
496:
497: /*
498: private void replaceString(CharBuffer result, String pattern,
499: Pattern regexp, String replacement)
500: throws Throwable
501: {
502: int len = replacement.length();
503:
504: for (int i = 0; i < len; i++) {
505: char ch = replacement.charAt(i);
506:
507: if (ch == '$' && i + 1 < len) {
508: i++;
509: ch = replacement.charAt(i);
510:
511: if (ch >= '0' && ch <= '9') {
512: int index = ch - '0';
513:
514: if (index < regexp.length()) {
515: int begin = regexp.getBegin(index);
516: int end = regexp.getEnd(index);
517: result.append(pattern.substring(begin, end));
518: }
519: } else if (ch == '$')
520: result.append('$');
521: else if (ch == '&') {
522: int begin = regexp.getBegin(0);
523: int end = regexp.getEnd(0);
524: result.append(pattern.substring(begin, end));
525: } else if (ch == '+') {
526: int begin = regexp.getBegin(regexp.length() - 1);
527: int end = regexp.getEnd(regexp.length() - 1);
528: result.append(pattern.substring(begin, end));
529: } else if (ch == '`') {
530: int begin = 0;
531: int end = regexp.getBegin(0);
532: result.append(pattern.substring(begin, end));
533: } else if (ch == '\'') {
534: int begin = regexp.getEnd(0);
535: int end = pattern.length();
536: result.append(pattern.substring(begin, end));
537: } else {
538: result.append('$');
539: result.append(ch);
540: }
541: } else {
542: result.append(ch);
543: }
544: }
545: }
546: */
547:
548: private ESBase replace(Call eval, int length) throws Throwable {
549: ESString string = eval.getArg(-1).toStr();
550:
551: if (length < 1)
552: return string;
553:
554: Global resin = Global.getGlobalProto();
555: ESBase arg = eval.getArg(0);
556: ESRegexp regexp;
557:
558: if (arg instanceof ESRegexp)
559: regexp = (ESRegexp) arg;
560: else
561: regexp = new ESRegexp(arg.toStr(), ESString.NULL);
562:
563: IntArray results = new IntArray();
564: String pattern = string.toString();
565:
566: ESBase replace = null;
567: String stringPattern = null;
568: if (length > 1)
569: replace = eval.getArg(1);
570:
571: /* XXX: convert to java.util.regex
572: int last = 0;
573: CharBuffer result = new CharBuffer();
574: resin.getRegexp().setRegexp(regexp);
575: if (regexp.regexp.isGlobal())
576: regexp.setLastIndex(0);
577: while (regexp.exec(string)) {
578: result.append(pattern.substring(last, regexp.regexp.getBegin(0)));
579: last = regexp.regexp.getEnd(0);
580:
581: if (replace instanceof ESClosure) {
582: replaceFun(result, pattern, regexp, replace);
583: } else {
584: if (stringPattern == null)
585: stringPattern = replace == null ? "" : replace.toString();
586:
587: replaceString(result, pattern, regexp.regexp, stringPattern);
588: }
589:
590: if (! regexp.regexp.isGlobal())
591: break;
592:
593: }
594: result.append(pattern.substring(last));
595:
596: return ESString.create(result);
597: */
598:
599: return null;
600: }
601:
602: private ESBase search(Call eval, int length) throws Throwable {
603: if (length == 0)
604: return ESNumber.create(-1);
605:
606: return ESNumber.create(-1);
607:
608: /* XXX: convert to java.util.regex
609: ESString string = eval.getArg(-1).toStr();
610:
611: ESBase arg = eval.getArg(0);
612: ESRegexp regexp;
613:
614: if (arg instanceof ESRegexp)
615: regexp = (ESRegexp) arg;
616: else if (length > 1)
617: regexp = new ESRegexp(arg.toStr(), eval.getArg(1).toStr());
618: else
619: regexp = new ESRegexp(arg.toStr(), ESString.NULL);
620:
621: Global.getGlobalProto().getRegexp().setRegexp(regexp);
622: if (! regexp.exec(string, false))
623: return ESNumber.create(-1);
624: else
625: return ESNumber.create(regexp.regexp.getBegin(0));
626: */
627: }
628:
629: private ESBase slice(Call eval, int length) throws Throwable {
630: ESString string = eval.getArg(-1).toStr();
631:
632: if (length == 0)
633: return string;
634:
635: int start = (int) eval.getArg(0).toNum();
636: int end = string.length();
637:
638: if (length > 1)
639: end = (int) eval.getArg(1).toNum();
640:
641: if (start < 0)
642: start += string.length();
643: if (end < 0)
644: end += string.length();
645:
646: if (start < 0)
647: start = 0;
648: if (start > string.length())
649: start = string.length();
650:
651: if (end < 0)
652: end = 0;
653: if (end > string.length())
654: end = string.length();
655:
656: if (start <= end)
657: return string.substring(start, end);
658: else
659: return ESString.NULL;
660: }
661:
662: private ESBase substr(Call eval, int length) throws Throwable {
663: ESString string = eval.getArg(-1).toStr();
664:
665: if (length == 0)
666: return string;
667:
668: int start = (int) eval.getArg(0).toNum();
669: int len = string.length();
670:
671: if (length > 1)
672: len = (int) eval.getArg(1).toNum();
673:
674: if (start < 0)
675: start += string.length();
676:
677: if (start < 0)
678: start = 0;
679: if (start > string.length())
680: start = string.length();
681:
682: if (len <= 0)
683: return ESString.NULL;
684:
685: int end = start + len;
686:
687: if (end > string.length())
688: end = string.length();
689:
690: return string.substring(start, end);
691: }
692:
693: private ESBase printf(Call eval, int length) throws Throwable {
694: if (length == 0)
695: return ESString.NULL;
696:
697: ESString format = eval.getArg(0).toStr();
698: CharBuffer cb = new CharBuffer();
699:
700: Printf.printf(cb, format, eval, length);
701:
702: return ESString.create(cb.toString());
703: }
704: }
|