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