package org.andork.swing.table; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import javax.swing.event.TableModelEvent; import javax.swing.table.TableModel; /** * A list of row objects designed specifically to back a {@link TableModel}. * <br> * <br> * The difference from {@link java.util.List} is that the available modification * methods have been reduced to a subset that are convenient for a table to * handle, and modifications fire events to registered * {@link TableModelList.Listener Listener}s. (However, since there is no * uniform way to listen for changes in the row element objects, when you change * a row you should call {@link #fireElementsUpdated(int, int)} manually.) * * @author James * * @param <E> * the type of elements used for the rows. */ public class TableModelList<E> implements Iterable<E> { private class Iter implements Iterator<E> { ListIterator<E> wrapped; int lastIndex; public Iter() { wrapped = elements.listIterator(); } @Override public boolean hasNext() { return wrapped.hasNext(); } @Override public E next() { lastIndex = wrapped.nextIndex(); return wrapped.next(); } @Override public void remove() { wrapped.remove(); fireElementsDeleted(lastIndex, lastIndex); } } /** * A listener for a {@link TableModelList}. * * @author James * * @param <E> * the {@link TableModelList}'s row element type. */ public static interface Listener<E> { /** * Indicates the row elements present in {@code list} and their internal * values have drastically changed. */ public void dataChanged(TableModelList<E> list); /** * Indicates row elements were deleted from {@code list} at * {@code fromIndex} to {@code toIndex} (inclusive). */ public void elementsDeleted(TableModelList<E> list, int fromIndex, int toIndex); /** * Indicates row elements were inserted into {@code list} at * {@code fromIndex} to {@code toIndex} (inclusive). */ public void elementsInserted(TableModelList<E> list, int fromIndex, int toIndex); /** * Indicates row elements were in {@code list} at {@code fromIndex} to * {@code toIndex} (inclusive) were updated. */ public void elementsUpdated(TableModelList<E> list, int fromIndex, int toIndex); /** * Indicates the structure of row elements in {@code list} (e.g. that * would determine which columns to show) has changed, as well which row * elements are present. */ public void structureChanged(TableModelList<E> list); } private final List<E> elements = new ArrayList<E>(); private final List<Listener<E>> listeners = new ArrayList<>(); public void add(E element) { add(elements.size(), element); } public void add(int index, E element) { elements.add(index, element); fireElementsInserted(index, index); } public void addAll(Collection<? extends E> elements) { addAll(elements.size(), elements); } public void addAll(int index, Collection<? extends E> elements) { this.elements.addAll(index, elements); fireElementsInserted(index, index + elements.size() - 1); } public void addListener(Listener<E> listener) { if (!listeners.contains(listener)) { listeners.add(listener); } } public void clear() { if (!isEmpty()) { int size = elements.size(); elements.clear(); fireElementsDeleted(0, size - 1); } } public void fireDataChanged() { for (Listener<E> listener : listeners) { listener.dataChanged(this); } } protected void fireElementsDeleted(int fromIndex, int toIndex) { for (Listener<E> listener : listeners) { listener.elementsDeleted(this, fromIndex, toIndex); } } protected void fireElementsInserted(int fromIndex, int toIndex) { for (Listener<E> listener : listeners) { listener.elementsInserted(this, fromIndex, toIndex); } } public void fireElementsUpdated(int fromIndex, int toIndex) { for (Listener<E> listener : listeners) { listener.elementsUpdated(this, fromIndex, toIndex); } } public void fireStructureChanged() { for (Listener<E> listener : listeners) { listener.structureChanged(this); } } public E get(int index) { return elements.get(index); } public boolean isEmpty() { return elements.isEmpty(); } @Override public Iterator<E> iterator() { return new Iter(); } public void remove(E element) { int index = elements.indexOf(element); if (index >= 0) { remove(index); } } public void remove(int index) { elements.remove(index); fireElementsDeleted(index, index); } public void removeListener(Listener<E> listener) { listeners.remove(listener); } /** * Removes a sublist of elements from this list and fires an * {@link Listener#elementsDeleted(TableModelList, int, int) * elementsDeleted} event. * * @param fromIndex * low endpoint (inclusive) of the sublist to remove * @param toIndex * high endpoint (exclusive) of the sublist to remove (note that * the event will use an <i>inclusive</i> toIndex to conform to * {@link TableModelEvent} semantics!) */ public void removeSublist(int fromIndex, int toIndex) { elements.subList(fromIndex, toIndex).clear(); if (fromIndex < toIndex) { fireElementsDeleted(fromIndex, toIndex - 1); } } public void set(int index, E element) { E previous = elements.set(index, element); if (!previous.equals(element)) { fireElementsUpdated(index, index); } } public int size() { return elements.size(); } }