norris%netscape.com 136cab8649 Publish Rhino as open source.
git-svn-id: svn://10.0.0.236/trunk@28070 18797224-902f-48f8-a5cc-f745e15eee43
1999-04-19 20:43:53 +00:00

712 lines
22 KiB
Java

/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* The contents of this file are subject to the Netscape Public License
* Version 1.0 (the "NPL"); you may not use this file except in
* compliance with the NPL. You may obtain a copy of the NPL at
* http://www.mozilla.org/NPL/
*
* Software distributed under the NPL is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
* for the specific language governing rights and limitations under the
* NPL.
*
* The Initial Developer of this code under the NPL is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1997-1999 Netscape Communications Corporation. All Rights
* Reserved.
*/
package org.mozilla.javascript;
import java.lang.reflect.Method;
import java.util.Vector;
/**
* This class implements the String native object.
*
* See ECMA 15.5.
*
* String methods for dealing with regular expressions are
* ported directly from C. Latest port is from version 1.40.12.19
* in the JSFUN13_BRANCH.
*
* @author Mike McCabe
* @author Norris Boyd
*/
public class NativeString extends ScriptableObject implements Wrapper {
/**
* Zero-parameter constructor: just used to create String.prototype
*/
public NativeString() {
string = defaultValue;
}
public static void finishInit(Scriptable scope, FunctionObject ctor,
Scriptable proto)
{
// Most of the methods of String.prototype are "vararg" form
// so that they can convert the "this" value to string, rather
// than being restricted to just operating on NativeString
// objects. However, this means that the values of the "length"
// properties of these methods are set to 1 by the constructor
// for FunctionObject. We must therefore fetch the function
// objects and set the length to the appropriate value.
String[] specialLengthNames = { "indexOf",
"lastIndexOf",
"substring",
"toUpperCase",
"toLowerCase",
"toString",
};
short[] specialLengthValues = { 2,
2,
2,
0,
0,
0,
};
for (int i=0; i < specialLengthNames.length; i++) {
Object obj = proto.get(specialLengthNames[i], proto);
((FunctionObject) obj).setLength(specialLengthValues[i]);
}
}
public NativeString(String s) {
string = s;
}
public String getClassName() {
return "String";
}
public static String jsStaticFunction_fromCharCode(Context cx,
Scriptable thisObj,
Object[] args,
Function funObj)
{
StringBuffer s = new java.lang.StringBuffer();
if (args.length < 1)
return "";
for (int i=0; i < args.length; i++) {
s.append(ScriptRuntime.toUint16(args[i]));
}
return s.toString();
}
public static Object js_String(Context cx, Object[] args, Function ctorObj,
boolean inNewExpr)
{
String s = args.length >= 1
? ScriptRuntime.toString(args[0])
: defaultValue;
if (inNewExpr) {
// new String(val) creates a new String object.
return new NativeString(s);
}
// String(val) converts val to a string value.
return s;
}
public String toString() {
return string;
}
/* ECMA 15.5.4.2: 'the toString function is not generic.' */
public String js_toString() {
return string;
}
public String js_valueOf() {
return string;
}
/* Make array-style property lookup work for strings.
* XXX is this ECMA? A version check is probably needed. In js too.
*/
public Object get(int index, Scriptable start) {
if (index >= 0 && index < string.length())
return string.substring(index, index + 1);
return super.get(index, start);
}
public void put(int index, Scriptable start, Object value) {
if (index >= 0 && index < string.length())
return;
super.put(index, start, value);
}
/**
*
* See ECMA 15.5.4.[4,5]
*/
public static String js_charAt(Context cx, Scriptable thisObj,
Object[] args, Function funObj)
{
if (args.length < 1)
args = ScriptRuntime.padArguments(args, 1);
// this'll return 0 if undefined... seems
// to be ECMA.
String target = ScriptRuntime.toString(thisObj);
double pos = ScriptRuntime.toInteger(args[0]);
if (pos < 0 || pos >= target.length())
return "";
return target.substring((int)pos, (int)pos + 1);
}
public static double js_charCodeAt(Context cx, Scriptable thisObj,
Object[] args, Function funObj)
{
if (args.length < 1)
args = ScriptRuntime.padArguments(args, 1);
String target = ScriptRuntime.toString(thisObj);
double pos = ScriptRuntime.toInteger(args[0]);
if (pos < 0 || pos >= target.length()) {
return ScriptRuntime.NaN;
}
return target.charAt((int)pos);
}
/**
*
* See ECMA 15.5.4.6. Uses Java String.indexOf()
* OPT to add - BMH searching from jsstr.c.
*/
public static int js_indexOf(Context cx, Scriptable thisObj,
Object[] args, Function funObj)
{
if (args.length < 2)
args = ScriptRuntime.padArguments(args, 2);
String target = ScriptRuntime.toString(thisObj);
String search = ScriptRuntime.toString(args[0]);
double begin = ScriptRuntime.toInteger(args[1]);
if (begin > target.length()) {
return -1;
} else {
if (begin < 0)
begin = 0;
return target.indexOf(search, (int)begin);
}
}
/**
*
* See ECMA 15.5.4.7
*
*/
public static int js_lastIndexOf(Context cx, Scriptable thisObj,
Object[] args, Function funObj)
{
if (args.length < 2)
args = ScriptRuntime.padArguments(args, 2);
String target = ScriptRuntime.toString(thisObj);
String search = ScriptRuntime.toString(args[0]);
double end = ScriptRuntime.toNumber(args[1]);
if (end != end || end > target.length())
end = target.length();
else if (end < 0)
end = 0;
return target.lastIndexOf(search, (int)end);
}
/*
* Used by js_split to find the next split point in target,
* starting at offset ip and looking either for the given
* separator substring, or for the next re match. ip and
* matchlen must be reference variables (assumed to be arrays of
* length 1) so they can be updated in the leading whitespace or
* re case.
*
* Return -1 on end of string, >= 0 for a valid index of the next
* separator occurrence if found, or the string length if no
* separator is found.
*/
private static int find_split(Function funObj, String target,
String separator, Object re,
int[] ip, int[] matchlen, boolean[] matched,
String[][] parensp)
{
int i = ip[0];
int length = target.length();
Context cx = Context.getContext();
int version = cx.getLanguageVersion();
/*
* Perl4 special case for str.split(' '), only if the user has selected
* JavaScript1.2 explicitly. Split on whitespace, and skip leading w/s.
* Strange but true, apparently modeled after awk.
*/
if (version == Context.VERSION_1_2 &&
re == null && separator.length() == 1 && separator.charAt(0) == ' ')
{
/* Skip leading whitespace if at front of str. */
if (i == 0) {
while (i < length && Character.isWhitespace(target.charAt(i)))
i++;
ip[0] = i;
}
/* Don't delimit whitespace at end of string. */
if (i == length)
return -1;
/* Skip over the non-whitespace chars. */
while (i < length
&& !Character.isWhitespace(target.charAt(i)))
i++;
/* Now skip the next run of whitespace. */
int j = i;
while (j < length && Character.isWhitespace(target.charAt(j)))
j++;
/* Update matchlen to count delimiter chars. */
matchlen[0] = j - i;
return i;
}
/*
* Stop if past end of string. If at end of string, we will
* return target length, so that
*
* "ab,".split(',') => new Array("ab", "")
*
* and the resulting array converts back to the string "ab,"
* for symmetry. NB: This differs from perl, which drops the
* trailing empty substring if the LIMIT argument is omitted.
*/
if (i > length)
return -1;
/*
* Match a regular expression against the separator at or
* above index i. Return -1 at end of string instead of
* trying for a match, so we don't get stuck in a loop.
*/
if (re != null) {
return cx.getRegExpProxy().find_split(funObj, target,
separator, re,
ip, matchlen, matched,
parensp);
}
/*
* Deviate from ECMA by never splitting an empty string by any separator
* string into a non-empty array (an array of length 1 that contains the
* empty string).
*/
if (version != Context.VERSION_DEFAULT && version < Context.VERSION_1_3
&& length == 0)
return -1;
/*
* Special case: if sep is the empty string, split str into
* one character substrings. Let our caller worry about
* whether to split once at end of string into an empty
* substring.
*
* For 1.2 compatibility, at the end of the string, we return the length as
* the result, and set the separator length to 1 -- this allows the caller
* to include an additional null string at the end of the substring list.
*/
if (separator.length() == 0) {
if (version == Context.VERSION_1_2) {
if (i == length) {
matchlen[0] = 1;
return i;
}
return i + 1;
}
return (i == length) ? -1 : i + 1;
}
/* Punt to j.l.s.indexOf; return target length if seperator is
* not found.
*/
if (ip[0] >= length)
return length;
i = target.indexOf(separator, ip[0]);
return (i != -1) ? i : length;
}
/**
* See ECMA 15.5.4.8. Modified to match JS 1.2 - optionally takes
* a limit argument and accepts a regular expression as the split
* argument.
*/
public static Object js_split(Context cx, Scriptable thisObj,
Object[] args, Function funObj)
{
String target = ScriptRuntime.toString(thisObj);
// create an empty Array to return;
Scriptable scope = getTopLevelScope(funObj);
Scriptable result = ScriptRuntime.newObject(cx, scope, "Array", null);
// return an array consisting of the target if no separator given
// don't check against undefined, because we want
// 'fooundefinedbar'.split(void 0) to split to ['foo', 'bar']
if (args.length < 1) {
result.put(0, result, target);
return result;
}
// Use the second argument as the split limit, if given.
boolean limited = (args.length > 1);
int limit = 0; // Initialize to avoid warning.
if (limited) {
/* Clamp limit between 0 and 1 + string length. */
double d = ScriptRuntime.toInteger(args[1]);
if (d < 0)
d = 0;
else if (d > target.length())
d = 1 + target.length();
limit = (int)d;
}
String separator = null;
int[] matchlen = { 0 };
Object re = null;
RegExpProxy reProxy = cx.getRegExpProxy();
if (reProxy != null && reProxy.isRegExp(args[0])) {
re = args[0];
} else {
separator = ScriptRuntime.toString(args[0]);
matchlen[0] = separator.length();
}
// split target with separator or re
int[] ip = { 0 };
int match;
int len = 0;
boolean[] matched = { false };
String[][] parens = { null };
while ((match = find_split(funObj, target, separator, re, ip,
matchlen, matched, parens)) >= 0)
{
if ((limited && len >= limit) || (match > target.length()))
break;
String substr;
if (target.length() == 0)
substr = target;
else
substr = target.substring(ip[0], match);
result.put(len, result, substr);
len++;
/*
* Imitate perl's feature of including parenthesized substrings
* that matched part of the delimiter in the new array, after the
* split substring that was delimited.
*/
if (re != null && matched[0] == true) {
int size = parens[0].length;
for (int num = 0; num < size; num++) {
if (limited && len >= limit)
break;
result.put(len, result, parens[0][num]);
len++;
}
matched[0] = false;
}
ip[0] = match + matchlen[0];
if (cx.getLanguageVersion() < Context.VERSION_1_3
&& cx.getLanguageVersion() != Context.VERSION_DEFAULT)
{
/*
* Deviate from ECMA to imitate Perl, which omits a final
* split unless a limit argument is given and big enough.
*/
if (!limited && ip[0] == target.length())
break;
}
}
return result;
}
/**
*
* See ECMA 15.5.4.[9,10]
*/
public static String js_substring(Context cx, Scriptable thisObj,
Object[] args, Function funObj)
{
if (args.length < 1)
args = ScriptRuntime.padArguments(args, 1);
String target = ScriptRuntime.toString(thisObj);
int length = target.length();
double start = ScriptRuntime.toInteger(args[0]);
double end;
if (start < 0)
start = 0;
else if (start > length)
start = length;
if (args.length == 1) {
end = length;
} else {
end = ScriptRuntime.toInteger(args[1]);
if (end < 0)
end = 0;
else if (end > length)
end = length;
// swap if end < start
if (end < start) {
if (cx.getLanguageVersion() != Context.VERSION_1_2) {
double temp = start;
start = end;
end = temp;
} else {
// Emulate old JDK1.0 java.lang.String.substring()
end = start;
}
}
}
return target.substring((int)start, (int)end);
}
/**
*
* See ECMA 15.5.4.[11,12]
*/
public static String js_toLowerCase(Context cx, Scriptable thisObj,
Object[] args, Function funObj)
{
String target = ScriptRuntime.toString(thisObj);
return target.toLowerCase();
}
public static String js_toUpperCase(Context cx, Scriptable thisObj,
Object[] args, Function funObj)
{
String target = ScriptRuntime.toString(thisObj);
return target.toUpperCase();
}
public double js_getLength() {
return (double) string.length();
}
/**
* Non-ECMA methods.
*/
public static String js_substr(Context cx, Scriptable thisObj,
Object[] args, Function funObj)
{
String target = ScriptRuntime.toString(thisObj);
if (args.length < 1)
return target;
double begin = ScriptRuntime.toInteger(args[0]);
double end;
int length = target.length();
if (begin < 0) {
begin += length;
if (begin < 0)
begin = 0;
} else if (begin > length) {
begin = length;
}
if (args.length == 1) {
end = length;
} else {
end = ScriptRuntime.toInteger(args[1]);
if (end < 0)
end = 0;
end += begin;
if (end > length)
end = length;
}
return target.substring((int)begin, (int)end);
}
/**
* Python-esque sequence operations.
*/
public static String js_concat(Context cx, Scriptable thisObj,
Object[] args, Function funObj)
{
StringBuffer result = new StringBuffer();
result.append(ScriptRuntime.toString(thisObj));
for (int i = 0; i < args.length; i++)
result.append(ScriptRuntime.toString(args[i]));
return result.toString();
}
public static String js_slice(Context cx, Scriptable thisObj,
Object[] args, Function funObj)
{
String target = ScriptRuntime.toString(thisObj);
if (args.length != 0) {
double begin = ScriptRuntime.toInteger(args[0]);
double end;
int length = target.length();
if (begin < 0) {
begin += length;
if (begin < 0)
begin = 0;
} else if (begin > length) {
begin = length;
}
if (args.length == 1) {
end = length;
} else {
end = ScriptRuntime.toInteger(args[1]);
if (end < 0) {
end += length;
if (end < 0)
end = 0;
} else if (end > length) {
end = length;
}
if (end < begin)
end = begin;
}
return target.substring((int)begin, (int)end);
}
return target;
}
/**
* HTML composition aids.
*/
private String tagify(String begin, String end, String value) {
StringBuffer result = new StringBuffer();
result.append('<');
result.append(begin);
if (value != null) {
result.append('=');
result.append(value);
}
result.append('>');
result.append(this.string);
result.append("</");
result.append((end == null) ? begin : end);
result.append('>');
return result.toString();
}
public String js_bold() {
return tagify("B", null, null);
}
public String js_italics() {
return tagify("I", null, null);
}
public String js_fixed() {
return tagify("TT", null, null);
}
public String js_strike() {
return tagify("STRIKE", null, null);
}
public String js_small() {
return tagify("SMALL", null, null);
}
public String js_big() {
return tagify("BIG", null, null);
}
public String js_blink() {
return tagify("BLINK", null, null);
}
public String js_sup() {
return tagify("SUP", null, null);
}
public String js_sub() {
return tagify("SUB", null, null);
}
public String js_fontsize(String value) {
return tagify("FONT SIZE", "FONT", value);
}
public String js_fontcolor(String value) {
return tagify("FONT COLOR", "FONT", value);
}
public String js_link(String value) {
return tagify("A HREF", "A", value);
}
public String js_anchor(String value) {
return tagify("A NAME", "A", value);
}
/**
* Unwrap this NativeString as a j.l.String for LiveConnect use.
*/
public Object unwrap() {
return string;
}
public static Object js_match(Context cx, Scriptable thisObj,
Object[] args, Function funObj)
throws JavaScriptException
{
return checkReProxy(cx).match(cx, thisObj, args, funObj);
}
public static Object js_search(Context cx, Scriptable thisObj,
Object[] args, Function funObj)
throws JavaScriptException
{
return checkReProxy(cx).search(cx, thisObj, args, funObj);
}
public static Object js_replace(Context cx, Scriptable thisObj,
Object[] args, Function funObj)
throws JavaScriptException
{
return checkReProxy(cx).replace(cx, thisObj, args, funObj);
}
private static RegExpProxy checkReProxy(Context cx) {
RegExpProxy result = cx.getRegExpProxy();
if (result == null) {
throw cx.reportRuntimeError(cx.getMessage("msg.no.regexp", null));
}
return result;
}
private static final String defaultValue = "";
private String string;
}