/*
* 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;
/**
* JavaScript object
*/
class NativeArray extends Native {
static ESId LENGTH = ESId.intern("length");
static final int NEW = 1;
static final int JOIN = NEW + 1;
static final int TO_STRING = JOIN + 1;
static final int REVERSE = TO_STRING + 1;
static final int SORT = REVERSE + 1;
// js1.2
static final int CONCAT = SORT + 1;
static final int POP = CONCAT + 1;
static final int PUSH = POP + 1;
static final int SHIFT = PUSH + 1;
static final int UNSHIFT = SHIFT + 1;
static final int SLICE = UNSHIFT + 1;
static final int SPLICE = SLICE + 1;
/**
* Create a new object based on a prototype
*/
private NativeArray(String name, int n, int len)
{
super(name, len);
this.n = n;
}
/**
* Creates the native Array object
*/
static ESObject create(Global resin)
{
Native nativeArray = new NativeArray("Array", NEW, 1);
ESArray proto = new ESArray();
proto.prototype = resin.objProto;
NativeWrapper array = new NativeWrapper(resin, nativeArray,
proto, ESThunk.ARRAY_THUNK);
resin.arrayProto = proto;
put(proto, "join", JOIN, 1);
put(proto, "toString", TO_STRING, 0);
put(proto, "reverse", REVERSE, 0);
put(proto, "sort", SORT, 0);
// js1.2
put(proto, "concat", CONCAT, 0);
put(proto, "pop", POP, 0);
put(proto, "push", PUSH, 0);
put(proto, "shift", SHIFT, 0);
put(proto, "unshift", UNSHIFT, 0);
put(proto, "slice", SLICE, 2);
put(proto, "splice", SPLICE, 0);
proto.setClean();
array.setClean();
return array;
}
private static void put(ESObject obj, String name, int n, int len)
{
ESId id = ESId.intern(name);
obj.put(id, new NativeArray(name, n, len), DONT_ENUM);
}
public ESBase call(Call eval, int length) throws Throwable
{
switch (n) {
case NEW:
return create(eval, length);
case JOIN:
if (length == 0)
return toString(eval, length);
else
return join(eval, length);
case TO_STRING:
return toString(eval, length);
case REVERSE:
return reverse(eval, length);
case SORT:
return sort(eval, length);
case CONCAT:
return concat(eval, length);
case POP:
return pop(eval, length);
case PUSH:
return push(eval, length);
case SHIFT:
return shift(eval, length);
case UNSHIFT:
return unshift(eval, length);
case SLICE:
return slice(eval, length);
case SPLICE:
return splice(eval, length);
default:
throw new ESException("Unknown object function");
}
}
ESBase create(Call eval, int length) throws Throwable
{
ESObject obj = Global.getGlobalProto().createArray();
if (length == 0)
return obj;
if (length == 1) {
ESBase arg = eval.getArg(0);
if (arg instanceof ESNumber)
obj.setProperty(LENGTH, ESNumber.create(arg.toInt32()));
else
obj.setProperty(0, arg);
return obj;
}
for (int i = 0; i < length; i++)
obj.setProperty(i, eval.getArg(i));
return obj;
}
static ESBase join(ESObject array, String separator) throws Throwable
{
if (array.mark != 0) {
return ESString.create("...");
}
array.mark = -1;
try {
int len = array.getProperty(LENGTH).toInt32();
StringBuffer sbuf = new StringBuffer();
for (int i = 0; i < len; i++) {
if (i != 0)
sbuf.append(separator);
ESBase value = array.hasProperty(i);
if (value != null && value != esNull && value != esUndefined)
sbuf.append(value.toString());
}
return ESString.create(sbuf.toString());
} finally {
array.mark = 0;
}
}
ESBase join(Call eval, int length) throws Throwable
{
String separator = length == 0 ? "," : eval.getArg(0).toString();
ESObject array = eval.getArg(-1).toObject();
return join(array, separator);
}
static ESBase toString(ESObject array) throws Throwable
{
return join(array, ",");
}
// XXX: different for non-js1.2
ESBase toString(Call eval, int length) throws Throwable
{
ESObject array = eval.getArg(-1).toObject();
return toString(array);
}
ESBase reverse(Call eval, int length) throws Throwable
{
ESObject array = eval.getArg(-1).toObject();
int len = (int) array.getProperty(LENGTH).toInt32();
for (int k = 0; k < len / 2; k++) {
int firstIndex = k;
int secondIndex = len - k - 1;
ESBase first = array.hasProperty(firstIndex);
ESBase second = array.hasProperty(secondIndex);
if (first == null)
array.delete(secondIndex);
else
array.setProperty(secondIndex, first);
if (second == null)
array.delete(firstIndex);
else
array.setProperty(firstIndex, second);
}
return array;
}
ESBase sort(Call eval, int length) throws Throwable
{
ESObject array = eval.getArg(-1).toObject();
ESBase cmp = length == 0 ? null : eval.getArg(0);
int len = (int) array.getProperty(LENGTH).toInt32();
ESBase []values = new ESBase[len];
for (int i = 0; i < len; i++)
values[i] = array.getProperty("" + i);
qsort(values, 0, len, cmp);
for (int i = 0; i < len; i++) {
if (values[i] == esUndefined)
array.delete("" + i);
else
array.setProperty("" + i, values[i]);
}
return array;
}
private void qsort(ESBase []array, int offset, int length, ESBase cmp)
throws Throwable
{
if (length == 2) {
if (compare(cmp, array[offset], array[offset + 1]) > 0) {
ESBase temp = array[offset];
array[offset] = array[offset + 1];
array[offset + 1] = temp;
}
} else if (length > 2) {
int keyIndex = offset + length / 2;
ESBase key = array[keyIndex];
int keys = 0;
int tail = 0;
int val;
if ((val = compare(cmp, array[offset], key)) > 0) {
key = array[offset];
array[offset] = array[keyIndex];
array[keyIndex] = key;
} else if (val == 0)
keys++;
if ((val = compare(cmp, key, array[offset + length - 1])) > 0) {
key = array[offset + length - 1];
array[offset + length - 1] = array[keyIndex];
array[keyIndex] = key;
keys = 0;
tail = 1;
if ((val = compare(cmp, array[offset], key)) > 0) {
key = array[offset];
array[offset] = array[keyIndex];
array[keyIndex] = key;
} else if (val == 0)
keys++;
} else if (val < 0)
tail = 1;
int i;
if (keyIndex == offset + 1) {
i = 2 + tail;
keys++;
}
else
i = 1 + tail;
for (; i < length; i++) {
int index = offset + i - tail;
if (array[index] == key) {
keys++;
continue;
}
int cmpResult = compare(cmp, key, array[index]);
if (cmpResult > 0 && keys != 0) {
ESBase temp = array[index];
array[index] = array[index - keys];
array[index - keys] = temp;
} else if (cmpResult < 0) {
ESBase temp = array[offset + length - tail - 1];
array[offset + length - tail - 1] = array[index];
array[index] = temp;
tail += 1;
} else if (cmpResult == 0)
keys++;
}
if (length - tail - keys > 1)
qsort(array, offset, length - tail - keys, cmp);
if (tail > 1)
qsort(array, offset + length - tail, tail, cmp);
}
}
private int compare(ESBase cmp, ESBase a, ESBase b)
throws Throwable
{
if (a == b)
return 0;
else if (a == esUndefined)
return 1;
else if (b == esUndefined)
return -1;
else if (a == esNull)
return 1;
else if (b == esNull)
return -1;
else if (cmp != null) {
// Call eval = new Call(ESGlobal.getGlobalProto(), false);
Global resin = Global.getGlobalProto();
Call eval = resin.getCall();
eval.stack[0] = esNull;
eval.stack[1] = a;
eval.stack[2] = b;
eval.top = 1;
int result = cmp.call(eval, 2).toInt32();
resin.freeCall(eval);
return result;
}
else {
String sa = a.toString();
String sb = b.toString();
return sa.compareTo(sb);
}
}
ESBase concat(Call eval, int length) throws Throwable
{
ESArray array = Global.getGlobalProto().createArray();
int k = 0;
for (int i = -1; i < length; i++) {
ESBase arg = eval.getArg(i);
if (arg == esNull || arg == esUndefined || arg == esEmpty)
continue;
ESBase arglen = arg.hasProperty(LENGTH);
if (arglen == null) {
array.setProperty(k++, arg);
continue;
}
int len = (int) arglen.toInt32();
if (len < 0) {
array.setProperty(k++, arg);
continue;
}
for (int j = 0; j < len; j++) {
ESBase obj = arg.hasProperty(j);
if (obj != null)
array.setProperty(k, obj);
k++;
}
}
array.setProperty(LENGTH, ESNumber.create(k));
return array;
}
ESBase pop(Call eval, int length) throws Throwable
{
ESObject obj = eval.getArg(-1).toObject();
ESBase lenObj = obj.hasProperty(LENGTH);
int len;
if (lenObj == null || (len = lenObj.toInt32()) <= 0)
return esUndefined;
ESBase value = obj.getProperty(len - 1);
obj.setProperty(LENGTH, ESNumber.create(len - 1));
return value;
}
ESBase push(Call eval, int length) throws Throwable
{
ESObject obj = eval.getArg(-1).toObject();
ESBase lenObj = obj.getProperty(LENGTH);
int len = lenObj.toInt32();
if (len < 0)
len = 0;
for (int i = 0; i < length; i++)
obj.setProperty(len + i, eval.getArg(i));
ESNumber newLen = ESNumber.create(len + length);
obj.setProperty(LENGTH, newLen);
return newLen;
}
ESBase shift(Call eval, int length) throws Throwable
{
ESObject obj = eval.getArg(-1).toObject();
ESBase lenObj = obj.hasProperty(LENGTH);
int len;
if (lenObj == null || (len = (int) lenObj.toInt32()) <= 0)
return esUndefined;
ESBase value = obj.getProperty(0);
for (int i = 1; i < len; i++) {
ESBase temp = obj.hasProperty(i);
if (temp == null)
obj.delete(ESString.create(i - 1));
else
obj.setProperty(i - 1, temp);
}
obj.setProperty(LENGTH, ESNumber.create(len - 1));
return value;
}
ESBase unshift(Call eval, int length) throws Throwable
{
ESObject obj = eval.getArg(-1).toObject();
ESBase lenObj = obj.getProperty(LENGTH);
int len = lenObj.toInt32();
if (len < 0)
len = 0;
if (length == 0)
return ESNumber.create(0);
for (int i = len - 1; i >= 0; i--) {
ESBase value = obj.getProperty(i);
if (value == null)
obj.delete(ESString.create(length + i));
else
obj.setProperty(length + i, value);
}
for (int i = 0; i < length; i++) {
ESBase value = eval.getArg(i);
if (value == null)
obj.delete(ESString.create(i));
else
obj.setProperty(i, value);
}
ESNumber numLen = ESNumber.create(len + length);
obj.setProperty(LENGTH, numLen);
return numLen;
}
ESBase slice(Call eval, int length) throws Throwable
{
ESObject obj = eval.getArg(-1).toObject();
ESBase lenObj = obj.getProperty(LENGTH);
int len = lenObj.toInt32();
ESArray array = Global.getGlobalProto().createArray();
if (len <= 0)
return array;
int start = 0;
if (length > 0)
start = eval.getArg(0).toInt32();
if (start < 0)
start += len;
if (start < 0)
start = 0;
if (start > len)
return array;
int end = len;
if (length > 1)
end = eval.getArg(1).toInt32();
if (end < 0)
end += len;
if (end < 0)
return array;
if (end > len)
end = len;
if (start >= end)
return array;
for (int i = 0; i < end - start; i++) {
ESBase value = obj.hasProperty(start + i);
if (value != null)
array.setProperty(i, value);
}
array.setProperty(LENGTH, ESNumber.create(end - start));
return array;
}
ESBase splice(Call eval, int length) throws Throwable
{
if (length < 2)
return esUndefined;
ESObject obj = eval.getArg(-1).toObject();
int index = eval.getArg(0).toInt32();
int count = eval.getArg(1).toInt32();
boolean single = count == 1;
ESBase lenObj = obj.getProperty(LENGTH);
int len = lenObj.toInt32();
if (index < 0)
index += len;
if (index < 0)
index = 0;
if (count < 0)
count = 0;
if (index + count > len)
count = len - index;
ESBase value;
if (count < 1)
value = esUndefined;
else {
value = Global.getGlobalProto().createArray();
for (int i = 0; i < count; i++)
value.setProperty(i, obj.getProperty(index + i));
}
int delta = length - 2 - count;
if (delta < 0) {
for (int i = 0; i < len - count; i++) {
ESBase temp = obj.getProperty(i + index + count);
if (temp == null)
obj.delete(ESString.create(i + index + count + delta));
else
obj.setProperty(i + index + count + delta, temp);
}
} else if (delta > 0) {
for (int i = len - count - 1; i >= 0; i--) {
ESBase temp = obj.getProperty(i + index + count);
if (temp == null)
obj.delete(ESString.create(i + index + count + delta));
else
obj.setProperty(i + index + count + delta, temp);
}
}
for (int i = 0; i < length - 2; i++)
obj.setProperty(i + index, eval.getArg(i + 2));
obj.setProperty(LENGTH, ESNumber.create(len - count + length - 2));
return value;
}
}