001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.type;
020
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.Comparator;
024import java.util.Iterator;
025import java.util.Map.Entry;
026
027import lucee.commons.lang.SizeOf;
028import lucee.runtime.PageContext;
029import lucee.runtime.converter.LazyConverter;
030import lucee.runtime.dump.DumpData;
031import lucee.runtime.dump.DumpProperties;
032import lucee.runtime.dump.DumpTable;
033import lucee.runtime.dump.DumpUtil;
034import lucee.runtime.dump.SimpleDumpData;
035import lucee.runtime.exp.ExpressionException;
036import lucee.runtime.exp.PageException;
037import lucee.runtime.exp.PageRuntimeException;
038import lucee.runtime.op.Caster;
039import lucee.runtime.op.Duplicator;
040import lucee.runtime.op.ThreadLocalDuplication;
041import lucee.runtime.type.it.EntryIterator;
042import lucee.runtime.type.it.KeyIterator;
043import lucee.runtime.type.it.StringIterator;
044import lucee.runtime.type.util.ArraySupport;
045import lucee.runtime.type.util.ArrayUtil;
046
047
048
049/**
050 * CFML array object
051 */
052public final class ArrayImplNS extends ArraySupport implements Array,Sizeable {
053        
054        private Object[] arr;
055        private int dimension=1;
056        private final int cap=32;
057        private int size=0;
058        private int offset=0;
059        private int offCount=0;
060        
061        /**
062         * constructor with definiton of the dimension
063         * @param dimension dimension goes from one to 3
064         * @throws ExpressionException
065         */
066        public ArrayImplNS(int dimension) throws ExpressionException {
067                if(dimension>3 || dimension<1)
068                        throw new ExpressionException("Array Dimension must be between 1 and 3");
069                this.dimension=dimension;
070                arr=new Object[offset+cap];
071        }
072        
073        /**
074         * constructor with default dimesnion (1)
075         */
076        public ArrayImplNS() {
077                arr=new Object[offset+cap];
078        }
079        
080        /**
081         * constructor with to data to fill
082         * @param objects Objects array data to fill
083         */
084        public ArrayImplNS(Object[] objects) {
085                arr=objects;
086                size=arr.length;
087                offset=0;
088        }
089        
090        /**
091         * return dimension of the array
092         * @return dimension of the array
093         */
094        public int getDimension() {
095                return dimension;
096        }
097        
098        @Override
099        public Object get(String key) throws ExpressionException {
100        return getE(Caster.toIntValue(key));
101        }       
102
103        @Override
104        public Object get(Collection.Key key) throws ExpressionException {
105        return getE(Caster.toIntValue(key.getString()));
106        }       
107        
108        @Override
109        public Object get(String key, Object defaultValue) {
110                double index=Caster.toIntValue(key,Integer.MIN_VALUE);
111                if(index==Integer.MIN_VALUE) return defaultValue;
112            return get((int)index,defaultValue);
113        }       
114        
115        @Override
116        public Object get(Collection.Key key, Object defaultValue) {
117                double index=Caster.toIntValue(key.getString(),Integer.MIN_VALUE);
118                if(index==Integer.MIN_VALUE) return defaultValue;
119            return get((int)index,defaultValue);
120        }               
121
122        @Override
123        public Object get(int key, Object defaultValue) {
124                if(key>size || key<1) {
125                        if(dimension>1) {
126                                ArrayImplNS ai = new ArrayImplNS();
127                                ai.dimension=dimension-1;
128                                return setEL(key,ai);
129                        }
130                        return defaultValue;
131                }
132                Object o=arr[(offset+key)-1];
133                
134                if(o==null) {
135                        if(dimension>1) {
136                                ArrayImplNS ai = new ArrayImplNS();
137                                ai.dimension=dimension-1;
138                                return setEL(key,ai);
139                        }
140                        return defaultValue;
141                }
142                return o;
143        }
144        
145        @Override
146        public Object getE(int key) throws ExpressionException {
147                if(key<1) {
148                        throw invalidPosition(key);
149                }
150                else if(key>size) {
151                        if(dimension>1)return setE(key,new ArrayImplNS(dimension-1));
152                        throw invalidPosition(key);
153                }
154                
155                Object o=arr[(offset+key)-1];
156                
157                if(o==null) {
158                        if(dimension>1) return setE(key,new ArrayImplNS(dimension-1));
159                        throw invalidPosition(key);
160                }
161                return o;
162        }
163        
164        /**
165         * Exception method if key doesn't exist at given position
166         * @param pos
167         * @return exception
168         */
169        private ExpressionException invalidPosition(int pos) {
170                return new ExpressionException("Element at position ["+pos+"] doesn't exist in array");
171        }
172        
173        @Override
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        @Override
183        public Object setEL(Collection.Key key, Object value) {
184                try {
185                        return setEL(Caster.toIntValue(key.getString()), value);
186                } catch (ExpressionException e) {
187                        return null;
188                }
189        }
190        
191        @Override
192        public Object set(String key, Object value) throws ExpressionException {
193                return setE(Caster.toIntValue(key),value);
194        }
195        
196        @Override
197        public Object set(Collection.Key key, Object value) throws ExpressionException {
198                return setE(Caster.toIntValue(key.getString()),value);
199        }
200
201        @Override
202        public Object setEL(int key, Object value) {
203                if(offset+key>arr.length)enlargeCapacity(key);
204                if(key>size)size=key;
205                arr[(offset+key)-1]=checkValueEL(value);
206                return value;
207        }
208        
209        /**
210         * set value at defined position
211         * @param key 
212         * @param value
213         * @return defined value
214         * @throws ExpressionException
215         */
216        public Object setE(int key, Object value) throws ExpressionException {
217                if(offset+key>arr.length)enlargeCapacity(key);
218                if(key>size)size=key;
219                arr[(offset+key)-1]=checkValue(value);
220                return value;           
221        }       
222        
223        /**
224     * !!! all methods that use this method must be sync
225         * enlarge the inner array to given size
226         * @param key min size of the array
227         */
228        private void enlargeCapacity(int key) {
229                int diff=offCount-offset;
230                
231                int newSize=arr.length;
232                if(newSize<1) newSize=1;
233                while(newSize<key+offset+diff) {
234                        newSize*=2;
235                }
236                if(newSize>arr.length) {
237                        Object[] na=new Object[newSize];
238                        for(int i=offset;i<offset+size;i++) {
239                                na[i+diff]=arr[i];
240                        }
241                        arr=na;
242                        offset+=diff;
243                }
244        }
245        
246        /**
247         * !!! all methods that use this method must be sync
248     * enlarge the offset if 0
249         */
250        private void enlargeOffset() {
251                if(offset==0) {
252                        offCount=offCount==0?1:offCount*2;
253                        offset=offCount;
254                        Object[] narr=new Object[arr.length+offset];
255                        for(int i=0;i<size;i++) {
256                                narr[offset+i]=arr[i];
257                        }
258                        arr=narr;
259                }
260        }
261
262        /**
263         * !!! all methods that use this method must be sync
264     * check if value is valid to insert to array (to a multidimesnional array only array with one smaller dimension can be inserted)
265         * @param value value to check
266         * @return checked value
267         * @throws ExpressionException
268        */
269        private Object checkValue(Object value) throws ExpressionException {
270                // is a 1 > Array
271                if(dimension>1)      {
272                        if(value instanceof Array)      {
273                                if(((Array)value).getDimension()!=dimension-1)
274                                        throw new ExpressionException("You can only Append an Array with "+(dimension-1)+" Dimension","array has wrong dimension, now is "+(((Array)value).getDimension())+ " but it must be "+(dimension-1));
275                        }
276                        else 
277                                throw new ExpressionException("You can only Append an Array with "+(dimension-1)+" Dimension","now is a object of type "+Caster.toClassName(value));
278                }
279                return value;
280        }
281        
282        /**
283         * !!! all methods that use this method must be sync
284     * 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;
285         * @param value value to check
286         * @return checked value
287        */
288        private Object checkValueEL(Object value) {
289                // is a 1 > Array
290                if(dimension>1)      {
291                        if(value instanceof Array)      {
292                                if(((Array)value).getDimension()!=dimension-1)
293                                        return null;
294                        }
295                        else 
296                                return null;
297                }
298                return value;
299        }
300
301        @Override
302        public int size() {
303                return size;
304        }
305        
306        @Override
307        public Collection.Key[] keys() {
308                
309                ArrayList<Collection.Key> lst=new ArrayList<Collection.Key>();
310                int count=0;
311                for(int i=offset;i<offset+size;i++) {
312                        Object o=arr[i];
313                        count++;
314                        if(o!=null) lst.add(KeyImpl.getInstance(count+""));
315                }
316                return  lst.toArray(new Collection.Key[lst.size()]);
317        }
318        
319        @Override
320        public int[] intKeys() {
321                ArrayList<Integer> lst=new ArrayList<Integer>();            
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(Integer.valueOf(count));
327                }
328
329                int[] ints=new int[lst.size()];
330                
331                for(int i=0;i<ints.length;i++){
332                        ints[i]=lst.get(i).intValue();
333                }
334                return ints;
335        }
336        
337        @Override
338        public Object remove(Collection.Key key) throws ExpressionException {
339                return removeE(Caster.toIntValue(key.getString()));
340        }
341
342        public Object removeEL(Collection.Key key) {
343                return removeEL(Caster.toIntValue(key.getString(),-1));
344        }
345        
346        @Override
347        public Object removeE(int key) throws ExpressionException {
348                if(key>size || key<1) throw invalidPosition(key);
349                Object obj=get(key,null);
350                for(int i=(offset+key)-1;i<(offset+size)-1;i++) {
351                        arr[i]=arr[i+1];
352                }
353                size--;
354                return obj;
355        }
356        
357        @Override
358        public Object removeEL(int key) {
359            if(key>size || key<1) return null;
360                Object obj=get(key,null);
361            
362                for(int i=(offset+key)-1;i<(offset+size)-1;i++) {
363                        arr[i]=arr[i+1];
364                }
365                size--;
366                return obj;
367        }
368        
369        @Override
370        public void clear() {
371            if(size()>0) {
372                        arr=new Object[cap];
373                        size=0;
374                        offCount=1;
375                        offset=0;
376            }
377        }
378        
379        @Override
380        public boolean insert(int key, Object value) throws ExpressionException {
381                if(key<1 || key>size+1) {
382                        throw new ExpressionException("can't insert value to array at position "+key+", array goes from 1 to "+size());
383                }
384                // Left
385                if((size/2)>=key) {
386                        enlargeOffset();
387                        for(int i=offset;i<(offset+key)-1;i++) {
388                                arr[i-1]=arr[i];
389                        }
390                        offset--;
391                        arr[(offset+key)-1]=checkValue(value);
392                        size++;
393                }
394                // Right
395                else {
396                        if((offset+key)>arr.length || size+offset>=arr.length)enlargeCapacity(arr.length+2);
397                        for(int i=size+offset;i>=key+offset;i--) {
398                                arr[i]=arr[i-1];
399                        }
400                        arr[(offset+key)-1]=checkValue(value);
401                        size++;
402                        
403                }
404                return true;
405        }
406
407        @Override
408    public Object append(Object o) throws ExpressionException {
409        if(offset+size+1>arr.length)enlargeCapacity(size+1);
410        arr[offset+size]=checkValue(o);
411        size++;
412        return o;
413    }
414    
415        /**
416         * append a new value to the end of the array
417         * @param o value to insert
418         * @return inserted value
419         */
420    public Object appendEL(Object o) {
421        
422        if(offset+size+1>arr.length)enlargeCapacity(size+1);
423        arr[offset+size]=o;
424        size++;
425        return o;
426    }
427    
428    /**
429     * adds a value and return this array
430     * @param o
431     * @return this Array
432     */
433    public boolean add(Object o) {
434        if(offset+size+1>arr.length)enlargeCapacity(size+1);
435        arr[offset+size]=o;
436        size++;
437        return true;
438    }
439
440        /**
441         * append a new value to the end of the array
442         * @param str value to insert
443         * @return inserted value
444         */
445        public String _append(String str) {
446                if(offset+size+1>arr.length)enlargeCapacity(size+1);
447                arr[offset+size]=str;
448                size++;
449                return str;
450        }
451        
452        /**
453         * add a new value to the begin of the array
454         * @param o value to insert
455         * @return inserted value
456         * @throws ExpressionException
457         */
458        public Object prepend(Object o) throws ExpressionException {
459                insert(1,o);
460                return o;
461        }
462        
463        /**
464         * resize array to defined size
465         * @param to new minimum size of the array
466         */
467        public void resize(int to) {
468                if(to>size) {
469                        enlargeCapacity(to);
470                        size=to;
471                }
472        }
473        
474        @Override
475        public void sort(String sortType, String sortOrder) throws PageException {
476                sort(ArrayUtil.toComparator(null, sortType, sortOrder, false));
477        }
478
479        @Override
480        public synchronized void sort(Comparator comp) {
481                if(getDimension()>1)
482                        throw new PageRuntimeException("only 1 dimensional arrays can be sorted");
483                Arrays.sort(arr,offset,offset+size,comp);       
484        }
485        
486        /**
487         * @return return arra as native (Java) Object Array
488         */
489        public Object[] toArray() {
490                Object[] rtn=new Object[size];
491                int count=0;
492                for(int i=offset;i<offset+size;i++) {
493                        rtn[count++]=arr[i];
494                }
495                
496                return rtn;
497        }
498
499        /**
500         * @return return array as ArrayList
501         */
502        public ArrayList toArrayList() {
503                ArrayList al=new ArrayList();
504                for(int i=offset;i<offset+size;i++) {
505                        al.add(arr[i]);
506                }
507                return al;
508        }
509        
510        @Override
511        public DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp) {
512                DumpTable table = new DumpTable("array","#ff9900","#ffcc00","#000000");
513                table.setTitle("Array");
514                
515                int length=size();
516                maxlevel--;
517                for(int i=1;i<=length;i++) {
518                        Object o=null;
519                        try {
520                                o = getE(i);
521                        } 
522                        catch (Exception e) {}
523                        table.appendRow(1,new SimpleDumpData(i),DumpUtil.toDumpData(o, pageContext,maxlevel,dp));
524                }
525                return table;
526        }
527        
528        /**
529         * return code print of the array as plain text
530         * @return content as string
531        */
532        public String toPlain() {
533                
534                StringBuffer sb=new StringBuffer();
535                int length=size();
536                for(int i=1;i<=length;i++) {
537                        sb.append(i);
538                        sb.append(": ");
539                        sb.append(get(i-1,null));
540                        sb.append("\n");
541                }
542                return sb.toString();
543        }
544        
545        @Override
546        public Object clone() {
547                return duplicate(true);
548        }
549
550        @Override
551        public Collection duplicate(boolean deepCopy) {
552                ArrayImplNS arr=new ArrayImplNS();
553                arr.dimension=dimension;
554                Iterator<Entry<Key, Object>> it = entryIterator();
555                boolean inside=deepCopy?ThreadLocalDuplication.set(this, arr):true;
556                Entry<Key, Object> e;
557                try {
558                        while(it.hasNext()){
559                                e = it.next();
560                                if(deepCopy)arr.set(e.getKey(),Duplicator.duplicate(e.getValue(),deepCopy));
561                                else arr.set(e.getKey(),e.getValue());
562                        }
563                }
564                catch (ExpressionException ee) {}
565                finally{
566                        if(!inside)ThreadLocalDuplication.reset();
567                }
568                
569                return arr;
570        }
571        
572        @Override
573        public Iterator<Collection.Key> keyIterator() {
574                return new KeyIterator(keys());
575        }
576    
577        @Override
578        public Iterator<String> keysAsStringIterator() {
579        return new StringIterator(keys());
580    }
581        
582        @Override
583        public Iterator<Entry<Key, Object>> entryIterator() {
584                return new EntryIterator(this, keys());
585        }
586        
587        public Iterator iterator() {
588                ArrayList lst=new ArrayList();
589                //int count=0;
590                for(int i=offset;i<offset+size;i++) {
591                        Object o=arr[i];
592                        //count++;
593                        if(o!=null) lst.add(o);
594                }
595                return lst.iterator();
596        }
597
598
599        @Override
600        public String toString() {
601                return LazyConverter.serialize(this);
602        }
603
604        @Override
605        public long sizeOf() {
606                return SizeOf.size(arr)
607                +SizeOf.size(dimension)
608                +SizeOf.size(cap)
609                +SizeOf.size(size)
610                +SizeOf.size(offset)
611                +SizeOf.size(offCount)
612                +SizeOf.REF_SIZE;
613        }
614}