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;
}
}