Package DisplayProject.binding.list

Source Code of DisplayProject.binding.list.SelectionInList$SelectionIndexChangeHandler

/*
Copyright (c) 2003-2009 ITerative Consulting Pty Ltd. All Rights Reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted
provided that the following conditions are met:

o Redistributions of source code must retain the above copyright notice, this list of conditions and
the following disclaimer.
 
o Redistributions in binary form must reproduce the above copyright notice, this list of conditions
and the following disclaimer in the documentation and/or other materials provided with the distribution.
   
o This jcTOOL Helper Class software, whether in binary or source form may not be used within,
or to derive, any other product without the specific prior written permission of the copyright holder

 
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


*/
package DisplayProject.binding.list;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import java.util.List;

import javax.swing.AbstractListModel;
import javax.swing.ListModel;
import javax.swing.event.EventListenerList;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;

import DisplayProject.binding.value.AbstractValueModel;
import DisplayProject.binding.value.ValueHolder;
import DisplayProject.binding.value.ValueModel;
import Framework.UsageException;


/**
* Represents a selection in a list of objects. Provides bound bean properties
* for the list, the selection, the selection index, and the selection empty
* state. The SelectionInList implements ValueModel with the selection as value.
* Selection changes fire an event only if the old and new value are not equal.
* If you need to compare the identity you can use and observe the selection
* index instead of the selection or value.<p>
*
* The SelectionInList uses three ValueModels to hold the list, the selection
* and selection index and provides bound bean properties for these models.
* You can access, observe and replace these ValueModels. This is useful
* to connect a SelectionInList with other ValueModels; for example you can
* use the SelectionInList's selection holder as bean channel for a
* PresentationModel. Since the SelectionInList is a ValueModel, it is often
* used as bean channel. See the Binding tutorial classes for examples on how
* to connect a SelectionInList with a PresentationModel.<p>
*
* This class also implements the {@link ListModel} interface that allows
* API users to observe fine grained changes in the structure and contents
* of the list. Hence instances of this class can be used directly as model of
* a JList. If you want to use a SelectionInList with a JComboBox or JTable,
* you can convert the SelectionInList to the associated component model
* interfaces using the adapter classes
* {@link DisplayProject.binding.adapter.ComboBoxAdapter}
* and {@link DisplayProject.binding.adapter.AbstractTableAdapter} respectively.
* These classes are part of the Binding library too.<p>
*
* The SelectionInList supports two list types as content of its list holder:
* <code>List</code> and <code>ListModel</code>. The two modes differ in how
* precise this class can fire events about changes to the content and structure
* of the list. If you use a List, this class can only report
* that the list changes completely; this is done by emitting
* a PropertyChangeEvent for the <em>list</em> property.
* Also, a <code>ListDataEvent</code> is fired that reports a complete change.
* In contrast, if you use a ListModel it will report the same
* PropertyChangeEvent. But fine grained changes in the list
* model will be fired by this class to notify observes about changes in
* the content, added and removed elements.<p>
*
* If the list content doesn't change at all, or if it always changes
* completely, you can work well with both List content and ListModel content.
* But if the list structure or content changes, the ListModel reports more
* fine grained events to registered ListDataListeners, which in turn allows
* list views to chooser better user interface gestures: for example, a table
* with scroll pane may retain the current selection and scroll offset.<p>
*
* An example for using a ListModel in a SelectionInList is the asynchronous
* transport of list elements from a server to a client. Let's say you transport
* the list elements in portions of 10 elements to improve the application's
* responsiveness. The user can then select and work with the SelectionInList
* as soon as the ListModel gets populated. If at a later time more elements
* are added to the list model, the SelectionInList can retain the selection
* index (and selection) and will just report a ListDataEvent about
* the interval added. JList, JTable and JComboBox will then just add
* the new elements at the end of the list presentation.<p>
*
* If you want to combine List operations and the ListModel change reports,
* you may consider using an implementation that combines these two interfaces,
* for example {@link ArrayListModel} or {@link LinkedListModel}.<p>
*
* <strong>Imporant Note:</strong> If you change the ListModel instance,
* either by calling <code>#setListModel(ListModel)</code> or by setting
* a new value to the underlying list holder, you must ensure that
* the list holder throws a PropertyChangeEvent whenever the instance changes.
* This event is used to remove a ListDataListener from the old ListModel
* instance and is later used to add it to the new ListModel instance.
* It is easy to violate this constraint, just because Java's standard
* PropertyChangeSupport helper class that is used by many beans, checks
* a changed property value via <code>#equals</code>, not <code>==</code>.
* For example, if you change the SelectionInList's list model from an empty
* list <code>L1</code> to another empty list instance <code>L2</code>,
* the PropertyChangeSupport won't generate a PropertyChangeEvent,
* and so, the SelectionInList won't know about the change, which
* may lead to unexpected behavior.<p>
*
* This binding library provides some help for firing PropertyChangeEvents
* if the old ListModel and new ListModel are equal but not the same.
* Class {@link DisplayProject.binding.beans.ExtendedPropertyChangeSupport}
* allows to permanently or individually check the identity (using
* <code>==</code>) instead of checking the equity (using <code>#equals</code>).
* Class {@link DisplayProject.binding.beans.Model} uses this extended
* property change support. And class {@link ValueHolder} uses it too
* and can be configured to always test the identity.<p>
*
* Since version 1.0.2 this class provides public convenience methods
* for firing ListDataEvents, see the methods <code>#fireContentsChanged</code>,
* <code>#fireIntervalAdded</code>, and <code>#fireIntervalRemoved</code>.
* These are automatically invoked if the list holder holds a ListModel
* that fires these events. If on the other hand the underlying List or
* ListModel does not fire a required ListDataEvent, you can use these
* methods to notify presentations about a change. It is recommended
* to avoid sending duplicate ListDataEvents; hence check if the underlying
* ListModel fires the necessary events or not. Typically an underlying
* ListModel will fire the add and remove events; but often it'll lack
* an event if the (seletcted) contents has changed. A convenient way to
* indicate that change is <code>#fireSelectedContentsChanged</code>. See
* the tutorial's AlbumManagerModel for an example how to use this feature.<p>
*
* <strong>Constraints:</strong> The list holder holds instances of {@link List}
* or {@link ListModel}, the selection holder values of type <code>Object</code>
* and the selection index holder of type <code>Integer</code>. The selection
* index holder must hold non-null index values; however, when firing
* an index value change event, both the old and new value may be null.
* If the ListModel changes, the underyling ValueModel must fire
* a PropertyChangeEvent.<p>
*
*
* @author  Karsten Lentzsch
* @version $Revision: 1.35 $
*
* @see     ValueModel
* @see     List
* @see     ListModel
* @see     DisplayProject.binding.adapter.ComboBoxAdapter
* @see     DisplayProject.binding.adapter.AbstractTableAdapter
* @see     DisplayProject.binding.beans.ExtendedPropertyChangeSupport
* @see     DisplayProject.binding.beans.Model
* @see     DisplayProject.binding.value.ValueHolder
* Copyright (c) 2002-2005 JGoodies Karsten Lentzsch. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*  o Redistributions of source code must retain the above copyright notice,
*    this list of conditions and the following disclaimer.
*    
*  o Redistributions in binary form must reproduce the above copyright notice,
*    this list of conditions and the following disclaimer in the documentation
*    and/or other materials provided with the distribution.
*    
*  o Neither the name of JGoodies Karsten Lentzsch nor the names of
*    its contributors may be used to endorse or promote products derived
*    from this software without specific prior written permission.
*    
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/

@SuppressWarnings("serial")
public final class SelectionInList extends AbstractValueModel
    implements ListModel {


    // Constant Names for Bound Properties ************************************

    /**
     * The name of the bound write-only <em>list</em> property.
     */
    public static final String PROPERTYNAME_LIST = "list";

    /**
     * The name of the bound read-write <em>list holder</em> property.
     */
    public static final String PROPERTYNAME_LIST_HOLDER = "listHolder";

    /**
     * The name of the bound read-write <em>selection</em> property.
     */
    public static final String PROPERTYNAME_SELECTION = "selection";

    /**
     * The name of the bound read-only <em>selectionEmpty</em> property.
     */
    public static final String PROPERTYNAME_SELECTION_EMPTY = "selectionEmpty";

    /**
     * The name of the bound read-write <em>selection holder</em> property.
     */
    public static final String PROPERTYNAME_SELECTION_HOLDER = "selectionHolder";

    /**
     * The name of the bound read-write <em>selectionIndex</em> property.
     */
    public static final String PROPERTYNAME_SELECTION_INDEX = "selectionIndex";

    /**
     * The name of the bound read-write <em>selection index holder</em> property.
     */
    public static final String PROPERTYNAME_SELECTION_INDEX_HOLDER = "selectionIndexHolder";


    // ************************************************************************

    /**
     * A special index that indicates that we have no selection.
     */
    private static final int NO_SELECTION_INDEX = -1;

    /**
     * An empty <code>ListModel</code> that is used if the list holder's
     * content is null.
     *
     * @see #getListModel()
     */
    private static final ListModel EMPTY_LIST_MODEL =
        new EmptyListModel();

    /**
     * Holds a <code>List</code> or <code>ListModel</code> that in turn
     * holds the elements.
     */
    private ValueModel listHolder;

    /**
     * Holds a copy of the listHolder's value. Used as the old list
     * when the listHolder's value changes. Required because a ValueModel
     * may use <code>null</code> as old value, but the SelectionInList
     * must know about the old and the new list.
     */
    private Object list;

    /**
     * Holds the selection, an instance of <code>Object</code>.         
     */
    private ValueModel selectionHolder;

    /**
     * Holds the selection index, an <code>Integer</code>.          
     */
    private ValueModel selectionIndexHolder;

    /**
     * Handles changes of the list.
     */
    private final PropertyChangeListener listChangeHandler;

    /**
     * Handles structural and content changes of the list model.
     */
    private final ListDataListener listDataChangeHandler;

    /**
     * The <code>PropertyChangeListener</code> used to handle
     * changes of the selection.
     */
    private final PropertyChangeListener selectionChangeHandler;

    /**
     * The <code>PropertyChangeListener</code> used to handle
     * changes of the selection index.
     */
    private final PropertyChangeListener selectionIndexChangeHandler;

    /**
     * Refers to the list of list data listeners that is used to notify
     * registered listeners if the list model changes.
     */
    private final EventListenerList listenerList = new EventListenerList();

    /**
     * Duplicates the value of the selectionHolder.
     * Used to provide better old values in PropertyChangeEvents
     * fired after selectionIndex changes.
     */
    private Object oldSelection;

    /**
     * Duplicates the value of the selectionIndexHolder.
     * Used to provide better old values in PropertyChangeEvents
     * fired after selectionIndex changes and selection changes.
     */
    private int oldSelectionIndex;


    // Instance creation ****************************************************

    /**
     * Constructs a SelectionInList with an empty initial
     * <code>ArrayListModel</code> using defaults for the selection holder
     * and selection index holder.
     */
    public SelectionInList() {    
        this((ListModel) new ArrayListModel<Object>());
    }


    /**
     * Constructs a SelectionInList on the given item array
     * using defaults for the selection holder and selection index holder.
     * The specified array will be converted to a List.<p>
     *
     * Changes to the list "write through" to the array, and changes
     * to the array contents will be reflected in the list.
     * 
     * @param listItems        the array of initial items
     *
     * @throws NullPointerException if <code>listItems</code> is <code>null</code>
     */
    public SelectionInList(Object[] listItems) {    
        this(Arrays.asList(listItems));
    }


    /**
     * Constructs a SelectionInList on the given item array and
     * selection holder using a default selection index holder.
     * The specified array will be converted to a List.<p>
     *
     * Changes to the list "write through" to the array, and changes
     * to the array contents will be reflected in the list.
     * 
     * @param listItems        the array of initial items
     * @param selectionHolder  holds the selection
     *
     * @throws NullPointerException if <code>listItems</code> or
     *     <code>selectionHolder</code> is <code>null</code>
     */
    public SelectionInList(Object[] listItems, ValueModel selectionHolder) {    
        this(Arrays.asList(listItems), selectionHolder);
    }


    /**
     * Constructs a SelectionInList on the given item array and
     * selection holder using a default selection index holder.
     * The specified array will be converted to a List.<p>
     *
     * Changes to the list "write through" to the array, and changes
     * to the array contents will be reflected in the list.
     * 
     * @param listItems        the array of initial items
     * @param selectionHolder  holds the selection
     * @param selectionIndexHolder  holds the selection index
     *
     * @throws NullPointerException if <code>listItems</code>,
     *     <code>selectionHolder</code>, or <code>selectionIndexHolder</code>
     *     is <code>null</code>
     */
    public SelectionInList(
            Object[] listItems,
            ValueModel selectionHolder,
            ValueModel selectionIndexHolder) {    
        this(Arrays.asList(listItems), selectionHolder, selectionIndexHolder);
    }


    /**
     * Constructs a SelectionInList on the given list
     * using defaults for the selection holder and selection index holder.<p>
     *
     * <strong>Note:</strong> Favor <code>ListModel</code> over
     * <code>List</code> when working with the SelectionInList.
     * Why? The SelectionInList can work with both types. What's the
     * difference? ListModel provides all list access features
     * required by the SelectionInList's. In addition it reports more
     * fine grained change events, instances of <code>ListDataEvents</code>.
     * In contrast developer often create Lists and operate on them
     * and the ListModel may be inconvenient for these operations.<p>
     *
     * A convenient solution for this situation is to use the
     * <code>ArrayListModel</code> and <code>LinkedListModel</code> classes.
     * These implement both List and ListModel, offer the standard List
     * operations and report the fine grained ListDataEvents.
     * 
     * @param list        the initial list
     */
    public SelectionInList(List<?> list) {    
        this(new ValueHolder(list, true));
    }


    /**
     * Constructs a SelectionInList on the given list and
     * selection holder using a default selection index holder.<p>
     *
     * <strong>Note:</strong> Favor <code>ListModel</code> over
     * <code>List</code> when working with the SelectionInList.
     * Why? The SelectionInList can work with both types. What's the
     * difference? ListModel provides all list access features
     * required by the SelectionInList's. In addition it reports more
     * fine grained change events, instances of <code>ListDataEvents</code>.
     * In contrast developer often create Lists and operate on them
     * and the ListModel may be inconvenient for these operations.<p>
     *
     * A convenient solution for this situation is to use the
     * <code>ArrayListModel</code> and <code>LinkedListModel</code> classes.
     * These implement both List and ListModel, offer the standard List
     * operations and report the fine grained ListDataEvents.
     * 
     * @param list             the initial list
     * @param selectionHolder  holds the selection
     *
     * @throws NullPointerException
     *     if <code>selectionHolder</code> is <code>null</code>
     */
    public SelectionInList(List<?> list, ValueModel selectionHolder) {    
        this(new ValueHolder(list, true), selectionHolder);
    }


    /**
     * Constructs a SelectionInList on the given list,
     * selection holder, and selection index holder.<p>
     *
     * <strong>Note:</strong> Favor <code>ListModel</code> over
     * <code>List</code> when working with the SelectionInList.
     * Why? The SelectionInList can work with both types. What's the
     * difference? ListModel provides all list access features
     * required by the SelectionInList's. In addition it reports more
     * fine grained change events, instances of <code>ListDataEvents</code>.
     * In contrast developer often create Lists and operate on them
     * and the ListModel may be inconvenient for these operations.<p>
     *
     * A convenient solution for this situation is to use the
     * <code>ArrayListModel</code> and <code>LinkedListModel</code> classes.
     * These implement both List and ListModel, offer the standard List
     * operations and report the fine grained ListDataEvents.
     * 
     * @param list                  the initial list
     * @param selectionHolder       holds the selection
     * @param selectionIndexHolder  holds the selection index
     *
     * @throws NullPointerException if <code>selectionHolder</code>,
     *     or <code>selectionIndexHolder</code> is <code>null</code>
     */
    public SelectionInList(
            List<?> list,
            ValueModel selectionHolder,
            ValueModel selectionIndexHolder) {    
        this(new ValueHolder(list, true),
                selectionHolder,
                selectionIndexHolder);
    }


    /**
     * Constructs a SelectionInList on the given list model
     * using defaults for the selection holder and selection index holder.
     *
     * @param listModel        the initial list model
     */
    public SelectionInList(ListModel listModel) {    
        this(new ValueHolder(listModel, true));
    }


    /**
     * Constructs a SelectionInList on the given list model
     * and selection holder using a default selection index holder.
     *
     * @param listModel        the initial list model
     * @param selectionHolder  holds the selection
     *
     * @throws NullPointerException
     *     if <code>selectionHolder</code> is <code>null</code>
     */
    public SelectionInList(ListModel listModel, ValueModel selectionHolder) {    
        this(new ValueHolder(listModel, true), selectionHolder);
    }


    /**
     * Constructs a SelectionInList on the given list model,
     * selection holder, and selection index holder.
     *
     * @param listModel             the initial list model
     * @param selectionHolder       holds the selection
     * @param selectionIndexHolder  holds the selection index
     *
     * @throws NullPointerException if <code>selectionHolder</code>,
     *     or <code>selectionIndexHolder</code> is <code>null</code>
     */
    public SelectionInList(
            ListModel listModel,
            ValueModel selectionHolder,
            ValueModel selectionIndexHolder) {    
        this(new ValueHolder(listModel, true),
                selectionHolder,
                selectionIndexHolder);
    }


    /**
     * Constructs a SelectionInList on the given list holder
     * using defaults for the selection holder and selection index holder.
     *
     * @param listHolder          holds the list or list model
     *
     * @throws NullPointerException
     *     if <code>listHolder</code> is <code>null</code>
     */
    public SelectionInList(ValueModel listHolder) {    
        this(listHolder, new ValueHolder());
    }


    /**
     * Constructs a SelectionInList on the given list holder,
     * selection holder and selection index holder.
     *
     * @param listHolder             holds the list or list model
     * @param selectionHolder        holds the selection
     * @throws NullPointerException  if <code>listHolder</code>
     *     or <code>selectionHolder</code> is <code>null</code>
     */
    public SelectionInList(ValueModel listHolder, ValueModel selectionHolder) {
        this(
            listHolder,
            selectionHolder,
            new ValueHolder(new Integer(NO_SELECTION_INDEX)));
    }


    /**
     * Constructs a SelectionInList on the given list holder,
     * selection holder and selection index holder.
     *
     * @param listHolder               holds the list or list model
     * @param selectionHolder          holds the selection
     * @param selectionIndexHolder     holds the selection index
     * @throws NullPointerException    if <code>listHolder</code>,
     *     <code>selectionHolder</code>, or <code>selectionIndexHolder</code>
     *     is <code>null</code>
     */
    public SelectionInList(
        ValueModel listHolder,
        ValueModel selectionHolder,
        ValueModel selectionIndexHolder) {
        if (listHolder == null)
            throw new NullPointerException("The list holder must not be null.");
        if (selectionHolder == null)
            throw new NullPointerException("The selection holder must not be null.");
        if (selectionIndexHolder == null)
            throw new NullPointerException("The selection index holder must not be null.");

        listChangeHandler           = new ListChangeHandler();
        listDataChangeHandler       = new ListDataChangeHandler();
        selectionChangeHandler      = new SelectionChangeHandler();
        selectionIndexChangeHandler = new SelectionIndexChangeHandler();

        this.listHolder = listHolder;
        this.selectionHolder = selectionHolder;
        this.selectionIndexHolder = selectionIndexHolder;
        initializeSelectionIndex();

        this.listHolder.addValueChangeListener(listChangeHandler);
        this.selectionHolder.addValueChangeListener(selectionChangeHandler);
        this.selectionIndexHolder.addValueChangeListener(selectionIndexChangeHandler);

        // If the ValueModel holds a ListModel observe list data changes too.
        list = listHolder.getValue();
        if (list != null && (list instanceof ListModel)) {
            ((ListModel) list).addListDataListener(listDataChangeHandler);
        }
    }


    // ListModel Implementation ***********************************************

    /**
     * Returns the length of the list, <code>0</code> if the list model
     * is <code>null</code>.
     *
     * @return the size of the list, <code>0</code> if the list model is
     *     <code>null</code>
     */
    public int getSize() {
        return getSize(getListHolder().getValue());
    }

    /**
     * Returns the length of the given list, <code>0</code> if the list model
     * is <code>null</code>.
     *
     * @return the size of the given list, <code>0</code> if the list model is
     *     <code>null</code>
     */
    private int getSize(Object aList) {
        if (aList == null)
            return 0;
        else if (aList instanceof ListModel)
            return ((ListModel) aList).getSize();
        else
            return ((List<?>) aList).size();
    }

    /**
     * Returns the value at the specified index, <code>null</code>
     * if the list model is <code>null</code>
     *
     * @param index  the requested index
     * @return the value at <code>index</code>, <code>null</code>
     *      if the list model is <code>null</code>
     */
    public Object getElementAt(int index) {
        return getElementAt(getListHolder().getValue(), index);
    }

    private Object getElementAt(Object aList, int index) {
        if (aList == null)
            throw new NullPointerException("The list contents is null.");
        else if (aList instanceof ListModel)
            return ((ListModel) aList).getElementAt(index);
        else
            return ((List<?>) aList).get(index);
    }

    private Object getSafeElementAt(int index) {
        return (index < 0 || index >= getSize())
            ? null
            : getElementAt(index);
    }

    /**
     * Adds a listener to the list that's notified each time a change
     * to the data model occurs.
     *
     * @param l the <code>ListDataListener</code> to be added
     */ 
    public void addListDataListener(ListDataListener l) {
        listenerList.add(ListDataListener.class, l);
    }


    /**
     * Removes a listener from the list that's notified each time a
     * change to the data model occurs.
     *
     * @param l the <code>ListDataListener</code> to be removed
     */ 
    public void removeListDataListener(ListDataListener l) {
        listenerList.remove(ListDataListener.class, l);
    }


    /**
     * Returns an array of all the list data listeners
     * registered on this <code>SelectionInList</code>.
     *
     * @return all of this model's <code>ListDataListener</code>s,
     *         or an empty array if no list data listeners
     *         are currently registered
     *
     * @see #addListDataListener(ListDataListener)
     * @see #removeListDataListener(ListDataListener)
     */
    public ListDataListener[] getListDataListeners() {
        return (ListDataListener[]) listenerList.getListeners(
                ListDataListener.class);
    }


    // ListModel Helper Code **************************************************

    /**
     * Notifies all registered ListDataListeners that the contents
     * of the selected list item - if any - has changed.
     * Useful to update a presentation after editing the selection.
     * See the tutorial's AlbumManagerModel for an example how to use
     * this feature.<p>
     *
     * If the list holder holds a ListModel, this SelectionInList listens
     * to ListDataEvents fired by that ListModel, and forwards these events
     * by invoking the associated <code>#fireXXX</code> method, which in turn
     * notifies all registered ListDataListeners. Therefore if you fire
     * ListDataEvents in an underlying ListModel, you don't need this method
     * and should not use it to avoid sending duplicate ListDataEvents.
     *
     * @see ListModel
     * @see ListDataListener
     * @see ListDataEvent
     *
     * @since 1.0.2
     */
    public void fireSelectedContentsChanged() {
        if (hasSelection()) {
            int selectionIndex = getSelectionIndex();
            fireContentsChanged(selectionIndex, selectionIndex);
        }
    }


    /**
     * Notifies all registered ListDataListeners that the contents
     * of one or more list elements has changed. 
     * The changed elements are specified by the closed interval index0, index1
     * -- the end points are included. Note that index0 need not be less than
     * or equal to index1.<p>
     *
     * If the list holder holds a ListModel, this SelectionInList listens
     * to ListDataEvents fired by that ListModel, and forwards these events
     * by invoking the associated <code>#fireXXX</code> method, which in turn
     * notifies all registered ListDataListeners. Therefore if you fire
     * ListDataEvents in an underlying ListModel, you don't need this method
     * and should not use it to avoid sending duplicate ListDataEvents.
     *
     * @param index0 one end of the new interval
     * @param index1 the other end of the new interval
     *
     * @see ListModel
     * @see ListDataListener
     * @see ListDataEvent
     *
     * @since 1.0.2
     */
    public void fireContentsChanged(int index0, int index1) {
        Object[] listeners = listenerList.getListenerList();
        ListDataEvent e = null;

        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == ListDataListener.class) {
                if (e == null) {
                    e = new ListDataEvent(this,
                            ListDataEvent.CONTENTS_CHANGED, index0, index1);
                }
                ((ListDataListener) listeners[i + 1]).contentsChanged(e);
            }
        }
    }


    /**
     * Notifies all registered ListDataListeners that one or more elements
     * have been added to this SelectionInList's List/ListModel.
     * The new elements are specified by a closed interval index0, index1
     * -- the end points are included. Note that index0 need not be less than
     * or equal to index1.<p>
     *
     * If the list holder holds a ListModel, this SelectionInList listens
     * to ListDataEvents fired by that ListModel, and forwards these events
     * by invoking the associated <code>#fireXXX</code> method, which in turn
     * notifies all registered ListDataListeners. Therefore if you fire
     * ListDataEvents in an underlying ListModel, you don't need this method
     * and should not use it to avoid sending duplicate ListDataEvents.
     *
     * @param index0 one end of the new interval
     * @param index1 the other end of the new interval
     *
     * @see ListModel
     * @see ListDataListener
     * @see ListDataEvent
     *
     * @since 1.0.2
     */
    public void fireIntervalAdded(int index0, int index1) {
        Object[] listeners = listenerList.getListenerList();
        ListDataEvent e = null;

        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == ListDataListener.class) {
                if (e == null) {
                    e = new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index0, index1);
                }
                ((ListDataListener) listeners[i + 1]).intervalAdded(e);
            }
        }
    }


    /**
     * Notifies all registered ListDataListeners that one or more elements
     * have been removed from this SelectionInList's List/ListModel.
     * <code>index0</code> and <code>index1</code> are the end points
     * of the interval that's been removed.  Note that <code>index0</code>
     * need not be less than or equal to <code>index1</code>.<p>
     *
     * If the list holder holds a ListModel, this SelectionInList listens
     * to ListDataEvents fired by that ListModel, and forwards these events
     * by invoking the associated <code>#fireXXX</code> method, which in turn
     * notifies all registered ListDataListeners. Therefore if you fire
     * ListDataEvents in an underlying ListModel, you don't need this method
     * and should not use it to avoid sending duplicate ListDataEvents.
     *
     * @param index0 one end of the removed interval,
     *               including <code>index0</code>
     * @param index1 the other end of the removed interval,
     *               including <code>index1</code>
     *              
     * @see ListModel
     * @see ListDataListener
     * @see ListDataEvent
     *
     * @since 1.0.2
     */
    public void fireIntervalRemoved(int index0, int index1) {
        Object[] listeners = listenerList.getListenerList();
        ListDataEvent e = null;

        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == ListDataListener.class) {
                if (e == null) {
                    e = new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index0, index1);
                }
                ((ListDataListener) listeners[i + 1]).intervalRemoved(e);
            }
        }
    }


    // Accessing the List, Selection and Index ********************************

    /**
     * Checks and answers if the list is empty or <code>null</code>.
     *
     * @return true if the list is empty or <code>null</code>, false otherwise
     */
    public boolean isEmpty() {
        return getSize() == 0;
    }


    /**
     * Returns the contents of the list holder as <code>ListModel</code>.
     * If the list content is <code>null</code> an empty <code>ListModel</code>
     * is returned; if it is a <code>ListModel</code> the content is returned;
     * otherwise it is a <code>List</code> that will be converted to a
     * <code>ListModel</code> using a wrapper.
     *
     * @return the list content as <code>ListModel</code>
     *
     * @see #setList(List)
     * @see #setListModel(ListModel)
     */
    public ListModel getListModel() {
        Object aList = getListHolder().getValue();
        if (aList == null)
            return EMPTY_LIST_MODEL;
        else if (aList instanceof ListModel)
            return (ListModel) aList;
        else
            return new ListModelAdapter((List<?>) aList);
    }

    /**
     * Sets the given list model as value of the list holder.
     *
     * @param newListModel   the list model to be set as new list content
     *
     * @see #getListModel()
     * @see #setList(List)
     */
    public void setListModel(ListModel newListModel) {
        getListHolder().setValue(newListModel);
    }


    /**
     * Sets the given list as value of the list holder.<p>
     *
     * <strong>Note:</strong> Favor <code>ListModel</code> over
     * <code>List</code> when working with the SelectionInList.
     * Why? The SelectionInList can work with both types. What's the
     * difference? ListModel provides all list access features
     * required by the SelectionInList's. In addition it reports more
     * fine grained change events, instances of <code>ListDataEvents</code>.
     * In contrast developer often create Lists and operate on them
     * and the ListModel may be inconvenient for these operations.<p>
     *
     * A convenient solution for this situation is to use the
     * <code>ArrayListModel</code> and <code>LinkedListModel</code> classes.
     * These implement both List and ListModel, offer the standard List
     * operations and report the fine grained ListDataEvents.
     *
     * @param newList   the list to be set as new list content
     *
     * @see #setListModel(ListModel)
     */
    public void setList(List<?> newList) {
        getListHolder().setValue(newList);
    }


    /**
     * Looks up and returns the current selection using
     * the current selection index. Returns <code>null</code> if
     * no object is selected or if the list has no elements.
     *
     * @return the current selection, <code>null</code> if none is selected
     */
    public Object getSelection() {
        return getSafeElementAt(getSelectionIndex());
    }

    /**
     * Sets the first list element that equals the given new selection
     * as new selection. Does nothing if the list is empty.
     *
     * @param newSelection   the object to be set as new selection
     */
    public void setSelection(Object newSelection) {
        if (!isEmpty())
            setSelectionIndex(indexOf(newSelection));
    }

    /**
     * Checks and answers if an element is selected.
     *
     * @return true if an element is selected, false otherwise
     */
    public boolean hasSelection() {
        return getSelectionIndex() != NO_SELECTION_INDEX;
    }


    /**
     * Checks and answers whether the selection is empty or not.
     * Unlike #hasSelection, the underlying property #selectionEmpty
     * for this method is bound. I.e. you can observe this property
     * using a PropertyChangeListener to update UI state.
     *
     * @return true if nothing is selected, false if there's a selection
     * @see #clearSelection
     * @see #hasSelection
     */
    public boolean isSelectionEmpty() {
        return !hasSelection();
    }


    /**
     * Clears the selection of this SelectionInList - if any.
     */
    public void clearSelection() {
        setSelectionIndex(NO_SELECTION_INDEX);
    }


    /**
     * Returns the selection index.
     *
     * @return the selection index
     *
     * @throws NullPointerException if the selection index holder
     *     has a null Object set
     */
    public int getSelectionIndex() {
        return ((Integer) getSelectionIndexHolder().getValue()).intValue();
    }

    /**
     * Sets a new selection index. Does nothing if it is the same as before.
     *
     * @param newSelectionIndex   the selection index to be set
     * @throws IndexOutOfBoundsException if the new selection index
     *    is outside the bounds of the list
     */
    public void setSelectionIndex(int newSelectionIndex) {
        if (newSelectionIndex < NO_SELECTION_INDEX || newSelectionIndex > getSize())
            throw new IndexOutOfBoundsException(
                    "The selection index must be between -1 and " + getSize());

        oldSelectionIndex = getSelectionIndex();
        if (oldSelectionIndex == newSelectionIndex)
            return;

        getSelectionIndexHolder().setValue(new Integer(newSelectionIndex));
    }


    // Accessing the Holders for: List, Selection and Index *******************

    /**
     * Returns the list holder.
     *
     * @return the list holder
     */
    public ValueModel getListHolder() {
        return listHolder;
    }

    /**
     * Sets a new list holder. Does nothing if old and new holder are equal.
     * Removes the list change handler from the old holder and adds
     * it to the new one. In case the list holder contents is a ListModel,
     * the list data change handler is updated too by invoking
     * <code>#updateListDataRegistration</code> in the same way as done in the
     * list change handler.<p>
     *
     * can be performed in one step <em>after</em> the listHolder has been
     * changed - instead of remove the list data change handler, then
     * changing the listHolder, and finally adding the list data change handler.
     *
     * @param newListHolder   the list holder to be set
     * @throws NullPointerException if the new list holder is <code>null</code>
     */
    public void setListHolder(ValueModel newListHolder) {
        if (newListHolder == null)
            throw new NullPointerException("The new list holder must not be null.");

        ValueModel oldListHolder = getListHolder();
        if (equals(oldListHolder, newListHolder))
            return;

        Object oldList = list;
        Object newList = newListHolder.getValue();

        oldListHolder.removeValueChangeListener(listChangeHandler);
        listHolder = newListHolder;
        newListHolder.addValueChangeListener(listChangeHandler);

        updateList(oldList, newList);
        firePropertyChange(PROPERTYNAME_LIST_HOLDER,
                            oldListHolder,
                            newListHolder);
    }

    /**
     * Returns the selection holder.
     *
     * @return the selection holder
     */
    public ValueModel getSelectionHolder() {
        return selectionHolder;
    }

    /**
     * Sets a new selection holder.
     * Does nothing if the new is the same as before.
     * The selection remains unchanged and is still driven
     * by the selection index holder. It's just that future
     * index changes will update the new selection holder
     * and that future selection holder changes affect the
     * selection index.
     *
     * @param newSelectionHolder   the selection holder to set
     *
     * @throws NullPointerException if the new selection holder is null
     */
    public void setSelectionHolder(ValueModel newSelectionHolder) {
        if (newSelectionHolder == null)
            throw new NullPointerException("The new selection holder must not be null.");

        ValueModel oldSelectionHolder = getSelectionHolder();
        if (equals(oldSelectionHolder, newSelectionHolder))
            return;

        oldSelectionHolder.removeValueChangeListener(selectionChangeHandler);
        selectionHolder = newSelectionHolder;
        oldSelection = newSelectionHolder.getValue();
        newSelectionHolder.addValueChangeListener(selectionChangeHandler);
        firePropertyChange(PROPERTYNAME_SELECTION_HOLDER,
                            oldSelectionHolder,
                            newSelectionHolder);
    }

    /**
     * Returns the selection index holder.
     *
     * @return the selection index holder
     */
    public ValueModel getSelectionIndexHolder() {
        return selectionIndexHolder;
    }

    /**
     * Sets a new selection index holder.
     * Does nothing if the new is the same as before.
     *
     * @param newSelectionIndexHolder   the selection index holder to set
     *
     * @throws NullPointerException if the new selection index holder is null
     * @throws UsageException if the value of the new selection index
     *     holder is null
     */
    public void setSelectionIndexHolder(ValueModel newSelectionIndexHolder) {
        if (newSelectionIndexHolder == null)
            throw new NullPointerException("The new selection index holder must not be null.");

        if (newSelectionIndexHolder.getValue() == null)
            throw new UsageException("The value of the new selection index holder must not be null.");

        ValueModel oldSelectionIndexHolder = getSelectionIndexHolder();
        if (equals(oldSelectionIndexHolder, newSelectionIndexHolder))
            return;

        oldSelectionIndexHolder.removeValueChangeListener(selectionIndexChangeHandler);
        selectionIndexHolder = newSelectionIndexHolder;
        newSelectionIndexHolder.addValueChangeListener(selectionIndexChangeHandler);
        oldSelectionIndex = getSelectionIndex();
        oldSelection = getSafeElementAt(oldSelectionIndex);
        firePropertyChange(PROPERTYNAME_SELECTION_INDEX_HOLDER,
                            oldSelectionIndexHolder,
                            newSelectionIndexHolder);
    }


    // ValueModel Implementation ********************************************

    /**
     * Returns the current selection, <code>null</code> if the selection index
     * does not represent a selection in the list.
     *
     * @return the selected element - if any
     */
    public Object getValue() {
        return getSelection();
    }

    /**
     * Sets the first list element that equals the given value as selection.
     *
     * @param newValue   the new value to set
     */
    public void setValue(Object newValue) {
        setSelection(newValue);
    }


    // Helper Code ***********************************************************

    /**
     * Returns the index in the list of the first occurrence of the specified
     * element, or -1 if the list does not contain this element.
     *
     * @param element  the element to search for
     * @return the index in the list of the first occurrence of the
     *     given element, or -1 if the list does not contain this element.
     */   
    protected int indexOf(Object element) {
        return indexOf(getListHolder().getValue(), element);
    }

    /**
     * Returns the index in the list of the first occurrence of the specified
     * element, or -1 if the list does not contain this element.
     *
     * @param aList    the list used to look up the element
     * @param element  the element to search for
     * @return the index in the list of the first occurrence of the
     *     given element, or -1 if the list does not contain this element.
     */   
    private int indexOf(Object aList, Object element) {
        if (element == null)
            return NO_SELECTION_INDEX;
        else if (getSize(aList) == 0)
            return NO_SELECTION_INDEX;

        if (aList instanceof List)
            return ((List<?>) aList).indexOf(element);

        // Search the first occurrence of element in the list model.
        ListModel listModel = (ListModel) aList;
        int size = listModel.getSize();
        for (int index = 0; index < size; index++) {
            if (element.equals(listModel.getElementAt(index)))
                return index;
        }
        return NO_SELECTION_INDEX;
    }


    /**
     * Sets the index according to the selection and initializes
     * the copied selection and selection index.
     * This method is invoked by the constructors to synchronize
     * the selection and index. No listeners are installed yet.
     */
    private void initializeSelectionIndex() {
        Object selectionValue = selectionHolder.getValue();
        if (selectionValue != null) {
            setSelectionIndex(indexOf(selectionValue));
        }
        oldSelection      = selectionValue;
        oldSelectionIndex = getSelectionIndex();
    }


    /**
     * Updates the selection index to keep the selection if possible.
     * Invoked when the list changes. First checks for empty selection
     * and an empty old list; in both cases the new selection is empty.
     * Otherwise looks up the old selection, computes the index of the
     * old selection in the new list and finally sets this index as
     * as selection index.
     *
     * @param newList   the list after the change
     */
    private void updateSelectionIndex(Object newList) {
        if (!hasSelection())
            return;

        Object oldSelectionHolderValue = getSelectionHolder().getValue();
        setSelectionIndex(indexOf(newList, oldSelectionHolderValue));
    }


    /**
     * Removes the list data change handler from the old list in case
     * it is a <code>ListModel</code> and adds it to new one in case
     * it is a <code>ListModel</code>.
     * Also updates the selection index, fires a property change for
     * the list and a contents change event.
     *
     * @param oldList   the old list content
     * @param newList   the new list content
     */
    private void updateList(Object oldList, Object newList) {
        if (oldList != null && (oldList instanceof ListModel)) {
            ((ListModel) oldList).removeListDataListener(listDataChangeHandler);
        }
        if (newList != null && (newList instanceof ListModel)) {
            ((ListModel) newList).addListDataListener(listDataChangeHandler);
        }
        list = newList;
        updateSelectionIndex(newList);
        firePropertyChange(PROPERTYNAME_LIST, oldList, newList);
        fireContentsChanged(0, Integer.MAX_VALUE);
    }


    // Helper Class ***********************************************************

    /**
     * An empty ListModel.
     */
    private static final class EmptyListModel extends AbstractListModel {

        /**
         * Returns zero to indicate an empty list.
         */
        public int getSize() { return 0; }

        /**
         * Returns <code>null</code> because this model has no elements.
         */
        public Object getElementAt(int index) { return null; }

    }

    /**
     * Converts a List to ListModel by wrapping the underlying list.
     */
    private static class ListModelAdapter extends AbstractListModel {

        private final List<?> aList;

        ListModelAdapter(List<?> list) { this.aList = list; }

        /**
     * Returns the length of the list.
     * @return the length of the list
     */
        public int getSize() { return aList.size(); }

        /**
         * Returns the value at the specified index. 
         * @param index the requested index
         * @return the value at <code>index</code>
         */
        public Object getElementAt(int index) { return aList.get(index); }

    }


    // Event Handlers *********************************************************

    /**
     * Handles changes of the list model.
     */
    private class ListChangeHandler implements PropertyChangeListener {

        /**
         * The list has been changed.
         * Notifies all registered listeners about the change.
         *
         * @param evt   the property change event to be handled
         */
        public void propertyChange(PropertyChangeEvent evt) {
            Object oldList = list;
            Object newList = evt.getNewValue();
            updateList(oldList, newList);
        }
    }


    /**
     * Handles ListDataEvents in the list model.
     */
    private class ListDataChangeHandler implements ListDataListener {

        /**
         * Sent after the indices in the index0, index1
         * interval have been inserted in the data model.
         * The new interval includes both index0 and index1.
         *
         * @param evt  a <code>ListDataEvent</code> encapsulating the
         *    event information
         */
        public void intervalAdded(ListDataEvent evt) {
            int index0 = evt.getIndex0();
            int index1 = evt.getIndex1();
            int index  = getSelectionIndex();
            fireIntervalAdded(index0, index1);
            // If the added elements are after the index; do nothing.
            if (index >= index0) {
                setSelectionIndex(index + (index1 - index0 + 1));
            }
        }


        /**
         * Sent after the indices in the index0, index1 interval
         * have been removed from the data model.  The interval
         * includes both index0 and index1.
         *
         * @param evt  a <code>ListDataEvent</code> encapsulating the
         *    event information
         */
        public void intervalRemoved(ListDataEvent evt) {
            int index0 = evt.getIndex0();
            int index1 = evt.getIndex1();
            int index  = getSelectionIndex();
            fireIntervalRemoved(index0, index1);
            if (index < index0) {
                // The removed elements are after the index; do nothing.
            } else if (index <= index1) {
                setSelectionIndex(NO_SELECTION_INDEX);
            } else {
                setSelectionIndex(index - (index1 - index0 + 1));
            }
        }


        /**
         * Sent when the contents of the list has changed in a way
         * that's too complex to characterize with the previous
         * methods. For example, this is sent when an item has been
         * replaced. Index0 and index1 bracket the change.
         *
         * @param evt  a <code>ListDataEvent</code> encapsulating the
         *    event information
         */
        public void contentsChanged(ListDataEvent evt) {
            fireContentsChanged(evt.getIndex0(), evt.getIndex1());
            updateSelectionContentsChanged(evt.getIndex0(), evt.getIndex1());
        }

        private void updateSelectionContentsChanged(int first, int last) {
            if (first < 0) return;
            int selectionIndex = getSelectionIndex();
            if (first <= selectionIndex && (selectionIndex <= last)) {
            // need to synch directly on the holder because the
            // usual methods for setting selection/-index check for
            // equality
                getSelectionHolder().setValue(getElementAt(selectionIndex));
            }
        }   

    }

    /**
     * Listens to changes of the selection.
     */
    private class SelectionChangeHandler implements PropertyChangeListener {

        /**
         * The selection has been changed. Updates the selection index holder's
         * value and notifies registered listeners about the changes - if any -
         * in the selection index, selection empty, selection, and value.<p>
         *
         * Adjusts the selection holder's value and the old selection index
         * before any event is fired. This ensures that the event old and
         * new values are consistent with the SelectionInList's state.
         *
         * @param evt   the property change event to be handled
         */
        public void propertyChange(PropertyChangeEvent evt) {
            Object oldValue     = evt.getOldValue();
            Object newSelection = evt.getNewValue();
            int newSelectionIndex = indexOf(newSelection);
            if (newSelectionIndex != oldSelectionIndex) {
                selectionIndexHolder.removeValueChangeListener(selectionIndexChangeHandler);
                selectionIndexHolder.setValue(new Integer(newSelectionIndex));
                selectionIndexHolder.addValueChangeListener(selectionIndexChangeHandler);
            }
            int theOldSelectionIndex = oldSelectionIndex;
            oldSelectionIndex = newSelectionIndex;
            oldSelection      = newSelection;
            firePropertyChange(PROPERTYNAME_SELECTION_INDEX,
                    theOldSelectionIndex,
                    newSelectionIndex);
            firePropertyChange(PROPERTYNAME_SELECTION_EMPTY,
                    theOldSelectionIndex == NO_SELECTION_INDEX,
                    newSelectionIndex    == NO_SELECTION_INDEX);
            /*
             * Implementation Note: The following two lines fire the
             * PropertyChangeEvents for the 'selection' and 'value' properties.
             * If the old and new value are equal, no event is fired.
             *
             */
            firePropertyChange(PROPERTYNAME_SELECTION, oldValue, newSelection);
            fireValueChange(oldValue, newSelection);
        }
    }

    /**
     * Listens to changes of the selection index.
     */
    private class SelectionIndexChangeHandler implements PropertyChangeListener {

        /**
         * The selection index has been changed. Updates the selection holder
         * value and notifies registered listeners about changes - if any -
         * in the selection index, selection empty, selection, and value.<p>
         *
         * Handles null old values in the index PropertyChangeEvent.
         * Ignores null new values in this events, because the selection
         * index value must always be a non-null value.<p>
         *
         * Adjusts the selection holder's value and the old selection index
         * before any event is fired. This ensures that the event old and
         * new values are consistent with the SelectionInList's state.
         *
         * @param evt   the property change event to be handled
         */
        public void propertyChange(PropertyChangeEvent evt) {
            int newSelectionIndex = getSelectionIndex();
            Object theOldSelection = oldSelection;
            //Object oldSelection = getSafeElementAt(oldSelectionIndex);
            Object newSelection = getSafeElementAt(newSelectionIndex);
            /*
             * Implementation Note: The following conditional suppresses
             * value change events if the old and new selection are equal.
             *
             */
            if (!SelectionInList.this.equals(theOldSelection, newSelection)) {
                selectionHolder.removeValueChangeListener(selectionChangeHandler);
                selectionHolder.setValue(newSelection);
                selectionHolder.addValueChangeListener(selectionChangeHandler);
            }
            int theOldSelectionIndex = oldSelectionIndex;
            oldSelectionIndex = newSelectionIndex;
            oldSelection      = newSelection;
            firePropertyChange(PROPERTYNAME_SELECTION_INDEX,
                    theOldSelectionIndex,
                    newSelectionIndex);
            firePropertyChange(PROPERTYNAME_SELECTION_EMPTY,
                    theOldSelectionIndex == NO_SELECTION_INDEX,
                    newSelectionIndex    == NO_SELECTION_INDEX);
            firePropertyChange(PROPERTYNAME_SELECTION, theOldSelection, newSelection);
            fireValueChange(theOldSelection, newSelection);
        }
    }


}
TOP

Related Classes of DisplayProject.binding.list.SelectionInList$SelectionIndexChangeHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.