/** * Copyright (c) 2014, the Railo Company Ltd. * Copyright (c) 2015, Lucee Assosication Switzerland * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. * */ package lucee.runtime.type; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import java.util.Map.Entry; import lucee.runtime.PageContext; import lucee.runtime.config.NullSupportHelper; import lucee.runtime.dump.DumpData; import lucee.runtime.dump.DumpProperties; import lucee.runtime.dump.DumpTable; import lucee.runtime.dump.DumpUtil; import lucee.runtime.dump.SimpleDumpData; import lucee.runtime.exp.ExpressionException; import lucee.runtime.exp.PageRuntimeException; import lucee.runtime.op.Caster; import lucee.runtime.op.Duplicator; import lucee.runtime.op.ThreadLocalDuplication; import lucee.runtime.type.it.EntryIterator; import lucee.runtime.type.it.KeyIterator; import lucee.runtime.type.it.StringIterator; import lucee.runtime.type.util.ArraySupport; import lucee.runtime.type.util.ArrayUtil; import lucee.runtime.type.util.ListIteratorImpl; /** * CFML array object */ public class ArrayClassic extends ArraySupport { private static final long serialVersionUID = -6187994169003839005L; private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 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 ArrayClassic(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 ArrayClassic() { arr=new Object[offset+cap]; } /** * constructor with to data to fill * @param objects Objects array data to fill */ public ArrayClassic(Object[] objects) { size=objects.length; arr=new Object[ Math.max(size, cap) ]; if (size > 0) arr = ArrayUtil.mergeNativeArrays(arr, objects, 0, false); offset=0; } /** * return dimension of the array * @return dimension of the array */ @Override 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 synchronized Object get(int key, Object defaultValue) { if(key>size || key<1) { if(dimension>1) { ArrayClassic ai = new ArrayClassic(); ai.dimension=dimension-1; return setEL(key,ai); } return defaultValue; } Object o=arr[(offset+key)-1]; if(o==null) { if(dimension>1) { ArrayClassic ai = new ArrayClassic(); ai.dimension=dimension-1; return setEL(key,ai); } if(!NullSupportHelper.full()) return defaultValue; } return o; } @Override public synchronized Object getE(int key) throws ExpressionException { if(key<1) { throw invalidPosition(key); } else if(key>size) { if(dimension>1)return setE(key,new ArrayClassic(dimension-1)); throw invalidPosition(key); } Object o=arr[(offset+key)-1]; if(NullSupportHelper.full()) { if(o==null && dimension>1) return setE(key,new ArrayClassic(dimension-1)); return o; } if(o==null) { if(dimension>1) return setE(key,new ArrayClassic(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 synchronized 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 */ @Override public synchronized Object setE(int key, Object value) throws ExpressionException { if(key<1)throw new ExpressionException("Invalid index ["+key+"] for array. Index must be a positive integer (1, 2, 3, ...)"); if(offset+key>arr.length)enlargeCapacity(key); if(key>size)size=key; arr[(offset+key)-1]=checkValue(value); return value; } public synchronized int ensureCapacity(int cap) { if (cap > arr.length) enlargeCapacity(cap); return arr.length; } /** * !!! 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 minCapacity = Math.max(arr.length, key + offset + diff + 1); if(minCapacity>arr.length) { int oldCapacity = arr.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: arr = Arrays.copyOf(arr, newCapacity); } } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } /** * !!! 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 synchronized 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 synchronized 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())); } @Override public Object removeEL(Collection.Key key) { return removeEL(Caster.toIntValue(key.getString(),-1)); } @Override public Object remove(Collection.Key key, Object defaultValue) { try { return removeE(Caster.toIntValue(key.getString(),-1)); } catch (ExpressionException e) { return defaultValue; } } @Override public synchronized 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 synchronized 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 synchronized void clear() { if(size()>0) { arr=new Object[cap]; size=0; offCount=1; offset=0; } } @Override public synchronized 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 synchronized 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 */ @Override public synchronized Object appendEL(Object o) { if(offset+size+1>arr.length)enlargeCapacity(size+1); arr[offset+size]=o; size++; return o; } /** * append a new value to the end of the array * @param str value to insert * @return inserted value */ public synchronized 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 */ @Override 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 */ @Override public synchronized void resize(int to) { if(to>size) { enlargeCapacity(to); size=to; } } @Override public synchronized void sortIt(Comparator comp) { if(getDimension()>1) throw new PageRuntimeException("only 1 dimensional arrays can be sorted"); Arrays.sort(arr,offset,offset+size,comp); } /** * @return return arra as native (Java) Object Array */ @Override public synchronized 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 synchronized 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","#99cc33","#ccff33","#000000"); table.setTitle("Array"); int top = dp.getMaxlevel(); if( size() > top ) table.setComment("Rows: " + size() + " (showing top " + top + ")"); else if(size()>10 && dp.getMetainfo()) table.setComment("Rows: "+size()); int length=size(); 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) ); if ( i == top ) break; } return table; } /** * return code print of the array as plain text * @return content as string */ public synchronized 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 synchronized Collection duplicate(boolean deepCopy) { return duplicate(new ArrayClassic(),deepCopy); } protected Collection duplicate(ArrayClassic arr,boolean deepCopy) { arr.dimension=dimension; Iterator<Entry<Key, Object>> it = entryIterator(); boolean inside=deepCopy?ThreadLocalDuplication.set(this, arr):true; Entry<Key, Object> e; try { while(it.hasNext()){ e = it.next(); if(deepCopy)arr.set(e.getKey(),Duplicator.duplicate(e.getValue(),deepCopy)); else arr.set(e.getKey(),e.getValue()); } } catch (ExpressionException ee) {} finally{ if(!inside)ThreadLocalDuplication.reset(); } 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()); } @Override public Iterator iterator() { return new ListIteratorImpl(this,0); } }