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__ */