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.beans.PropertyChangeEvent;
034: import java.beans.PropertyChangeListener;
035: import java.io.Serializable;
036: import java.util.Arrays;
037: import java.util.Collections;
038: import java.util.List;
039:
040: import javax.swing.ListModel;
041: import javax.swing.event.EventListenerList;
042: import javax.swing.event.ListDataEvent;
043: import javax.swing.event.ListDataListener;
044:
045: import com.jgoodies.binding.PresentationModel;
046: import com.jgoodies.binding.beans.BeanAdapter;
047: import com.jgoodies.binding.beans.Model;
048: import com.jgoodies.binding.value.ValueHolder;
049: import com.jgoodies.binding.value.ValueModel;
050:
051: /**
052: * A ListModel implementation that holds a List or ListModel in a ValueModel.
053: * If you hold a List, this class can only report that the List has been
054: * replaced; this is done by firing a PropertyChangeEvent for the <em>list</em>
055: * property. Also, a <code>ListDataEvent</code> is fired that reports
056: * a complete change. In contrast, if you use a ListModel it will report
057: * the same PropertyChangeEvent. But fine grained changes in the ListModel
058: * will be fired by this class to notify observes about changes in the content,
059: * added and removed elements.<p>
060: *
061: * If the list content doesn't change at all, or if it always changes
062: * completely, you can work well with both List content and ListModel content.
063: * But if the list structure or content changes, the ListModel reports more
064: * fine grained events to registered ListDataListeners, which in turn allows
065: * list views to chooser better user interface gestures: for example, a table
066: * with scroll pane may retain the current selection and scroll offset.<p>
067: *
068: * If you want to combine List operations and the ListModel change reports,
069: * you may consider using an implementation that combines these two interfaces,
070: * for example {@link ArrayListModel} or {@link LinkedListModel}.<p>
071: *
072: * <strong>Important Note:</strong> If you change the ListModel instance,
073: * either by calling <code>#setListModel(ListModel)</code> or by setting
074: * a new value to the underlying list holder, you must ensure that
075: * the list holder throws a PropertyChangeEvent whenever the instance changes.
076: * This event is used to remove a ListDataListener from the old ListModel
077: * instance and is later used to add it to the new ListModel instance.
078: * It is easy to violate this constraint, just because Java's standard
079: * PropertyChangeSupport helper class that is used by many beans, checks
080: * a changed property value via <code>#equals</code>, not <code>==</code>.
081: * For example, if you change the IndirectListModel's list model from an empty
082: * list <code>L1</code> to another empty list instance <code>L2</code>,
083: * the PropertyChangeSupport won't generate a PropertyChangeEvent,
084: * and so, the IndirectListModel won't know about the change, which
085: * may lead to unexpected behavior.<p>
086: *
087: * This binding library provides some help for firing PropertyChangeEvents
088: * if the old ListModel and new ListModel are equal but not the same.
089: * Class {@link com.jgoodies.binding.beans.ExtendedPropertyChangeSupport}
090: * allows to permanently or individually check the identity (using
091: * <code>==</code>) instead of checking the equity (using <code>#equals</code>).
092: * Class {@link com.jgoodies.binding.beans.Model} uses this extended
093: * property change support. And class {@link ValueHolder} uses it too
094: * and can be configured to always test the identity.<p>
095: *
096: * This class provides public convenience methods for firing ListDataEvents,
097: * see the methods <code>#fireContentsChanged</code>,
098: * <code>#fireIntervalAdded</code>, and <code>#fireIntervalRemoved</code>.
099: * These are automatically invoked if the list holder holds a ListModel
100: * that fires these events. If on the other hand the underlying List or
101: * ListModel does not fire a required ListDataEvent, you can use these
102: * methods to notify presentations about a change. It is recommended
103: * to avoid sending duplicate ListDataEvents; hence check if the underlying
104: * ListModel fires the necessary events or not.<p>
105: *
106: * <strong>Constraints:</strong> The list holder holds instances of {@link List}
107: * or {@link ListModel}. If the ListModel changes, the underlying ValueModel
108: * must fire a PropertyChangeEvent.
109: *
110: * @author Karsten Lentzsch
111: * @version $Revision: 1.7 $
112: *
113: * @see List
114: * @see ListModel
115: * @see SelectionInList
116: * @see ValueModel
117: * @see com.jgoodies.binding.adapter.ComboBoxAdapter
118: * @see com.jgoodies.binding.adapter.AbstractTableAdapter
119: * @see com.jgoodies.binding.beans.ExtendedPropertyChangeSupport
120: * @see com.jgoodies.binding.beans.Model
121: * @see com.jgoodies.binding.value.ValueHolder
122: *
123: * @param <E> the type of the list elements
124: *
125: * @since 2.0
126: */
127: public class IndirectListModel<E> extends Model implements ListModel {
128:
129: // Constant Names for Bound Properties ************************************
130:
131: /**
132: * The name of the bound write-only <em>list</em> property.
133: */
134: public static final String PROPERTYNAME_LIST = "list";
135:
136: /**
137: * The name of the bound read-write <em>listHolder</em> property.
138: */
139: public static final String PROPERTYNAME_LIST_HOLDER = "listHolder";
140:
141: // ************************************************************************
142:
143: /**
144: * An empty <code>ListModel</code> that is used if the list holder's
145: * content is null.
146: *
147: * @see #getListModel()
148: */
149: private static final ListModel EMPTY_LIST_MODEL = new EmptyListModel();
150:
151: // Instance Fields ********************************************************
152:
153: /**
154: * Holds a <code>List</code> or <code>ListModel</code> that in turn
155: * holds the elements.
156: */
157: private ValueModel listHolder;
158:
159: /**
160: * Holds a copy of the listHolder's value. Used as the old list
161: * when the listHolder's value changes. Required because a ValueModel
162: * may use {@code null} as old value, but the IndirectListModel
163: * must know about the old and the new list.
164: */
165: private Object list;
166:
167: /**
168: * The size of the current list. Used during changes from an old
169: * to a new list to check for shorter or longer lists, which in turn
170: * leads to different ListDataEvents.
171: * Required only if the old and new list are the same instance.
172: */
173: private int listSize;
174:
175: /**
176: * Handles changes of the list.
177: */
178: private final PropertyChangeListener listChangeHandler;
179:
180: /**
181: * Handles structural and content changes of the list model.
182: */
183: private final ListDataListener listDataChangeHandler;
184:
185: /**
186: * Refers to the list of list data listeners that is used
187: * to notify registered listeners if the ListModel changes.
188: */
189: private final EventListenerList listenerList = new EventListenerList();
190:
191: // Instance creation ****************************************************
192:
193: /**
194: * Constructs an IndirectListModel with an empty initial
195: * {@code ArrayListModel}.
196: */
197: public IndirectListModel() {
198: this ((ListModel) new ArrayListModel<E>());
199: }
200:
201: /**
202: * Constructs an IndirectListModel on the given item array.
203: * The specified array will be converted to a List.<p>
204: *
205: * Changes to the list "write through" to the array, and changes
206: * to the array contents will be reflected in the list.
207: *
208: * @param listItems the array of initial items
209: *
210: * @throws NullPointerException if <code>listItems</code> is {@code null}
211: */
212: public IndirectListModel(E[] listItems) {
213: this (Arrays.asList(listItems));
214: }
215:
216: /**
217: * Constructs an IndirectListModel on the given list.<p>
218: *
219: * <strong>Note:</strong> Favor <code>ListModel</code> over
220: * <code>List</code> when working with an IndirectListModel.
221: * Why? The IndirectListModel can work with both types. What's the
222: * difference? ListModel provides all list access features
223: * required by the IndirectListModel's. In addition it reports more
224: * fine grained change events, instances of <code>ListDataEvents</code>.
225: * In contrast developer often create Lists and operate on them
226: * and the ListModel may be inconvenient for these operations.<p>
227: *
228: * A convenient solution for this situation is to use the
229: * <code>ArrayListModel</code> and <code>LinkedListModel</code> classes.
230: * These implement both List and ListModel, offer the standard List
231: * operations and report the fine grained ListDataEvents.
232: *
233: * @param list the initial list
234: */
235: public IndirectListModel(List<E> list) {
236: this (new ValueHolder(list, true));
237: }
238:
239: /**
240: * Constructs an IndirectListModel on the given list model
241: * using a default list holder.
242: *
243: * @param listModel the initial list model
244: */
245: public IndirectListModel(ListModel listModel) {
246: this (new ValueHolder(listModel, true));
247: }
248:
249: /**
250: * Constructs an IndirectListModel on the given list holder.<p>
251: *
252: * <strong>Constraints:</strong>
253: * 1) The listHolder must hold instances of List or ListModel and
254: * 2) must report a value change whenever the value's identity changes.
255: * Note that many bean properties don't fire a PropertyChangeEvent
256: * if the old and new value are equal - and so would break this constraint.
257: * If you provide a ValueHolder, enable its identityCheck feature
258: * during construction. If you provide an adapted bean property from
259: * a bean that extends the JGoodies <code>Model</code> class,
260: * you can enable the identity check feature in the methods
261: * <code>#firePropertyChange</code> by setting the trailing boolean
262: * parameter to <code>true</code>.
263: *
264: * @param listHolder holds the list or list model
265: *
266: * @throws NullPointerException
267: * if <code>listHolder</code> is {@code null}
268: */
269: public IndirectListModel(ValueModel listHolder) {
270: if (listHolder == null)
271: throw new NullPointerException(
272: "The list holder must not be null.");
273: checkListHolderIdentityCheck(listHolder);
274:
275: listChangeHandler = new ListChangeHandler();
276: listDataChangeHandler = createListDataChangeHandler();
277:
278: this .listHolder = listHolder;
279:
280: this .listHolder.addValueChangeListener(listChangeHandler);
281:
282: // If the ValueModel holds a ListModel observe list data changes too.
283: list = listHolder.getValue();
284: listSize = getSize(list);
285: if (list != null) {
286: if (list instanceof ListModel) {
287: ((ListModel) list)
288: .addListDataListener(listDataChangeHandler);
289: } else if (!(list instanceof List)) {
290: throw new ClassCastException(
291: "The listHolder's value must be a List or ListModel.");
292: }
293: }
294: }
295:
296: // Accessing the List/ListModel *******************************************
297:
298: /**
299: * Returns the list holder's List or an empty List, if it
300: * holds {@code null}. Throws an exception if the list holder holds
301: * any other type, including ListModels.
302: *
303: * @return the List content or an empty List if the content is {@code null}
304: *
305: * @throws ClassCastException if the list holder is neither
306: * {@code null} nor a List
307: *
308: * @see #setList(List)
309: * @see #getListModel()
310: * @see #setListModel(ListModel)
311: *
312: * @since 2.0
313: */
314: public final List<E> getList() {
315: Object aList = getListHolder().getValue();
316: if (aList == null)
317: return Collections.<E> emptyList();
318: if (aList instanceof List)
319: return (List<E>) aList;
320: throw new ClassCastException(
321: "#getList assumes that the list holder holds a List");
322: }
323:
324: /**
325: * Sets the given list as value of the list holder.<p>
326: *
327: * <strong>Note:</strong> Favor <code>ListModel</code> over
328: * <code>List</code> when working with an IndirectListModel.
329: * Why? The IndirectListModel can work with both types. What's the
330: * difference? ListModel provides all list access features
331: * required by the IndirectListModel's. In addition it reports more
332: * fine grained change events, instances of <code>ListDataEvents</code>.
333: * In contrast developer often create Lists and operate on them
334: * and the ListModel may be inconvenient for these operations.<p>
335: *
336: * A convenient solution for this situation is to use the
337: * <code>ArrayListModel</code> and <code>LinkedListModel</code> classes.
338: * These implement both List and ListModel, offer the standard List
339: * operations and report the fine grained ListDataEvents.
340: *
341: * @param newList the list to be set as new list content
342: *
343: * @see #getList()
344: * @see #getListModel()
345: * @see #setListModel(ListModel)
346: */
347: public final void setList(List<E> newList) {
348: getListHolder().setValue(newList);
349: }
350:
351: /**
352: * Returns the list holder's ListModel or an empty ListModel, if it
353: * holds {@code null}. Throws an exception if the list holder holds
354: * any other type, including Lists.
355: *
356: * @return the ListModel content or an empty ListModel
357: * if the content is {@code null}
358: *
359: * @throws ClassCastException if the list holder is neither
360: * {@code null} nor a ListModel
361: *
362: * @see #setListModel(ListModel)
363: * @see #setList(List)
364: */
365: public final ListModel getListModel() {
366: Object aListModel = getListHolder().getValue();
367: if (aListModel == null)
368: return EMPTY_LIST_MODEL;
369: if (aListModel instanceof ListModel)
370: return (ListModel) aListModel;
371: throw new ClassCastException(
372: "#getListModel assumes that the list holder holds a ListModel");
373: }
374:
375: /**
376: * Sets the given list model as value of the list holder.
377: *
378: * @param newListModel the list model to be set as new list content
379: *
380: * @see #getListModel()
381: * @see #setList(List)
382: */
383: public final void setListModel(ListModel newListModel) {
384: getListHolder().setValue(newListModel);
385: }
386:
387: // Accessing the List/ListModel Holder ************************************
388:
389: /**
390: * Returns the model that holds the List/ListModel.
391: *
392: * @return the model that holds the List/ListModel
393: */
394: public final ValueModel getListHolder() {
395: return listHolder;
396: }
397:
398: /**
399: * Sets a new list holder. Does nothing if old and new holder are equal.
400: * Removes the list change handler from the old holder and adds
401: * it to the new one. In case the list holder contents is a ListModel,
402: * the list data change handler is updated too by invoking
403: * <code>#updateListDataRegistration</code> in the same way as done in the
404: * list change handler.<p>
405: *
406: * TODO: Check and verify whether the list data registration update
407: * can be performed in one step <em>after</em> the listHolder has been
408: * changed - instead of remove the list data change handler, then
409: * changing the listHolder, and finally adding the list data change handler.
410: *
411: * @param newListHolder the list holder to be set
412: *
413: * @throws NullPointerException if the new list holder is {@code null}
414: * @throws IllegalArgumentException if the listHolder is a ValueHolder
415: * that doesn't check the identity when changing its value
416: */
417: public final void setListHolder(ValueModel newListHolder) {
418: if (newListHolder == null)
419: throw new NullPointerException(
420: "The new list holder must not be null.");
421: checkListHolderIdentityCheck(newListHolder);
422:
423: ValueModel oldListHolder = getListHolder();
424: if (oldListHolder == newListHolder)
425: return;
426:
427: Object oldList = list;
428: int oldSize = listSize;
429: Object newList = newListHolder.getValue();
430:
431: oldListHolder.removeValueChangeListener(listChangeHandler);
432: listHolder = newListHolder;
433: newListHolder.addValueChangeListener(listChangeHandler);
434:
435: updateList(oldList, oldSize, newList);
436: firePropertyChange(PROPERTYNAME_LIST_HOLDER, oldListHolder,
437: newListHolder);
438: }
439:
440: // ListModel Implementation ***********************************************
441:
442: /**
443: * Checks and answers if the list is empty or {@code null}.
444: *
445: * @return true if the list is empty or {@code null}, false otherwise
446: */
447: public final boolean isEmpty() {
448: return getSize() == 0;
449: }
450:
451: /**
452: * Returns the length of the list, <code>0</code> if the list model
453: * is {@code null}.
454: *
455: * @return the size of the list, <code>0</code> if the list model is
456: * {@code null}
457: */
458: public final int getSize() {
459: return getSize(getListHolder().getValue());
460: }
461:
462: /**
463: * Returns the value at the specified index, {@code null}
464: * if the list model is {@code null}.
465: *
466: * @param index the requested index
467: * @return the value at <code>index</code>, {@code null}
468: * if the list model is {@code null}
469: *
470: * @throws NullPointerException if the list holder's content is null
471: */
472: public final E getElementAt(int index) {
473: return getElementAt(getListHolder().getValue(), index);
474: }
475:
476: /**
477: * Adds a listener to the list that's notified each time a change
478: * to the data model occurs.
479: *
480: * @param l the <code>ListDataListener</code> to be added
481: */
482: public final void addListDataListener(ListDataListener l) {
483: listenerList.add(ListDataListener.class, l);
484: }
485:
486: /**
487: * Removes a listener from the list that's notified each time a
488: * change to the data model occurs.
489: *
490: * @param l the <code>ListDataListener</code> to be removed
491: */
492: public final void removeListDataListener(ListDataListener l) {
493: listenerList.remove(ListDataListener.class, l);
494: }
495:
496: /**
497: * Returns an array of all the list data listeners
498: * registered on this <code>IndirectListModel</code>.
499: *
500: * @return all of this model's <code>ListDataListener</code>s,
501: * or an empty array if no list data listeners
502: * are currently registered
503: *
504: * @see #addListDataListener(ListDataListener)
505: * @see #removeListDataListener(ListDataListener)
506: */
507: public final ListDataListener[] getListDataListeners() {
508: return listenerList.getListeners(ListDataListener.class);
509: }
510:
511: // ListModel Helper Code **************************************************
512:
513: /**
514: * Notifies all registered ListDataListeners that the contents
515: * of one or more list elements has changed.
516: * The changed elements are specified by the closed interval index0, index1
517: * -- the end points are included. Note that index0 need not be less than
518: * or equal to index1.<p>
519: *
520: * If the list holder holds a ListModel, this IndirectListModel listens
521: * to ListDataEvents fired by that ListModel, and forwards these events
522: * by invoking the associated <code>#fireXXX</code> method, which in turn
523: * notifies all registered ListDataListeners. Therefore if you fire
524: * ListDataEvents in an underlying ListModel, you don't need this method
525: * and should not use it to avoid sending duplicate ListDataEvents.
526: *
527: * @param index0 one end of the new interval
528: * @param index1 the other end of the new interval
529: *
530: * @see ListModel
531: * @see ListDataListener
532: * @see ListDataEvent
533: *
534: * @since 1.0.2
535: */
536: public final void fireContentsChanged(int index0, int index1) {
537: Object[] listeners = listenerList.getListenerList();
538: ListDataEvent e = null;
539:
540: for (int i = listeners.length - 2; i >= 0; i -= 2) {
541: if (listeners[i] == ListDataListener.class) {
542: if (e == null) {
543: e = new ListDataEvent(this ,
544: ListDataEvent.CONTENTS_CHANGED, index0,
545: index1);
546: }
547: ((ListDataListener) listeners[i + 1])
548: .contentsChanged(e);
549: }
550: }
551: }
552:
553: /**
554: * Notifies all registered ListDataListeners that one or more elements
555: * have been added to this IndirectListModel's List/ListModel.
556: * The new elements are specified by a closed interval index0, index1
557: * -- the end points are included. Note that index0 need not be less than
558: * or equal to index1.<p>
559: *
560: * If the list holder holds a ListModel, this IndirectListModel listens
561: * to ListDataEvents fired by that ListModel, and forwards these events
562: * by invoking the associated <code>#fireXXX</code> method, which in turn
563: * notifies all registered ListDataListeners. Therefore if you fire
564: * ListDataEvents in an underlying ListModel, you don't need this method
565: * and should not use it to avoid sending duplicate ListDataEvents.
566: *
567: * @param index0 one end of the new interval
568: * @param index1 the other end of the new interval
569: *
570: * @see ListModel
571: * @see ListDataListener
572: * @see ListDataEvent
573: *
574: * @since 1.0.2
575: */
576: public final void fireIntervalAdded(int index0, int index1) {
577: Object[] listeners = listenerList.getListenerList();
578: ListDataEvent e = null;
579: listSize = getSize();
580:
581: for (int i = listeners.length - 2; i >= 0; i -= 2) {
582: if (listeners[i] == ListDataListener.class) {
583: if (e == null) {
584: e = new ListDataEvent(this ,
585: ListDataEvent.INTERVAL_ADDED, index0,
586: index1);
587: }
588: ((ListDataListener) listeners[i + 1]).intervalAdded(e);
589: }
590: }
591: }
592:
593: /**
594: * Notifies all registered ListDataListeners that one or more elements
595: * have been removed from this IndirectListModel's List/ListModel.
596: * <code>index0</code> and <code>index1</code> are the end points
597: * of the interval that's been removed. Note that <code>index0</code>
598: * need not be less than or equal to <code>index1</code>.<p>
599: *
600: * If the list holder holds a ListModel, this IndirectListModel listens
601: * to ListDataEvents fired by that ListModel, and forwards these events
602: * by invoking the associated <code>#fireXXX</code> method, which in turn
603: * notifies all registered ListDataListeners. Therefore if you fire
604: * ListDataEvents in an underlying ListModel, you don't need this method
605: * and should not use it to avoid sending duplicate ListDataEvents.
606: *
607: * @param index0 one end of the removed interval,
608: * including <code>index0</code>
609: * @param index1 the other end of the removed interval,
610: * including <code>index1</code>
611: *
612: * @see ListModel
613: * @see ListDataListener
614: * @see ListDataEvent
615: *
616: * @since 1.0.2
617: */
618: public final void fireIntervalRemoved(int index0, int index1) {
619: Object[] listeners = listenerList.getListenerList();
620: ListDataEvent e = null;
621: listSize = getSize();
622:
623: for (int i = listeners.length - 2; i >= 0; i -= 2) {
624: if (listeners[i] == ListDataListener.class) {
625: if (e == null) {
626: e = new ListDataEvent(this ,
627: ListDataEvent.INTERVAL_REMOVED, index0,
628: index1);
629: }
630: ((ListDataListener) listeners[i + 1])
631: .intervalRemoved(e);
632: }
633: }
634: }
635:
636: // Misc ******************************************************************
637:
638: /**
639: * Removes the internal listeners from the list holder. If the current list
640: * is a ListModel, the internal ListDataListener is removed from it.
641: * This IndirectListModel must not be used after calling
642: * <code>#release</code>.<p>
643: *
644: * To avoid memory leaks it is recommended to invoke this method,
645: * if the list holder, selection holder, or selection index holder
646: * live much longer than this IndirectListModel.
647: * Instead of releasing the IndirectListModel, you typically make the
648: * list holder obsolete by releasing the PresentationModel or BeanAdapter
649: * that has created them before.<p>
650: *
651: * As an alternative you may use ValueModels that in turn use
652: * event listener lists implemented using <code>WeakReference</code>.<p>
653: *
654: * Basically this release method performs the reverse operation
655: * performed during the IndirectListModel construction.
656: *
657: * @see PresentationModel#release()
658: * @see BeanAdapter#release()
659: * @see java.lang.ref.WeakReference
660: *
661: * @since 1.2
662: */
663: public void release() {
664: listHolder.removeValueChangeListener(listChangeHandler);
665: if (list != null && (list instanceof ListModel)) {
666: ((ListModel) list)
667: .removeListDataListener(listDataChangeHandler);
668: }
669: listHolder = null;
670: list = null;
671: }
672:
673: // Default Behavior *******************************************************
674:
675: /**
676: * Creates and returns the ListDataListener used to observe
677: * changes in the underlying ListModel. It is re-registered
678: * in <code>#updateListModel</code>.
679: *
680: * @return the ListDataListener that handles changes
681: * in the underlying ListModel
682: */
683: protected ListDataListener createListDataChangeHandler() {
684: return new ListDataChangeHandler();
685: }
686:
687: /**
688: * Removes the list data change handler from the old list in case
689: * it is a <code>ListModel</code> and adds it to new one in case
690: * it is a <code>ListModel</code>.
691: * It then fires a property change for the list and a contents change event
692: * for the list content.
693: *
694: * @param oldList the old list content
695: * @param oldSize the size of the old List content
696: * @param newList the new list content
697: *
698: * @see javax.swing.JTable#tableChanged(javax.swing.event.TableModelEvent)
699: */
700: protected void updateList(Object oldList, int oldSize,
701: Object newList) {
702: if (oldList != null && (oldList instanceof ListModel)) {
703: ((ListModel) oldList)
704: .removeListDataListener(listDataChangeHandler);
705: }
706: if (newList != null && (newList instanceof ListModel)) {
707: ((ListModel) newList)
708: .addListDataListener(listDataChangeHandler);
709: }
710: int newSize = getSize(newList);
711: list = newList;
712: listSize = getSize(newList);
713: firePropertyChange(PROPERTYNAME_LIST, oldList, newList);
714: fireListChanged(oldSize - 1, newSize - 1);
715: }
716:
717: /**
718: * Notifies all registered ListDataListeners that this ListModel
719: * has changed from an old list to a new list content.
720: * If the old and new list size differ, a remove or add event for
721: * the removed or added interval is fired. A content change
722: * is reported for the interval common to the old and new list.<p>
723: *
724: * This method is invoked by #updateList during the transition
725: * from an old List(Model) to a new List(Model).<p>
726: *
727: * <strong>Note:</strong>
728: * The order of the events fired ensures that after each event
729: * the size described by the ListDataEvents equals the ListModel size.
730: *
731: * @param oldLastIndex the last index of the old list
732: * @param newLastIndex the last index of the new list
733: */
734: protected final void fireListChanged(int oldLastIndex,
735: int newLastIndex) {
736: if (newLastIndex < oldLastIndex) {
737: fireIntervalRemoved(newLastIndex + 1, oldLastIndex);
738: } else if (oldLastIndex < newLastIndex) {
739: fireIntervalAdded(oldLastIndex + 1, newLastIndex);
740: }
741: int lastCommonIndex = Math.min(oldLastIndex, newLastIndex);
742: if (lastCommonIndex >= 0) {
743: fireContentsChanged(0, lastCommonIndex);
744: }
745: }
746:
747: // Helper Code ************************************************************
748:
749: /**
750: * Returns the length of the given list, <code>0</code> if the list model
751: * is {@code null}.
752: *
753: * @param aListListModelOrNull a List, ListModel or null
754: * @return the size of the given list, <code>0</code> if the list model is
755: * {@code null}
756: */
757: protected final int getSize(Object aListListModelOrNull) {
758: if (aListListModelOrNull == null)
759: return 0;
760: else if (aListListModelOrNull instanceof ListModel)
761: return ((ListModel) aListListModelOrNull).getSize();
762: else
763: return ((List<?>) aListListModelOrNull).size();
764: }
765:
766: private E getElementAt(Object aList, int index) {
767: if (aList == null)
768: throw new NullPointerException("The list contents is null.");
769: else if (aList instanceof ListModel)
770: return (E) ((ListModel) aList).getElementAt(index);
771: else
772: return ((List<E>) aList).get(index);
773: }
774:
775: /**
776: * Throws an IllegalArgumentException if the given ValueModel
777: * is a ValueHolder that has the identityCheck feature disabled.
778: */
779: private void checkListHolderIdentityCheck(ValueModel aListHolder) {
780: if (!(aListHolder instanceof ValueHolder))
781: return;
782:
783: ValueHolder valueHolder = (ValueHolder) aListHolder;
784: if (!valueHolder.isIdentityCheckEnabled())
785: throw new IllegalArgumentException(
786: "The list holder must have the identity check enabled.");
787: }
788:
789: // Helper Classes *********************************************************
790:
791: /**
792: * A ListModel that has no elements, a size of 0, and never fires an event.
793: */
794: private static final class EmptyListModel implements ListModel,
795: Serializable {
796:
797: /**
798: * Returns zero to indicate an empty list.
799: */
800: public int getSize() {
801: return 0;
802: }
803:
804: /**
805: * Returns {@code null} because this model has no elements.
806: */
807: public Object getElementAt(int index) {
808: return null;
809: }
810:
811: /**
812: * Does nothing, because the empty list will never fire an event.
813: *
814: * @param l the <code>ListDataListener</code> to be ignored
815: */
816: public void addListDataListener(ListDataListener l) {
817: // Do nothing.
818: }
819:
820: /**
821: * Does nothing, because the empty list will never fire an event.
822: *
823: * @param l the <code>ListDataListener</code> to be ignored
824: */
825: public void removeListDataListener(ListDataListener l) {
826: // Do nothing.
827: }
828:
829: }
830:
831: // Event Handlers *********************************************************
832:
833: /**
834: * Handles changes of the List or ListModel.
835: */
836: private final class ListChangeHandler implements
837: PropertyChangeListener {
838:
839: /**
840: * The list has been changed.
841: * Notifies all registered listeners about the change.
842: *
843: * @param evt the property change event to be handled
844: */
845: public void propertyChange(PropertyChangeEvent evt) {
846: Object oldList = list;
847: int oldSize = listSize;
848: Object newList = evt.getNewValue();
849: updateList(oldList, oldSize, newList);
850: }
851: }
852:
853: /**
854: * Handles ListDataEvents in the list model.
855: */
856: private final class ListDataChangeHandler implements
857: ListDataListener {
858:
859: /**
860: * Sent after the indices in the index0, index1
861: * interval have been inserted in the data model.
862: * The new interval includes both index0 and index1.
863: *
864: * @param evt a <code>ListDataEvent</code> encapsulating the
865: * event information
866: */
867: public void intervalAdded(ListDataEvent evt) {
868: int index0 = evt.getIndex0();
869: int index1 = evt.getIndex1();
870: fireIntervalAdded(index0, index1);
871: }
872:
873: /**
874: * Sent after the indices in the index0, index1 interval
875: * have been removed from the data model. The interval
876: * includes both index0 and index1.
877: *
878: * @param evt a <code>ListDataEvent</code> encapsulating the
879: * event information
880: */
881: public void intervalRemoved(ListDataEvent evt) {
882: int index0 = evt.getIndex0();
883: int index1 = evt.getIndex1();
884: fireIntervalRemoved(index0, index1);
885: }
886:
887: /**
888: * Sent when the contents of the list has changed in a way
889: * that's too complex to characterize with the previous
890: * methods. For example, this is sent when an item has been
891: * replaced. Index0 and index1 bracket the change.
892: *
893: * @param evt a <code>ListDataEvent</code> encapsulating the
894: * event information
895: */
896: public void contentsChanged(ListDataEvent evt) {
897: fireContentsChanged(evt.getIndex0(), evt.getIndex1());
898: }
899:
900: }
901:
902: }
|