/*
* Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
* Free SoftwareFoundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.es;
import java.util.Locale;
import com.caucho.util.CharBuffer;
import com.caucho.util.IntArray;
/**
* JavaScript object
*/
class NativeString extends Native {
static final int NEW = 1;
static final int TO_STRING = NEW + 1;
static final int FROM_CHAR_CODE = TO_STRING + 1;
static final int VALUE_OF = FROM_CHAR_CODE + 1;
static final int CHAR_AT = VALUE_OF + 1;
static final int CHAR_CODE_AT = CHAR_AT + 1;
static final int INDEX_OF = CHAR_CODE_AT + 1;
static final int LAST_INDEX_OF = INDEX_OF + 1;
static final int SPLIT = LAST_INDEX_OF + 1;
static final int SUBSTRING = SPLIT + 1;
static final int TO_UPPER_CASE = SUBSTRING + 1;
static final int TO_LOWER_CASE = TO_UPPER_CASE + 1;
// js1.2
static final int CONCAT = TO_LOWER_CASE + 1;
static final int MATCH = CONCAT + 1;
static final int REPLACE = MATCH + 1;
static final int SEARCH = REPLACE + 1;
static final int SLICE = SEARCH + 1;
static final int SUBSTR = SLICE + 1;
// caucho
static final int PRINTF = SUBSTR + 1;
static final int CONTAINS = PRINTF + 1;
static final int STARTS_WITH = CONTAINS + 1;
static final int ENDS_WITH = STARTS_WITH + 1;
static final int GET_BYTES = ENDS_WITH + 1;
static final int REPLACE_WS = 0;
static final int REPLACE_DIGIT = REPLACE_WS + 1;
static final int REPLACE_ID = REPLACE_DIGIT + 1;
private NativeString(String name, int n, int len)
{
super(name, len);
this.n = n;
}
/**
* Creates the native String object
*/
static ESObject create(Global resin)
{
NativeString nativeString = new NativeString("String", NEW, 1);
ESObject stringProto = new ESWrapper("String", resin.objProto,
ESString.NULL);
NativeWrapper string;
string = new NativeWrapper(resin, nativeString,
stringProto, ESThunk.STRING_THUNK);
resin.stringProto = stringProto;
stringProto.put("length", ESNumber.create(0),
DONT_ENUM|DONT_DELETE|READ_ONLY);
put(stringProto, "valueOf", VALUE_OF, 0);
put(stringProto, "toString", TO_STRING, 0);
put(stringProto, "charAt", CHAR_AT, 1);
put(stringProto, "charCodeAt", CHAR_CODE_AT, 1);
put(stringProto, "indexOf", INDEX_OF, 2);
put(stringProto, "lastIndexOf", LAST_INDEX_OF, 2);
put(stringProto, "split", SPLIT, 1);
put(stringProto, "substring", SUBSTRING, 2);
put(stringProto, "toUpperCase", TO_UPPER_CASE, 0);
put(stringProto, "toLowerCase", TO_LOWER_CASE, 0);
put(string, "fromCharCode", FROM_CHAR_CODE, 0);
// js1.2
put(stringProto, "concat", CONCAT, 1);
put(stringProto, "match", MATCH, 1);
put(stringProto, "replace", REPLACE, 2);
put(stringProto, "search", SEARCH, 1);
put(stringProto, "slice", SLICE, 2);
put(stringProto, "substr", SUBSTR, 2);
// caucho extensions
put(string, "printf", PRINTF, 1);
put(stringProto, "contains", CONTAINS, 1);
put(stringProto, "startsWith", STARTS_WITH, 1);
put(stringProto, "endsWith", ENDS_WITH, 1);
put(stringProto, "getBytes", GET_BYTES, 1);
stringProto.setClean();
string.setClean();
return string;
}
private static void put(ESObject obj, String name, int n, int len)
{
ESId id = ESId.intern(name);
obj.put(id, new NativeString(name, n, len), DONT_ENUM);
}
public ESBase call(Call eval, int length) throws Throwable
{
switch (n) {
case NEW:
if (length == 0)
return ESString.create("");
else
return eval.getArg(0).toStr();
case FROM_CHAR_CODE:
return fromCharCode(eval, length);
case VALUE_OF:
case TO_STRING:
try {
return (ESBase) ((ESWrapper) eval.getArg(-1)).value;
} catch (ClassCastException e) {
if (eval.getArg(-1) instanceof ESString)
return eval.getArg(-1);
if (eval.getArg(-1) instanceof ESThunk)
return (ESBase) ((ESWrapper) ((ESThunk) eval.getArg(-1)).getObject()).value;
throw new ESException("toString expects string object");
}
case CHAR_AT:
return charAt(eval, length);
case CHAR_CODE_AT:
return charCodeAt(eval, length);
case INDEX_OF:
return indexOf(eval, length);
case LAST_INDEX_OF:
return lastIndexOf(eval, length);
case SPLIT:
return split(eval, length);
case SUBSTRING:
return substring(eval, length);
case TO_UPPER_CASE:
return eval.getArg(-1).toStr().toUpperCase();
case TO_LOWER_CASE:
return eval.getArg(-1).toStr().toLowerCase();
case CONCAT:
return concat(eval, length);
case MATCH:
return match(eval, length);
case REPLACE:
return replace(eval, length);
case SEARCH:
return search(eval, length);
case SLICE:
return slice(eval, length);
case SUBSTR:
return substr(eval, length);
case PRINTF:
return printf(eval, length);
case CONTAINS:
if (length < 1)
return ESBoolean.FALSE;
else
return eval.getArg(-1).toStr().contains(eval.getArg(0));
case STARTS_WITH:
if (length < 1)
return ESBoolean.FALSE;
else
return eval.getArg(-1).toStr().startsWith(eval.getArg(0));
case ENDS_WITH:
if (length < 1)
return ESBoolean.FALSE;
else
return eval.getArg(-1).toStr().endsWith(eval.getArg(0));
case GET_BYTES:
if (length < 1)
return eval.wrap(eval.getArgString(-1, length).getBytes());
else
return eval.wrap(eval.getArgString(-1, length).getBytes(eval.getArgString(0, length)));
default:
throw new ESException("Unknown object function");
}
}
public ESBase construct(Call eval, int length) throws Throwable
{
if (n != NEW)
throw new ESException("Unknown object function");
return (ESObject) create(eval, length);
}
private ESBase create(Call eval, int length) throws Throwable
{
ESBase value;
if (length == 0)
value = ESString.create("");
else
value = eval.getArg(0).toStr();
return value.toObject();
}
private ESBase fromCharCode(Call eval, int length) throws Throwable
{
StringBuffer sbuf = new StringBuffer();
for (int i = 0; i < length; i++) {
int value = eval.getArg(i).toInt32() & 0xffff;
sbuf.append((char) value);
}
return ESString.create(sbuf.toString());
}
private ESBase charAt(Call eval, int length) throws Throwable
{
ESString string = eval.getArg(-1).toStr();
if (length == 0)
return ESString.create("");
int value = (int) eval.getArg(0).toNum();
if (value < 0 || value >= string.length())
return ESString.create("");
else
return ESString.create("" + (char) string.charAt(value));
}
private ESBase charCodeAt(Call eval, int length) throws Throwable
{
ESString string = eval.getArg(-1).toStr();
if (length == 0)
return ESNumber.NaN;
int value = (int) eval.getArg(0).toNum();
if (value < 0 || value >= string.length())
return ESNumber.NaN;
else
return ESNumber.create(string.charAt(value));
}
private ESBase indexOf(Call eval, int length) throws Throwable
{
ESString string = eval.getArg(-1).toStr();
if (length == 0)
return ESNumber.create(-1);
int pos = 0;
if (length > 1)
pos = (int) eval.getArg(1).toNum();
ESString test = eval.getArg(0).toStr();
return ESNumber.create(string.indexOf(test, pos));
}
private ESBase lastIndexOf(Call eval, int length) throws Throwable
{
ESString string = eval.getArg(-1).toStr();
if (length == 0)
return ESNumber.create(-1);
int pos = string.length() + 1;
if (length > 1)
pos = (int) eval.getArg(1).toNum();
ESString test = eval.getArg(0).toStr();
return ESNumber.create(string.lastIndexOf(test, pos));
}
private String escapeRegexp(String arg)
{
CharBuffer cb = new CharBuffer();
for (int i = 0; i < arg.length(); i++) {
int ch;
switch ((ch = arg.charAt(i))) {
case '\\': case '-': case '[': case ']': case '(': case ')':
case '$': case '^': case '|': case '?': case '*': case '{':
case '}': case '.':
cb.append('\\');
cb.append((char) ch);
break;
default:
cb.append((char) ch);
}
}
return cb.toString();
}
private ESBase split(Call eval, int length) throws Throwable
{
Global resin = Global.getGlobalProto();
ESString string = eval.getArg(-1).toStr();
ESArray array = resin.createArray();
if (length == 0) {
array.setProperty(0, string);
return array;
}
else if (eval.getArg(0) instanceof ESRegexp) {
throw new UnsupportedOperationException();
// splitter = (ESRegexp) eval.getArg(0);
}
else {
String arg = eval.getArg(0).toString();
String []values = string.toString().split(arg);
for (int i = 0; i < values.length; i++) {
array.setProperty(i, ESString.create(values[i]));
}
/*
if (arg.length() == 1 && arg.charAt(0) == ' ') {
splitter = new ESRegexp("\\s", "g");
} else
splitter = new ESRegexp(escapeRegexp(arg), "g");
*/
}
return array;
}
private ESBase substring(Call eval, int length) throws Throwable
{
ESString string = eval.getArg(-1).toStr();
if (length == 0)
return string;
int start = (int) eval.getArg(0).toNum();
int end = string.length();
if (length > 1)
end = (int) eval.getArg(1).toNum();
if (start < 0)
start = 0;
if (end > string.length())
end = string.length();
if (start > end)
return string.substring(end, start);
else
return string.substring(start, end);
}
private ESBase concat(Call eval, int length) throws Throwable
{
ESString string = eval.getArg(-1).toStr();
if (length == 0)
return string;
CharBuffer cb = new CharBuffer();
cb.append(string.toString());
for (int i = 0; i < length; i++) {
ESString next = eval.getArg(i).toStr();
cb.append(next.toString());
}
return ESString.create(cb);
}
private ESBase match(Call eval, int length) throws Throwable
{
/*
if (length == 0)
return esNull;
Global resin = Global.getGlobalProto();
ESString string = eval.getArg(-1).toStr();
ESBase arg = eval.getArg(0);
ESRegexp regexp;
if (arg instanceof ESRegexp)
regexp = (ESRegexp) arg;
else if (length > 1)
regexp = new ESRegexp(arg.toStr(), eval.getArg(1).toStr());
else
regexp = new ESRegexp(arg.toStr(), ESString.NULL);
IntArray results = new IntArray();
resin.getRegexp().setRegexp(regexp);
regexp.setLastIndex(0);
if (! regexp.exec(string))
return esNull;
ESArray array = resin.createArray();
if (! regexp.regexp.isGlobal()) {
for (int i = 0; i < regexp.regexp.length(); i++) {
array.setProperty(i, string.substring(regexp.regexp.getBegin(i),
regexp.regexp.getEnd(i)));
}
return array;
}
int i = 0;
do {
array.setProperty(i, string.substring(regexp.regexp.getBegin(0),
regexp.regexp.getEnd(0)));
i++;
} while (regexp.exec(string));
return array;
*/
return esNull;
}
private void replaceFun(CharBuffer result, String pattern,
ESRegexp regexp, ESBase fun)
throws Throwable
{
/*
Call call = Global.getGlobalProto().getCall();
call.top = 1;
call.setThis(regexp);
for (int i = 0; i < regexp.regexp.length(); i++) {
int begin = regexp.regexp.getBegin(i);
int end = regexp.regexp.getEnd(i);
call.setArg(i, ESString.create(pattern.substring(begin, end)));
}
ESBase value = fun.call(call, regexp.regexp.length());
Global.getGlobalProto().freeCall(call);
String string = value.toStr().toString();
result.append(string);
*/
}
/*
private void replaceString(CharBuffer result, String pattern,
Pattern regexp, String replacement)
throws Throwable
{
int len = replacement.length();
for (int i = 0; i < len; i++) {
char ch = replacement.charAt(i);
if (ch == '$' && i + 1 < len) {
i++;
ch = replacement.charAt(i);
if (ch >= '0' && ch <= '9') {
int index = ch - '0';
if (index < regexp.length()) {
int begin = regexp.getBegin(index);
int end = regexp.getEnd(index);
result.append(pattern.substring(begin, end));
}
} else if (ch == '$')
result.append('$');
else if (ch == '&') {
int begin = regexp.getBegin(0);
int end = regexp.getEnd(0);
result.append(pattern.substring(begin, end));
} else if (ch == '+') {
int begin = regexp.getBegin(regexp.length() - 1);
int end = regexp.getEnd(regexp.length() - 1);
result.append(pattern.substring(begin, end));
} else if (ch == '`') {
int begin = 0;
int end = regexp.getBegin(0);
result.append(pattern.substring(begin, end));
} else if (ch == '\'') {
int begin = regexp.getEnd(0);
int end = pattern.length();
result.append(pattern.substring(begin, end));
} else {
result.append('$');
result.append(ch);
}
} else {
result.append(ch);
}
}
}
*/
private ESBase replace(Call eval, int length) throws Throwable
{
ESString string = eval.getArg(-1).toStr();
if (length < 1)
return string;
Global resin = Global.getGlobalProto();
ESBase arg = eval.getArg(0);
ESRegexp regexp;
if (arg instanceof ESRegexp)
regexp = (ESRegexp) arg;
else
regexp = new ESRegexp(arg.toStr(), ESString.NULL);
IntArray results = new IntArray();
String pattern = string.toString();
ESBase replace = null;
String stringPattern = null;
if (length > 1)
replace = eval.getArg(1);
/* XXX: convert to java.util.regex
int last = 0;
CharBuffer result = new CharBuffer();
resin.getRegexp().setRegexp(regexp);
if (regexp.regexp.isGlobal())
regexp.setLastIndex(0);
while (regexp.exec(string)) {
result.append(pattern.substring(last, regexp.regexp.getBegin(0)));
last = regexp.regexp.getEnd(0);
if (replace instanceof ESClosure) {
replaceFun(result, pattern, regexp, replace);
} else {
if (stringPattern == null)
stringPattern = replace == null ? "" : replace.toString();
replaceString(result, pattern, regexp.regexp, stringPattern);
}
if (! regexp.regexp.isGlobal())
break;
}
result.append(pattern.substring(last));
return ESString.create(result);
*/
return null;
}
private ESBase search(Call eval, int length) throws Throwable
{
if (length == 0)
return ESNumber.create(-1);
return ESNumber.create(-1);
/* XXX: convert to java.util.regex
ESString string = eval.getArg(-1).toStr();
ESBase arg = eval.getArg(0);
ESRegexp regexp;
if (arg instanceof ESRegexp)
regexp = (ESRegexp) arg;
else if (length > 1)
regexp = new ESRegexp(arg.toStr(), eval.getArg(1).toStr());
else
regexp = new ESRegexp(arg.toStr(), ESString.NULL);
Global.getGlobalProto().getRegexp().setRegexp(regexp);
if (! regexp.exec(string, false))
return ESNumber.create(-1);
else
return ESNumber.create(regexp.regexp.getBegin(0));
*/
}
private ESBase slice(Call eval, int length) throws Throwable
{
ESString string = eval.getArg(-1).toStr();
if (length == 0)
return string;
int start = (int) eval.getArg(0).toNum();
int end = string.length();
if (length > 1)
end = (int) eval.getArg(1).toNum();
if (start < 0)
start += string.length();
if (end < 0)
end += string.length();
if (start < 0)
start = 0;
if (start > string.length())
start = string.length();
if (end < 0)
end = 0;
if (end > string.length())
end = string.length();
if (start <= end)
return string.substring(start, end);
else
return ESString.NULL;
}
private ESBase substr(Call eval, int length) throws Throwable
{
ESString string = eval.getArg(-1).toStr();
if (length == 0)
return string;
int start = (int) eval.getArg(0).toNum();
int len = string.length();
if (length > 1)
len = (int) eval.getArg(1).toNum();
if (start < 0)
start += string.length();
if (start < 0)
start = 0;
if (start > string.length())
start = string.length();
if (len <= 0)
return ESString.NULL;
int end = start + len;
if (end > string.length())
end = string.length();
return string.substring(start, end);
}
private ESBase printf(Call eval, int length) throws Throwable
{
if (length == 0)
return ESString.NULL;
ESString format = eval.getArg(0).toStr();
CharBuffer cb = new CharBuffer();
Printf.printf(cb, format, eval, length);
return ESString.create(cb.toString());
}
}