001: /*
002: * Copyright (c) 2002-2007 JGoodies Karsten Lentzsch. All Rights Reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * o Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * o Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * o Neither the name of JGoodies Karsten Lentzsch nor the names of
015: * its contributors may be used to endorse or promote products derived
016: * from this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package com.jgoodies.binding.list;
032:
033: import java.util.Collection;
034: import java.util.LinkedList;
035: import java.util.ListIterator;
036: import java.util.NoSuchElementException;
037:
038: import javax.swing.event.EventListenerList;
039: import javax.swing.event.ListDataEvent;
040: import javax.swing.event.ListDataListener;
041:
042: /**
043: * Adds {@link javax.swing.ListModel} capabilities to its superclass
044: * <code>LinkedList</code>, i. e. allows to observe changes in the content and
045: * structure. Useful for lists that are bound to list views, for example
046: * JList, JComboBox and JTable.
047: *
048: * @author Karsten Lentzsch
049: * @version $Revision: 1.6 $
050: *
051: * @see ArrayListModel
052: * @see java.util.ListIterator
053: *
054: * @param <E> the type of the list elements
055: */
056: public final class LinkedListModel<E> extends LinkedList<E> implements
057: ObservableList<E> {
058:
059: private static final long serialVersionUID = 5753378113505707237L;
060:
061: // Instance Creation ******************************************************
062:
063: /**
064: * Constructs an empty linked list.
065: */
066: public LinkedListModel() {
067: // Just invoke the super constructor implicitly.
068: }
069:
070: /**
071: * Constructs a linked list containing the elements of the specified
072: * collection, in the order they are returned by the collection's
073: * iterator.
074: *
075: * @param c the collection whose elements are to be placed into this list.
076: * @throws NullPointerException if the specified collection is
077: * <code>null</code>
078: */
079: public LinkedListModel(Collection<? extends E> c) {
080: super (c);
081: }
082:
083: // Overriding Superclass Behavior *****************************************
084:
085: /**
086: * Inserts the specified element at the specified position in this
087: * list. Shifts the element currently at that position (if any) and
088: * any subsequent elements to the right (adds one to their indices).
089: *
090: * @param index index at which the specified element is to be inserted.
091: * @param element element to be inserted.
092: * @throws IndexOutOfBoundsException if index is out of range
093: * <code>(index < 0 || index > size())</code>.
094: */
095: @Override
096: public void add(int index, E element) {
097: super .add(index, element);
098: fireIntervalAdded(index, index);
099: }
100:
101: /**
102: * Appends the specified element to the end of this list.
103: *
104: * @param e element to be appended to this list.
105: * @return <code>true</code> (as per the general contract of Collection.add).
106: */
107: @Override
108: public boolean add(E e) {
109: int newIndex = size();
110: super .add(e);
111: fireIntervalAdded(newIndex, newIndex);
112: return true;
113: }
114:
115: /**
116: * Inserts all of the elements in the specified Collection into this
117: * list, starting at the specified position. Shifts the element
118: * currently at that position (if any) and any subsequent elements to
119: * the right (increases their indices). The new elements will appear
120: * in the list in the order that they are returned by the
121: * specified Collection's iterator.
122: *
123: * @param index index at which to insert first element
124: * from the specified collection.
125: * @param c elements to be inserted into this list.
126: * @return <code>true</code> if this list changed as a result of the call.
127: *
128: * @throws IndexOutOfBoundsException if index out of range
129: * <code>(index < 0 || index > size())</code>.
130: * @throws NullPointerException if the specified Collection is null.
131: */
132: @Override
133: public boolean addAll(int index, Collection<? extends E> c) {
134: boolean changed = super .addAll(index, c);
135: if (changed) {
136: int lastIndex = index + c.size() - 1;
137: fireIntervalAdded(index, lastIndex);
138: }
139: return changed;
140: }
141:
142: /**
143: * Inserts the given element at the beginning of this list.
144: *
145: * @param e the element to be inserted at the beginning of this list.
146: */
147: @Override
148: public void addFirst(E e) {
149: super .addFirst(e);
150: fireIntervalAdded(0, 0);
151: }
152:
153: /**
154: * Appends the given element to the end of this list. (Identical in
155: * function to the <code>add</code> method; included only for consistency.)
156: *
157: * @param e the element to be inserted at the end of this list.
158: */
159: @Override
160: public void addLast(E e) {
161: int newIndex = size();
162: super .addLast(e);
163: fireIntervalAdded(newIndex, newIndex);
164: }
165:
166: /**
167: * Removes all of the elements from this list. The list will
168: * be empty after this call returns.
169: */
170: @Override
171: public void clear() {
172: if (isEmpty())
173: return;
174:
175: int oldLastIndex = size() - 1;
176: super .clear();
177: fireIntervalRemoved(0, oldLastIndex);
178: }
179:
180: /**
181: * Removes the element at the specified position in this list.
182: * Shifts any subsequent elements to the left (subtracts one from their
183: * indices).
184: *
185: * @param index the index of the element to removed.
186: * @return the element that was removed from the list.
187: * @throws IndexOutOfBoundsException if index out of range
188: * <code>(index < 0 || index >= size())</code>.
189: */
190: @Override
191: public E remove(int index) {
192: E removedElement = super .remove(index);
193: fireIntervalRemoved(index, index);
194: return removedElement;
195: }
196:
197: /**
198: * Removes a single instance of the specified element from this
199: * collection, if it is present (optional operation). More formally,
200: * removes an element <code>e</code> such that <code>(o==null ? e==null :
201: * o.equals(e))</code>, if the collection contains one or more such
202: * elements. Returns <code>true</code> if the collection contained the
203: * specified element (or equivalently, if the collection changed as a
204: * result of the call).<p>
205: *
206: * This implementation iterates over the collection looking for the
207: * specified element. If it finds the element, it removes the element
208: * from the collection using the iterator's remove method.<p>
209: *
210: * Note that this implementation throws an
211: * <code>UnsupportedOperationException</code> if the iterator returned by this
212: * collection's iterator method does not implement the <code>remove</code>
213: * method and this collection contains the specified object.
214: *
215: * @param o element to be removed from this collection, if present.
216: * @return <code>true</code> if the collection contained the specified
217: * element.
218: */
219: @Override
220: public boolean remove(Object o) {
221: int index = indexOf(o);
222: if (index == -1) {
223: return false;
224: }
225: remove(index);
226: return true;
227: }
228:
229: /**
230: * Removes and returns the first element from this list.
231: *
232: * @return the first element from this list.
233: * @throws java.util.NoSuchElementException if this list is empty.
234: */
235: @Override
236: public E removeFirst() {
237: E first = super .removeFirst();
238: fireIntervalRemoved(0, 0);
239: return first;
240: }
241:
242: /**
243: * Removes and returns the last element from this list.
244: *
245: * @return the last element from this list.
246: * @throws java.util.NoSuchElementException if this list is empty.
247: */
248: @Override
249: public E removeLast() {
250: int lastIndex = size() - 1;
251: E last = super .removeLast();
252: fireIntervalRemoved(lastIndex, lastIndex);
253: return last;
254: }
255:
256: /**
257: * Removes from this List all of the elements whose index is between
258: * fromIndex, inclusive and toIndex, exclusive. Shifts any succeeding
259: * elements to the left (reduces their index).
260: * This call shortens the list by <code>(toIndex - fromIndex)</code> elements.
261: * (If <code>toIndex==fromIndex</code>, this operation has no effect.)
262: *
263: * @param fromIndex index of first element to be removed.
264: * @param toIndex index after last element to be removed.
265: */
266: @Override
267: protected void removeRange(int fromIndex, int toIndex) {
268: super .removeRange(fromIndex, toIndex);
269: fireIntervalRemoved(fromIndex, toIndex - 1);
270: }
271:
272: /**
273: * Replaces the element at the specified position in this list with
274: * the specified element.
275: *
276: * @param index index of element to replace.
277: * @param element element to be stored at the specified position.
278: * @return the element previously at the specified position.
279: * @throws IndexOutOfBoundsException if index out of range
280: * <code>(index < 0 || index >= size())</code>.
281: */
282: @Override
283: public E set(int index, E element) {
284: E previousElement = super .set(index, element);
285: fireContentsChanged(index, index);
286: return previousElement;
287: }
288:
289: /**
290: * Returns a list-iterator of the elements in this list (in proper
291: * sequence), starting at the specified position in the list.
292: * Obeys the general contract of <tt>List.listIterator(int)</tt>.<p>
293: *
294: * The list-iterator is <i>fail-fast</i>: if the list is structurally
295: * modified at any time after the Iterator is created, in any way except
296: * through the list-iterator's own <tt>remove</tt> or <tt>add</tt>
297: * methods, the list-iterator will throw a
298: * <tt>ConcurrentModificationException</tt>. Thus, in the face of
299: * concurrent modification, the iterator fails quickly and cleanly, rather
300: * than risking arbitrary, non-deterministic behavior at an undetermined
301: * time in the future.
302: *
303: * @param index index of first element to be returned from the
304: * list-iterator (by a call to <tt>next</tt>).
305: * @return a ListIterator of the elements in this list (in proper
306: * sequence), starting at the specified position in the list.
307: * @throws IndexOutOfBoundsException if index is out of range
308: * (<tt>index < 0 || index > size()</tt>).
309: * @see java.util.List#listIterator(int)
310: */
311: @Override
312: public ListIterator<E> listIterator(int index) {
313: return new ReportingListIterator(super .listIterator(index));
314: }
315:
316: // ListModel Field ********************************************************
317:
318: /**
319: * Holds the registered ListDataListeners. The list that holds these
320: * listeners is initialized lazily in <code>#getEventListenerList</code>.
321: *
322: * @see #addListDataListener(ListDataListener)
323: * @see #removeListDataListener(ListDataListener)
324: */
325: private EventListenerList listenerList;
326:
327: // ListModel Implementation ***********************************************
328:
329: /**
330: * Adds a listener to the list that's notified each time a change
331: * to the data model occurs.
332: *
333: * @param l the <code>ListDataListener</code> to be added
334: */
335: public void addListDataListener(ListDataListener l) {
336: getEventListenerList().add(ListDataListener.class, l);
337: }
338:
339: /**
340: * Removes a listener from the list that's notified each time a
341: * change to the data model occurs.
342: *
343: * @param l the <code>ListDataListener</code> to be removed
344: */
345: public void removeListDataListener(ListDataListener l) {
346: getEventListenerList().remove(ListDataListener.class, l);
347: }
348:
349: /**
350: * Returns the value at the specified index.
351: *
352: * @param index the requested index
353: * @return the value at <code>index</code>
354: */
355: public E getElementAt(int index) {
356: return get(index);
357: }
358:
359: /**
360: * Returns the length of the list or 0 if there's no list.
361: *
362: * @return the length of the list or 0 if there's no list
363: */
364: public int getSize() {
365: return size();
366: }
367:
368: // Explicit Change Notification *******************************************
369:
370: /**
371: * Notifies all registered <code>ListDataListeners</code> that the element
372: * at the specified index has changed. Useful if there's a content change
373: * without any structural change.<p>
374: *
375: * This method must be called <em>after</em> the element of the list changes.
376: *
377: * @param index the index of the element that has changed
378: *
379: * @see EventListenerList
380: */
381: public void fireContentsChanged(int index) {
382: fireContentsChanged(index, index);
383: }
384:
385: // ListModel Helper Code **************************************************
386:
387: /**
388: * Returns an array of all the list data listeners
389: * registered on this <code>LinkedListModel</code>.
390: *
391: * @return all of this model's <code>ListDataListener</code>s,
392: * or an empty array if no list data listeners
393: * are currently registered
394: *
395: * @see #addListDataListener(ListDataListener)
396: * @see #removeListDataListener(ListDataListener)
397: */
398: public ListDataListener[] getListDataListeners() {
399: return getEventListenerList().getListeners(
400: ListDataListener.class);
401: }
402:
403: /**
404: * This method must be called <em>after</em> one or more elements
405: * of the list change. The changed elements
406: * are specified by the closed interval index0, index1 -- the end points
407: * are included. Note that index0 need not be less than or equal to index1.
408: *
409: * @param index0 one end of the new interval
410: * @param index1 the other end of the new interval
411: * @see EventListenerList
412: */
413: private void fireContentsChanged(int index0, int index1) {
414: Object[] listeners = getEventListenerList().getListenerList();
415: ListDataEvent e = null;
416:
417: for (int i = listeners.length - 2; i >= 0; i -= 2) {
418: if (listeners[i] == ListDataListener.class) {
419: if (e == null) {
420: e = new ListDataEvent(this ,
421: ListDataEvent.CONTENTS_CHANGED, index0,
422: index1);
423: }
424: ((ListDataListener) listeners[i + 1])
425: .contentsChanged(e);
426: }
427: }
428: }
429:
430: /**
431: * This method must be called <em>after</em> one or more elements
432: * are added to the model. The new elements
433: * are specified by a closed interval index0, index1 -- the end points
434: * are included. Note that index0 need not be less than or equal to index1.
435: *
436: * @param index0 one end of the new interval
437: * @param index1 the other end of the new interval
438: * @see EventListenerList
439: */
440: private void fireIntervalAdded(int index0, int index1) {
441: Object[] listeners = getEventListenerList().getListenerList();
442: ListDataEvent e = null;
443:
444: for (int i = listeners.length - 2; i >= 0; i -= 2) {
445: if (listeners[i] == ListDataListener.class) {
446: if (e == null) {
447: e = new ListDataEvent(this ,
448: ListDataEvent.INTERVAL_ADDED, index0,
449: index1);
450: }
451: ((ListDataListener) listeners[i + 1]).intervalAdded(e);
452: }
453: }
454: }
455:
456: /**
457: * This method must be called <em>after</em> one or more elements
458: * are removed from the model.
459: * <code>index0</code> and <code>index1</code> are the end points
460: * of the interval that's been removed. Note that <code>index0</code>
461: * need not be less than or equal to <code>index1</code>.
462: *
463: * @param index0 one end of the removed interval,
464: * including <code>index0</code>
465: * @param index1 the other end of the removed interval,
466: * including <code>index1</code>
467: * @see EventListenerList
468: */
469: private void fireIntervalRemoved(int index0, int index1) {
470: Object[] listeners = getEventListenerList().getListenerList();
471: ListDataEvent e = null;
472:
473: for (int i = listeners.length - 2; i >= 0; i -= 2) {
474: if (listeners[i] == ListDataListener.class) {
475: if (e == null) {
476: e = new ListDataEvent(this ,
477: ListDataEvent.INTERVAL_REMOVED, index0,
478: index1);
479: }
480: ((ListDataListener) listeners[i + 1])
481: .intervalRemoved(e);
482: }
483: }
484: }
485:
486: /**
487: * Lazily initializes and returns the event listener list used
488: * to notify registered listeners.
489: *
490: * @return the event listener list used to notify listeners
491: */
492: private EventListenerList getEventListenerList() {
493: if (listenerList == null) {
494: listenerList = new EventListenerList();
495: }
496: return listenerList;
497: }
498:
499: // Helper Class ***********************************************************
500:
501: /**
502: * A ListIterator that fires ListDataEvents if elements are added or removed.
503: */
504: private final class ReportingListIterator implements
505: ListIterator<E> {
506:
507: /**
508: * Refers to the wrapped ListIterator that is used
509: * to forward all ListIterator methods to.
510: */
511: private final ListIterator<E> delegate;
512:
513: /**
514: * Holds the object that was returned last by the underlying
515: * ListIteratur. Used to determine the index of the element
516: * removed.
517: */
518: private int lastReturnedIndex;
519:
520: ReportingListIterator(ListIterator<E> delegate) {
521: this .delegate = delegate;
522: lastReturnedIndex = -1;
523: }
524:
525: /**
526: * Returns <tt>true</tt> if this list iterator has more elements when
527: * traversing the list in the forward direction. (In other words, returns
528: * <tt>true</tt> if <tt>next</tt> would return an element rather than
529: * throwing an exception.)
530: *
531: * @return <tt>true</tt> if the list iterator has more elements when
532: * traversing the list in the forward direction.
533: */
534: public boolean hasNext() {
535: return delegate.hasNext();
536: }
537:
538: /**
539: * Returns the next element in the list. This method may be called
540: * repeatedly to iterate through the list, or intermixed with calls to
541: * <tt>previous</tt> to go back and forth. (Note that alternating calls
542: * to <tt>next</tt> and <tt>previous</tt> will return the same element
543: * repeatedly.)
544: *
545: * @return the next element in the list.
546: *
547: * @throws NoSuchElementException if the iteration has no next element.
548: */
549: public E next() {
550: lastReturnedIndex = nextIndex();
551: return delegate.next();
552: }
553:
554: /**
555: * Returns <tt>true</tt> if this list iterator has more elements when
556: * traversing the list in the reverse direction. (In other words, returns
557: * <tt>true</tt> if <tt>previous</tt> would return an element rather than
558: * throwing an exception.)
559: *
560: * @return <tt>true</tt> if the list iterator has more elements when
561: * traversing the list in the reverse direction.
562: */
563: public boolean hasPrevious() {
564: return delegate.hasPrevious();
565: }
566:
567: /**
568: * Returns the previous element in the list. This method may be called
569: * repeatedly to iterate through the list backwards, or intermixed with
570: * calls to <tt>next</tt> to go back and forth. (Note that alternating
571: * calls to <tt>next</tt> and <tt>previous</tt> will return the same
572: * element repeatedly.)
573: *
574: * @return the previous element in the list.
575: *
576: * @throws NoSuchElementException if the iteration has no previous
577: * element.
578: */
579: public E previous() {
580: lastReturnedIndex = previousIndex();
581: return delegate.previous();
582: }
583:
584: /**
585: * Returns the index of the element that would be returned by a subsequent
586: * call to <tt>next</tt>. (Returns list size if the list iterator is at the
587: * end of the list.)
588: *
589: * @return the index of the element that would be returned by a subsequent
590: * call to <tt>next</tt>, or list size if list iterator is at end
591: * of list.
592: */
593: public int nextIndex() {
594: return delegate.nextIndex();
595: }
596:
597: /**
598: * Returns the index of the element that would be returned by a subsequent
599: * call to <tt>previous</tt>. (Returns -1 if the list iterator is at the
600: * beginning of the list.)
601: *
602: * @return the index of the element that would be returned by a subsequent
603: * call to <tt>previous</tt>, or -1 if list iterator is at
604: * beginning of list.
605: */
606: public int previousIndex() {
607: return delegate.previousIndex();
608: }
609:
610: /**
611: * Removes from the list the last element that was returned by
612: * <tt>next</tt> or <tt>previous</tt> (optional operation). This call can
613: * only be made once per call to <tt>next</tt> or <tt>previous</tt>. It
614: * can be made only if <tt>ListIterator.add</tt> has not been called after
615: * the last call to <tt>next</tt> or <tt>previous</tt>.
616: *
617: * @throws UnsupportedOperationException if the <tt>remove</tt>
618: * operation is not supported by this list iterator.
619: * @throws IllegalStateException neither <tt>next</tt> nor
620: * <tt>previous</tt> have been called, or <tt>remove</tt> or
621: * <tt>add</tt> have been called after the last call to *
622: * <tt>next</tt> or <tt>previous</tt>.
623: */
624: public void remove() {
625: int oldSize = size();
626: delegate.remove();
627: int newSize = size();
628: if (newSize < oldSize)
629: LinkedListModel.this .fireIntervalRemoved(
630: lastReturnedIndex, lastReturnedIndex);
631: }
632:
633: /**
634: * Replaces the last element returned by <tt>next</tt> or
635: * <tt>previous</tt> with the specified element (optional operation).
636: * This call can be made only if neither <tt>ListIterator.remove</tt> nor
637: * <tt>ListIterator.add</tt> have been called after the last call to
638: * <tt>next</tt> or <tt>previous</tt>.
639: *
640: * @param e the element with which to replace the last element returned by
641: * <tt>next</tt> or <tt>previous</tt>.
642: * @throws UnsupportedOperationException if the <tt>set</tt> operation
643: * is not supported by this list iterator.
644: * @throws ClassCastException if the class of the specified element
645: * prevents it from being added to this list.
646: * @throws IllegalArgumentException if some aspect of the specified
647: * element prevents it from being added to this list.
648: * @throws IllegalStateException if neither <tt>next</tt> nor
649: * <tt>previous</tt> have been called, or <tt>remove</tt> or
650: * <tt>add</tt> have been called after the last call to
651: * <tt>next</tt> or <tt>previous</tt>.
652: */
653: public void set(E e) {
654: delegate.set(e);
655: }
656:
657: /**
658: * Inserts the specified element into the list (optional operation). The
659: * element is inserted immediately before the next element that would be
660: * returned by <tt>next</tt>, if any, and after the next element that
661: * would be returned by <tt>previous</tt>, if any. (If the list contains
662: * no elements, the new element becomes the sole element on the list.)
663: * The new element is inserted before the implicit cursor: a subsequent
664: * call to <tt>next</tt> would be unaffected, and a subsequent call to
665: * <tt>previous</tt> would return the new element. (This call increases
666: * by one the value that would be returned by a call to <tt>nextIndex</tt>
667: * or <tt>previousIndex</tt>.)
668: *
669: * @param e the element to insert.
670: * @throws UnsupportedOperationException if the <tt>add</tt> method is
671: * not supported by this list iterator.
672: *
673: * @throws ClassCastException if the class of the specified element
674: * prevents it from being added to this list.
675: *
676: * @throws IllegalArgumentException if some aspect of this element
677: * prevents it from being added to this list.
678: */
679: public void add(E e) {
680: delegate.add(e);
681: int newIndex = previousIndex();
682: LinkedListModel.this .fireIntervalAdded(newIndex, newIndex);
683: lastReturnedIndex = -1;
684: }
685:
686: }
687:
688: }
|