Source code: org/jdom/ContentList.java
1 /*--
2
3 $Id: ContentList.java,v 1.39 2004/02/28 03:30:27 jhunter Exp $
4
5 Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin.
6 All rights reserved.
7
8 Redistribution and use in source and binary forms, with or without
9 modification, are permitted provided that the following conditions
10 are met:
11
12 1. Redistributions of source code must retain the above copyright
13 notice, this list of conditions, and the following disclaimer.
14
15 2. Redistributions in binary form must reproduce the above copyright
16 notice, this list of conditions, and the disclaimer that follows
17 these conditions in the documentation and/or other materials
18 provided with the distribution.
19
20 3. The name "JDOM" must not be used to endorse or promote products
21 derived from this software without prior written permission. For
22 written permission, please contact <request_AT_jdom_DOT_org>.
23
24 4. Products derived from this software may not be called "JDOM", nor
25 may "JDOM" appear in their name, without prior written permission
26 from the JDOM Project Management <request_AT_jdom_DOT_org).
27
28 In addition, we request (but do not require) that you include in the
29 end-user documentation provided with the redistribution and/or in the
30 software itself an acknowledgement equivalent to the following:
31 "This product includes software developed by the
32 JDOM Project (http://www.jdom.org/)."
33 Alternatively, the acknowledgment may be graphical using the logos
34 available at http://www.jdom.org/images/logos.
35
36 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39 DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
40 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43 USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 SUCH DAMAGE.
48
49 This software consists of voluntary contributions made by many
50 individuals on behalf of the JDOM Project and was originally
51 created by Jason Hunter <jhunter_AT_jdom_DOT_org> and
52 Brett McLaughlin <brett_AT_jdom_DOT_org>. For more information
53 on the JDOM Project, please see <http://www.jdom.org/>.
54
55 */
56
57 package org.jdom;
58
59 import java.util.*;
60
61 import org.jdom.filter.*;
62
63 /**
64 * A non-public list implementation holding only legal JDOM content, including
65 * content for Document or Element nodes. Users see this class as a simple List
66 * implementation.
67 *
68 * @see CDATA
69 * @see Comment
70 * @see Element
71 * @see EntityRef
72 * @see ProcessingInstruction
73 * @see Text
74 *
75 * @version $Revision: 1.39 $, $Date: 2004/02/28 03:30:27 $
76 * @author Alex Rosen
77 * @author Philippe Riand
78 * @author Bradley S. Huffman
79 */
80 final class ContentList extends AbstractList implements java.io.Serializable {
81
82 private static final String CVS_ID =
83 "@(#) $RCSfile: ContentList.java,v $ $Revision: 1.39 $ $Date: 2004/02/28 03:30:27 $ $Name: jdom_1_0 $";
84
85 private static final int INITIAL_ARRAY_SIZE = 5;
86
87 /**
88 * Used inner class FilterListIterator to help hasNext and
89 * hasPrevious the next index of our cursor (must be here
90 * for JDK1.1).
91 */
92 private static final int CREATE = 0;
93 private static final int HASPREV = 1;
94 private static final int HASNEXT = 2;
95 private static final int PREV = 3;
96 private static final int NEXT = 4;
97 private static final int ADD = 5;
98 private static final int REMOVE = 6;
99
100 /** Our backing list */
101 // protected ArrayList list;
102 private Content elementData[];
103 private int size;
104
105 /** Document or Element this list belongs to */
106 private Parent parent;
107
108 /** Force either a Document or Element parent */
109 ContentList(Parent parent) {
110 this.parent = parent;
111 }
112
113 /**
114 * Package internal method to support building from sources that are
115 * 100% trusted.
116 *
117 * @param c content to add without any checks
118 */
119 final void uncheckedAddContent(Content c) {
120 c.parent = parent;
121 ensureCapacity(size + 1);
122 elementData[size++] = c;
123 modCount++;
124 }
125
126 /**
127 * Inserts the specified object at the specified position in this list.
128 * Shifts the object currently at that position (if any) and any
129 * subsequent objects to the right (adds one to their indices).
130 *
131 * @param index The location to set the value to.
132 * @param obj The object to insert into the list.
133 * throws IndexOutOfBoundsException if index < 0 || index > size()
134 */
135 public void add(int index, Object obj) {
136 if (obj == null) {
137 throw new IllegalAddException("Cannot add null object");
138 }
139 if ((obj instanceof Content)) {
140 add(index, (Content) obj);
141 } else {
142 throw new IllegalAddException("Class " +
143 obj.getClass().getName() +
144 " is of unrecognized type and cannot be added");
145 }
146 }
147
148 /**
149 * @see org.jdom.ContentList#add(int, org.jdom.Content)
150 */
151 private void documentCanContain(int index, Content child) throws IllegalAddException {
152 if (child instanceof Element) {
153 if (indexOfFirstElement() >= 0) {
154 throw new IllegalAddException(
155 "Cannot add a second root element, only one is allowed");
156 }
157 if (indexOfDocType() > index) {
158 throw new IllegalAddException(
159 "A root element cannot be added before the DocType");
160 }
161 }
162 if (child instanceof DocType) {
163 if (indexOfDocType() >= 0) {
164 throw new IllegalAddException(
165 "Cannot add a second doctype, only one is allowed");
166 }
167 int firstElt = indexOfFirstElement();
168 if (firstElt != -1 && firstElt < index) {
169 throw new IllegalAddException(
170 "A DocType cannot be added after the root element");
171 }
172 }
173 if (child instanceof CDATA) {
174 throw new IllegalAddException("A CDATA is not allowed at the document root");
175 }
176
177 if (child instanceof Text) {
178 throw new IllegalAddException("A Text is not allowed at the document root");
179 }
180
181 if (child instanceof EntityRef) {
182 throw new IllegalAddException("An EntityRef is not allowed at the document root");
183 }
184 }
185
186 private static void elementCanContain(int index, Content child) throws IllegalAddException {
187 if (child instanceof DocType) {
188 throw new IllegalAddException(
189 "A DocType is not allowed except at the document level");
190 }
191 }
192
193 /**
194 * Check and add the <code>Element</code> to this list at
195 * the given index.
196 *
197 * @param index index where to add <code>Element</code>
198 * @param child <code>Element</code> to add
199 */
200 void add(int index, Content child) {
201 if (child == null) {
202 throw new IllegalAddException("Cannot add null object");
203 }
204 if (parent instanceof Document) {
205 documentCanContain(index, child);
206 }
207 else {
208 elementCanContain(index, child);
209 }
210
211 if (child.getParent() != null) {
212 Parent p = child.getParent();
213 if (p instanceof Document) {
214 throw new IllegalAddException((Element)child,
215 "The Content already has an existing parent document");
216 }
217 else {
218 throw new IllegalAddException(
219 "The Content already has an existing parent \"" +
220 ((Element)p).getQualifiedName() + "\"");
221 }
222 }
223
224 if (child == parent) {
225 throw new IllegalAddException(
226 "The Element cannot be added to itself");
227 }
228
229 // Detect if we have <a><b><c/></b></a> and c.add(a)
230 if ((parent instanceof Element && child instanceof Element) &&
231 ((Element) child).isAncestor((Element)parent)) {
232 throw new IllegalAddException(
233 "The Element cannot be added as a descendent of itself");
234 }
235
236 if (index<0 || index>size) {
237 throw new IndexOutOfBoundsException("Index: " + index +
238 " Size: " + size());
239 }
240
241 child.setParent(parent);
242
243 ensureCapacity(size+1);
244 if( index==size ) {
245 elementData[size++] = child;
246 } else {
247 System.arraycopy(elementData, index, elementData, index + 1, size - index);
248 elementData[index] = child;
249 size++;
250 }
251 modCount++;
252 }
253
254 /**
255 * Add the specified collecton to the end of this list.
256 *
257 * @param collection The collection to add to the list.
258 * @return <code>true</code> if the list was modified as a result of
259 * the add.
260 */
261 public boolean addAll(Collection collection) {
262 return addAll(size(), collection);
263 }
264
265 /**
266 * Inserts the specified collecton at the specified position in this list.
267 * Shifts the object currently at that position (if any) and any
268 * subsequent objects to the right (adds one to their indices).
269 *
270 * @param index The offset to start adding the data in the collection
271 * @param collection The collection to insert into the list.
272 * @return <code>true</code> if the list was modified as a result of
273 * the add.
274 * throws IndexOutOfBoundsException if index < 0 || index > size()
275 */
276 public boolean addAll(int index, Collection collection) {
277 if (index<0 || index>size) {
278 throw new IndexOutOfBoundsException("Index: " + index +
279 " Size: " + size());
280 }
281
282 if ((collection == null) || (collection.size() == 0)) {
283 return false;
284 }
285 ensureCapacity(size() + collection.size());
286
287 int count = 0;
288 try {
289 Iterator i = collection.iterator();
290 while (i.hasNext()) {
291 Object obj = i.next();
292 add(index + count, obj);
293 count++;
294 }
295 }
296 catch (RuntimeException exception) {
297 for (int i = 0; i < count; i++) {
298 remove(index);
299 }
300 throw exception;
301 }
302
303 return true;
304 }
305
306 /**
307 * Clear the current list.
308 */
309 public void clear() {
310 if (elementData != null) {
311 for (int i = 0; i < size; i++) {
312 Content obj = elementData[i];
313 removeParent(obj);
314 }
315 elementData = null;
316 size = 0;
317 }
318 modCount++;
319 }
320
321 /**
322 * Clear the current list and set it to the contents
323 * of the <code>Collection</code>.
324 * object.
325 *
326 * @param collection The collection to use.
327 */
328 void clearAndSet(Collection collection) {
329 Content[] old = elementData;
330 int oldSize = size;
331
332 elementData = null;
333 size = 0;
334
335 if ((collection != null) && (collection.size() != 0)) {
336 ensureCapacity(collection.size());
337 try {
338 addAll(0, collection);
339 }
340 catch (RuntimeException exception) {
341 elementData = old;
342 size = oldSize;
343 throw exception;
344 }
345 }
346
347 if (old != null) {
348 for (int i = 0; i < oldSize; i++) {
349 removeParent(old[i]);
350 }
351 }
352 modCount++;
353 }
354
355 /**
356 * Increases the capacity of this <code>ContentList</code> instance,
357 * if necessary, to ensure that it can hold at least the number of
358 * items specified by the minimum capacity argument.
359 *
360 * @param minCapacity the desired minimum capacity.
361 */
362 void ensureCapacity(int minCapacity) {
363 if( elementData==null ) {
364 elementData = new Content[Math.max(minCapacity, INITIAL_ARRAY_SIZE)];
365 } else {
366 int oldCapacity = elementData.length;
367 if (minCapacity > oldCapacity) {
368 Object oldData[] = elementData;
369 int newCapacity = (oldCapacity * 3)/2 + 1;
370 if (newCapacity < minCapacity)
371 newCapacity = minCapacity;
372 elementData = new Content[newCapacity];
373 System.arraycopy(oldData, 0, elementData, 0, size);
374 }
375 }
376 }
377
378 /**
379 * Return the object at the specified offset.
380 *
381 * @param index The offset of the object.
382 * @return The Object which was returned.
383 */
384 public Object get(int index) {
385 if (index<0 || index>=size) {
386 throw new IndexOutOfBoundsException("Index: " + index +
387 " Size: " + size());
388 }
389 return elementData[index];
390 }
391
392 /**
393 * Return a view of this list based on the given filter.
394 *
395 * @param filter <code>Filter</code> for this view.
396 * @return a list representing the rules of the <code>Filter</code>.
397 */
398 List getView(Filter filter) {
399 return new FilterList(filter);
400 }
401
402 /**
403 * Return the index of the first Element in the list. If the parent
404 * is a <code>Document</code> then the element is the root element.
405 * If the list contains no Elements, it returns -1.
406 *
407 * @return index of first element, or -1 if one doesn't exist
408 */
409 int indexOfFirstElement() {
410 if( elementData!=null ) {
411 for (int i = 0; i < size; i++) {
412 if (elementData[i] instanceof Element) {
413 return i;
414 }
415 }
416 }
417 return -1;
418 }
419
420 /**
421 * Return the index of the DocType element in the list. If the list contains
422 * no DocType, it returns -1.
423 *
424 * @return index of the DocType, or -1 if it doesn't
425 * exist
426 */
427 int indexOfDocType() {
428 if (elementData != null) {
429 for (int i = 0; i < size; i++) {
430 if (elementData[i] instanceof DocType) {
431 return i;
432 }
433 }
434 }
435 return -1;
436 }
437
438 /**
439 * Remove the object at the specified offset.
440 *
441 * @param index The offset of the object.
442 * @return The Object which was removed.
443 */
444 public Object remove(int index) {
445 if (index<0 || index>=size)
446 throw new IndexOutOfBoundsException("Index: " + index +
447 " Size: " + size());
448
449 Content old = elementData[index];
450 removeParent(old);
451 int numMoved = size - index - 1;
452 if (numMoved > 0)
453 System.arraycopy(elementData, index+1, elementData, index,numMoved);
454 elementData[--size] = null; // Let gc do its work
455 modCount++;
456 return old;
457 }
458
459
460 /** Remove the parent of a Object */
461 private static void removeParent(Content c) {
462 c.setParent(null);
463 }
464
465 /**
466 * Set the object at the specified location to the supplied
467 * object.
468 *
469 * @param index The location to set the value to.
470 * @param obj The location to set the value to.
471 * @return The object which was replaced.
472 * throws IndexOutOfBoundsException if index < 0 || index >= size()
473 */
474 public Object set(int index, Object obj) {
475 if (index<0 || index>=size)
476 throw new IndexOutOfBoundsException("Index: " + index +
477 " Size: " + size());
478
479 if ((obj instanceof Element) && (parent instanceof Document)) {
480 int root = indexOfFirstElement();
481 if ((root >= 0) && (root != index)) {
482 throw new IllegalAddException(
483 "Cannot add a second root element, only one is allowed");
484 }
485 }
486
487 if ((obj instanceof DocType) && (parent instanceof Document)) {
488 int docTypeIndex = indexOfDocType();
489 if ((docTypeIndex >= 0) && (docTypeIndex != index)) {
490 throw new IllegalAddException(
491 "Cannot add a second doctype, only one is allowed");
492 }
493 }
494
495 Object old = remove(index);
496 try {
497 add(index, obj);
498 }
499 catch (RuntimeException exception) {
500 add(index, old);
501 throw exception;
502 }
503 return old;
504 }
505
506 /**
507 * Return the number of items in this list
508 *
509 * @return The number of items in this list.
510 */
511 public int size() {
512 return size;
513 }
514
515 /**
516 * Return this list as a <code>String</code>
517 *
518 * @return The number of items in this list.
519 */
520 public String toString() {
521 return super.toString();
522 }
523
524 /** Give access of ContentList.modCount to FilterList */
525 private int getModCount() {
526 return modCount;
527 }
528
529 /* * * * * * * * * * * * * FilterList * * * * * * * * * * * * * * * */
530 /* * * * * * * * * * * * * FilterList * * * * * * * * * * * * * * * */
531
532 /**
533 * <code>FilterList</code> represents legal JDOM content, including content
534 * for <code>Document</code>s or <code>Element</code>s.
535 */
536
537 class FilterList extends AbstractList implements java.io.Serializable {
538
539 /** The Filter */
540 Filter filter;
541
542 /** Current number of items in this view */
543 int count = 0;
544
545 /** Expected modCount in our backing list */
546 int expected = -1;
547
548 // Implementation Note: Directly after size() is called, expected
549 // is sync'd with ContentList.modCount and count provides
550 // the true size of this view. Before the first call to
551 // size() or if the backing list is modified outside this
552 // FilterList, both might contain bogus values and should
553 // not be used without first calling size();
554
555 /**
556 * Create a new instance of the FilterList with the specified Filter.
557 */
558 FilterList(Filter filter) {
559 this.filter = filter;
560 }
561
562 /**
563 * Inserts the specified object at the specified position in this list.
564 * Shifts the object currently at that position (if any) and any
565 * subsequent objects to the right (adds one to their indices).
566 *
567 * @param index The location to set the value to.
568 * @param obj The object to insert into the list.
569 * throws IndexOutOfBoundsException if index < 0 || index > size()
570 */
571 public void add(int index, Object obj) {
572 if (filter.matches(obj)) {
573 int adjusted = getAdjustedIndex(index);
574 ContentList.this.add(adjusted, obj);
575 expected++;
576 count++;
577 }
578 else throw new IllegalAddException("Filter won't allow the " +
579 obj.getClass().getName() +
580 " '" + obj + "' to be added to the list");
581 }
582
583 /**
584 * Return the object at the specified offset.
585 *
586 * @param index The offset of the object.
587 * @return The Object which was returned.
588 */
589 public Object get(int index) {
590 int adjusted = getAdjustedIndex(index);
591 return ContentList.this.get(adjusted);
592 }
593
594 public Iterator iterator() {
595 return new FilterListIterator(filter, 0);
596 }
597
598 public ListIterator listIterator() {
599 return new FilterListIterator(filter, 0);
600 }
601
602 public ListIterator listIterator(int index) {
603 return new FilterListIterator(filter, index);
604 }
605
606 /**
607 * Remove the object at the specified offset.
608 *
609 * @param index The offset of the object.
610 * @return The Object which was removed.
611 */
612 public Object remove(int index) {
613 int adjusted = getAdjustedIndex(index);
614 Object old = ContentList.this.get(adjusted);
615 if (filter.matches(old)) {
616 old = ContentList.this.remove(adjusted);
617 expected++;
618 count--;
619 }
620 else {
621 throw new IllegalAddException("Filter won't allow the " +
622 (old.getClass()).getName() +
623 " '" + old + "' (index " + index +
624 ") to be removed");
625 }
626 return old;
627 }
628
629 /**
630 * Set the object at the specified location to the supplied
631 * object.
632 *
633 * @param index The location to set the value to.
634 * @param obj The location to set the value to.
635 * @return The object which was replaced.
636 * throws IndexOutOfBoundsException if index < 0 || index >= size()
637 */
638 public Object set(int index, Object obj) {
639 Object old = null;
640 if (filter.matches(obj)) {
641 int adjusted = getAdjustedIndex(index);
642 old = ContentList.this.get(adjusted);
643 if (!filter.matches(old)) {
644 throw new IllegalAddException("Filter won't allow the " +
645 (old.getClass()).getName() +
646 " '" + old + "' (index " + index +
647 ") to be removed");
648 }
649 old = ContentList.this.set(adjusted, obj);
650 expected += 2;
651 }
652 else {
653 throw new IllegalAddException("Filter won't allow index " +
654 index + " to be set to " +
655 (obj.getClass()).getName());
656 }
657 return old;
658 }
659
660 /**
661 * Return the number of items in this list
662 *
663 * @return The number of items in this list.
664 */
665 public int size() {
666 // Implementation Note: Directly after size() is called, expected
667 // is sync'd with ContentList.modCount and count provides
668 // the true size of this view. Before the first call to
669 // size() or if the backing list is modified outside this
670 // FilterList, both might contain bogus values and should
671 // not be used without first calling size();
672
673 if (expected == ContentList.this.getModCount()) {
674 return count;
675 }
676
677 count = 0;
678 for (int i = 0; i < ContentList.this.size(); i++) {
679 Object obj = ContentList.this.elementData[i];
680 if (filter.matches(obj)) {
681 count++;
682 }
683 }
684 expected = ContentList.this.getModCount();
685 return count;
686 }
687
688 /**
689 * Return the adjusted index
690 *
691 * @param index Index of in this view.
692 * @return True index in backing list
693 */
694 final private int getAdjustedIndex(int index) {
695 int adjusted = 0;
696 for (int i = 0; i < ContentList.this.size; i++) {
697 Object obj = ContentList.this.elementData[i];
698 if (filter.matches(obj)) {
699 if (index == adjusted) {
700 return i;
701 }
702 adjusted++;
703 }
704 }
705
706 if (index == adjusted) {
707 return ContentList.this.size;
708 }
709
710 return ContentList.this.size + 1;
711 }
712 }
713
714 /* * * * * * * * * * * * * FilterListIterator * * * * * * * * * * * */
715 /* * * * * * * * * * * * * FilterListIterator * * * * * * * * * * * */
716
717 class FilterListIterator implements ListIterator {
718
719 /** The Filter that applies */
720 Filter filter;
721
722 /** The last operation performed */
723 int lastOperation;
724
725 /** Initial start index in backing list */
726 int initialCursor;
727
728 /** Index in backing list of next object */
729 int cursor;
730
731 /** Index in backing list of last object returned */
732 int last;
733
734 /** Expected modCount in our backing list */
735 int expected;
736
737 /**
738 * Default constructor
739 */
740 FilterListIterator(Filter filter, int start) {
741 this.filter = filter;
742 initialCursor = initializeCursor(start);
743 last = -1;
744 expected = ContentList.this.getModCount();
745 lastOperation = CREATE;
746 }
747
748 /**
749 * Returns <code>true</code> if this list iterator has a next element.
750 */
751 public boolean hasNext() {
752 checkConcurrentModification();
753
754 switch(lastOperation) {
755 case CREATE: cursor = initialCursor;
756 break;
757 case PREV: cursor = last;
758 break;
759 case ADD:
760 case NEXT: cursor = moveForward(last + 1);
761 break;
762 case REMOVE: cursor = moveForward(last);
763 break;
764 case HASPREV: cursor = moveForward(cursor + 1);
765 break;
766 case HASNEXT: break;
767 default: throw new IllegalStateException("Unknown operation");
768 }
769
770 if (lastOperation != CREATE) {
771 lastOperation = HASNEXT;
772 }
773
774 return (cursor < ContentList.this.size()) ? true : false;
775 }
776
777 /**
778 * Returns the next element in the list.
779 */
780 public Object next() {
781 checkConcurrentModification();
782
783 if (hasNext()) {
784 last = cursor;
785 }
786 else {
787 last = ContentList.this.size();
788 throw new NoSuchElementException();
789 }
790
791 lastOperation = NEXT;
792 return ContentList.this.get(last);
793 }
794
795 /**
796 * Returns <code>true</code> if this list iterator has more
797 * elements when traversing the list in the reverse direction.
798 */
799 public boolean hasPrevious() {
800 checkConcurrentModification();
801
802 switch(lastOperation) {
803 case CREATE: cursor = initialCursor;
804 int size = ContentList.this.size();
805 if (cursor >= size) {
806 cursor = moveBackward(size - 1);
807 }
808 break;
809 case PREV:
810 case REMOVE: cursor = moveBackward(last - 1);
811 break;
812 case HASNEXT: cursor = moveBackward(cursor - 1);
813 break;
814 case ADD:
815 case NEXT: cursor = last;
816 break;
817 case HASPREV: break;
818 default: throw new IllegalStateException("Unknown operation");
819 }
820
821 if (lastOperation != CREATE) {
822 lastOperation = HASPREV;
823 }
824
825 return (cursor < 0) ? false : true;
826 }
827
828 /**
829 * Returns the previous element in the list.
830 */
831 public Object previous() {
832 checkConcurrentModification();
833
834 if (hasPrevious()) {
835 last = cursor;
836 }
837 else {
838 last = -1;
839 throw new NoSuchElementException();
840 }
841
842 lastOperation = PREV;
843 return ContentList.this.get(last);
844 }
845
846 /**
847 * Returns the index of the element that would be returned by a
848 * subsequent call to <code>next</code>.
849 */
850 public int nextIndex() {
851 checkConcurrentModification();
852 hasNext();
853
854 int count = 0;
855 for (int i = 0; i < ContentList.this.size(); i++) {
856 if (filter.matches(ContentList.this.get(i))) {
857 if (i == cursor) {
858 return count;
859 }
860 count++;
861 }
862 }
863 expected = ContentList.this.getModCount();
864 return count;
865 }
866
867 /**
868 * Returns the index of the element that would be returned by a
869 * subsequent call to <code>previous</code>. (Returns -1 if the
870 * list iterator is at the beginning of the list.)
871 */
872 public int previousIndex() {
873 checkConcurrentModification();
874
875 if (hasPrevious()) {
876 int count = 0;
877 for (int i = 0; i < ContentList.this.size(); i++) {
878 if (filter.matches(ContentList.this.get(i))) {
879 if (i == cursor) {
880 return count;
881 }
882 count++;
883 }
884 }
885 }
886 return -1;
887 }
888
889 /**
890 * Inserts the specified element into the list.
891 */
892 public void add(Object obj) {
893 checkConcurrentModification();
894
895 if (filter.matches(obj)) {
896 last = cursor + 1;
897 ContentList.this.add(last, obj);
898 }
899 else {
900 throw new IllegalAddException("Filter won't allow add of " +
901 (obj.getClass()).getName());
902 }
903 expected = ContentList.this.getModCount();
904 lastOperation = ADD;
905 }
906
907 /**
908 * Removes from the list the last element that was returned by
909 * <code>next</code> or <code>previous</code>.
910 * the last call to <code>next</code> or <code>previous</code>.
911 */
912 public void remove() {
913 checkConcurrentModification();
914
915 if ((last < 0) || (lastOperation == REMOVE)) {
916 throw new IllegalStateException("no preceeding call to " +
917 "prev() or next()");
918 }
919
920 if (lastOperation == ADD) {
921 throw new IllegalStateException("cannot call remove() " +
922 "after add()");
923 }
924
925 Object old = ContentList.this.get(last);
926 if (filter.matches(old)) {
927 ContentList.this.remove(last);
928 }
929 else throw new IllegalAddException("Filter won't allow " +
930 (old.getClass()).getName() +
931 " (index " + last +
932 ") to be removed");
933 expected = ContentList.this.getModCount();
934 lastOperation = REMOVE;
935 }
936
937 /**
938 * Replaces the last element returned by <code>next</code> or
939 * <code>previous</code> with the specified element.
940 */
941 public void set(Object obj) {
942 checkConcurrentModification();
943
944 if ((lastOperation == ADD) || (lastOperation == REMOVE)) {
945 throw new IllegalStateException("cannot call set() after " +
946 "add() or remove()");
947 }
948
949 if (last < 0) {
950 throw new IllegalStateException("no preceeding call to " +
951 "prev() or next()");
952 }
953
954 if (filter.matches(obj)) {
955 Object old = ContentList.this.get(last);
956 if (!filter.matches(old)) {
957 throw new IllegalAddException("Filter won't allow " +
958 (old.getClass()).getName() + " (index " +
959 last + ") to be removed");
960 }
961 ContentList.this.set(last, obj);
962 }
963 else {
964 throw new IllegalAddException("Filter won't allow index " +
965 last + " to be set to " +
966 (obj.getClass()).getName());
967 }
968
969 expected = ContentList.this.getModCount();
970 // Don't set lastOperation
971 }
972
973 /**
974 * Returns index in the backing list by moving forward start
975 * objects that match our filter.
976 */
977 private int initializeCursor(int start) {
978 if (start < 0) {
979 throw new IndexOutOfBoundsException("Index: " + start);
980 }
981
982 int count = 0;
983 for (int i = 0; i < ContentList.this.size(); i++) {
984 Object obj = ContentList.this.get(i);
985 if (filter.matches(obj)) {
986 if (start == count) {
987 return i;
988 }
989 count++;
990 }
991 }
992
993 if (start > count) {
994 throw new IndexOutOfBoundsException("Index: " + start +
995 " Size: " + count);
996 }
997
998 return ContentList.this.size();
999 }
1000
1001 /**
1002 * Returns index in the backing list of the next object matching
1003 * our filter, starting at the given index and moving forwards.
1004 */
1005 private int moveForward(int start) {
1006 if (start < 0) {
1007 start = 0;
1008 }
1009 for (int i = start; i < ContentList.this.size(); i++) {
1010 Object obj = ContentList.this.get(i);
1011 if (filter.matches(obj)) {
1012 return i;
1013 }
1014 }
1015 return ContentList.this.size();
1016 }
1017
1018 /**
1019 * Returns index in the backing list of the next object matching
1020 * our filter, starting at the given index and moving backwards.
1021 */
1022 private int moveBackward(int start) {
1023 if (start >= ContentList.this.size()) {
1024 start = ContentList.this.size() - 1;
1025 }
1026
1027 for (int i = start; i >= 0; --i) {
1028 Object obj = ContentList.this.get(i);
1029 if (filter.matches(obj)) {
1030 return i;
1031 }
1032 }
1033 return -1;
1034 }
1035
1036 /**
1037 * Check if are backing list is being modified by someone else.
1038 */
1039 private void checkConcurrentModification() {
1040 if (expected != ContentList.this.getModCount()) {
1041 throw new ConcurrentModificationException();
1042 }
1043 }
1044 }
1045}