001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.runtime.type; 020 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Comparator; 024import java.util.Iterator; 025import java.util.Map.Entry; 026 027import lucee.commons.lang.SizeOf; 028import lucee.runtime.PageContext; 029import lucee.runtime.converter.LazyConverter; 030import lucee.runtime.dump.DumpData; 031import lucee.runtime.dump.DumpProperties; 032import lucee.runtime.dump.DumpTable; 033import lucee.runtime.dump.DumpUtil; 034import lucee.runtime.dump.SimpleDumpData; 035import lucee.runtime.exp.ExpressionException; 036import lucee.runtime.exp.PageException; 037import lucee.runtime.exp.PageRuntimeException; 038import lucee.runtime.op.Caster; 039import lucee.runtime.op.Duplicator; 040import lucee.runtime.op.ThreadLocalDuplication; 041import lucee.runtime.type.it.EntryIterator; 042import lucee.runtime.type.it.KeyIterator; 043import lucee.runtime.type.it.StringIterator; 044import lucee.runtime.type.util.ArraySupport; 045import lucee.runtime.type.util.ArrayUtil; 046 047 048 049/** 050 * CFML array object 051 */ 052public final class ArrayImplNS extends ArraySupport implements Array,Sizeable { 053 054 private Object[] arr; 055 private int dimension=1; 056 private final int cap=32; 057 private int size=0; 058 private int offset=0; 059 private int offCount=0; 060 061 /** 062 * constructor with definiton of the dimension 063 * @param dimension dimension goes from one to 3 064 * @throws ExpressionException 065 */ 066 public ArrayImplNS(int dimension) throws ExpressionException { 067 if(dimension>3 || dimension<1) 068 throw new ExpressionException("Array Dimension must be between 1 and 3"); 069 this.dimension=dimension; 070 arr=new Object[offset+cap]; 071 } 072 073 /** 074 * constructor with default dimesnion (1) 075 */ 076 public ArrayImplNS() { 077 arr=new Object[offset+cap]; 078 } 079 080 /** 081 * constructor with to data to fill 082 * @param objects Objects array data to fill 083 */ 084 public ArrayImplNS(Object[] objects) { 085 arr=objects; 086 size=arr.length; 087 offset=0; 088 } 089 090 /** 091 * return dimension of the array 092 * @return dimension of the array 093 */ 094 public int getDimension() { 095 return dimension; 096 } 097 098 @Override 099 public Object get(String key) throws ExpressionException { 100 return getE(Caster.toIntValue(key)); 101 } 102 103 @Override 104 public Object get(Collection.Key key) throws ExpressionException { 105 return getE(Caster.toIntValue(key.getString())); 106 } 107 108 @Override 109 public Object get(String key, Object defaultValue) { 110 double index=Caster.toIntValue(key,Integer.MIN_VALUE); 111 if(index==Integer.MIN_VALUE) return defaultValue; 112 return get((int)index,defaultValue); 113 } 114 115 @Override 116 public Object get(Collection.Key key, Object defaultValue) { 117 double index=Caster.toIntValue(key.getString(),Integer.MIN_VALUE); 118 if(index==Integer.MIN_VALUE) return defaultValue; 119 return get((int)index,defaultValue); 120 } 121 122 @Override 123 public Object get(int key, Object defaultValue) { 124 if(key>size || key<1) { 125 if(dimension>1) { 126 ArrayImplNS ai = new ArrayImplNS(); 127 ai.dimension=dimension-1; 128 return setEL(key,ai); 129 } 130 return defaultValue; 131 } 132 Object o=arr[(offset+key)-1]; 133 134 if(o==null) { 135 if(dimension>1) { 136 ArrayImplNS ai = new ArrayImplNS(); 137 ai.dimension=dimension-1; 138 return setEL(key,ai); 139 } 140 return defaultValue; 141 } 142 return o; 143 } 144 145 @Override 146 public Object getE(int key) throws ExpressionException { 147 if(key<1) { 148 throw invalidPosition(key); 149 } 150 else if(key>size) { 151 if(dimension>1)return setE(key,new ArrayImplNS(dimension-1)); 152 throw invalidPosition(key); 153 } 154 155 Object o=arr[(offset+key)-1]; 156 157 if(o==null) { 158 if(dimension>1) return setE(key,new ArrayImplNS(dimension-1)); 159 throw invalidPosition(key); 160 } 161 return o; 162 } 163 164 /** 165 * Exception method if key doesn't exist at given position 166 * @param pos 167 * @return exception 168 */ 169 private ExpressionException invalidPosition(int pos) { 170 return new ExpressionException("Element at position ["+pos+"] doesn't exist in array"); 171 } 172 173 @Override 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 @Override 183 public Object setEL(Collection.Key key, Object value) { 184 try { 185 return setEL(Caster.toIntValue(key.getString()), value); 186 } catch (ExpressionException e) { 187 return null; 188 } 189 } 190 191 @Override 192 public Object set(String key, Object value) throws ExpressionException { 193 return setE(Caster.toIntValue(key),value); 194 } 195 196 @Override 197 public Object set(Collection.Key key, Object value) throws ExpressionException { 198 return setE(Caster.toIntValue(key.getString()),value); 199 } 200 201 @Override 202 public Object setEL(int key, Object value) { 203 if(offset+key>arr.length)enlargeCapacity(key); 204 if(key>size)size=key; 205 arr[(offset+key)-1]=checkValueEL(value); 206 return value; 207 } 208 209 /** 210 * set value at defined position 211 * @param key 212 * @param value 213 * @return defined value 214 * @throws ExpressionException 215 */ 216 public Object setE(int key, Object value) throws ExpressionException { 217 if(offset+key>arr.length)enlargeCapacity(key); 218 if(key>size)size=key; 219 arr[(offset+key)-1]=checkValue(value); 220 return value; 221 } 222 223 /** 224 * !!! all methods that use this method must be sync 225 * enlarge the inner array to given size 226 * @param key min size of the array 227 */ 228 private void enlargeCapacity(int key) { 229 int diff=offCount-offset; 230 231 int newSize=arr.length; 232 if(newSize<1) newSize=1; 233 while(newSize<key+offset+diff) { 234 newSize*=2; 235 } 236 if(newSize>arr.length) { 237 Object[] na=new Object[newSize]; 238 for(int i=offset;i<offset+size;i++) { 239 na[i+diff]=arr[i]; 240 } 241 arr=na; 242 offset+=diff; 243 } 244 } 245 246 /** 247 * !!! all methods that use this method must be sync 248 * enlarge the offset if 0 249 */ 250 private void enlargeOffset() { 251 if(offset==0) { 252 offCount=offCount==0?1:offCount*2; 253 offset=offCount; 254 Object[] narr=new Object[arr.length+offset]; 255 for(int i=0;i<size;i++) { 256 narr[offset+i]=arr[i]; 257 } 258 arr=narr; 259 } 260 } 261 262 /** 263 * !!! all methods that use this method must be sync 264 * check if value is valid to insert to array (to a multidimesnional array only array with one smaller dimension can be inserted) 265 * @param value value to check 266 * @return checked value 267 * @throws ExpressionException 268 */ 269 private Object checkValue(Object value) throws ExpressionException { 270 // is a 1 > Array 271 if(dimension>1) { 272 if(value instanceof Array) { 273 if(((Array)value).getDimension()!=dimension-1) 274 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)); 275 } 276 else 277 throw new ExpressionException("You can only Append an Array with "+(dimension-1)+" Dimension","now is a object of type "+Caster.toClassName(value)); 278 } 279 return value; 280 } 281 282 /** 283 * !!! all methods that use this method must be sync 284 * 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; 285 * @param value value to check 286 * @return checked value 287 */ 288 private Object checkValueEL(Object value) { 289 // is a 1 > Array 290 if(dimension>1) { 291 if(value instanceof Array) { 292 if(((Array)value).getDimension()!=dimension-1) 293 return null; 294 } 295 else 296 return null; 297 } 298 return value; 299 } 300 301 @Override 302 public int size() { 303 return size; 304 } 305 306 @Override 307 public Collection.Key[] keys() { 308 309 ArrayList<Collection.Key> lst=new ArrayList<Collection.Key>(); 310 int count=0; 311 for(int i=offset;i<offset+size;i++) { 312 Object o=arr[i]; 313 count++; 314 if(o!=null) lst.add(KeyImpl.getInstance(count+"")); 315 } 316 return lst.toArray(new Collection.Key[lst.size()]); 317 } 318 319 @Override 320 public int[] intKeys() { 321 ArrayList<Integer> lst=new ArrayList<Integer>(); 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(Integer.valueOf(count)); 327 } 328 329 int[] ints=new int[lst.size()]; 330 331 for(int i=0;i<ints.length;i++){ 332 ints[i]=lst.get(i).intValue(); 333 } 334 return ints; 335 } 336 337 @Override 338 public Object remove(Collection.Key key) throws ExpressionException { 339 return removeE(Caster.toIntValue(key.getString())); 340 } 341 342 public Object removeEL(Collection.Key key) { 343 return removeEL(Caster.toIntValue(key.getString(),-1)); 344 } 345 346 @Override 347 public Object removeE(int key) throws ExpressionException { 348 if(key>size || key<1) throw invalidPosition(key); 349 Object obj=get(key,null); 350 for(int i=(offset+key)-1;i<(offset+size)-1;i++) { 351 arr[i]=arr[i+1]; 352 } 353 size--; 354 return obj; 355 } 356 357 @Override 358 public Object removeEL(int key) { 359 if(key>size || key<1) return null; 360 Object obj=get(key,null); 361 362 for(int i=(offset+key)-1;i<(offset+size)-1;i++) { 363 arr[i]=arr[i+1]; 364 } 365 size--; 366 return obj; 367 } 368 369 @Override 370 public void clear() { 371 if(size()>0) { 372 arr=new Object[cap]; 373 size=0; 374 offCount=1; 375 offset=0; 376 } 377 } 378 379 @Override 380 public boolean insert(int key, Object value) throws ExpressionException { 381 if(key<1 || key>size+1) { 382 throw new ExpressionException("can't insert value to array at position "+key+", array goes from 1 to "+size()); 383 } 384 // Left 385 if((size/2)>=key) { 386 enlargeOffset(); 387 for(int i=offset;i<(offset+key)-1;i++) { 388 arr[i-1]=arr[i]; 389 } 390 offset--; 391 arr[(offset+key)-1]=checkValue(value); 392 size++; 393 } 394 // Right 395 else { 396 if((offset+key)>arr.length || size+offset>=arr.length)enlargeCapacity(arr.length+2); 397 for(int i=size+offset;i>=key+offset;i--) { 398 arr[i]=arr[i-1]; 399 } 400 arr[(offset+key)-1]=checkValue(value); 401 size++; 402 403 } 404 return true; 405 } 406 407 @Override 408 public Object append(Object o) throws ExpressionException { 409 if(offset+size+1>arr.length)enlargeCapacity(size+1); 410 arr[offset+size]=checkValue(o); 411 size++; 412 return o; 413 } 414 415 /** 416 * append a new value to the end of the array 417 * @param o value to insert 418 * @return inserted value 419 */ 420 public Object appendEL(Object o) { 421 422 if(offset+size+1>arr.length)enlargeCapacity(size+1); 423 arr[offset+size]=o; 424 size++; 425 return o; 426 } 427 428 /** 429 * adds a value and return this array 430 * @param o 431 * @return this Array 432 */ 433 public boolean add(Object o) { 434 if(offset+size+1>arr.length)enlargeCapacity(size+1); 435 arr[offset+size]=o; 436 size++; 437 return true; 438 } 439 440 /** 441 * append a new value to the end of the array 442 * @param str value to insert 443 * @return inserted value 444 */ 445 public String _append(String str) { 446 if(offset+size+1>arr.length)enlargeCapacity(size+1); 447 arr[offset+size]=str; 448 size++; 449 return str; 450 } 451 452 /** 453 * add a new value to the begin of the array 454 * @param o value to insert 455 * @return inserted value 456 * @throws ExpressionException 457 */ 458 public Object prepend(Object o) throws ExpressionException { 459 insert(1,o); 460 return o; 461 } 462 463 /** 464 * resize array to defined size 465 * @param to new minimum size of the array 466 */ 467 public void resize(int to) { 468 if(to>size) { 469 enlargeCapacity(to); 470 size=to; 471 } 472 } 473 474 @Override 475 public void sort(String sortType, String sortOrder) throws PageException { 476 sort(ArrayUtil.toComparator(null, sortType, sortOrder, false)); 477 } 478 479 @Override 480 public synchronized void sort(Comparator comp) { 481 if(getDimension()>1) 482 throw new PageRuntimeException("only 1 dimensional arrays can be sorted"); 483 Arrays.sort(arr,offset,offset+size,comp); 484 } 485 486 /** 487 * @return return arra as native (Java) Object Array 488 */ 489 public Object[] toArray() { 490 Object[] rtn=new Object[size]; 491 int count=0; 492 for(int i=offset;i<offset+size;i++) { 493 rtn[count++]=arr[i]; 494 } 495 496 return rtn; 497 } 498 499 /** 500 * @return return array as ArrayList 501 */ 502 public ArrayList toArrayList() { 503 ArrayList al=new ArrayList(); 504 for(int i=offset;i<offset+size;i++) { 505 al.add(arr[i]); 506 } 507 return al; 508 } 509 510 @Override 511 public DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp) { 512 DumpTable table = new DumpTable("array","#ff9900","#ffcc00","#000000"); 513 table.setTitle("Array"); 514 515 int length=size(); 516 maxlevel--; 517 for(int i=1;i<=length;i++) { 518 Object o=null; 519 try { 520 o = getE(i); 521 } 522 catch (Exception e) {} 523 table.appendRow(1,new SimpleDumpData(i),DumpUtil.toDumpData(o, pageContext,maxlevel,dp)); 524 } 525 return table; 526 } 527 528 /** 529 * return code print of the array as plain text 530 * @return content as string 531 */ 532 public String toPlain() { 533 534 StringBuffer sb=new StringBuffer(); 535 int length=size(); 536 for(int i=1;i<=length;i++) { 537 sb.append(i); 538 sb.append(": "); 539 sb.append(get(i-1,null)); 540 sb.append("\n"); 541 } 542 return sb.toString(); 543 } 544 545 @Override 546 public Object clone() { 547 return duplicate(true); 548 } 549 550 @Override 551 public Collection duplicate(boolean deepCopy) { 552 ArrayImplNS arr=new ArrayImplNS(); 553 arr.dimension=dimension; 554 Iterator<Entry<Key, Object>> it = entryIterator(); 555 boolean inside=deepCopy?ThreadLocalDuplication.set(this, arr):true; 556 Entry<Key, Object> e; 557 try { 558 while(it.hasNext()){ 559 e = it.next(); 560 if(deepCopy)arr.set(e.getKey(),Duplicator.duplicate(e.getValue(),deepCopy)); 561 else arr.set(e.getKey(),e.getValue()); 562 } 563 } 564 catch (ExpressionException ee) {} 565 finally{ 566 if(!inside)ThreadLocalDuplication.reset(); 567 } 568 569 return arr; 570 } 571 572 @Override 573 public Iterator<Collection.Key> keyIterator() { 574 return new KeyIterator(keys()); 575 } 576 577 @Override 578 public Iterator<String> keysAsStringIterator() { 579 return new StringIterator(keys()); 580 } 581 582 @Override 583 public Iterator<Entry<Key, Object>> entryIterator() { 584 return new EntryIterator(this, keys()); 585 } 586 587 public Iterator iterator() { 588 ArrayList lst=new ArrayList(); 589 //int count=0; 590 for(int i=offset;i<offset+size;i++) { 591 Object o=arr[i]; 592 //count++; 593 if(o!=null) lst.add(o); 594 } 595 return lst.iterator(); 596 } 597 598 599 @Override 600 public String toString() { 601 return LazyConverter.serialize(this); 602 } 603 604 @Override 605 public long sizeOf() { 606 return SizeOf.size(arr) 607 +SizeOf.size(dimension) 608 +SizeOf.size(cap) 609 +SizeOf.size(size) 610 +SizeOf.size(offset) 611 +SizeOf.size(offCount) 612 +SizeOf.REF_SIZE; 613 } 614}