automatically try call(JS[]) if method
[org.ibex.js.git] / src / org / ibex / js / JSArray.java
1 // Copyright 2000-2005 the Contributors, as shown in the revision logs.
2 // Licensed under the Apache Public Source License 2.0 ("the License").
3 // You may not use this file except in compliance with the License.
4
5 package org.ibex.js; 
6
7 import java.io.InputStream;
8 import org.ibex.util.*; 
9
10 /** A JavaScript JSArray */
11 public class JSArray extends Basket.Array implements JS, Basket.CompareFunc {
12     private static final JS.Method METHOD = new JS.Method();
13     private static final String[] empty = new String[0];
14
15     JSArray() { }
16     JSArray(int size) { super(size); }
17     JSArray(JS[] args) { super(args.length); addAll(args); }
18     JSArray(JS arg) { super(1); add(arg); }
19
20     public JS unclone() { return this; }
21     public JS.Enumeration keys() throws JSExn {
22         return new Enumeration(null) {
23             private int n = 0;
24             public boolean _hasNext() { return n < size(); }
25             public JS _next() { return JSU.N(n++); }
26         };
27     }
28     public JS get(JS key) throws JSExn {
29         if (key == null || !(key instanceof JSNumber.I)) {
30             //#switch(JSU.str(key))
31             case "pop": return METHOD;
32             case "reverse": return METHOD;
33             case "toString": return METHOD;
34             case "shift": return METHOD;
35             case "join": return METHOD;
36             case "sort": return METHOD;
37             case "slice": return METHOD;
38             case "push": return METHOD;
39             case "unshift": return METHOD;
40             case "splice": return METHOD;
41             case "length": return JSU.N(size());
42             //#end
43             throw new JSExn("arrays only support positive integer keys, can not use: "+JSU.str(key));
44         }
45         return (JS)get(((JSNumber.I)key).toInt());
46     }
47     public void put(JS key, JS val) throws JSExn {
48         if (JSU.str(key).equals("length")) { setSize(JSU.toInt(val)); }
49
50         if (key == null || !(key instanceof JSNumber.I)) throw new JSExn(
51             "arrays only support positive integer keys, can not use: "+JSU.str(key));
52         int i = ((JSNumber.I)key).toInt();
53         if (i < 0) throw new JSExn("arrays can not use negative integer keys "+i);
54         size(i + 1); while (size() < i) add(null);
55         set(i, val);
56     }
57     public InputStream getInputStream() { return null; }
58
59     public String[] getFormalArgs() { return empty; }
60     public String coerceToString() { return "array"; } // FIXME
61
62     public Object run(Object o) throws JSExn { return call(null); }
63     public void pause() throws NotPausableException { throw new NotPausableException(); }
64     public JS call(JS[] args) throws JSExn { throw new JSExn("can not call an array as a function"); }
65     public JS call(JS method, JS[] args) throws JSExn {
66         //#switch(JSU.str(method))
67         case "pop": return size() == 0 ? null : (JS)remove(size() - 1);
68         case "push": addAll(args); return JSU.N(size());
69         case "reverse": reverse(); return this;
70         case "toString": return join(",");
71         case "shift": return size() == 0 ? null : (JS)remove(0);
72         case "join": return join(args.length == 0 ? "," : JSU.str(args[0]));
73         case "sort": return sort(args.length == 0 ? null : args[0]);
74         case "slice":
75             int start = JSU.toInt(args.length < 1 ? null : args[0]);
76             int end = args.length < 2 ? size() : JSU.toInt(args[1]);
77             return slice(start, end);
78         case "unshift":
79             for (int i=0; i < args.length; i++) add(i, args[i]);
80             return JSU.N(size());
81         case "splice": return splice(args);
82         //#end
83
84         throw new JSExn("arrays have no function: "+JSU.str(method));
85     }
86
87     public JS putAndTriggerTraps(JS key, JS val) throws JSExn { put(key, val); return val; }
88     public JS getAndTriggerTraps(JS key) throws JSExn { return get(key); }
89     public JS justTriggerTraps(JS key, JS val) throws JSExn { return val; }
90
91     public void addTrap(JS k, JS f) throws JSExn { throw new JSExn("arrays do not support traps"); }
92     public void delTrap(JS k, JS f) throws JSExn { throw new JSExn("arrays do not support traps"); }
93     public JS.Trap getTrap(JS k) throws JSExn { throw new JSExn("arrays do not support traps"); }
94
95     /** FIXME: move to specialised ArrayStore superclass. */
96     public void addAll(JS[] entries) { for (int i=0; i < entries.length; i++) add(entries[i]); }
97
98     public void setSize(int newSize) {
99         size(newSize);
100         for (int i=size(); i < newSize; i++) add(null);
101         for (int i=size() - 1; i >= newSize; i--) remove(i);
102     }
103
104
105     // ECMA Implementation ////////////////////////////////////////////////////
106
107     private JS join(String sep) throws JSExn {
108         int length = size();
109         if(length == 0) return JSU.S("");
110         StringBuffer sb = new StringBuffer(64);
111         int i=0;
112         while(true) {
113             JS o = (JS)get(i);
114             if(o != null) sb.append(JSU.toString(o));
115             if(++i == length) break;
116             sb.append(sep);
117         }
118         return JSU.S(sb.toString());
119     }
120  
121     private JS slice(int start, int end) {
122         int length = size();
123         if(start < 0) start = length+start;
124         if(end < 0) end = length+end;
125         if(start < 0) start = 0;
126         if(end < 0) end = 0;
127         if(start > length) start = length;
128         if(end > length) end = length;
129         JSArray a = new JSArray(end-start);
130         for(int i=0;i<end-start;i++) // FIXME: with ArrayStore do System.arraycopy()
131             a.set(i, get(start+i));
132         return a;
133     }
134
135     private JS splice(JS[] args) throws JSExn {
136         int oldLength = size();
137         int start = JSU.toInt(args.length < 1 ? null : args[0]);
138         int deleteCount = JSU.toInt(args.length < 2 ? null : args[1]);
139         int newCount = args.length - 2;
140         if(newCount < 0) newCount = 0;
141         if(start < 0) start = oldLength+start;
142         if(start < 0) start = 0;
143         if(start > oldLength) start = oldLength;
144         if(deleteCount < 0) deleteCount = 0;
145         if(deleteCount > oldLength-start) deleteCount = oldLength-start;
146         int newLength = oldLength - deleteCount + newCount;
147         int lengthChange = newLength - oldLength;
148         JSArray ret = new JSArray(deleteCount);
149         for(int i=0;i<deleteCount;i++) // FIXME: ArrayStore System.arraycopy()
150             ret.set(i, get(start+i));
151         if(lengthChange > 0) {
152             setSize(newLength);
153             for(int i=newLength-1;i>=start+newCount;i--)
154                 set(i, get(i-lengthChange));
155         } else if(lengthChange < 0) {
156             for(int i=start+newCount;i<newLength;i++)
157                 set(i, get(i-lengthChange));
158             setSize(newLength);
159         }
160         for(int i=0;i<newCount;i++)
161             set(start+i, args[i+2]);
162         return ret;
163     }
164
165     private static final Basket.CompareFunc defaultSort = new Basket.CompareFunc() {
166         public int compare(Object a, Object b) {
167             try { return JSU.toString((JS)a).compareTo(JSU.toString((JS)b)); }
168             catch (JSExn e) { throw new JSExn.Wrapper(e); }
169         }
170     };
171     private JS sort(JS comparator) throws JSExn {
172         try {
173             if (comparator == null) sort(defaultSort);
174             else { sort = comparator; sort((CompareFunc)this); }
175             return this;
176         } catch (JSExn.Wrapper w) { throw w.unwrap();
177         } finally { sort = null; }
178     }
179
180     private JS sort = null;
181     private final JS[] sortargs = new JS[2];
182     public int compare(Object a, Object b) throws JSExn.Wrapper {
183         try {
184             sortargs[0] = (JS)a; sortargs[1] = (JS)b;
185             return JSU.toInt(sort.call(sortargs));
186         } catch (JSExn e) { throw new JSExn.Wrapper(e);
187         } finally { sortargs[0] = null; sortargs[1] = null; }
188     }
189
190 }