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