1 /* 2 * Copyright (c) 2003-2008 by Cosylab d. d. 3 * 4 * This file is part of Java-Common. 5 * 6 * Java-Common is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * 11 * Java-Common is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with Java-Common. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package com.cosylab.util; 21 22 import java.io.Serializable; 23 import java.lang.reflect.Array; 24 25 import java.util.Collection; 26 import java.util.Iterator; 27 import java.util.List; 28 import java.util.ListIterator; 29 import java.util.NoSuchElementException; 30 31 32 /** 33 * A simple resizable threadsafe array. Offers minimal capability and overhead 34 * for all operations except <code>toArray(Class)</code>. The main cost of the 35 * array maintainance is in the addition and removal of the elements. The 36 * potential traversals are very fast, because they can occur outside the 37 * synchronized blocks of code through normal arrays. The arrays returned by 38 * the <code>toArray</code> methods are of the correct runtime type. If, for 39 * example, the type of the list was specified to be <code>X</code>, the 40 * following cast is permitted: 41 * 42 * <p> 43 * <code>X[] array = (X[])list.toArray()</code> 44 * </p> 45 * Only one type cast is thus needed to obtain the final array for traversals. 46 * 47 * @author <a href="mailto:gasper.tkacik@cosylab.com">Gasper Tkacik</a> 48 * @author <a href="mailto:gasper.pajor@cosylab.com">Gasper Pajor</a> 49 * @version $id$ 50 */ 51 public class ObjectList implements List, Serializable 52 { 53 private static final long serialVersionUID = 2766482196760164880L; 54 private Class type = null; 55 private Object[] array = null; 56 57 private class ArrayIterator implements ListIterator 58 { 59 private Object[] array; 60 private int index = 0; 61 62 /** 63 * Creates a new ArrayIterator object. 64 * 65 * @param o the array for which the iterator is created 66 * @param index DOCUMENT ME! 67 * 68 * @throws NullPointerException when object is null 69 */ 70 public ArrayIterator(Object[] o, int index) 71 { 72 if (o == null) { 73 throw new NullPointerException(); 74 } 75 76 array = o; 77 this.index = index; 78 } 79 80 /* (non-Javadoc) 81 * @see java.util.Iterator#hasNext() 82 */ 83 public boolean hasNext() 84 { 85 return index < array.length; 86 } 87 88 /* (non-Javadoc) 89 * @see java.util.Iterator#next() 90 */ 91 public Object next() 92 { 93 if (index == array.length) { 94 throw new NoSuchElementException(); 95 } 96 97 return array[index++]; 98 } 99 100 /** 101 * DOCUMENT ME! 102 * 103 * @throws UnsupportedOperationException when invoked 104 * 105 * @see java.util.Iterator#remove() 106 * @deprecated uniplemented 107 */ 108 public void remove() 109 { 110 throw new UnsupportedOperationException(); 111 } 112 113 /* (non-Javadoc) 114 * @see java.util.ListIterator#nextIndex() 115 */ 116 public int nextIndex() 117 { 118 if (index == array.length) { 119 throw new NoSuchElementException(); 120 } 121 122 return index; 123 } 124 125 /* (non-Javadoc) 126 * @see java.util.ListIterator#previousIndex() 127 */ 128 public int previousIndex() 129 { 130 if (index == 0) { 131 throw new NoSuchElementException(); 132 } 133 134 return index - 1; 135 } 136 137 /* (non-Javadoc) 138 * @see java.util.ListIterator#hasPrevious() 139 */ 140 public boolean hasPrevious() 141 { 142 return index > 0; 143 } 144 145 /* (non-Javadoc) 146 * @see java.util.ListIterator#previous() 147 */ 148 public Object previous() 149 { 150 if (index == 0) { 151 throw new NoSuchElementException(); 152 } 153 154 index--; 155 156 return array[index]; 157 } 158 159 /* (non-Javadoc) 160 * @see java.util.ListIterator#add(java.lang.Object) 161 */ 162 public void add(Object o) 163 { 164 throw new UnsupportedOperationException(); 165 } 166 167 /** 168 * DOCUMENT ME! 169 * 170 * @param o DOCUMENT ME! 171 * 172 * @throws UnsupportedOperationException when invoked 173 * 174 * @see java.util.ListIterator#set(java.lang.Object) 175 * @deprecated uniplemented 176 */ 177 public void set(Object o) 178 { 179 throw new UnsupportedOperationException(); 180 } 181 } 182 183 /** 184 * Creates a new ObjectList object. 185 * 186 * @param type the type of objects this list will hold 187 * 188 * @throws NullPointerException if the type is <code>null</code> 189 */ 190 public ObjectList(Class type) 191 { 192 super(); 193 194 if (type == null) { 195 throw new NullPointerException("type"); 196 } 197 198 this.type = type; 199 array = (Object[])Array.newInstance(type, 0); 200 } 201 202 /** 203 * Adds an object to this list. This list can contain multiple entries of 204 * the same object. 205 * 206 * @param o object to add to the collection 207 * @param index where the object should be inserted 208 * 209 * @deprecated replaced by {@link #add(int, Object)} 210 */ 211 public void add(Object o, int index) 212 { 213 add(index, o); 214 } 215 216 /** 217 * Adds an object to this list. This list can contain multiple entries of 218 * the same object. 219 * 220 * @param o object to add to the collection 221 * 222 * @return true 223 * 224 * @see java.util.List#add(Object) 225 */ 226 public boolean add(Object o) 227 { 228 add(o, array.length); 229 230 return true; 231 } 232 233 /** 234 * Removes an element from the list. If there is more than one reference to 235 * the same object in the array, this method will remove only one 236 * reference. 237 * 238 * @param o element to be removed from this list, if present. 239 * 240 * @return <tt>true</tt> if this list contained the specified element. 241 * 242 * @see java.util.List#remove(java.lang.Object) 243 */ 244 public boolean remove(Object o) 245 { 246 if (o == null) { 247 return false; 248 } 249 250 if (!type.isAssignableFrom(o.getClass())) { 251 return false; 252 } 253 254 synchronized (this) { 255 int index = -1; 256 257 for (int i = 0; i < array.length; i++) { 258 if (array[i] == o) { 259 index = i; 260 261 break; 262 } 263 } 264 265 if (index == -1) { 266 return false; 267 } 268 269 int length = array.length; 270 Object els = Array.newInstance(type, length - 1); 271 System.arraycopy(array, 0, els, 0, index); 272 System.arraycopy(array, index + 1, els, index, length - index - 1); 273 array = (Object[])els; 274 } 275 276 return true; 277 } 278 279 /** 280 * Returns the size of the list. 281 * 282 * @return int list size 283 */ 284 public int size() 285 { 286 return array.length; 287 } 288 289 /** 290 * Returns all objects in the list which are subtypes of the 291 * <code>class</code> parameter. This necessitates the construction of a 292 * new array which must occur in a synchronized block of code. This method 293 * therefore takes some time to execute. 294 * 295 * @param type the type of the objects to look for 296 * 297 * @return Object[] an array containing only those elements of the list, 298 * which are a subtype of <code>type</code> 299 * 300 * @throws NullPointerException when type is <code>null</code> 301 * @exception IllegalArgumentException if <code>type</code> is not a 302 * subtype of the type specified as the constructor parameter 303 */ 304 public Object[] toArray(Class type) 305 { 306 if (type == null) { 307 throw new NullPointerException("type"); 308 } 309 310 if (!this.type.isAssignableFrom(type)) { 311 throw new IllegalArgumentException("Type '" + type 312 + "' is not a subtype of '" + this.type + "'. Cannot cast."); 313 } 314 315 synchronized (this) { 316 int count = 0; 317 318 for (int i = 0; i < array.length; i++) { 319 if (type.isAssignableFrom(array[i].getClass())) { 320 count++; 321 } 322 } 323 324 Object retVal = Array.newInstance(type, count); 325 count = 0; 326 327 for (int i = 0; i < array.length; i++) { 328 if (type.isAssignableFrom(array[i].getClass())) { 329 Array.set(retVal, count, array[i]); 330 count++; 331 } 332 } 333 334 return (Object[])retVal; 335 } 336 } 337 338 /** 339 * Returns the elements of this list. <b>Do not modify this array in any 340 * way.</b>. List membership must only be affected through 341 * <code>add</code> and <code>remove()</code> methods of this class. 342 * 343 * @return an array of stored objects, of the run time type specified by 344 * the constructor argument 345 */ 346 public Object[] toArray() 347 { 348 return array; 349 } 350 351 /** 352 * Checks if the list contains a given element. Comparison with <code>equals</code>. 353 * Uses linear search. 354 * 355 * @param o the object to look for 356 * 357 * @return <code>true</code> iff the list contains object <code>o</code>. 358 */ 359 public boolean contains(Object o) 360 { 361 if (array == null || array.length == 0) { 362 return false; 363 } 364 365 for (int i = 0; i < array.length; i++) { 366 if (array[i]==null ? o==null : array[i].equals(o)) { 367 return true; 368 } 369 } 370 371 return false; 372 } 373 374 /** 375 * Returns the object at the specified index 376 * 377 * @param index the index of the requested object 378 * 379 * @return the object at the specified index. 380 */ 381 public Object get(int index) 382 { 383 return array[index]; 384 } 385 386 /** 387 * Searches for the first occurence of the given argument, testing for 388 * equality using the <tt>equals</tt> method. 389 * 390 * @param o an object. 391 * 392 * @return the index of the first occurrence of the argument in this list; 393 * returns <tt>-1</tt> if the object is not found. 394 * 395 * @see Object#equals(Object) 396 */ 397 public int indexOf(Object o) 398 { 399 for (int i = 0; i < array.length; i++) { 400 if (o.equals(array[i])) { 401 return i; 402 } 403 } 404 405 return -1; 406 } 407 408 /** 409 * Produces a string rendering of this instance by enumerating all members. 410 * Contract of <code>Object.toString</code>. 411 * 412 * @return String all members 413 */ 414 public String toString() 415 { 416 StringBuffer sb = new StringBuffer(500); 417 sb.append("ObjectList (size "); 418 sb.append(toArray().length); 419 sb.append(") = { "); 420 421 Object[] array = toArray(); 422 423 for (int i = 0; i < array.length; i++) { 424 if (array[i] == null) { 425 continue; 426 } 427 428 sb.append(array[i].toString()); 429 sb.append(' '); 430 } 431 432 sb.append('}'); 433 434 return new String(sb); 435 } 436 437 /** 438 * Empties this list. References to contained elementsw are released. 439 */ 440 public synchronized void clear() 441 { 442 array = (Object[])Array.newInstance(type, 0); 443 } 444 445 /** 446 * Returns iterator which iterates over elements, which was contained in 447 * list at the moment iterator was called. Iteration does not fail, if 448 * elements are added or removed during the iteration. 449 * 450 * @return non-failing iterator with copy of elements 451 */ 452 public Iterator iterator() 453 { 454 return new ArrayIterator(array, 0); 455 } 456 457 /** 458 * Tests if this list has no elements. 459 * 460 * @return <tt>true</tt> if this list has no elements; <tt>false</tt> 461 * otherwise. 462 */ 463 public boolean isEmpty() 464 { 465 return array.length == 0; 466 } 467 468 /** 469 * Removes the element at the specified position in this list (optional 470 * operation). Shifts any subsequent elements to the left (subtracts one 471 * from their indices). Returns the element that was removed from the 472 * list. 473 * 474 * @param index the index of the element to removed. 475 * 476 * @return the element previously at the specified position. 477 * 478 * @throws IndexOutOfBoundsException DOCUMENT ME! 479 */ 480 public Object remove(int index) 481 { 482 if (index < 0 || index > array.length) { 483 throw new IndexOutOfBoundsException("Index: " + index); 484 } 485 486 Object retObj = array[index]; 487 488 synchronized (this) { 489 int length = array.length; 490 Object els = Array.newInstance(type, length - 1); 491 System.arraycopy(array, 0, els, 0, index); 492 System.arraycopy(array, index + 1, els, index, length - index - 1); 493 array = (Object[])els; 494 } 495 496 return retObj; 497 } 498 499 /** 500 * Adds an object to this list. This list can contain multiple entries of 501 * the same object. 502 * 503 * @param index where the object should be inserted 504 * @param o object to add to the collection 505 * 506 * @throws NullPointerException 507 * @exception ClassCastException if the RTT of o is not assignable to the 508 * type specified in the constructor 509 * 510 * @see java.util.List#add(int, Object) 511 */ 512 public void add(int index, Object o) 513 { 514 if (o == null) { 515 throw new NullPointerException("o"); 516 } 517 518 if (type.isAssignableFrom(o.getClass()) == false) { 519 throw new ClassCastException("Object '" + o + "' !instanceof '" 520 + type + "'."); 521 } 522 523 synchronized (this) { 524 int length = array.length; 525 Object els = Array.newInstance(type, length + 1); 526 index = index > length ? length : index; 527 528 if (index == length) { 529 System.arraycopy(array, 0, els, 0, length); 530 Array.set(els, length, o); 531 } else { 532 System.arraycopy(array, 0, els, 0, index); 533 Array.set(els, index, o); 534 System.arraycopy(array, index, els, index + 1, length - index); 535 } 536 537 array = (Object[])els; 538 } 539 } 540 541 /** 542 * Returns the index of the last occurrence of the specified object in this 543 * list. 544 * 545 * @param elem the desired element. 546 * 547 * @return the index of the last occurrence of the specified object in this 548 * list; returns -1 if the object is not found. 549 * 550 * @see java.util.List#lastIndexOf(java.lang.Object) 551 */ 552 public int lastIndexOf(Object elem) 553 { 554 if (elem == null) { 555 for (int i = array.length - 1; i >= 0; i--) { 556 if (array[i] == null) { 557 return i; 558 } 559 } 560 } else { 561 for (int i = array.length - 1; i >= 0; i--) { 562 if (elem.equals(array[i])) { 563 return i; 564 } 565 } 566 } 567 568 return -1; 569 } 570 571 /** 572 * Inserts all of the elements in the specified Collection into this list, 573 * starting at the specified position. Shifts the element currently at 574 * that position (if any) and any subsequent elements to the right 575 * (increases their indices). The new elements will appear in the list in 576 * the order that they are returned by the specified Collection's 577 * iterator. 578 * 579 * @param index index at which to insert first element from the specified 580 * collection. 581 * @param c elements to be inserted into this list. 582 * 583 * @return <tt>true</tt> if this list changed as a result of the call. 584 * 585 * @see java.util.List#addAll(int, java.util.Collection) 586 */ 587 public boolean addAll(int index, Collection c) 588 { 589 Object[] a = c.toArray(); 590 int numNew = a.length; 591 592 synchronized (this) { 593 int length = array.length; 594 Object nArray = Array.newInstance(type, length + a.length); 595 596 //if (index == length) { 597 // System.arraycopy(array, 0, nArray, 0, length); 598 // Array.set(nArray, length, o); 599 //} else { 600 System.arraycopy(array, 0, nArray, 0, index); 601 System.arraycopy(a, 0, nArray, index, a.length); 602 603 //Array.set(els, index, o); 604 System.arraycopy(array, index, nArray, index + a.length, 605 length - index); 606 607 //} 608 array = (Object[])nArray; 609 } 610 611 return numNew != 0; 612 } 613 614 /** 615 * Appends all of the elements in the specified Collection to the end of 616 * this list, in the order that they are returned by the specified 617 * Collection's Iterator. 618 * 619 * @param c the elements to be inserted into this list. 620 * 621 * @return <tt>true</tt> if this list changed as a result of the call. 622 * 623 * @see java.util.List#addAll(java.util.Collection) 624 */ 625 public boolean addAll(Collection c) 626 { 627 Object[] a = c.toArray(); 628 int numNew = a.length; 629 630 for (int i = 0; i < a.length; i++) { 631 add(a[i]); 632 } 633 634 return numNew != 0; 635 } 636 637 /** 638 * Returns <tt>true</tt> if this collection contains all of the elements in 639 * the specified collection. 640 * 641 * <p> 642 * This implementation iterates over the specified collection, checking 643 * each element returned by the iterator in turn to see if it's contained 644 * in this collection. If all elements are so contained <tt>true</tt> is 645 * returned, otherwise <tt>false</tt>. 646 * </p> 647 * 648 * @param c collection to be checked for containment in this collection. 649 * 650 * @return <tt>true</tt> if this collection contains all of the elements in 651 * the specified collection. 652 * 653 * @see #contains(Object) 654 * @see java.util.List#containsAll(java.util.Collection) 655 */ 656 public boolean containsAll(Collection c) 657 { 658 Iterator e = c.iterator(); 659 660 while (e.hasNext()) { 661 if (!contains(e.next())) { 662 return false; 663 } 664 } 665 666 return true; 667 } 668 669 /** 670 * Removes the elements that are contained in the collection. 671 * 672 * @param c the collection of elements to be removed 673 * 674 * @return <code>true</code> if at least one element is removed 675 * 676 * @see java.util.List#removeAll(java.util.Collection) 677 */ 678 public boolean removeAll(Collection c) 679 { 680 boolean modified = false; 681 682 for (int i = 0; i < array.length; i++) { 683 if (c.contains(array[i])) { 684 remove(i); 685 i = i - 1; 686 modified = true; 687 } 688 } 689 690 return modified; 691 } 692 693 /** 694 * Removes all elements but those contained in the collection. 695 * 696 * @param c the collection of elements to keep 697 * 698 * @return <code>true</code> if at lest one element is removed 699 * 700 * @see java.util.List#retainAll(java.util.Collection) 701 */ 702 public boolean retainAll(Collection c) 703 { 704 boolean modified = false; 705 706 for (int i = 0; i < array.length; i++) { 707 if (!c.contains(array[i])) { 708 remove(i); 709 i = i - 1; 710 modified = true; 711 } 712 } 713 714 return modified; 715 } 716 717 /** 718 * DOCUMENT ME! 719 * 720 * @param fromIndex DOCUMENT ME! 721 * @param toIndex DOCUMENT ME! 722 * 723 * @return DOCUMENT ME! 724 * 725 * @throws UnsupportedOperationException when invoked 726 * 727 * @see java.util.List#subList(int, int) 728 * @deprecated This method is not implemented. 729 */ 730 public List subList(int fromIndex, int toIndex) 731 { 732 /* 733 * to implement this method, a new extended class should be made from object list, 734 * where all methods that change the elements would need to reflect the change onto 735 * the referenced (this) object list. 736 */ 737 /*if (fromIndex < 0 || toIndex > array.length || fromIndex>toIndex) throw new IllegalArgumentException(); 738 739 ObjectList ol = new ObjectList(type); 740 for (int i = fromIndex; i < toIndex; i++) { 741 ol.add(i, array[i]); 742 743 } 744 return ol; 745 */ 746 throw new UnsupportedOperationException(); 747 } 748 749 /** 750 * Returns iterator which iterates over elements, which was contained in 751 * list at the moment iterator was called. Iteration does not fail, if 752 * elements are added or removed during the iteration. 753 * 754 * @param index index of the first element to be returned from the list 755 * iterator (by a call to the <tt>next</tt> method). 756 * 757 * @return a list iterator of the elements in this list, starting at the 758 * specified position in the list. 759 * 760 * @throws IndexOutOfBoundsException if the specified index is out of range 761 * (<tt>index < 0 || index > size()</tt>). 762 * 763 * @see java.util.List#listIterator(int) 764 */ 765 public ListIterator listIterator(final int index) 766 { 767 if (index < 0 || index > array.length) { 768 throw new IndexOutOfBoundsException("Index: " + index); 769 } 770 771 return new ArrayIterator(array, index); 772 } 773 774 /** 775 * Sets the element at specified index to specified object. 776 * 777 * @param index the index of object to set 778 * @param element the object to be set to the index 779 * 780 * @return the element previously at the specified position 781 * 782 * @throws NullPointerException DOCUMENT ME! 783 * @throws ClassCastException DOCUMENT ME! 784 * 785 * @see java.util.List#set(int, Object) 786 */ 787 public Object set(int index, Object element) 788 { 789 if (element == null) { 790 throw new NullPointerException("element"); 791 } 792 793 if (type.isAssignableFrom(element.getClass()) == false) { 794 throw new ClassCastException("Object '" + element 795 + "' !instanceof '" + type + "'."); 796 } 797 798 Object retObj = null; 799 800 synchronized (this) { 801 retObj = array[index]; 802 array[index] = element; 803 } 804 805 return retObj; 806 } 807 808 /** 809 * Returns an array containing all of the elements in this list. 810 * 811 * @param a the array into which the elements of this list are to be 812 * stored, if it is big enough; otherwise, a new array is 813 * allocated for this purpose. 814 * 815 * @return an array containing the elements of this list. 816 * 817 * @throws NullPointerException if the specified array is <tt>null</tt>. 818 * @throws ArrayStoreException if the runtime type of the specified array 819 * is not a supertype of the runtime type of every element in this 820 * list. 821 * 822 * @see java.util.List#toArray(java.lang.Object[]) 823 */ 824 public Object[] toArray(Object[] a) 825 { 826 if (a == null) { 827 throw new NullPointerException("o"); 828 } 829 830 if (type.isAssignableFrom(a.getClass().getComponentType()) == false) { 831 throw new ArrayStoreException("Object '" + a + "' !instanceof '" 832 + type + "'."); 833 } 834 835 return (Object[])toArray(); 836 } 837 838 /** 839 * Returns a list iterator of the elements in this list, iterator will 840 * iterate over the elements, which were in list at the moment iterator 841 * was created thus iteration will not fail if list is modified during 842 * iteration. 843 * 844 * @return a list iterator of the elements in this list 845 * 846 * @see java.util.List#listIterator(int) 847 */ 848 public ListIterator listIterator() 849 { 850 return new ArrayIterator(array, 0); 851 } 852 } 853 854 /* __oOo__ */