001 
002 /*
003  *  Descripter 1.0 - Java Script Engines
004  *  Copyright (C) 2010-2015  Jianjun Liu (J.J.Liu)
005  *  
006  *  This program is free software: you can redistribute it and/or modify
007  *  it under the terms of the GNU Affero General Public License as published by
008  *  the Free Software Foundation, either version 3 of the License, or
009  *  (at your option) any later version.
010  *  
011  *  This program is distributed in the hope that it will be useful,
012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014  *  GNU Affero General Public License for more details.
015  *  
016  *  You should have received a copy of the GNU Affero General Public License
017  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
018  */
019 
020 package org.descripter.js.api.core;
021 
022 import java.util.AbstractList;
023 import java.util.Collections;
024 import java.util.Comparator;
025 import java.util.List;
026 
027 import org.descripter.js.api.Function;
028 import org.descripter.js.api.Key;
029 
030 /**
031  * <p>Emulates JavaScript Array objects.</p>
032  * 
033  * @author <a href="mailto:jianjunliu@126.com">J.J.Liu (Jianjun Liu)</a> at <a href="http://www.descripter.org" target="_blank">http://www.descripter.org</a>
034  * @since Descripter 1.0
035  */
036 public class CArray extends CObject
037 {
038     /**
039      * <p>Constructs a {@link CObject} context of this type.</p>
040      * @param constructor The constructor {@link Function} object.
041      * @since Descripter 1.0
042      */
043     public CArray(Function<?> constructor) {
044         super(constructor);
045     }
046 
047     /**
048      * <p>Constructs a {@link CObject} context of this type.</p>
049      * @param constructor The constructor {@link Function} object.
050      * @param length The length of the array being constructed
051      * @since Descripter 1.0
052      */
053     public CArray(Function<?> constructor, int length) {
054         this(constructor);
055         length(length);
056     }
057 
058     /**
059      * <p>Constructs a {@link CObject} context of this type.</p>
060      * @param constructor The constructor {@link Function} object.
061      * @param array An array of initial elements for the array being constructed
062      * @since Descripter 1.0
063      */
064     public CArray(Function<?> constructor, Object ...array) {
065         this(constructor, array.length);
066         for (int i = 0; i < array.length; i++) {
067             Object o = array[i];
068             if (o != null) {
069                 super.put(i, o);
070             }
071         }
072     }
073 
074     /**
075      * <p>Returns a string representation of the current object.</p>
076      * @return The string representation of the current object
077      * @since Descripter 1.0
078      */
079     @Override
080     public final String toString() {
081         StringBuilder sb = new StringBuilder("[");
082         for (int i = 0, length = length(); i < length; i++) {
083             if (i > 0) {
084                 sb.append(',');
085             }
086             Object o = get(i);
087             if (o instanceof String) {
088                 sb.append('"');
089                 sb.append(o);
090                 sb.append('"');
091                 
092             } else {
093                 sb.append(o);
094             }
095         }
096         sb.append(']');
097         return sb.toString();
098     }
099 
100     /**
101      * <p>Returns the length of the current array.</p>
102      * @return The length of the current array
103      * @since Descripter 1.0
104      */
105     public final int length() {
106         return intValue(getNumber(core()._length));
107     }
108 
109     /**
110      * <p>Sets the length of the current array.</p>
111      * @param length The new length for the current array
112      * @since Descripter 1.0
113      */
114     public final void length(int length) {
115         if (length < 0) {
116             length = 0;
117         }
118         for (int i = length, len = length(); i < len; i++) {
119             hide(i);
120         }
121         super.put(core()._length, length);
122     }
123 
124     /**
125      * <p>Gets the <tt>callee</tt> {@link Function} property of the current array.</p>
126      * @return The <tt>callee</tt> {@link Function} property of the current array
127      * @since Descripter 1.0
128      */
129     public final Function<?> callee() {
130         return (Function<?>)get(core()._callee);
131     }
132 
133     /**
134      * <p>Gets the <tt>index</tt> property of the current array.</p>
135      * @return The <tt>index</tt> property of the current array
136      * @since Descripter 1.0
137      */
138     public final int index() {
139         return getNumber(core()._index).intValue();
140     }
141 
142     /**
143      * <p>Gets the <tt>input</tt> property of the current array.</p>
144      * @return The <tt>input</tt> property of the current array
145      * @since Descripter 1.0
146      */
147     public final String input() {
148         return getString(core()._index);
149     }
150 
151     /**
152      * <p>Sets the value associated with the specified key.</p>
153      * @param key A {@link Key} to set the value
154      * @param val The value to set
155      * @throws RuntimeException if the current context is read-only.
156      * @since Descripter 1.0
157      */
158     @Override
159     public final void put(Key key, Object val) {
160         if (key.equals(core()._length)) {
161             length(intValue(val));
162         } else {
163             if (key.hashCode() >= length()) {
164                 length(key.hashCode() + 1);
165             }
166             super.put(key, val);
167         }
168     }
169 
170     /**
171      * <p>Creates and returns a new array object that is the result of concatenating the
172      * arguments to the current array instance. This invocation does not modify the current 
173      * array.</p>
174      * @param args An array of values to be concatenated with the current array.
175      * @return A new array object, which is formed by concatenating the specified arguments
176      * to the current array.
177      * @since Descripter 1.0
178      */
179     public final CArray concat(CArray args) {
180         CArray array = slice(0);
181         for (int j = 0, length = args.length(); j < length; j++) {
182             Object o = args.get(j);
183             if (o instanceof CArray) {
184                 CArray a = (CArray)o;
185                 for (int i = 0, len = a.length(); i < len; i++) {
186                     array.put(array.length(), a.get(i));
187                 }
188             } else {
189                 array.put(array.length(), o);
190             }
191         }
192         return array;
193     }
194 
195     /**
196      * <p>Converts each element of the current array instance to a string and then 
197      * concatenates those strings, inserting a comma between the elements and returns 
198      * the resulting string.</p>
199      * @return The string that results from converting each element of the current array
200      * to a string and then concatenating them together with a comma between elements.
201      * @see #join(String)
202      * @since Descripter 1.0
203      */
204     public final String join() {
205         return join("");
206     }
207 
208     /**
209      * <p>Converts each element of the current array instance to a string and then 
210      * concatenates those strings, inserting the separator string specified by 
211      * <tt>separator</tt> between the elements and returns the resulting string.</p>
212      * @param separator An optional string used to separate one element of the current array
213      * from the next in the returned string. If this argument is omitted, 
214      * <tt>undefined</tt> or <tt>null</tt>, a comma is used.
215      * @return The string that results from converting each element of the current array
216      * to a string and then concatenating them together, with the <tt>separator</tt>
217      * string between elements.
218      * @see #join()
219      * @since Descripter 1.0
220      */
221     public final String join(String separator) {
222         if (separator == null) {
223             return join();
224         }
225         StringBuilder sb = new StringBuilder();
226         for (int i = 0, length = length(); i < length; i++) {
227             if (i > 0) {
228                 sb.append(separator);
229             }
230             sb.append(get(i).toString());
231         }
232         return sb.toString();
233     }
234 
235     /**
236      * <p>Deletes the last element of the current array instance, decrements the length of 
237      * the current array, and returns the value of the deleted element. If the current array is 
238      * already empty, this invocation does not change the array and returns the undefined <tt>null</tt> value.</p>
239      * @return The last element of the current array.
240      * @since Descripter 1.0
241      */
242     public final Object pop() {
243         int last = length() - 1;
244         Object o = get(last);
245         length(last);
246         return o;
247     }
248 
249     /**
250      * <p>Appends the arguments to the end of the current array instance by modifying the 
251      * array directly rather than creating a new one.</p>
252      * @param args An array of values to be appended to the end of the current array.
253      * @return The new length of the array, after the specified value are appended to it.
254      * @since Descripter 1.0
255      */
256     public final int push(CArray args) {
257         for (int i = 0, length = args.length(); i < length; i++) {
258             put(length(), args.get(i));
259         }
260         return length();
261     }
262 
263     /**
264      * <p>Reverses the order of the elements of the current array instance by rearranging 
265      * them in place without creating a new array. If there are multiple references to the 
266      * array, the new order of the array elements is visible through all references after 
267      * this invocation.</p>
268      * @return The reversed array
269      * @since Descripter 1.0
270      */
271     public final CArray reverse() {
272         CArray array = new CArray(constructor);
273         for (int i = length() - 1; i >= 0; i--) {
274             array.put(array.length(), get(i));
275         }
276         return array;
277     }
278 
279     private final void move(int start, int count) {
280         int length = length();
281         if (count < 0) {
282             for (int i = start, j = i - count; i < length; i++, j++) {
283                 put(i, get(j));
284             }
285             length(length - count);
286         } else if (count > 0) {
287             for (int i = length - 1, j = i + count; i >= start; i--, j--) {
288                 put(j, get(i));
289             }
290         }
291     }
292 
293     /**
294      * <p>Removes and returns the first element of the current array instance, shifting 
295      * all subsequent elements down one place to occupy the newly vacant space at the 
296      * start of the array. If the current array is empty, this invocation does nothing 
297      * and returns the undefined value <tt>null</tt>. Note that this invocation does 
298      * not create a new array; instead, it modifies the current array directly.</p>
299      * @return The former first element of the current array.
300      * @see #pop()
301      * @since Descripter 1.0
302      */
303     public final Object shift() {
304         Object o = get(0);
305         move(1, -1);
306         return o;
307     }
308 
309     /**
310      * <p>Returns a slice, or sub-array, of the current array instance without modifying 
311      * it. The returned array contains the element positioned by the first value of the 
312      * argument list and all subsequent elements up to, but not including, the element 
313      * positioned by the second value of the argument list. If the second value is not 
314      * specified or undefined, the returned array contains all elements from the position 
315      * specified by the first value to the end of the current array.</p>
316      * @param args A list of the argument values. The first value specifies the array 
317      * index at which the slice is to begin. If this value is negative, it specifies a 
318      * position measured from the end of the current array. That is, -1 indicates the 
319      * last element, -2 indicates the next from the last element, and so on. The second 
320      * value specifies the array index immediately after the end of the slice. If it is 
321      * undefined or not specified, the slice includes all array elements from the position 
322      * specified by the first value to the end of the array. If the second value is 
323      * negative, it specifies the array element measured from the end of the array.
324      * @return A new array that contains the elements of current array instance from the 
325      * element positioned by the first value of <tt>args</tt>, up to, but not including, 
326      * the element positioned by the second value of <tt>args</tt>.
327      * @see #slice(int)
328      * @see #slice(int, int)
329      * @see #splice(CArray)
330      * @see #splice(int)
331      * @see #splice(int, int)
332      * @see #splice(int, int, CArray)
333      * @since Descripter 1.0
334      */
335     public final CArray slice(CArray args) {
336         switch (args.length()) {
337             case 1:
338                 return slice(intValue(args.get(0)));
339             case 2:
340                 return slice(intValue(args.get(0)), intValue(args.get(1)));
341             default:
342                 return null;
343         }
344     }
345 
346     /**
347      * <p>Returns a slice, or sub-array, of the current array instance without modifying 
348      * it. The returned array contains the element positioned by <tt>start</tt> and 
349      * all subsequent elements up to the end of the current array.</p>
350      * @param start The array index at which the slice is to begin. If negative, this 
351      * argument specifies a position measured from the end of the current array. That is, 
352      * -1 indicates the last element, -2 indicates the next from the last element, and so on.
353      * @return A new array that contains the elements of current array instance from the 
354      * element positioned by <tt>start</tt>, up to the end of the current array.
355      * @see #slice(CArray)
356      * @see #slice(int, int)
357      * @see #splice(CArray)
358      * @see #splice(int)
359      * @see #splice(int, int)
360      * @see #splice(int, int, CArray)
361      * @since Descripter 1.0
362      */
363     public final CArray slice(int start) {
364         return slice(start, length());
365     }
366 
367     /**
368      * <p>Returns a slice, or sub-array, of the current array instance without modifying 
369      * it. The returned array contains the element positioned by <tt>start</tt> and 
370      * all subsequent elements up to, but not including, the element positioned by 
371      * <tt>end</tt>. If <tt>end</tt> is an undefined value, the returned array 
372      * contains all elements from the <tt>start</tt> to the end of the current array.</p>
373      * @param start The array index at which the slice is to begin. If negative, this 
374      * argument specifies a position measured from the end of the current array. That is, 
375      * -1 indicates the last element, -2 indicates the next from the last element, and so on.
376      * @param end The array index immediately after the end of the slice. If undefined, 
377      * the slice includes all array elements from the <tt>start</tt> to the end 
378      * of the array. If this argument is negative, it specifies an array element measured 
379      * from the end of the array.
380      * @return A new array that contains the elements of current array instance from the 
381      * element positioned by <tt>start</tt>, up to, but not including, the element 
382      * positioned by <tt>end</tt>.
383      * @see #slice(CArray)
384      * @see #slice(int)
385      * @see #splice(CArray)
386      * @see #splice(int)
387      * @see #splice(int, int)
388      * @see #splice(int, int, CArray)
389      * @since Descripter 1.0
390      */
391     public final CArray slice(int start, int end) {
392         int length = length();
393         if (start < 0) {
394             start += length;
395         }
396         if (end < 0) {
397             end += length;
398         }
399         CArray array = new CArray(constructor);
400         for (int i = start, j = 0; i < end; i++, j++) {
401             if (has(i)) {
402                 array.put(j, get(i));
403             }
404         }
405         return array;
406     }
407 
408     /**
409      * <p>Sorts the elements of the current array instance in place by arranging them in 
410      * alphabetical order (more precisely, the order determined by the character encoding).
411      * To do this, elements are first converted to strings, if necessary, so that they can 
412      * be compared. Note that the array is sorted in place, and no copy is made.
413      * And undefined elements are always sorted to the end of the array.</p>
414      * @return A reference to the current array.
415      * @see #sort(Function)
416      * @since Descripter 1.0
417      */
418     public final CArray sort() {
419         Collections.sort(list());
420         return this;
421     }
422 
423     private List<Element> list() {
424         return new AbstractList<Element>() {
425             @Override
426             public Element get(int index) {
427                 return new Element(CArray.this.get(index));
428             }
429 
430             @Override
431             public Element set(int index, Element element) {
432                 CArray.this.put(index, element.value);
433                 return element;
434             }
435 
436             @Override
437             public int size() {
438                 return CArray.this.length();
439             }
440         };
441     }
442 
443     private static class Element implements Comparable<Element>
444     {
445         public final Object value;
446     
447         public Element(Object value) {
448             this.value = value;
449         }
450 
451         @Override
452         public int compareTo(Element e) {
453             return toString().compareTo(e.toString());
454         }
455 
456         @Override
457         public String toString() {
458             return CArray.toString(value);
459         }
460     }
461 
462     /**
463      * <p>Sorts the elements of the current array instance with the custom ordering function 
464      * <tt>orderer</tt>. Note that the array is sorted in place, and no copy is made.
465      * And undefined elements are always sorted to the end of the array because undefined 
466      * values are never passed to the ordering function you supply.</p>
467      * @param orderer A comparison function that compares two values and returns a 
468      * number indicating their relative order. This function should take two arguments, 
469      * <tt>a</tt> and <tt>b</tt> for instance, and should return one of the following:
470      * <ul>
471      * <li>A value less than zero, if, according to your sort criteria, <tt>a</tt> is 
472      * less than <tt>b</tt> and should appear before <tt>b</tt> in the sorted 
473      * array.</li>
474      * <li>Zero, if <tt>a</tt> and <tt>b</tt> are equivalent for the purposes of 
475      * this sort.</li>
476      * <li>A value greater than zero, if <tt>a</tt> is greater than <tt>b</tt> 
477      * for the purposes of the sort.</li>
478      * </ul>
479      * @return A reference to the current array.
480      * @see #sort()
481      * @since Descripter 1.0
482      */
483     public final CArray sort(final Function<?> orderer) {
484         if (orderer == null) {
485             return sort();
486         }
487         Collections.sort(
488                 list(),
489                 new Comparator<Element>() {
490                     @Override
491                     public int compare(Element e1, Element e2) {
492                         return intValue(core().call(orderer, e1.value, e2.value));
493                     }
494                 }
495         );
496         return this;
497     }
498 
499     /**
500      * <p>Deletes elements, numbered by the second value of <tt>args</tt>, starting with and 
501      * including the element positioned by the first value of <tt>args</tt>, and replaces 
502      * them with the values listed by <tt>args</tt> from the third value. Array elements 
503      * that appear after the insertion or deletion are moved as necessary so that they 
504      * remain contiguous with the rest of the array.</p> 
505      * <p>Note that, this invocation modifies the current array directly.</p>
506      * @param args A list of the argument values. The first value specifies the array 
507      * index at which the deletion and insertion is to begin. The second value specifies 
508      * the number of elements, starting with and including the element positioned by the 
509      * first value, to be deleted from the current array. If the second value is undefined, 
510      * this invocation deletes all elements from the position specified by the first value 
511      * to the end of the array. The rest of the list provides the values to be inserted 
512      * into the current array, beginning at the position specified by the first value.
513      * @return An array containing the elements, if any, deleted from the current array.
514      * @see #slice(CArray)
515      * @see #slice(int)
516      * @see #slice(int, int)
517      * @see #splice(int)
518      * @see #splice(int, int)
519      * @see #splice(int, int, CArray)
520      * @since Descripter 1.0
521      */
522     public final CArray splice(CArray args) {
523         switch (args.length()) {
524             case 1:
525                 return splice(intValue(args.get(0)));
526             case 2:
527                 return splice(intValue(args.get(0)), intValue(args.get(1)));
528             case 3:
529                 return splice(intValue(args.get(0)), intValue(args.get(1)), (CArray)args.get(2));
530             default:
531                 return null;
532         }
533     }
534 
535     /**
536      * <p>Deletes zero or more array elements starting with and including the element 
537      * positioned by <tt>start</tt>.</p>
538      * <p>Note that, this invocation modifies the current array directly.</p>
539      * @param start The array index at which the insertion and/or deletion is to begin.
540      * @return An array containing the elements, if any, deleted from the current array.
541      * @see #slice(CArray)
542      * @see #slice(int)
543      * @see #slice(int, int)
544      * @see #splice(CArray)
545      * @see #splice(int, int)
546      * @see #splice(int, int, CArray)
547      * @since Descripter 1.0
548      */
549     public final CArray splice(int start) {
550         return splice(start, length());
551     }
552 
553     /**
554      * <p>Deletes <tt>deleteCount</tt> elements of the current array instance starting 
555      * with and including the element positioned by <tt>start</tt>. Array elements 
556      * that appear after deletion are moved as necessary so that they remain contiguous 
557      * with the rest of the array.</p> 
558      * <p>Note that, this invocation modifies the current array directly.</p>
559      * @param start The array index at which the deletion is to begin.
560      * @param deleteCount The number of elements, starting with and including the element 
561      * positioned by <tt>start</tt>, to be deleted from the current array. If this 
562      * argument is undefined, this invocation deletes all elements from <tt>start</tt> to the end 
563      * of the array.
564      * @return An array containing the elements, if any, deleted from the current array.
565      * @see #slice(CArray)
566      * @see #slice(int)
567      * @see #slice(int, int)
568      * @see #splice(CArray)
569      * @see #splice(int)
570      * @see #splice(int, int, CArray)
571      * @since Descripter 1.0
572      */
573     public final CArray splice(int start, int deleteCount) {
574         CArray array = slice(start, start + deleteCount);
575         move(start, - deleteCount);
576         return array;
577     }
578 
579     /**
580      * <p>Deletes <tt>deleteCount</tt> elements starting with and including the 
581      * element positioned by <tt>start</tt> and replaces them with the argument 
582      * <tt>values</tt>. Array elements that appear after the insertion or deletion are 
583      * moved as necessary so that they remain contiguous with the rest of the array.</p> 
584      * <p>Note that, this invocation modifies the current array directly.</p>
585      * @param start The array index at which the deletion and insertion is to begin.
586      * @param deleteCount The number of elements, starting with and including the element 
587      * positioned by <tt>start</tt>, to be deleted from the current array. If this 
588      * argument is undefined, this invocation deletes all elements from <tt>start</tt> to the end 
589      * of the array.
590      * @param values The array of values to be inserted into the current array, beginning at the index 
591      * specified by <tt>start</tt>.
592      * @return An array containing the elements, if any, deleted from the current array.
593      * @see #slice(CArray)
594      * @see #slice(int)
595      * @see #slice(int, int)
596      * @see #splice(CArray)
597      * @see #splice(int)
598      * @see #splice(int, int)
599      * @since Descripter 1.0
600      */
601     public final CArray splice(int start, int deleteCount, CArray values) {
602         CArray array = slice(start, start + deleteCount);
603         move(start, values.length() - deleteCount);
604         for (int i = start, j = 0; j < values.length(); i++, j++) {
605             put(i, values.get(j));
606         }
607         return array;
608     }
609 
610     /**
611      * <p>Inserts the arguments at the beginning of the current array instance, 
612      * shifting the existing elements to higher indexes to make room. The first argument becomes 
613      * the new element 0 of the array. Note that this invocation does not create a new 
614      * array; it modifies the current array directly.</p>
615      * @param values An array of values that are inserted at the start of the current array.
616      * @return The new length of the current array.
617      * @see #shift()
618      * @since Descripter 1.0
619      */
620     public final int unshift(CArray values) {
621         splice(0, 0, values);
622         return length();
623     }
624 }