0001: /*--
0002:
0003: $Id: ContentList.java,v 1.1 2005/04/27 09:32:38 wittek Exp $
0004:
0005: Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin.
0006: All rights reserved.
0007:
0008: Redistribution and use in source and binary forms, with or without
0009: modification, are permitted provided that the following conditions
0010: are met:
0011:
0012: 1. Redistributions of source code must retain the above copyright
0013: notice, this list of conditions, and the following disclaimer.
0014:
0015: 2. Redistributions in binary form must reproduce the above copyright
0016: notice, this list of conditions, and the disclaimer that follows
0017: these conditions in the documentation and/or other materials
0018: provided with the distribution.
0019:
0020: 3. The name "JDOM" must not be used to endorse or promote products
0021: derived from this software without prior written permission. For
0022: written permission, please contact <request_AT_jdom_DOT_org>.
0023:
0024: 4. Products derived from this software may not be called "JDOM", nor
0025: may "JDOM" appear in their name, without prior written permission
0026: from the JDOM Project Management <request_AT_jdom_DOT_org).
0027:
0028: In addition, we request (but do not require) that you include in the
0029: end-user documentation provided with the redistribution and/or in the
0030: software itself an acknowledgement equivalent to the following:
0031: "This product includes software developed by the
0032: JDOM Project (http://www.jdom.org/)."
0033: Alternatively, the acknowledgment may be graphical using the logos
0034: available at http://www.jdom.org/images/logos.
0035:
0036: THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0037: WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0038: OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0039: DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
0040: CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0041: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0042: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0043: USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0044: ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0045: OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0046: OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0047: SUCH DAMAGE.
0048:
0049: This software consists of voluntary contributions made by many
0050: individuals on behalf of the JDOM Project and was originally
0051: created by Jason Hunter <jhunter_AT_jdom_DOT_org> and
0052: Brett McLaughlin <brett_AT_jdom_DOT_org>. For more information
0053: on the JDOM Project, please see <http://www.jdom.org/>.
0054:
0055: */
0056:
0057: package org.jdom;
0058:
0059: import java.util.*;
0060:
0061: import org.jdom.filter.*;
0062:
0063: /**
0064: * A non-public list implementation holding only legal JDOM content, including
0065: * content for Document or Element nodes. Users see this class as a simple List
0066: * implementation.
0067: *
0068: * @see CDATA
0069: * @see Comment
0070: * @see Element
0071: * @see EntityRef
0072: * @see ProcessingInstruction
0073: * @see Text
0074: *
0075: * @version $Revision: 1.1 $, $Date: 2005/04/27 09:32:38 $
0076: * @author Alex Rosen
0077: * @author Philippe Riand
0078: * @author Bradley S. Huffman
0079: */
0080: final class ContentList extends AbstractList implements
0081: java.io.Serializable {
0082:
0083: private static final String CVS_ID = "@(#) $RCSfile: ContentList.java,v $ $Revision: 1.1 $ $Date: 2005/04/27 09:32:38 $ $Name: $";
0084:
0085: private static final int INITIAL_ARRAY_SIZE = 5;
0086:
0087: /**
0088: * Used inner class FilterListIterator to help hasNext and
0089: * hasPrevious the next index of our cursor (must be here
0090: * for JDK1.1).
0091: */
0092: private static final int CREATE = 0;
0093: private static final int HASPREV = 1;
0094: private static final int HASNEXT = 2;
0095: private static final int PREV = 3;
0096: private static final int NEXT = 4;
0097: private static final int ADD = 5;
0098: private static final int REMOVE = 6;
0099:
0100: /** Our backing list */
0101: // protected ArrayList list;
0102: private Content elementData[];
0103: private int size;
0104:
0105: /** Document or Element this list belongs to */
0106: private Parent parent;
0107:
0108: /** Force either a Document or Element parent */
0109: ContentList(Parent parent) {
0110: this .parent = parent;
0111: }
0112:
0113: /**
0114: * Package internal method to support building from sources that are
0115: * 100% trusted.
0116: *
0117: * @param c content to add without any checks
0118: */
0119: final void uncheckedAddContent(Content c) {
0120: c.parent = parent;
0121: ensureCapacity(size + 1);
0122: elementData[size++] = c;
0123: modCount++;
0124: }
0125:
0126: /**
0127: * Inserts the specified object at the specified position in this list.
0128: * Shifts the object currently at that position (if any) and any
0129: * subsequent objects to the right (adds one to their indices).
0130: *
0131: * @param index The location to set the value to.
0132: * @param obj The object to insert into the list.
0133: * throws IndexOutOfBoundsException if index < 0 || index > size()
0134: */
0135: public void add(int index, Object obj) {
0136: if (obj == null) {
0137: throw new IllegalAddException("Cannot add null object");
0138: }
0139: if ((obj instanceof Content)) {
0140: add(index, (Content) obj);
0141: } else {
0142: throw new IllegalAddException("Class "
0143: + obj.getClass().getName()
0144: + " is of unrecognized type and cannot be added");
0145: }
0146: }
0147:
0148: /**
0149: * @see org.jdom.ContentList#add(int, org.jdom.Content)
0150: */
0151: private void documentCanContain(int index, Content child)
0152: throws IllegalAddException {
0153: if (child instanceof Element) {
0154: if (indexOfFirstElement() >= 0) {
0155: throw new IllegalAddException(
0156: "Cannot add a second root element, only one is allowed");
0157: }
0158: if (indexOfDocType() > index) {
0159: throw new IllegalAddException(
0160: "A root element cannot be added before the DocType");
0161: }
0162: }
0163: if (child instanceof DocType) {
0164: if (indexOfDocType() >= 0) {
0165: throw new IllegalAddException(
0166: "Cannot add a second doctype, only one is allowed");
0167: }
0168: int firstElt = indexOfFirstElement();
0169: if (firstElt != -1 && firstElt < index) {
0170: throw new IllegalAddException(
0171: "A DocType cannot be added after the root element");
0172: }
0173: }
0174: if (child instanceof CDATA) {
0175: throw new IllegalAddException(
0176: "A CDATA is not allowed at the document root");
0177: }
0178:
0179: if (child instanceof Text) {
0180: throw new IllegalAddException(
0181: "A Text is not allowed at the document root");
0182: }
0183:
0184: if (child instanceof EntityRef) {
0185: throw new IllegalAddException(
0186: "An EntityRef is not allowed at the document root");
0187: }
0188: }
0189:
0190: private static void elementCanContain(int index, Content child)
0191: throws IllegalAddException {
0192: if (child instanceof DocType) {
0193: throw new IllegalAddException(
0194: "A DocType is not allowed except at the document level");
0195: }
0196: }
0197:
0198: /**
0199: * Check and add the <code>Element</code> to this list at
0200: * the given index.
0201: *
0202: * @param index index where to add <code>Element</code>
0203: * @param child <code>Element</code> to add
0204: */
0205: void add(int index, Content child) {
0206: if (child == null) {
0207: throw new IllegalAddException("Cannot add null object");
0208: }
0209: if (parent instanceof Document) {
0210: documentCanContain(index, child);
0211: } else {
0212: elementCanContain(index, child);
0213: }
0214:
0215: if (child.getParent() != null) {
0216: Parent p = child.getParent();
0217: if (p instanceof Document) {
0218: throw new IllegalAddException((Element) child,
0219: "The Content already has an existing parent document");
0220: } else {
0221: throw new IllegalAddException(
0222: "The Content already has an existing parent \""
0223: + ((Element) p).getQualifiedName()
0224: + "\"");
0225: }
0226: }
0227:
0228: if (child == parent) {
0229: throw new IllegalAddException(
0230: "The Element cannot be added to itself");
0231: }
0232:
0233: // Detect if we have <a><b><c/></b></a> and c.add(a)
0234: if ((parent instanceof Element && child instanceof Element)
0235: && ((Element) child).isAncestor((Element) parent)) {
0236: throw new IllegalAddException(
0237: "The Element cannot be added as a descendent of itself");
0238: }
0239:
0240: if (index < 0 || index > size) {
0241: throw new IndexOutOfBoundsException("Index: " + index
0242: + " Size: " + size());
0243: }
0244:
0245: child.setParent(parent);
0246:
0247: ensureCapacity(size + 1);
0248: if (index == size) {
0249: elementData[size++] = child;
0250: } else {
0251: System.arraycopy(elementData, index, elementData,
0252: index + 1, size - index);
0253: elementData[index] = child;
0254: size++;
0255: }
0256: modCount++;
0257: }
0258:
0259: /**
0260: * Add the specified collecton to the end of this list.
0261: *
0262: * @param collection The collection to add to the list.
0263: * @return <code>true</code> if the list was modified as a result of
0264: * the add.
0265: */
0266: public boolean addAll(Collection collection) {
0267: return addAll(size(), collection);
0268: }
0269:
0270: /**
0271: * Inserts the specified collecton at the specified position in this list.
0272: * Shifts the object currently at that position (if any) and any
0273: * subsequent objects to the right (adds one to their indices).
0274: *
0275: * @param index The offset to start adding the data in the collection
0276: * @param collection The collection to insert into the list.
0277: * @return <code>true</code> if the list was modified as a result of
0278: * the add.
0279: * throws IndexOutOfBoundsException if index < 0 || index > size()
0280: */
0281: public boolean addAll(int index, Collection collection) {
0282: if (index < 0 || index > size) {
0283: throw new IndexOutOfBoundsException("Index: " + index
0284: + " Size: " + size());
0285: }
0286:
0287: if ((collection == null) || (collection.size() == 0)) {
0288: return false;
0289: }
0290: ensureCapacity(size() + collection.size());
0291:
0292: int count = 0;
0293: try {
0294: Iterator i = collection.iterator();
0295: while (i.hasNext()) {
0296: Object obj = i.next();
0297: add(index + count, obj);
0298: count++;
0299: }
0300: } catch (RuntimeException exception) {
0301: for (int i = 0; i < count; i++) {
0302: remove(index);
0303: }
0304: throw exception;
0305: }
0306:
0307: return true;
0308: }
0309:
0310: /**
0311: * Clear the current list.
0312: */
0313: public void clear() {
0314: if (elementData != null) {
0315: for (int i = 0; i < size; i++) {
0316: Content obj = elementData[i];
0317: removeParent(obj);
0318: }
0319: elementData = null;
0320: size = 0;
0321: }
0322: modCount++;
0323: }
0324:
0325: /**
0326: * Clear the current list and set it to the contents
0327: * of the <code>Collection</code>.
0328: * object.
0329: *
0330: * @param collection The collection to use.
0331: */
0332: void clearAndSet(Collection collection) {
0333: Content[] old = elementData;
0334: int oldSize = size;
0335:
0336: elementData = null;
0337: size = 0;
0338:
0339: if ((collection != null) && (collection.size() != 0)) {
0340: ensureCapacity(collection.size());
0341: try {
0342: addAll(0, collection);
0343: } catch (RuntimeException exception) {
0344: elementData = old;
0345: size = oldSize;
0346: throw exception;
0347: }
0348: }
0349:
0350: if (old != null) {
0351: for (int i = 0; i < oldSize; i++) {
0352: removeParent(old[i]);
0353: }
0354: }
0355: modCount++;
0356: }
0357:
0358: /**
0359: * Increases the capacity of this <code>ContentList</code> instance,
0360: * if necessary, to ensure that it can hold at least the number of
0361: * items specified by the minimum capacity argument.
0362: *
0363: * @param minCapacity the desired minimum capacity.
0364: */
0365: void ensureCapacity(int minCapacity) {
0366: if (elementData == null) {
0367: elementData = new Content[Math.max(minCapacity,
0368: INITIAL_ARRAY_SIZE)];
0369: } else {
0370: int oldCapacity = elementData.length;
0371: if (minCapacity > oldCapacity) {
0372: Object oldData[] = elementData;
0373: int newCapacity = (oldCapacity * 3) / 2 + 1;
0374: if (newCapacity < minCapacity)
0375: newCapacity = minCapacity;
0376: elementData = new Content[newCapacity];
0377: System.arraycopy(oldData, 0, elementData, 0, size);
0378: }
0379: }
0380: }
0381:
0382: /**
0383: * Return the object at the specified offset.
0384: *
0385: * @param index The offset of the object.
0386: * @return The Object which was returned.
0387: */
0388: public Object get(int index) {
0389: if (index < 0 || index >= size) {
0390: throw new IndexOutOfBoundsException("Index: " + index
0391: + " Size: " + size());
0392: }
0393: return elementData[index];
0394: }
0395:
0396: /**
0397: * Return a view of this list based on the given filter.
0398: *
0399: * @param filter <code>Filter</code> for this view.
0400: * @return a list representing the rules of the <code>Filter</code>.
0401: */
0402: List getView(Filter filter) {
0403: return new FilterList(filter);
0404: }
0405:
0406: /**
0407: * Return the index of the first Element in the list. If the parent
0408: * is a <code>Document</code> then the element is the root element.
0409: * If the list contains no Elements, it returns -1.
0410: *
0411: * @return index of first element, or -1 if one doesn't exist
0412: */
0413: int indexOfFirstElement() {
0414: if (elementData != null) {
0415: for (int i = 0; i < size; i++) {
0416: if (elementData[i] instanceof Element) {
0417: return i;
0418: }
0419: }
0420: }
0421: return -1;
0422: }
0423:
0424: /**
0425: * Return the index of the DocType element in the list. If the list contains
0426: * no DocType, it returns -1.
0427: *
0428: * @return index of the DocType, or -1 if it doesn't
0429: * exist
0430: */
0431: int indexOfDocType() {
0432: if (elementData != null) {
0433: for (int i = 0; i < size; i++) {
0434: if (elementData[i] instanceof DocType) {
0435: return i;
0436: }
0437: }
0438: }
0439: return -1;
0440: }
0441:
0442: /**
0443: * Remove the object at the specified offset.
0444: *
0445: * @param index The offset of the object.
0446: * @return The Object which was removed.
0447: */
0448: public Object remove(int index) {
0449: if (index < 0 || index >= size)
0450: throw new IndexOutOfBoundsException("Index: " + index
0451: + " Size: " + size());
0452:
0453: Content old = elementData[index];
0454: removeParent(old);
0455: int numMoved = size - index - 1;
0456: if (numMoved > 0)
0457: System.arraycopy(elementData, index + 1, elementData,
0458: index, numMoved);
0459: elementData[--size] = null; // Let gc do its work
0460: modCount++;
0461: return old;
0462: }
0463:
0464: /** Remove the parent of a Object */
0465: private static void removeParent(Content c) {
0466: c.setParent(null);
0467: }
0468:
0469: /**
0470: * Set the object at the specified location to the supplied
0471: * object.
0472: *
0473: * @param index The location to set the value to.
0474: * @param obj The location to set the value to.
0475: * @return The object which was replaced.
0476: * throws IndexOutOfBoundsException if index < 0 || index >= size()
0477: */
0478: public Object set(int index, Object obj) {
0479: if (index < 0 || index >= size)
0480: throw new IndexOutOfBoundsException("Index: " + index
0481: + " Size: " + size());
0482:
0483: if ((obj instanceof Element) && (parent instanceof Document)) {
0484: int root = indexOfFirstElement();
0485: if ((root >= 0) && (root != index)) {
0486: throw new IllegalAddException(
0487: "Cannot add a second root element, only one is allowed");
0488: }
0489: }
0490:
0491: if ((obj instanceof DocType) && (parent instanceof Document)) {
0492: int docTypeIndex = indexOfDocType();
0493: if ((docTypeIndex >= 0) && (docTypeIndex != index)) {
0494: throw new IllegalAddException(
0495: "Cannot add a second doctype, only one is allowed");
0496: }
0497: }
0498:
0499: Object old = remove(index);
0500: try {
0501: add(index, obj);
0502: } catch (RuntimeException exception) {
0503: add(index, old);
0504: throw exception;
0505: }
0506: return old;
0507: }
0508:
0509: /**
0510: * Return the number of items in this list
0511: *
0512: * @return The number of items in this list.
0513: */
0514: public int size() {
0515: return size;
0516: }
0517:
0518: /**
0519: * Return this list as a <code>String</code>
0520: *
0521: * @return The number of items in this list.
0522: */
0523: public String toString() {
0524: return super .toString();
0525: }
0526:
0527: /** Give access of ContentList.modCount to FilterList */
0528: private int getModCount() {
0529: return modCount;
0530: }
0531:
0532: /* * * * * * * * * * * * * FilterList * * * * * * * * * * * * * * * */
0533: /* * * * * * * * * * * * * FilterList * * * * * * * * * * * * * * * */
0534:
0535: /**
0536: * <code>FilterList</code> represents legal JDOM content, including content
0537: * for <code>Document</code>s or <code>Element</code>s.
0538: */
0539:
0540: class FilterList extends AbstractList implements
0541: java.io.Serializable {
0542:
0543: /** The Filter */
0544: Filter filter;
0545:
0546: /** Current number of items in this view */
0547: int count = 0;
0548:
0549: /** Expected modCount in our backing list */
0550: int expected = -1;
0551:
0552: // Implementation Note: Directly after size() is called, expected
0553: // is sync'd with ContentList.modCount and count provides
0554: // the true size of this view. Before the first call to
0555: // size() or if the backing list is modified outside this
0556: // FilterList, both might contain bogus values and should
0557: // not be used without first calling size();
0558:
0559: /**
0560: * Create a new instance of the FilterList with the specified Filter.
0561: */
0562: FilterList(Filter filter) {
0563: this .filter = filter;
0564: }
0565:
0566: /**
0567: * Inserts the specified object at the specified position in this list.
0568: * Shifts the object currently at that position (if any) and any
0569: * subsequent objects to the right (adds one to their indices).
0570: *
0571: * @param index The location to set the value to.
0572: * @param obj The object to insert into the list.
0573: * throws IndexOutOfBoundsException if index < 0 || index > size()
0574: */
0575: public void add(int index, Object obj) {
0576: if (filter.matches(obj)) {
0577: int adjusted = getAdjustedIndex(index);
0578: ContentList.this .add(adjusted, obj);
0579: expected++;
0580: count++;
0581: } else
0582: throw new IllegalAddException("Filter won't allow the "
0583: + obj.getClass().getName() + " '" + obj
0584: + "' to be added to the list");
0585: }
0586:
0587: /**
0588: * Return the object at the specified offset.
0589: *
0590: * @param index The offset of the object.
0591: * @return The Object which was returned.
0592: */
0593: public Object get(int index) {
0594: int adjusted = getAdjustedIndex(index);
0595: return ContentList.this .get(adjusted);
0596: }
0597:
0598: public Iterator iterator() {
0599: return new FilterListIterator(filter, 0);
0600: }
0601:
0602: public ListIterator listIterator() {
0603: return new FilterListIterator(filter, 0);
0604: }
0605:
0606: public ListIterator listIterator(int index) {
0607: return new FilterListIterator(filter, index);
0608: }
0609:
0610: /**
0611: * Remove the object at the specified offset.
0612: *
0613: * @param index The offset of the object.
0614: * @return The Object which was removed.
0615: */
0616: public Object remove(int index) {
0617: int adjusted = getAdjustedIndex(index);
0618: Object old = ContentList.this .get(adjusted);
0619: if (filter.matches(old)) {
0620: old = ContentList.this .remove(adjusted);
0621: expected++;
0622: count--;
0623: } else {
0624: throw new IllegalAddException("Filter won't allow the "
0625: + (old.getClass()).getName() + " '" + old
0626: + "' (index " + index + ") to be removed");
0627: }
0628: return old;
0629: }
0630:
0631: /**
0632: * Set the object at the specified location to the supplied
0633: * object.
0634: *
0635: * @param index The location to set the value to.
0636: * @param obj The location to set the value to.
0637: * @return The object which was replaced.
0638: * throws IndexOutOfBoundsException if index < 0 || index >= size()
0639: */
0640: public Object set(int index, Object obj) {
0641: Object old = null;
0642: if (filter.matches(obj)) {
0643: int adjusted = getAdjustedIndex(index);
0644: old = ContentList.this .get(adjusted);
0645: if (!filter.matches(old)) {
0646: throw new IllegalAddException(
0647: "Filter won't allow the "
0648: + (old.getClass()).getName() + " '"
0649: + old + "' (index " + index
0650: + ") to be removed");
0651: }
0652: old = ContentList.this .set(adjusted, obj);
0653: expected += 2;
0654: } else {
0655: throw new IllegalAddException(
0656: "Filter won't allow index " + index
0657: + " to be set to "
0658: + (obj.getClass()).getName());
0659: }
0660: return old;
0661: }
0662:
0663: /**
0664: * Return the number of items in this list
0665: *
0666: * @return The number of items in this list.
0667: */
0668: public int size() {
0669: // Implementation Note: Directly after size() is called, expected
0670: // is sync'd with ContentList.modCount and count provides
0671: // the true size of this view. Before the first call to
0672: // size() or if the backing list is modified outside this
0673: // FilterList, both might contain bogus values and should
0674: // not be used without first calling size();
0675:
0676: if (expected == ContentList.this .getModCount()) {
0677: return count;
0678: }
0679:
0680: count = 0;
0681: for (int i = 0; i < ContentList.this .size(); i++) {
0682: Object obj = ContentList.this .elementData[i];
0683: if (filter.matches(obj)) {
0684: count++;
0685: }
0686: }
0687: expected = ContentList.this .getModCount();
0688: return count;
0689: }
0690:
0691: /**
0692: * Return the adjusted index
0693: *
0694: * @param index Index of in this view.
0695: * @return True index in backing list
0696: */
0697: final private int getAdjustedIndex(int index) {
0698: int adjusted = 0;
0699: for (int i = 0; i < ContentList.this .size; i++) {
0700: Object obj = ContentList.this .elementData[i];
0701: if (filter.matches(obj)) {
0702: if (index == adjusted) {
0703: return i;
0704: }
0705: adjusted++;
0706: }
0707: }
0708:
0709: if (index == adjusted) {
0710: return ContentList.this .size;
0711: }
0712:
0713: return ContentList.this .size + 1;
0714: }
0715: }
0716:
0717: /* * * * * * * * * * * * * FilterListIterator * * * * * * * * * * * */
0718: /* * * * * * * * * * * * * FilterListIterator * * * * * * * * * * * */
0719:
0720: class FilterListIterator implements ListIterator {
0721:
0722: /** The Filter that applies */
0723: Filter filter;
0724:
0725: /** The last operation performed */
0726: int lastOperation;
0727:
0728: /** Initial start index in backing list */
0729: int initialCursor;
0730:
0731: /** Index in backing list of next object */
0732: int cursor;
0733:
0734: /** Index in backing list of last object returned */
0735: int last;
0736:
0737: /** Expected modCount in our backing list */
0738: int expected;
0739:
0740: /**
0741: * Default constructor
0742: */
0743: FilterListIterator(Filter filter, int start) {
0744: this .filter = filter;
0745: initialCursor = initializeCursor(start);
0746: last = -1;
0747: expected = ContentList.this .getModCount();
0748: lastOperation = CREATE;
0749: }
0750:
0751: /**
0752: * Returns <code>true</code> if this list iterator has a next element.
0753: */
0754: public boolean hasNext() {
0755: checkConcurrentModification();
0756:
0757: switch (lastOperation) {
0758: case CREATE:
0759: cursor = initialCursor;
0760: break;
0761: case PREV:
0762: cursor = last;
0763: break;
0764: case ADD:
0765: case NEXT:
0766: cursor = moveForward(last + 1);
0767: break;
0768: case REMOVE:
0769: cursor = moveForward(last);
0770: break;
0771: case HASPREV:
0772: cursor = moveForward(cursor + 1);
0773: break;
0774: case HASNEXT:
0775: break;
0776: default:
0777: throw new IllegalStateException("Unknown operation");
0778: }
0779:
0780: if (lastOperation != CREATE) {
0781: lastOperation = HASNEXT;
0782: }
0783:
0784: return (cursor < ContentList.this .size()) ? true : false;
0785: }
0786:
0787: /**
0788: * Returns the next element in the list.
0789: */
0790: public Object next() {
0791: checkConcurrentModification();
0792:
0793: if (hasNext()) {
0794: last = cursor;
0795: } else {
0796: last = ContentList.this .size();
0797: throw new NoSuchElementException();
0798: }
0799:
0800: lastOperation = NEXT;
0801: return ContentList.this .get(last);
0802: }
0803:
0804: /**
0805: * Returns <code>true</code> if this list iterator has more
0806: * elements when traversing the list in the reverse direction.
0807: */
0808: public boolean hasPrevious() {
0809: checkConcurrentModification();
0810:
0811: switch (lastOperation) {
0812: case CREATE:
0813: cursor = initialCursor;
0814: int size = ContentList.this .size();
0815: if (cursor >= size) {
0816: cursor = moveBackward(size - 1);
0817: }
0818: break;
0819: case PREV:
0820: case REMOVE:
0821: cursor = moveBackward(last - 1);
0822: break;
0823: case HASNEXT:
0824: cursor = moveBackward(cursor - 1);
0825: break;
0826: case ADD:
0827: case NEXT:
0828: cursor = last;
0829: break;
0830: case HASPREV:
0831: break;
0832: default:
0833: throw new IllegalStateException("Unknown operation");
0834: }
0835:
0836: if (lastOperation != CREATE) {
0837: lastOperation = HASPREV;
0838: }
0839:
0840: return (cursor < 0) ? false : true;
0841: }
0842:
0843: /**
0844: * Returns the previous element in the list.
0845: */
0846: public Object previous() {
0847: checkConcurrentModification();
0848:
0849: if (hasPrevious()) {
0850: last = cursor;
0851: } else {
0852: last = -1;
0853: throw new NoSuchElementException();
0854: }
0855:
0856: lastOperation = PREV;
0857: return ContentList.this .get(last);
0858: }
0859:
0860: /**
0861: * Returns the index of the element that would be returned by a
0862: * subsequent call to <code>next</code>.
0863: */
0864: public int nextIndex() {
0865: checkConcurrentModification();
0866: hasNext();
0867:
0868: int count = 0;
0869: for (int i = 0; i < ContentList.this .size(); i++) {
0870: if (filter.matches(ContentList.this .get(i))) {
0871: if (i == cursor) {
0872: return count;
0873: }
0874: count++;
0875: }
0876: }
0877: expected = ContentList.this .getModCount();
0878: return count;
0879: }
0880:
0881: /**
0882: * Returns the index of the element that would be returned by a
0883: * subsequent call to <code>previous</code>. (Returns -1 if the
0884: * list iterator is at the beginning of the list.)
0885: */
0886: public int previousIndex() {
0887: checkConcurrentModification();
0888:
0889: if (hasPrevious()) {
0890: int count = 0;
0891: for (int i = 0; i < ContentList.this .size(); i++) {
0892: if (filter.matches(ContentList.this .get(i))) {
0893: if (i == cursor) {
0894: return count;
0895: }
0896: count++;
0897: }
0898: }
0899: }
0900: return -1;
0901: }
0902:
0903: /**
0904: * Inserts the specified element into the list.
0905: */
0906: public void add(Object obj) {
0907: checkConcurrentModification();
0908:
0909: if (filter.matches(obj)) {
0910: last = cursor + 1;
0911: ContentList.this .add(last, obj);
0912: } else {
0913: throw new IllegalAddException(
0914: "Filter won't allow add of "
0915: + (obj.getClass()).getName());
0916: }
0917: expected = ContentList.this .getModCount();
0918: lastOperation = ADD;
0919: }
0920:
0921: /**
0922: * Removes from the list the last element that was returned by
0923: * <code>next</code> or <code>previous</code>.
0924: * the last call to <code>next</code> or <code>previous</code>.
0925: */
0926: public void remove() {
0927: checkConcurrentModification();
0928:
0929: if ((last < 0) || (lastOperation == REMOVE)) {
0930: throw new IllegalStateException(
0931: "no preceeding call to " + "prev() or next()");
0932: }
0933:
0934: if (lastOperation == ADD) {
0935: throw new IllegalStateException("cannot call remove() "
0936: + "after add()");
0937: }
0938:
0939: Object old = ContentList.this .get(last);
0940: if (filter.matches(old)) {
0941: ContentList.this .remove(last);
0942: } else
0943: throw new IllegalAddException("Filter won't allow "
0944: + (old.getClass()).getName() + " (index "
0945: + last + ") to be removed");
0946: expected = ContentList.this .getModCount();
0947: lastOperation = REMOVE;
0948: }
0949:
0950: /**
0951: * Replaces the last element returned by <code>next</code> or
0952: * <code>previous</code> with the specified element.
0953: */
0954: public void set(Object obj) {
0955: checkConcurrentModification();
0956:
0957: if ((lastOperation == ADD) || (lastOperation == REMOVE)) {
0958: throw new IllegalStateException(
0959: "cannot call set() after "
0960: + "add() or remove()");
0961: }
0962:
0963: if (last < 0) {
0964: throw new IllegalStateException(
0965: "no preceeding call to " + "prev() or next()");
0966: }
0967:
0968: if (filter.matches(obj)) {
0969: Object old = ContentList.this .get(last);
0970: if (!filter.matches(old)) {
0971: throw new IllegalAddException("Filter won't allow "
0972: + (old.getClass()).getName() + " (index "
0973: + last + ") to be removed");
0974: }
0975: ContentList.this .set(last, obj);
0976: } else {
0977: throw new IllegalAddException(
0978: "Filter won't allow index " + last
0979: + " to be set to "
0980: + (obj.getClass()).getName());
0981: }
0982:
0983: expected = ContentList.this .getModCount();
0984: // Don't set lastOperation
0985: }
0986:
0987: /**
0988: * Returns index in the backing list by moving forward start
0989: * objects that match our filter.
0990: */
0991: private int initializeCursor(int start) {
0992: if (start < 0) {
0993: throw new IndexOutOfBoundsException("Index: " + start);
0994: }
0995:
0996: int count = 0;
0997: for (int i = 0; i < ContentList.this .size(); i++) {
0998: Object obj = ContentList.this .get(i);
0999: if (filter.matches(obj)) {
1000: if (start == count) {
1001: return i;
1002: }
1003: count++;
1004: }
1005: }
1006:
1007: if (start > count) {
1008: throw new IndexOutOfBoundsException("Index: " + start
1009: + " Size: " + count);
1010: }
1011:
1012: return ContentList.this .size();
1013: }
1014:
1015: /**
1016: * Returns index in the backing list of the next object matching
1017: * our filter, starting at the given index and moving forwards.
1018: */
1019: private int moveForward(int start) {
1020: if (start < 0) {
1021: start = 0;
1022: }
1023: for (int i = start; i < ContentList.this .size(); i++) {
1024: Object obj = ContentList.this .get(i);
1025: if (filter.matches(obj)) {
1026: return i;
1027: }
1028: }
1029: return ContentList.this .size();
1030: }
1031:
1032: /**
1033: * Returns index in the backing list of the next object matching
1034: * our filter, starting at the given index and moving backwards.
1035: */
1036: private int moveBackward(int start) {
1037: if (start >= ContentList.this .size()) {
1038: start = ContentList.this .size() - 1;
1039: }
1040:
1041: for (int i = start; i >= 0; --i) {
1042: Object obj = ContentList.this .get(i);
1043: if (filter.matches(obj)) {
1044: return i;
1045: }
1046: }
1047: return -1;
1048: }
1049:
1050: /**
1051: * Check if are backing list is being modified by someone else.
1052: */
1053: private void checkConcurrentModification() {
1054: if (expected != ContentList.this .getModCount()) {
1055: throw new ConcurrentModificationException();
1056: }
1057: }
1058: }
1059: }
|