package railo.runtime.type; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import java.util.Map.Entry; import railo.commons.lang.SizeOf; import railo.runtime.PageContext; import railo.runtime.converter.LazyConverter; import railo.runtime.dump.DumpData; import railo.runtime.dump.DumpProperties; import railo.runtime.dump.DumpTable; import railo.runtime.dump.DumpUtil; import railo.runtime.dump.SimpleDumpData; import railo.runtime.exp.ExpressionException; import railo.runtime.exp.PageException; import railo.runtime.op.Caster; import railo.runtime.op.Duplicator; import railo.runtime.op.ThreadLocalDuplication; import railo.runtime.type.it.EntryIterator; import railo.runtime.type.it.KeyIterator; import railo.runtime.type.it.StringIterator; import railo.runtime.type.util.ArraySupport; import railo.runtime.type.util.ArrayUtil; /** * CFML array object */ public final class ArrayImplNS extends ArraySupport implements Array,Sizeable { private Object[] arr; private int dimension=1; private final int cap=32; private int size=0; private int offset=0; private int offCount=0; /** * constructor with definiton of the dimension * @param dimension dimension goes from one to 3 * @throws ExpressionException */ public ArrayImplNS(int dimension) throws ExpressionException { if(dimension>3 || dimension<1) throw new ExpressionException("Array Dimension must be between 1 and 3"); this.dimension=dimension; arr=new Object[offset+cap]; } /** * constructor with default dimesnion (1) */ public ArrayImplNS() { arr=new Object[offset+cap]; } /** * constructor with to data to fill * @param objects Objects array data to fill */ public ArrayImplNS(Object[] objects) { arr=objects; size=arr.length; offset=0; } /** * return dimension of the array * @return dimension of the array */ public int getDimension() { return dimension; } @Override public Object get(String key) throws ExpressionException { return getE(Caster.toIntValue(key)); } @Override public Object get(Collection.Key key) throws ExpressionException { return getE(Caster.toIntValue(key.getString())); } @Override public Object get(String key, Object defaultValue) { double index=Caster.toIntValue(key,Integer.MIN_VALUE); if(index==Integer.MIN_VALUE) return defaultValue; return get((int)index,defaultValue); } @Override public Object get(Collection.Key key, Object defaultValue) { double index=Caster.toIntValue(key.getString(),Integer.MIN_VALUE); if(index==Integer.MIN_VALUE) return defaultValue; return get((int)index,defaultValue); } @Override public Object get(int key, Object defaultValue) { if(key>size || key<1) { if(dimension>1) { ArrayImplNS ai = new ArrayImplNS(); ai.dimension=dimension-1; return setEL(key,ai); } return defaultValue; } Object o=arr[(offset+key)-1]; if(o==null) { if(dimension>1) { ArrayImplNS ai = new ArrayImplNS(); ai.dimension=dimension-1; return setEL(key,ai); } return defaultValue; } return o; } @Override public Object getE(int key) throws ExpressionException { if(key<1) { throw invalidPosition(key); } else if(key>size) { if(dimension>1)return setE(key,new ArrayImplNS(dimension-1)); throw invalidPosition(key); } Object o=arr[(offset+key)-1]; if(o==null) { if(dimension>1) return setE(key,new ArrayImplNS(dimension-1)); throw invalidPosition(key); } return o; } /** * Exception method if key doesn't exist at given position * @param pos * @return exception */ private ExpressionException invalidPosition(int pos) { return new ExpressionException("Element at position ["+pos+"] doesn't exist in array"); } @Override public Object setEL(String key, Object value) { try { return setEL(Caster.toIntValue(key), value); } catch (ExpressionException e) { return null; } } @Override public Object setEL(Collection.Key key, Object value) { try { return setEL(Caster.toIntValue(key.getString()), value); } catch (ExpressionException e) { return null; } } @Override public Object set(String key, Object value) throws ExpressionException { return setE(Caster.toIntValue(key),value); } @Override public Object set(Collection.Key key, Object value) throws ExpressionException { return setE(Caster.toIntValue(key.getString()),value); } @Override public Object setEL(int key, Object value) { if(offset+key>arr.length)enlargeCapacity(key); if(key>size)size=key; arr[(offset+key)-1]=checkValueEL(value); return value; } /** * set value at defined position * @param key * @param value * @return defined value * @throws ExpressionException */ public Object setE(int key, Object value) throws ExpressionException { if(offset+key>arr.length)enlargeCapacity(key); if(key>size)size=key; arr[(offset+key)-1]=checkValue(value); return value; } /** * !!! all methods that use this method must be sync * enlarge the inner array to given size * @param key min size of the array */ private void enlargeCapacity(int key) { int diff=offCount-offset; int newSize=arr.length; if(newSize<1) newSize=1; while(newSize<key+offset+diff) { newSize*=2; } if(newSize>arr.length) { Object[] na=new Object[newSize]; for(int i=offset;i<offset+size;i++) { na[i+diff]=arr[i]; } arr=na; offset+=diff; } } /** * !!! all methods that use this method must be sync * enlarge the offset if 0 */ private void enlargeOffset() { if(offset==0) { offCount=offCount==0?1:offCount*2; offset=offCount; Object[] narr=new Object[arr.length+offset]; for(int i=0;i<size;i++) { narr[offset+i]=arr[i]; } arr=narr; } } /** * !!! all methods that use this method must be sync * check if value is valid to insert to array (to a multidimesnional array only array with one smaller dimension can be inserted) * @param value value to check * @return checked value * @throws ExpressionException */ private Object checkValue(Object value) throws ExpressionException { // is a 1 > Array if(dimension>1) { if(value instanceof Array) { if(((Array)value).getDimension()!=dimension-1) 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)); } else throw new ExpressionException("You can only Append an Array with "+(dimension-1)+" Dimension","now is a object of type "+Caster.toClassName(value)); } return value; } /** * !!! all methods that use this method must be sync * 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; * @param value value to check * @return checked value */ private Object checkValueEL(Object value) { // is a 1 > Array if(dimension>1) { if(value instanceof Array) { if(((Array)value).getDimension()!=dimension-1) return null; } else return null; } return value; } @Override public int size() { return size; } @Override public Collection.Key[] keys() { ArrayList<Collection.Key> lst=new ArrayList<Collection.Key>(); int count=0; for(int i=offset;i<offset+size;i++) { Object o=arr[i]; count++; if(o!=null) lst.add(KeyImpl.getInstance(count+"")); } return lst.toArray(new Collection.Key[lst.size()]); } @Override public int[] intKeys() { ArrayList<Integer> lst=new ArrayList<Integer>(); int count=0; for(int i=offset;i<offset+size;i++) { Object o=arr[i]; count++; if(o!=null) lst.add(Integer.valueOf(count)); } int[] ints=new int[lst.size()]; for(int i=0;i<ints.length;i++){ ints[i]=lst.get(i).intValue(); } return ints; } @Override public Object remove(Collection.Key key) throws ExpressionException { return removeE(Caster.toIntValue(key.getString())); } public Object removeEL(Collection.Key key) { return removeEL(Caster.toIntValue(key.getString(),-1)); } @Override public Object removeE(int key) throws ExpressionException { if(key>size || key<1) throw invalidPosition(key); Object obj=get(key,null); for(int i=(offset+key)-1;i<(offset+size)-1;i++) { arr[i]=arr[i+1]; } size--; return obj; } @Override public Object removeEL(int key) { if(key>size || key<1) return null; Object obj=get(key,null); for(int i=(offset+key)-1;i<(offset+size)-1;i++) { arr[i]=arr[i+1]; } size--; return obj; } @Override public void clear() { if(size()>0) { arr=new Object[cap]; size=0; offCount=1; offset=0; } } @Override public boolean insert(int key, Object value) throws ExpressionException { if(key<1 || key>size+1) { throw new ExpressionException("can't insert value to array at position "+key+", array goes from 1 to "+size()); } // Left if((size/2)>=key) { enlargeOffset(); for(int i=offset;i<(offset+key)-1;i++) { arr[i-1]=arr[i]; } offset--; arr[(offset+key)-1]=checkValue(value); size++; } // Right else { if((offset+key)>arr.length || size+offset>=arr.length)enlargeCapacity(arr.length+2); for(int i=size+offset;i>=key+offset;i--) { arr[i]=arr[i-1]; } arr[(offset+key)-1]=checkValue(value); size++; } return true; } @Override public Object append(Object o) throws ExpressionException { if(offset+size+1>arr.length)enlargeCapacity(size+1); arr[offset+size]=checkValue(o); size++; return o; } /** * append a new value to the end of the array * @param o value to insert * @return inserted value */ public Object appendEL(Object o) { if(offset+size+1>arr.length)enlargeCapacity(size+1); arr[offset+size]=o; size++; return o; } /** * adds a value and return this array * @param o * @return this Array */ public boolean add(Object o) { if(offset+size+1>arr.length)enlargeCapacity(size+1); arr[offset+size]=o; size++; return true; } /** * append a new value to the end of the array * @param str value to insert * @return inserted value */ public String _append(String str) { if(offset+size+1>arr.length)enlargeCapacity(size+1); arr[offset+size]=str; size++; return str; } /** * add a new value to the begin of the array * @param o value to insert * @return inserted value * @throws ExpressionException */ public Object prepend(Object o) throws ExpressionException { insert(1,o); return o; } /** * resize array to defined size * @param to new minimum size of the array */ public void resize(int to) { if(to>size) { enlargeCapacity(to); size=to; } } @Override public void sort(String sortType, String sortOrder) throws PageException { sort(ArrayUtil.toComparator(null, sortType, sortOrder, false)); } @Override public synchronized void sort(Comparator comp) throws PageException { if(getDimension()>1) throw new ExpressionException("only 1 dimensional arrays can be sorted"); Arrays.sort(arr,offset,offset+size,comp); } /** * @return return arra as native (Java) Object Array */ public Object[] toArray() { Object[] rtn=new Object[size]; int count=0; for(int i=offset;i<offset+size;i++) { rtn[count++]=arr[i]; } return rtn; } /** * @return return array as ArrayList */ public ArrayList toArrayList() { ArrayList al=new ArrayList(); for(int i=offset;i<offset+size;i++) { al.add(arr[i]); } return al; } @Override public DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp) { DumpTable table = new DumpTable("array","#ff9900","#ffcc00","#000000"); table.setTitle("Array"); int length=size(); maxlevel--; for(int i=1;i<=length;i++) { Object o=null; try { o = getE(i); } catch (Exception e) {} table.appendRow(1,new SimpleDumpData(i),DumpUtil.toDumpData(o, pageContext,maxlevel,dp)); } return table; } /** * return code print of the array as plain text * @return content as string */ public String toPlain() { StringBuffer sb=new StringBuffer(); int length=size(); for(int i=1;i<=length;i++) { sb.append(i); sb.append(": "); sb.append(get(i-1,null)); sb.append("\n"); } return sb.toString(); } @Override public Object clone() { return duplicate(true); } @Override public Collection duplicate(boolean deepCopy) { ArrayImplNS arr=new ArrayImplNS(); arr.dimension=dimension; Collection.Key[] keys=this.keys(); ThreadLocalDuplication.set(this, arr); try { Collection.Key k; for(int i=0;i<keys.length;i++) { k=keys[i]; arr.set(k,Duplicator.duplicate(this.get(k,null),deepCopy)); } } catch (ExpressionException e) {} finally{ // ThreadLocalDuplication.remove(this); removed "remove" to catch sisters and brothers } return arr; } @Override public Iterator<Collection.Key> keyIterator() { return new KeyIterator(keys()); } @Override public Iterator<String> keysAsStringIterator() { return new StringIterator(keys()); } @Override public Iterator<Entry<Key, Object>> entryIterator() { return new EntryIterator(this, keys()); } public Iterator iterator() { ArrayList lst=new ArrayList(); //int count=0; for(int i=offset;i<offset+size;i++) { Object o=arr[i]; //count++; if(o!=null) lst.add(o); } return lst.iterator(); } @Override public String toString() { return LazyConverter.serialize(this); } @Override public long sizeOf() { return SizeOf.size(arr) +SizeOf.size(dimension) +SizeOf.size(cap) +SizeOf.size(size) +SizeOf.size(offset) +SizeOf.size(offCount) +SizeOf.REF_SIZE; } }