Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

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}