All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jgoodies.binding.list.LinkedListModel Maven / Gradle / Ivy

/*
 * Copyright (c) 2002-2008 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.
 */

package com.jgoodies.binding.list;

import java.util.Collection;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.NoSuchElementException;

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

/**
 * Adds {@link javax.swing.ListModel} capabilities to its superclass
 * LinkedList,  i. e. allows to observe changes in the content and
 * structure. Useful for lists that are bound to list views, for example
 * JList, JComboBox and JTable.
 *
 * @author Karsten Lentzsch
 * @version $Revision: 1.8 $
 *
 * @see ArrayListModel
 * @see java.util.ListIterator
 *
 * @param   the type of the list elements
 */
public final class LinkedListModel extends LinkedList implements ObservableList {

    private static final long serialVersionUID = 5753378113505707237L;


    // Instance Creation ******************************************************

    /**
     * Constructs an empty linked list.
     */
    public LinkedListModel() {
        // Just invoke the super constructor implicitly.
    }


    /**
     * Constructs a linked list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param  c the collection whose elements are to be placed into this list.
     * @throws NullPointerException if the specified collection is
     *     {@code null}
     */
    public LinkedListModel(Collection c) {
        super(c);
    }


    // Overriding Superclass Behavior *****************************************

    /**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted.
     * @param element element to be inserted.
     * @throws    IndexOutOfBoundsException if index is out of range
     *            (index < 0 || index > size()).
     */
    @Override
    public void add(int index, E element) {
        super.add(index, element);
        fireIntervalAdded(index, index);
    }


    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list.
     * @return {@code true} (as per the general contract of Collection.add).
     */
    @Override
    public boolean add(E e) {
        int newIndex = size();
        super.add(e);
        fireIntervalAdded(newIndex, newIndex);
        return true;
    }


    /**
     * Inserts all of the elements in the specified Collection into this
     * list, starting at the specified position.  Shifts the element
     * currently at that position (if any) and any subsequent elements to
     * the right (increases their indices).  The new elements will appear
     * in the list in the order that they are returned by the
     * specified Collection's iterator.
     *
     * @param index    index at which to insert first element
     *                 from the specified collection.
     * @param c        elements to be inserted into this list.
     * @return {@code true} if this list changed as a result of the call.
     *
     * @throws    IndexOutOfBoundsException if index out of range
     *            (index < 0 || index > size()).
     * @throws    NullPointerException if the specified Collection is null.
     */
    @Override
    public boolean addAll(int index, Collection c) {
        boolean changed = super.addAll(index, c);
        if (changed) {
            int lastIndex = index + c.size() - 1;
            fireIntervalAdded(index, lastIndex);
        }
        return changed;
    }


    /**
     * Inserts the given element at the beginning of this list.
     *
     * @param e the element to be inserted at the beginning of this list.
     */
    @Override
    public void addFirst(E e) {
        super.addFirst(e);
        fireIntervalAdded(0, 0);
    }


    /**
     * Appends the given element to the end of this list.  (Identical in
     * function to the add method; included only for consistency.)
     *
     * @param e the element to be inserted at the end of this list.
     */
    @Override
    public void addLast(E e) {
        int newIndex = size();
        super.addLast(e);
        fireIntervalAdded(newIndex, newIndex);
    }


    /**
     * Removes all of the elements from this list.  The list will
     * be empty after this call returns.
     */
    @Override
    public void clear() {
        if (isEmpty())
            return;

        int oldLastIndex = size() - 1;
        super.clear();
        fireIntervalRemoved(0, oldLastIndex);
    }


    /**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *
     * @param index the index of the element to removed.
     * @return the element that was removed from the list.
     * @throws    IndexOutOfBoundsException if index out of range
     *            (index < 0 || index >= size()).
     */
    @Override
    public E remove(int index) {
        E removedElement = super.remove(index);
        fireIntervalRemoved(index, index);
        return removedElement;
    }


