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