001    package railo.runtime.type;
002    
003    import java.util.ArrayList;
004    import java.util.Arrays;
005    import java.util.Comparator;
006    import java.util.Iterator;
007    
008    import railo.commons.lang.SizeOf;
009    import railo.runtime.PageContext;
010    import railo.runtime.dump.DumpData;
011    import railo.runtime.dump.DumpProperties;
012    import railo.runtime.dump.DumpTable;
013    import railo.runtime.dump.DumpTablePro;
014    import railo.runtime.dump.DumpUtil;
015    import railo.runtime.dump.SimpleDumpData;
016    import railo.runtime.exp.ExpressionException;
017    import railo.runtime.exp.PageException;
018    import railo.runtime.op.Caster;
019    import railo.runtime.op.Duplicator;
020    import railo.runtime.op.ThreadLocalDuplication;
021    import railo.runtime.type.comparator.NumberComparator;
022    import railo.runtime.type.comparator.TextComparator;
023    import railo.runtime.type.it.KeyIterator;
024    import railo.runtime.type.util.ArraySupport;
025    import railo.runtime.type.util.ListIteratorImpl;
026    
027    
028    
029    /**
030     * cold fusion array object
031     */
032    public class ArrayImpl extends ArraySupport implements Sizeable {
033            
034    
035            private Object[] arr;
036            private int dimension=1;
037            private final int cap=32;
038            private int size=0;
039            private int offset=0;
040            private int offCount=0;
041            
042            /**
043             * constructor with definiton of the dimension
044             * @param dimension dimension goes from one to 3
045             * @throws ExpressionException
046             */
047            public ArrayImpl(int dimension) throws ExpressionException {
048                    if(dimension>3 || dimension<1)
049                            throw new ExpressionException("Array Dimension must be between 1 and 3");
050                    this.dimension=dimension;
051                    arr=new Object[offset+cap];
052            }
053            
054            /**
055             * constructor with default dimesnion (1)
056             */
057            public ArrayImpl() {
058                    arr=new Object[offset+cap];
059            }
060            
061            /**
062             * constructor with to data to fill
063             * @param objects Objects array data to fill
064             */
065            public ArrayImpl(Object[] objects) {
066                    arr=new Object[objects.length];
067                    for(int i=0;i<arr.length;i++){
068                            arr[i]=objects[i];
069                    }
070                    size=arr.length;
071                    offset=0;
072            }
073            
074            /**
075             * return dimension of the array
076             * @return dimension of the array
077             */
078            public int getDimension() {
079                    return dimension;
080            }
081            
082            /**
083             * @see railo.runtime.type.Collection#get(java.lang.String)
084             */
085            public Object get(String key) throws ExpressionException {
086            return getE(Caster.toIntValue(key));
087            }       
088    
089            /**
090             * @see railo.runtime.type.Collection#get(railo.runtime.type.Collection.Key)
091             */
092            public Object get(Collection.Key key) throws ExpressionException {
093            return getE(Caster.toIntValue(key.getString()));
094            }       
095            
096            /**
097             *
098             * @see railo.runtime.type.Collection#get(java.lang.String, java.lang.Object)
099             */
100            public Object get(String key, Object defaultValue) {
101                    double index=Caster.toIntValue(key,Integer.MIN_VALUE);
102                    if(index==Integer.MIN_VALUE) return defaultValue;
103                return get((int)index,defaultValue);
104            }       
105            
106            /**
107             * @see railo.runtime.type.Collection#get(railo.runtime.type.Collection.Key, java.lang.Object)
108             */
109            public Object get(Collection.Key key, Object defaultValue) {
110                    double index=Caster.toIntValue(key.getString(),Integer.MIN_VALUE);
111                    if(index==Integer.MIN_VALUE) return defaultValue;
112                return get((int)index,defaultValue);
113            }               
114    
115            /**
116             *
117             * @see railo.runtime.type.Array#get(int, java.lang.Object)
118             */
119            public synchronized Object get(int key, Object defaultValue) {
120                    if(key>size || key<1) {
121                            if(dimension>1) {
122                                    ArrayImpl ai = new ArrayImpl();
123                                    ai.dimension=dimension-1;
124                                    return setEL(key,ai);
125                            }
126                            return defaultValue;
127                    }
128                    Object o=arr[(offset+key)-1];
129                    
130                    if(o==null) {
131                            if(dimension>1) {
132                                    ArrayImpl ai = new ArrayImpl();
133                                    ai.dimension=dimension-1;
134                                    return setEL(key,ai);
135                            }
136                            return defaultValue;
137                    }
138                    return o;
139            }
140            
141            /**
142             * @see railo.runtime.type.Array#getE(int)
143             */
144            public synchronized Object getE(int key) throws ExpressionException {
145                    if(key<1) {
146                            throw invalidPosition(key);
147                    }
148                    else if(key>size) {
149                            if(dimension>1)return setE(key,new ArrayImpl(dimension-1));
150                            throw invalidPosition(key);
151                    }
152                    
153                    Object o=arr[(offset+key)-1];
154                    
155                    if(o==null) {
156                            if(dimension>1) return setE(key,new ArrayImpl(dimension-1));
157                            throw invalidPosition(key);
158                    }
159                    return o;
160            }
161            
162            /**
163             * Exception method if key doesn't exist at given position
164             * @param pos
165             * @return exception
166             */
167            private ExpressionException invalidPosition(int pos) {
168                    return new ExpressionException("Element at position ["+pos+"] doesn't exist in array");
169            }
170            
171            /**
172             * @see railo.runtime.type.Collection#setEL(java.lang.String, java.lang.Object)
173             */
174            public Object setEL(String key, Object value) {
175                    try {
176                            return setEL(Caster.toIntValue(key), value);
177                    } catch (ExpressionException e) {
178                            return null;
179                    }
180            }
181            
182            /**
183             * @see railo.runtime.type.Collection#_setEL(java.lang.String, java.lang.Object)
184             */
185            public Object setEL(Collection.Key key, Object value) {
186                    try {
187                            return setEL(Caster.toIntValue(key.getString()), value);
188                    } catch (ExpressionException e) {
189                            return null;
190                    }
191            }
192            
193            /**
194             * @see railo.runtime.type.Collection#set(java.lang.String, java.lang.Object)
195             */
196            public Object set(String key, Object value) throws ExpressionException {
197                    return setE(Caster.toIntValue(key),value);
198            }
199            
200            /**
201             * @see railo.runtime.type.Collection#_set(java.lang.String, java.lang.Object)
202             */
203            public Object set(Collection.Key key, Object value) throws ExpressionException {
204                    return setE(Caster.toIntValue(key.getString()),value);
205            }
206    
207            /**
208             * @see railo.runtime.type.Array#setEL(int, java.lang.Object)
209             */
210            public synchronized Object setEL(int key, Object value) {
211                    if(offset+key>arr.length)enlargeCapacity(key);
212                    if(key>size)size=key;
213                    arr[(offset+key)-1]=checkValueEL(value);
214                    return value;
215            }
216            
217            /**
218             * set value at defined position
219             * @param key 
220             * @param value
221             * @return defined value
222             * @throws ExpressionException
223             */
224            public synchronized Object setE(int key, Object value) throws ExpressionException {
225                    if(key<1)throw new ExpressionException("Invalid index ["+key+"] for array. Index must be a positive integer (1, 2, 3, ...)");
226                    if(offset+key>arr.length)enlargeCapacity(key);
227                    if(key>size)size=key;
228                    arr[(offset+key)-1]=checkValue(value);
229                    return value;           
230            }       
231            
232            /**
233         * !!! all methods that use this method must be sync
234             * enlarge the inner array to given size
235             * @param key min size of the array
236             */
237            private void enlargeCapacity(int key) {
238                    int diff=offCount-offset;
239                    int newSize=arr.length;
240                    if(newSize<1) newSize=1;
241                    while(newSize<key+offset+diff) {
242                            newSize*=2;
243                    }
244                    if(newSize>arr.length) {
245                            Object[] na=new Object[newSize];
246                            for(int i=offset;i<offset+size;i++) {
247                                    na[i+diff]=arr[i];
248                            }
249                            arr=na;
250                            offset+=diff;
251                    }
252            }
253            
254            /**
255             * !!! all methods that use this method must be sync
256         * enlarge the offset if 0
257             */
258            private void enlargeOffset() {
259                    if(offset==0) {
260                            offCount=offCount==0?1:offCount*2;
261                            offset=offCount;
262                            Object[] narr=new Object[arr.length+offset];
263                            for(int i=0;i<size;i++) {
264                                    narr[offset+i]=arr[i];
265                            }
266                            arr=narr;
267                    }
268            }
269    
270            /**
271             * !!! all methods that use this method must be sync
272         * check if value is valid to insert to array (to a multidimesnional array only array with one smaller dimension can be inserted)
273             * @param value value to check
274             * @return checked value
275             * @throws ExpressionException
276            */
277            private Object checkValue(Object value) throws ExpressionException {
278                    // is a 1 > Array
279                    if(dimension>1)      {
280                            if(value instanceof Array)      {
281                                    if(((Array)value).getDimension()!=dimension-1)
282                                            throw new ExpressionException("You can only Append an Array with "+(dimension-1)+" Dimension","aray has wron dimension, now is "+(((Array)value).getDimension())+ " but it must be "+(dimension-1));
283                            }
284                            else 
285                                    throw new ExpressionException("You can only Append an Array with "+(dimension-1)+" Dimension","now is a object of type "+Caster.toClassName(value));
286                    }
287                    return value;
288            }
289            
290            /**
291             * !!! all methods that use this method must be sync
292         * check if value is valid to insert to array (to a multidimesnional array only array with one smaller dimension can be inserted), if value is invalid return null;
293             * @param value value to check
294             * @return checked value
295            */
296            private Object checkValueEL(Object value) {
297                    // is a 1 > Array
298                    if(dimension>1)      {
299                            if(value instanceof Array)      {
300                                    if(((Array)value).getDimension()!=dimension-1)
301                                            return null;
302                            }
303                            else 
304                                    return null;
305                    }
306                    return value;
307            }
308    
309            /**
310             * @see railo.runtime.type.Collection#size()
311             */
312            public int size() {
313                    return size;
314            }
315            
316            /**
317             * @see railo.runtime.type.Collection#keys()
318             */
319            public synchronized Collection.Key[] keys() {
320                    
321                    ArrayList lst=new ArrayList();
322                    int count=0;
323                    for(int i=offset;i<offset+size;i++) {
324                            Object o=arr[i];
325                            count++;
326                            if(o!=null) lst.add(KeyImpl.getInstance(count+""));
327                    }
328                    return (Collection.Key[]) lst.toArray(new Collection.Key[lst.size()]);
329            }
330            
331            /**
332             * @see railo.runtime.type.Collection#keysAsString()
333             */
334            public synchronized String[] keysAsString() {
335                    
336                    ArrayList lst=new ArrayList();
337                    int count=0;
338                    for(int i=offset;i<offset+size;i++) {
339                            Object o=arr[i];
340                            count++;
341                            if(o!=null) lst.add(count+"");
342                    }
343                    return (String[]) lst.toArray(new String[lst.size()]);
344            }
345            
346            /**
347             * @see railo.runtime.type.Array#intKeys()
348             */
349            public synchronized int[] intKeys() {
350                    ArrayList lst=new ArrayList();          
351                    int count=0;
352                    for(int i=offset;i<offset+size;i++) {
353                            Object o=arr[i];
354                            count++;
355                            if(o!=null) lst.add(Integer.valueOf(count));
356                    }
357    
358                    int[] ints=new int[lst.size()];
359                    
360                    for(int i=0;i<ints.length;i++){
361                            ints[i]=((Integer)lst.get(i)).intValue();
362                    }
363                    return ints;
364            }
365    
366            /**
367             * @see railo.runtime.type.Collection#remove(railo.runtime.type.Collection.Key)
368             */
369            public Object remove(Collection.Key key) throws ExpressionException {
370                    return removeE(Caster.toIntValue(key.getString()));
371            }
372            
373            public Object removeEL(Collection.Key key) {
374                    return removeEL(Caster.toIntValue(key.getString(),-1));
375            }
376            
377            /**
378             * @see railo.runtime.type.Array#removeE(int)
379             */
380            public synchronized Object removeE(int key) throws ExpressionException {
381                    if(key>size || key<1) throw invalidPosition(key);
382                    Object obj=get(key,null);
383                    for(int i=(offset+key)-1;i<(offset+size)-1;i++) {
384                            arr[i]=arr[i+1];
385                    }
386                    size--;
387                    return obj;
388            }
389            
390            /**
391             * @see railo.runtime.type.Array#removeEL(int)
392             */
393            public synchronized Object removeEL(int key) {
394                if(key>size || key<1) return null;
395                    Object obj=get(key,null);
396                
397                    for(int i=(offset+key)-1;i<(offset+size)-1;i++) {
398                            arr[i]=arr[i+1];
399                    }
400                    size--;
401                    return obj;
402            }
403            
404            /**
405             * @see railo.runtime.type.Collection#clear()
406             */
407            public synchronized void clear() {
408                if(size()>0) {
409                            arr=new Object[cap];
410                            size=0;
411                            offCount=1;
412                            offset=0;
413                }
414            }
415            
416            /**
417             * @see railo.runtime.type.Array#insert(int, java.lang.Object)
418             */
419            public synchronized boolean insert(int key, Object value) throws ExpressionException {
420                    if(key<1 || key>size+1) {
421                            throw new ExpressionException("can't insert value to array at position "+key+", array goes from 1 to "+size());
422                    }
423                    // Left
424                    if((size/2)>=key) {
425                            enlargeOffset();
426                            for(int i=offset;i<(offset+key)-1;i++) {
427                                    arr[i-1]=arr[i];
428                            }
429                            offset--;
430                            arr[(offset+key)-1]=checkValue(value);
431                            size++;
432                    }
433                    // Right
434                    else {
435                            if((offset+key)>arr.length || size+offset>=arr.length)enlargeCapacity(arr.length+2);
436                            for(int i=size+offset;i>=key+offset;i--) {
437                                    arr[i]=arr[i-1];
438                            }
439                            arr[(offset+key)-1]=checkValue(value);
440                            size++;
441                            
442                    }
443                    return true;
444            }
445    
446            /**
447             * @see railo.runtime.type.Array#append(java.lang.Object)
448             */
449        public synchronized Object append(Object o) throws ExpressionException {
450            if(offset+size+1>arr.length)enlargeCapacity(size+1);
451            arr[offset+size]=checkValue(o);
452            size++;
453            return o;
454        }
455        
456            /**
457             * append a new value to the end of the array
458             * @param o value to insert
459             * @return inserted value
460             */
461        public synchronized Object appendEL(Object o) {
462            
463            if(offset+size+1>arr.length)enlargeCapacity(size+1);
464            arr[offset+size]=o;
465            size++;
466            return o;
467        }
468    
469            /**
470             * append a new value to the end of the array
471             * @param str value to insert
472             * @return inserted value
473             */
474            public synchronized String _append(String str) {
475                    if(offset+size+1>arr.length)enlargeCapacity(size+1);
476                    arr[offset+size]=str;
477                    size++;
478                    return str;
479            }
480            
481            /**
482             * add a new value to the begin of the array
483             * @param o value to insert
484             * @return inserted value
485             * @throws ExpressionException
486             */
487            public Object prepend(Object o) throws ExpressionException {
488                    insert(1,o);
489                    return o;
490            }
491            
492            /**
493             * resize array to defined size
494             * @param to new minimum size of the array
495             */
496            public synchronized void resize(int to) {
497                    if(to>size) {
498                            enlargeCapacity(to);
499                            size=to;
500                    }
501            }
502    
503            /**
504             * sort values of a array
505             * @param sortType search type (text,textnocase,numeric)
506             * @param sortOrder (asc,desc)
507             * @throws PageException
508             */
509            public synchronized void sort(String sortType, String sortOrder) throws PageException {
510                    if(getDimension()>1)
511                            throw new ExpressionException("only 1 dimensional arrays can be sorted");
512                    
513                    // check sortorder
514                    boolean isAsc=true;
515                    PageException ee=null;
516                    if(sortOrder.equalsIgnoreCase("asc"))isAsc=true;
517                    else if(sortOrder.equalsIgnoreCase("desc"))isAsc=false;
518                    else throw new ExpressionException("invalid sort order type ["+sortOrder+"], sort order types are [asc and desc]");
519                    
520                    // text
521                    if(sortType.equalsIgnoreCase("text")) {
522                            TextComparator comp=new TextComparator(isAsc,false);
523                            //Collections.sort(list,comp);
524                            Arrays.sort(arr,offset,offset+size,comp);
525                            ee=comp.getPageException();
526                    }
527                    // text no case
528                    else if(sortType.equalsIgnoreCase("textnocase")) {
529                            TextComparator comp=new TextComparator(isAsc,true);
530                            //Collections.sort(list,comp);
531                            Arrays.sort(arr,offset,offset+size,comp);
532                            ee=comp.getPageException();
533                            
534                    }
535                    // numeric
536                    else if(sortType.equalsIgnoreCase("numeric")) {
537                            NumberComparator comp=new NumberComparator(isAsc);
538                            //Collections.sort(list,comp);
539                            Arrays.sort(arr,offset,offset+size,comp);
540                            ee=comp.getPageException();
541                            
542                    }
543                    else {
544                            throw new ExpressionException("invalid sort type ["+sortType+"], sort types are [text, textNoCase, numeric]");
545                    }
546                    if(ee!=null) {
547                            throw new ExpressionException("can only sort arrays with simple values",ee.getMessage());
548                    }
549                            
550            }
551            
552            
553            public synchronized void sort(Comparator comp) throws PageException {
554                    if(getDimension()>1)
555                            throw new ExpressionException("only 1 dimensional arrays can be sorted");
556                    Arrays.sort(arr,offset,offset+size,comp);       
557            }
558            
559            /**
560             * @return return arra as native (Java) Object Array
561             */
562            public synchronized Object[] toArray() {
563                    Object[] rtn=new Object[size];
564                    int count=0;
565                    for(int i=offset;i<offset+size;i++) {
566                            rtn[count++]=arr[i];
567                    }
568                    
569                    return rtn;
570            }
571    
572            /**
573             * @return return array as ArrayList
574             */
575            /*public synchronized ArrayList toArrayList() {
576                    ArrayList al=new ArrayList();
577                    for(int i=offset;i<offset+size;i++) {
578                            al.add(arr[i]);
579                    }
580                    return al;
581            }*/
582            
583            /**
584             * @see railo.runtime.dump.Dumpable#toDumpData(railo.runtime.PageContext, int)
585             */
586            public DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp) {
587                    DumpTable table = new DumpTablePro("array","#99cc33","#ccff33","#000000");
588                    table.setTitle("Array");
589                    //if(size()>10)table.setComment("Size:"+size());
590                    int length=size();
591                    maxlevel--;
592                    for(int i=1;i<=length;i++) {
593                            Object o=null;
594                            try {
595                                    o = getE(i);
596                            } 
597                            catch (Exception e) {}
598                            table.appendRow(1,new SimpleDumpData(i),DumpUtil.toDumpData(o, pageContext,maxlevel,dp));
599                    }
600                    return table;
601            }
602            
603            /**
604             * return code print of the array as plain text
605             * @return content as string
606            */
607            public synchronized String toPlain() {
608                    
609                    StringBuffer sb=new StringBuffer();
610                    int length=size();
611                    for(int i=1;i<=length;i++) {
612                            sb.append(i);
613                            sb.append(": ");
614                            sb.append(get(i-1,null));
615                            sb.append("\n");
616                    }
617                    return sb.toString();
618            }
619            
620            /**
621             * @see railo.runtime.type.Collection#duplicate(boolean)
622             */
623            public synchronized Collection duplicate(boolean deepCopy) {
624                    return duplicate(new ArrayImpl(),deepCopy);
625            }
626            
627            
628            
629            protected Collection duplicate(ArrayImpl arr,boolean deepCopy) {
630                    arr.dimension=dimension;
631                    String[] keys=this.keysAsString();
632                    ThreadLocalDuplication.set(this, arr);
633                    try {
634                            for(int i=0;i<keys.length;i++) {
635                                    String key=keys[i];
636                                    if(deepCopy)arr.set(key,Duplicator.duplicate(this.get(key,null),deepCopy));
637                                    else arr.set(key,this.get(key,null));
638                            }
639                    }
640                    catch (ExpressionException e) {}
641                    finally{
642                            ThreadLocalDuplication.remove(this);
643                    }
644                    
645                    return arr;
646            }
647    
648            /**
649             * @see railo.runtime.type.Collection#keyIterator()
650             */
651            public Iterator keyIterator() {
652                    return new KeyIterator(keys());
653            }
654    
655            /**
656             *
657             * @see railo.runtime.type.Iteratorable#iterator()
658             */
659            public Iterator iterator() {
660                    return new ListIteratorImpl(this,0);
661            }
662    
663            /**
664             * @see railo.runtime.engine.Sizeable#sizeOf()
665             */
666            public long sizeOf() {
667                    return SizeOf.size(arr)
668                    +SizeOf.size(dimension)
669                    +SizeOf.size(cap)
670                    +SizeOf.size(size)
671                    +SizeOf.size(offset)
672                    +SizeOf.size(offCount)
673                    +SizeOf.REF_SIZE;
674            }
675            
676            
677            
678            
679    }