    /**
     * Removes a single instance of the specified element from this
     * collection, if it is present (optional operation).  More formally,
     * removes an element e such that (o==null ? e==null :
     * o.equals(e)), if the collection contains one or more such
     * elements.  Returns {@code true} if the collection contained the
     * specified element (or equivalently, if the collection changed as a
     * result of the call).

* * This implementation iterates over the collection looking for the * specified element. If it finds the element, it removes the element * from the collection using the iterator's remove method.

* * Note that this implementation throws an * UnsupportedOperationException if the iterator returned by this * collection's iterator method does not implement the remove * method and this collection contains the specified object. * * @param o element to be removed from this collection, if present. * @return {@code true} if the collection contained the specified * element. */ @Override public boolean remove(Object o) { int index = indexOf(o); if (index == -1) { return false; } remove(index); return true; } /** * Removes and returns the first element from this list. * * @return the first element from this list. * @throws java.util.NoSuchElementException if this list is empty. */ @Override public E removeFirst() { E first = super.removeFirst(); fireIntervalRemoved(0, 0); return first; } /** * Removes and returns the last element from this list. * * @return the last element from this list. * @throws java.util.NoSuchElementException if this list is empty. */ @Override public E removeLast() { int lastIndex = size() - 1; E last = super.removeLast(); fireIntervalRemoved(lastIndex, lastIndex); return last; } /** * Removes from this List all of the elements whose index is between * fromIndex, inclusive and toIndex, exclusive. Shifts any succeeding * elements to the left (reduces their index). * This call shortens the list by (toIndex - fromIndex) elements. * (If toIndex==fromIndex, this operation has no effect.) * * @param fromIndex index of first element to be removed. * @param toIndex index after last element to be removed. */ @Override protected void removeRange(int fromIndex, int toIndex) { super.removeRange(fromIndex, toIndex); fireIntervalRemoved(fromIndex, toIndex - 1); } /** * Replaces the element at the specified position in this list with * the specified element. * * @param index index of element to replace. * @param element element to be stored at the specified position. * @return the element previously at the specified position. * @throws IndexOutOfBoundsException if index out of range * (index < 0 || index >= size()). */ @Override public E set(int index, E element) { E previousElement = super.set(index, element); fireContentsChanged(index, index); return previousElement; } /** * Returns a list-iterator of the elements in this list (in proper * sequence), starting at the specified position in the list. * Obeys the general contract of List.listIterator(int).

* * The list-iterator is fail-fast: if the list is structurally * modified at any time after the Iterator is created, in any way except * through the list-iterator's own remove or add * methods, the list-iterator will throw a * ConcurrentModificationException. Thus, in the face of * concurrent modification, the iterator fails quickly and cleanly, rather * than risking arbitrary, non-deterministic behavior at an undetermined * time in the future. * * @param index index of first element to be returned from the * list-iterator (by a call to next). * @return a ListIterator of the elements in this list (in proper * sequence), starting at the specified position in the list. * @throws IndexOutOfBoundsException if index is out of range * (index < 0 || index > size()). * @see java.util.List#listIterator(int) */ @Override public ListIterator listIterator(int index) { return new ReportingListIterator(super.listIterator(index)); } // ListModel Field ******************************************************** /** * Holds the registered ListDataListeners. The list that holds these * listeners is initialized lazily in #getEventListenerList. * * @see #addListDataListener(ListDataListener) * @see #removeListDataListener(ListDataListener) */ private EventListenerList listenerList; // ListModel Implementation *********************************************** /** * Adds a listener to the list that's notified each time a change * to the data model occurs. * * @param l the ListDataListener to be added */ public void addListDataListener(ListDataListener l) { getEventListenerList().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 ListDataListener to be removed */ public void removeListDataListener(ListDataListener l) { getEventListenerList().remove(ListDataListener.class, l); } /** * Returns the value at the specified index. * * @param index the requested index * @return the value at index */ public E getElementAt(int index) { return get(index); } /** * Returns the length of the list or 0 if there's no list. * * @return the length of the list or 0 if there's no list */ public int getSize() { return size(); } // Explicit Change Notification ******************************************* /** * Notifies all registered ListDataListeners that the element * at the specified index has changed. Useful if there's a content change * without any structural change.

* * This method must be called after the element of the list changes. * * @param index the index of the element that has changed * * @see EventListenerList */ public void fireContentsChanged(int index) { fireContentsChanged(index, index); } // ListModel Helper Code ************************************************** /** * Returns an array of all the list data listeners * registered on this LinkedListModel. * * @return all of this model's ListDataListeners, * or an empty array if no list data listeners * are currently registered * * @see #addListDataListener(ListDataListener) * @see #removeListDataListener(ListDataListener) */ public ListDataListener[] getListDataListeners() { return getEventListenerList().getListeners(ListDataListener.class); } /** * This method must be called after one or more elements * of the list change. 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. * * @param index0 one end of the new interval * @param index1 the other end of the new interval * @see EventListenerList */ private void fireContentsChanged(int index0, int index1) { Object[] listeners = getEventListenerList().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); } } } /** * This method must be called after one or more elements * are added to the model. 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. * * @param index0 one end of the new interval * @param index1 the other end of the new interval * @see EventListenerList */ private void fireIntervalAdded(int index0, int index1) { Object[] listeners = getEventListenerList().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); } } } /** * This method must be called after one or more elements * are removed from the model. * index0 and index1 are the end points * of the interval that's been removed. Note that index0 * need not be less than or equal to index1. * * @param index0 one end of the removed interval, * including index0 * @param index1 the other end of the removed interval, * including index1 * @see EventListenerList */ private void fireIntervalRemoved(int index0, int index1) { Object[] listeners = getEventListenerList().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); } } } /** * Lazily initializes and returns the event listener list used * to notify registered listeners. * * @return the event listener list used to notify listeners */ private EventListenerList getEventListenerList() { if (listenerList == null) { listenerList = new EventListenerList(); } return listenerList; } // Helper Class *********************************************************** /** * A ListIterator that fires ListDataEvents if elements are added or removed. */ private final class ReportingListIterator implements ListIterator { /** * Refers to the wrapped ListIterator that is used * to forward all ListIterator methods to. */ private final ListIterator delegate; /** * Holds the object that was returned last by the underlying * ListIteratur. Used to determine the index of the element * removed. */ private int lastReturnedIndex; ReportingListIterator(ListIterator delegate) { this.delegate = delegate; lastReturnedIndex = -1; } /** * Returns true if this list iterator has more elements when * traversing the list in the forward direction. (In other words, returns * true if next would return an element rather than * throwing an exception.) * * @return true if the list iterator has more elements when * traversing the list in the forward direction. */ public boolean hasNext() { return delegate.hasNext(); } /** * Returns the next element in the list. This method may be called * repeatedly to iterate through the list, or intermixed with calls to * previous to go back and forth. (Note that alternating calls * to next and previous will return the same element * repeatedly.) * * @return the next element in the list. * * @throws NoSuchElementException if the iteration has no next element. */ public E next() { lastReturnedIndex = nextIndex(); return delegate.next(); } /** * Returns true if this list iterator has more elements when * traversing the list in the reverse direction. (In other words, returns * true if previous would return an element rather than * throwing an exception.) * * @return true if the list iterator has more elements when * traversing the list in the reverse direction. */ public boolean hasPrevious() { return delegate.hasPrevious(); } /** * Returns the previous element in the list. This method may be called * repeatedly to iterate through the list backwards, or intermixed with * calls to next to go back and forth. (Note that alternating * calls to next and previous will return the same * element repeatedly.) * * @return the previous element in the list. * * @throws NoSuchElementException if the iteration has no previous * element. */ public E previous() { lastReturnedIndex = previousIndex(); return delegate.previous(); } /** * Returns the index of the element that would be returned by a subsequent * call to next. (Returns list size if the list iterator is at the * end of the list.) * * @return the index of the element that would be returned by a subsequent * call to next, or list size if list iterator is at end * of list. */ public int nextIndex() { return delegate.nextIndex(); } /** * Returns the index of the element that would be returned by a subsequent * call to previous. (Returns -1 if the list iterator is at the * beginning of the list.) * * @return the index of the element that would be returned by a subsequent * call to previous, or -1 if list iterator is at * beginning of list. */ public int previousIndex() { return delegate.previousIndex(); } /** * Removes from the list the last element that was returned by * next or previous (optional operation). This call can * only be made once per call to next or previous. It * can be made only if ListIterator.add has not been called after * the last call to next or previous. * * @throws UnsupportedOperationException if the remove * operation is not supported by this list iterator. * @throws IllegalStateException neither next nor * previous have been called, or remove or * add have been called after the last call to * * next or previous. */ public void remove() { int oldSize = size(); delegate.remove(); int newSize = size(); if (newSize < oldSize) LinkedListModel.this.fireIntervalRemoved(lastReturnedIndex, lastReturnedIndex); } /** * Replaces the last element returned by next or * previous with the specified element (optional operation). * This call can be made only if neither ListIterator.remove nor * ListIterator.add have been called after the last call to * next or previous. * * @param e the element with which to replace the last element returned by * next or previous. * @throws UnsupportedOperationException if the set operation * is not supported by this list iterator. * @throws ClassCastException if the class of the specified element * prevents it from being added to this list. * @throws IllegalArgumentException if some aspect of the specified * element prevents it from being added to this list. * @throws IllegalStateException if neither next nor * previous have been called, or remove or * add have been called after the last call to * next or previous. */ public void set(E e) { delegate.set(e); } /** * Inserts the specified element into the list (optional operation). The * element is inserted immediately before the next element that would be * returned by next, if any, and after the next element that * would be returned by previous, if any. (If the list contains * no elements, the new element becomes the sole element on the list.) * The new element is inserted before the implicit cursor: a subsequent * call to next would be unaffected, and a subsequent call to * previous would return the new element. (This call increases * by one the value that would be returned by a call to nextIndex * or previousIndex.) * * @param e the element to insert. * @throws UnsupportedOperationException if the add method is * not supported by this list iterator. * * @throws ClassCastException if the class of the specified element * prevents it from being added to this list. * * @throws IllegalArgumentException if some aspect of this element * prevents it from being added to this list. */ public void add(E e) { delegate.add(e); int newIndex = previousIndex(); LinkedListModel.this.fireIntervalAdded(newIndex, newIndex); lastReturnedIndex = -1; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy