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.ArrayList;
034: import java.util.Collection;
035:
036: import javax.swing.event.EventListenerList;
037: import javax.swing.event.ListDataEvent;
038: import javax.swing.event.ListDataListener;
039:
040: /**
041: * Adds {@link javax.swing.ListModel} capabilities to its superclass
042: * <code>ArrayList</code>, i. e. allows to observe changes in the content and
043: * structure. Useful for lists that are bound to list views, for example
044: * JList, JComboBox and JTable.
045: *
046: * @author Karsten Lentzsch
047: * @version $Revision: 1.5 $
048: *
049: * @param <E> the type of the list elements
050: */
051: public final class ArrayListModel<E> extends ArrayList<E> implements
052: ObservableList<E> {
053:
054: private static final long serialVersionUID = -6165677201152015546L;
055:
056: // Instance Creation ******************************************************
057:
058: /**
059: * Constructs an empty list with an initial capacity of ten.
060: */
061: public ArrayListModel() {
062: this (10);
063: }
064:
065: /**
066: * Constructs an empty list with the specified initial capacity.
067: *
068: * @param initialCapacity the initial capacity of the list.
069: * @throws IllegalArgumentException if the specified initial capacity
070: * is negative
071: */
072: public ArrayListModel(int initialCapacity) {
073: super (initialCapacity);
074: }
075:
076: /**
077: * Constructs a list containing the elements of the specified collection,
078: * in the order they are returned by the collection's iterator.
079: * The <code>ArrayListModel</code> instance has an initial capacity of
080: * 110% the size of the specified collection.
081: *
082: * @param c the collection whose elements are to be placed into this list.
083: * @throws NullPointerException if the specified collection is
084: * <code>null</code>
085: */
086: public ArrayListModel(Collection<? extends E> c) {
087: super (c);
088: }
089:
090: // Overriding Superclass Behavior *****************************************
091:
092: /**
093: * Inserts the specified element at the specified position in this
094: * list. Shifts the element currently at that position (if any) and
095: * any subsequent elements to the right (adds one to their indices).
096: *
097: * @param index index at which the specified element is to be inserted.
098: * @param element element to be inserted.
099: * @throws IndexOutOfBoundsException if index is out of range
100: * <code>(index < 0 || index > size())</code>.
101: */
102: @Override
103: public void add(int index, E element) {
104: super .add(index, element);
105: fireIntervalAdded(index, index);
106: }
107:
108: /**
109: * Appends the specified element to the end of this list.
110: *
111: * @param e element to be appended to this list.
112: * @return <code>true</code> (as per the general contract of Collection.add).
113: */
114: @Override
115: public boolean add(E e) {
116: int newIndex = size();
117: super .add(e);
118: fireIntervalAdded(newIndex, newIndex);
119: return true;
120: }
121:
122: /**
123: * Inserts all of the elements in the specified Collection into this
124: * list, starting at the specified position. Shifts the element
125: * currently at that position (if any) and any subsequent elements to
126: * the right (increases their indices). The new elements will appear
127: * in the list in the order that they are returned by the
128: * specified Collection's iterator.
129: *
130: * @param index index at which to insert first element
131: * from the specified collection.
132: * @param c elements to be inserted into this list.
133: * @return <code>true</code> if this list changed as a result of the call.
134: * @throws IndexOutOfBoundsException if index out of range <code>(index
135: * < 0 || index > size())</code>.
136: * @throws NullPointerException if the specified Collection is null.
137: */
138: @Override
139: public boolean addAll(int index, Collection<? extends E> c) {
140: boolean changed = super .addAll(index, c);
141: if (changed) {
142: int lastIndex = index + c.size() - 1;
143: fireIntervalAdded(index, lastIndex);
144: }
145: return changed;
146: }
147:
148: /**
149: * Appends all of the elements in the specified Collection to the end of
150: * this list, in the order that they are returned by the
151: * specified Collection's Iterator. The behavior of this operation is
152: * undefined if the specified Collection is modified while the operation
153: * is in progress. (This implies that the behavior of this call is
154: * undefined if the specified Collection is this list, and this
155: * list is nonempty.)
156: *
157: * @param c the elements to be inserted into this list.
158: * @return <code>true</code> if this list changed as a result of the call.
159: * @throws NullPointerException if the specified collection is null.
160: */
161: @Override
162: public boolean addAll(Collection<? extends E> c) {
163: int firstIndex = size();
164: boolean changed = super .addAll(c);
165: if (changed) {
166: int lastIndex = firstIndex + c.size() - 1;
167: fireIntervalAdded(firstIndex, lastIndex);
168: }
169: return changed;
170: }
171:
172: /**
173: * Removes all of the elements from this list. The list will
174: * be empty after this call returns.
175: */
176: @Override
177: public void clear() {
178: if (isEmpty())
179: return;
180:
181: int oldLastIndex = size() - 1;
182: super .clear();
183: fireIntervalRemoved(0, oldLastIndex);
184: }
185:
186: /**
187: * Removes the element at the specified position in this list.
188: * Shifts any subsequent elements to the left (subtracts one from their
189: * indices).
190: *
191: * @param index the index of the element to removed.
192: * @return the element that was removed from the list.
193: * @throws IndexOutOfBoundsException if index out of range <code>(index
194: * < 0 || index >= size())</code>.
195: */
196: @Override
197: public E remove(int index) {
198: E removedElement = super .remove(index);
199: fireIntervalRemoved(index, index);
200: return removedElement;
201: }
202:
203: /**
204: * Removes a single instance of the specified element from this
205: * list, if it is present (optional operation). More formally,
206: * removes an element <tt>e</tt> such that <tt>(o==null ? e==null :
207: * o.equals(e))</tt>, if the list contains one or more such
208: * elements. Returns <tt>true</tt> if the list contained the
209: * specified element (or equivalently, if the list changed as a
210: * result of the call).<p>
211: *
212: * This implementation looks for the index of the specified element.
213: * If it finds the element, it removes the element at this index
214: * by calling <code>#remove(int)</code> that fires a ListDataEvent.
215: *
216: * @param o element to be removed from this list, if present.
217: * @return <tt>true</tt> if the list contained the specified element.
218: */
219: @Override
220: public boolean remove(Object o) {
221: int index = indexOf(o);
222: boolean contained = index != -1;
223: if (contained) {
224: remove(index);
225: }
226: return contained;
227: }
228:
229: /**
230: * Removes from this List all of the elements whose index is between
231: * fromIndex, inclusive and toIndex, exclusive. Shifts any succeeding
232: * elements to the left (reduces their index).
233: * This call shortens the list by <code>(toIndex - fromIndex)</code> elements.
234: * (If <code>toIndex==fromIndex</code>, this operation has no effect.)
235: *
236: * @param fromIndex index of first element to be removed.
237: * @param toIndex index after last element to be removed.
238: */
239: @Override
240: protected void removeRange(int fromIndex, int toIndex) {
241: super .removeRange(fromIndex, toIndex);
242: fireIntervalRemoved(fromIndex, toIndex - 1);
243: }
244:
245: /**
246: * Replaces the element at the specified position in this list with
247: * the specified element.
248: *
249: * @param index index of element to replace.
250: * @param element element to be stored at the specified position.
251: * @return the element previously at the specified position.
252: * @throws IndexOutOfBoundsException if index out of range
253: * <code>(index < 0 || index >= size())</code>.
254: */
255: @Override
256: public E set(int index, E element) {
257: E previousElement = super .set(index, element);
258: fireContentsChanged(index, index);
259: return previousElement;
260: }
261:
262: // ListModel Field ********************************************************
263:
264: /**
265: * Holds the registered ListDataListeners. The list that holds these
266: * listeners is initialized lazily in <code>#getEventListenerList</code>.
267: *
268: * @see #addListDataListener(ListDataListener)
269: * @see #removeListDataListener(ListDataListener)
270: */
271: private EventListenerList listenerList;
272:
273: // ListModel Implementation ***********************************************
274:
275: /**
276: * Adds a listener to the list that's notified each time a change
277: * to the data model occurs.
278: *
279: * @param l the <code>ListDataListener</code> to be added
280: */
281: public void addListDataListener(ListDataListener l) {
282: getEventListenerList().add(ListDataListener.class, l);
283: }
284:
285: /**
286: * Removes a listener from the list that's notified each time a
287: * change to the data model occurs.
288: *
289: * @param l the <code>ListDataListener</code> to be removed
290: */
291: public void removeListDataListener(ListDataListener l) {
292: getEventListenerList().remove(ListDataListener.class, l);
293: }
294:
295: /**
296: * Returns the value at the specified index.
297: *
298: * @param index the requested index
299: * @return the value at <code>index</code>
300: */
301: public Object getElementAt(int index) {
302: return get(index);
303: }
304:
305: /**
306: * Returns the length of the list or 0 if there's no list.
307: *
308: * @return the length of the list or 0 if there's no list
309: */
310: public int getSize() {
311: return size();
312: }
313:
314: // Explicit Change Notification *******************************************
315:
316: /**
317: * Notifies all registered <code>ListDataListeners</code> that the element
318: * at the specified index has changed. Useful if there's a content change
319: * without any structural change.<p>
320: *
321: * This method must be called <em>after</em> the element of the list changes.
322: *
323: * @param index the index of the element that has changed
324: *
325: * @see EventListenerList
326: */
327: public void fireContentsChanged(int index) {
328: fireContentsChanged(index, index);
329: }
330:
331: // ListModel Helper Code **************************************************
332:
333: /**
334: * Returns an array of all the list data listeners
335: * registered on this <code>ArrayListModel</code>.
336: *
337: * @return all of this model's <code>ListDataListener</code>s,
338: * or an empty array if no list data listeners
339: * are currently registered
340: *
341: * @see #addListDataListener(ListDataListener)
342: * @see #removeListDataListener(ListDataListener)
343: */
344: public ListDataListener[] getListDataListeners() {
345: return getEventListenerList().getListeners(
346: ListDataListener.class);
347: }
348:
349: /**
350: * This method must be called <em>after</em> one or more elements
351: * of the list change. The changed elements
352: * are specified by the closed interval index0, index1 -- the end points
353: * are included. Note that index0 need not be less than or equal to index1.
354: *
355: * @param index0 one end of the new interval
356: * @param index1 the other end of the new interval
357: * @see EventListenerList
358: */
359: private void fireContentsChanged(int index0, int index1) {
360: Object[] listeners = getEventListenerList().getListenerList();
361: ListDataEvent e = null;
362:
363: for (int i = listeners.length - 2; i >= 0; i -= 2) {
364: if (listeners[i] == ListDataListener.class) {
365: if (e == null) {
366: e = new ListDataEvent(this ,
367: ListDataEvent.CONTENTS_CHANGED, index0,
368: index1);
369: }
370: ((ListDataListener) listeners[i + 1])
371: .contentsChanged(e);
372: }
373: }
374: }
375:
376: /**
377: * This method must be called <em>after</em> one or more elements
378: * are added to the model. The new elements
379: * are specified by a closed interval index0, index1 -- the end points
380: * are included. Note that index0 need not be less than or equal to index1.
381: *
382: * @param index0 one end of the new interval
383: * @param index1 the other end of the new interval
384: * @see EventListenerList
385: */
386: private void fireIntervalAdded(int index0, int index1) {
387: Object[] listeners = getEventListenerList().getListenerList();
388: ListDataEvent e = null;
389:
390: for (int i = listeners.length - 2; i >= 0; i -= 2) {
391: if (listeners[i] == ListDataListener.class) {
392: if (e == null) {
393: e = new ListDataEvent(this ,
394: ListDataEvent.INTERVAL_ADDED, index0,
395: index1);
396: }
397: ((ListDataListener) listeners[i + 1]).intervalAdded(e);
398: }
399: }
400: }
401:
402: /**
403: * This method must be called <em>after</em> one or more elements
404: * are removed from the model.
405: * <code>index0</code> and <code>index1</code> are the end points
406: * of the interval that's been removed. Note that <code>index0</code>
407: * need not be less than or equal to <code>index1</code>.
408: *
409: * @param index0 one end of the removed interval,
410: * including <code>index0</code>
411: * @param index1 the other end of the removed interval,
412: * including <code>index1</code>
413: * @see EventListenerList
414: */
415: private void fireIntervalRemoved(int index0, int index1) {
416: Object[] listeners = getEventListenerList().getListenerList();
417: ListDataEvent e = null;
418:
419: for (int i = listeners.length - 2; i >= 0; i -= 2) {
420: if (listeners[i] == ListDataListener.class) {
421: if (e == null) {
422: e = new ListDataEvent(this ,
423: ListDataEvent.INTERVAL_REMOVED, index0,
424: index1);
425: }
426: ((ListDataListener) listeners[i + 1])
427: .intervalRemoved(e);
428: }
429: }
430: }
431:
432: /**
433: * Lazily initializes and returns the event listener list used
434: * to notify registered listeners.
435: *
436: * @return the event listener list used to notify listeners
437: */
438: private EventListenerList getEventListenerList() {
439: if (listenerList == null) {
440: listenerList = new EventListenerList();
441: }
442: return listenerList;
443: }
444:
445: }
|