org.intermine.common.swing.table.GenericTableModel.java Source code

Java tutorial

Introduction

Here is the source code for org.intermine.common.swing.table.GenericTableModel.java

Source

package org.intermine.common.swing.table;

/*
 * Copyright (C) 2002-2013 FlyMine
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  See the LICENSE file for more
 * information or http://www.gnu.org/copyleft/lesser.html.
 *
 */

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.ListIterator;

import javax.swing.table.AbstractTableModel;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * A generic and extended version of the AbstractTableModel.
 * 
 * @param <T> The type of items stored in this model.
 */
public abstract class GenericTableModel<T> extends AbstractTableModel implements Iterable<T> {
    private static final long serialVersionUID = 5726481155581036139L;

    /**
     * A logger for this model.
     */
    protected transient Log logger = LogFactory.getLog(getClass());

    /**
     * The rows (objects) in this model.
     * <p>This is transient as the objects in this list may cause issues with serialization.</p>
     */
    protected transient ArrayList<T> rows = new ArrayList<T>();

    /**
     * Create a new GenericTableModel.
     */
    public GenericTableModel() {
    }

    /**
     * Deserialisation method. Recreates a logger and tries to deserialise the rows.
     * 
     * @param in The ObjectInputStream doing the reading.
     * 
     * @throws ClassNotFoundException if the class of a deserialised object cannot
     * be found. 
     * @throws IOException if there is a problem reading from the stream.
     * 
     * @serialData Recreates the transient logger after deserialisation, and reads the
     * rows of the model if any were written out.
     */
    private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
        logger = LogFactory.getLog(getClass());
        in.defaultReadObject();

        @SuppressWarnings("unchecked")
        ArrayList<T> fetched = (ArrayList) in.readObject();
        if (fetched == null) {
            rows = new ArrayList<T>();
        } else {
            rows = fetched;
        }
    }

    /**
     * Serialisation method. Checks whether the model objects in <code>rows</code> can be
     * written and does so if this is possible.
     * 
     * @param out The ObjectOutputStream to write to.
     * 
     * @throws IOException if there is a problem writing the object.
     * 
     * @serialData If all objects in <code>rows</code> can be serialised, the list is written
     * to the stream. Otherwise, a <code>null</code> is written and the data of this model
     * is not written. 
     */
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();

        // Strictly speaking, each item in rows may be Serializable but may link to
        // something else that is not. Really, we should perform a test write of the
        // list and proceed from there.

        boolean canSerialize = true;
        for (T next : rows) {
            if (next != null) {
                canSerialize = canSerialize && next instanceof Serializable;
            }
        }
        out.writeObject(canSerialize ? rows : null);
    }

    /**
     * Remove all rows from this model.
     */
    public void clear() {
        int size = rows.size();
        rows.clear();
        fireTableRowsDeleted(0, size - 1);
    }

    /**
     * Append a row to this model.
     * 
     * @param row The row to add. If <code>null</code>, no action is taken.
     */
    public void addRow(T row) {
        if (row != null) {
            int rowIndex = rows.size();
            rows.add(row);
            fireTableRowsInserted(rowIndex, rowIndex);
        }
    }

    /**
     * Append a collection of row objects to this model.
     * <p>Any <code>null</code> elements in <code>newRows</code> are skipped.
     * 
     * @param newRows The collection of objects to add.
     */
    public void addRows(Collection<T> newRows) {
        if (newRows != null && !newRows.isEmpty()) {
            int index = rows.size();
            int addCount = 0;
            for (T next : newRows) {
                if (next != null) {
                    rows.add(next);
                    ++addCount;
                }
            }
            if (addCount > 0) {
                fireTableRowsInserted(index, index + addCount - 1);
            }
        }
    }

    /**
     * Insert a row object at the given index.
     * 
     * @param row The object to add.
     * @param rowIndex The index at which to insert the object.
     * 
     * @throws IndexOutOfBoundsException if <code>rowIndex</code> is out of range.
     */
    public void insertRow(T row, int rowIndex) {
        if (rowIndex < 0 || rowIndex > rows.size()) {
            throw new IndexOutOfBoundsException("rowIndex must be between 0 and " + rows.size());
        }

        if (row != null) {
            rows.add(rowIndex, row);
            fireTableRowsInserted(rowIndex, rowIndex);
        }
    }

    /**
     * Replace the current objects in this model with those given.
     * 
     * @param newRows A collection of replacement row objects.
     */
    public void setRows(Collection<T> newRows) {
        if (newRows != null && rows != newRows) {
            rows.clear();
            rows.ensureCapacity(newRows.size());
            for (T next : newRows) {
                if (next != null) {
                    rows.add(next);
                }
            }
            fireTableDataChanged();
        }
    }

    /**
     * Remove the given row from this model.
     * 
     * @param row The row to remove.
     */
    public void removeRow(T row) {
        if (row != null) {
            int index = rows.indexOf(row);
            if (index >= 0) {
                rows.remove(row);
                fireTableRowsDeleted(index, index);
            }
        }
    }

    /**
     * Get the number of rows in this model.
     * 
     * @return The number of rows.
     */
    @Override
    public int getRowCount() {
        return rows.size();
    }

    /**
     * Get the row at the given index.
     * 
     * @param rowIndex The row index.
     * 
     * @return The object at the given row index.
     * 
     * @throws IndexOutOfBoundsException if <code>rowIndex</code> is out of range.
     * 
     * @see java.util.List#get(int)
     */
    public T getRowAt(int rowIndex) {
        return rows.get(rowIndex);
    }

    /**
     * Get the index in this model of the given object.
     * 
     * @param thing The object to find.
     * 
     * @return The index of <code>thing</code> in the model, or -1 if it cannot be found.
     * 
     * @see java.util.List#indexOf(Object)
     */
    public int indexOf(T thing) {
        return rows.indexOf(thing);
    }

    /**
     * Method to call when an object in this model is changed. This allows the appropriate
     * <code>TableModelEvent</code> to be raised.
     * 
     * @param row The object that has changed.
     * 
     * @see #rowUpdated(int)
     */
    public void rowUpdated(T row) {
        int index = rows.indexOf(row);
        if (index >= 0) {
            rowUpdated(index);
        }
    }

    /**
     * Fires the appropriate <code>TableModelEvent</code> indicating that the given row
     * has changed.
     * 
     * @param rowIndex The index of the row that has changed.
     */
    public void rowUpdated(int rowIndex) {
        fireTableRowsUpdated(rowIndex, rowIndex);
    }

    /**
     * Get an iterator over the rows in this model.
     * <p>All the normal rules about concurrent modification of this model and using
     * the iterator apply.</p>
     * 
     * @return An iterator.
     */
    public Iterator<T> iterator() {
        return new TableIterator();
    }

    /**
     * Get a ListIterator over the rows in this model.
     * <p>All the normal rules about concurrent modification of this model and using
     * the iterator apply.</p>
     * 
     * @return A ListIterator, initialised before the start of the rows.
     */
    public ListIterator<T> listIterator() {
        return new TableIterator();
    }

    /**
     * ListIterator implementation for the parent GenericTableModel.
     * Removals are permitted and raise the appropriate events.
     * Additions and changes are not.
     */
    private class TableIterator implements ListIterator<T> {
        /**
         * The internal ListIterator over the parent's <code>rows</code> list.
         */
        private ListIterator<T> realIterator;

        /**
         * The object currently indicated by <code>realIterator</code>.
         */
        private T current;

        /**
         * Create a new TableIterator, indicating before the start of the rows list.
         */
        public TableIterator() {
            realIterator = rows.listIterator();
        }

        /**
         * Test whether there is an element following the current element of the iterator.
         * 
         * @return <code>true</code> if there is a subsequent element, <code>false</code>
         * if not.
         * 
         * @see ListIterator#hasNext()
         */
        public boolean hasNext() {
            return realIterator.hasNext();
        }

        /**
         * Get the index of the next object indicated by this iterator.
         * 
         * @return The index of the next object.
         * 
         * @see ListIterator#nextIndex()
         */
        public int nextIndex() {
            return realIterator.nextIndex();
        }

        /**
         * Get the next element following the current element of the iterator.
         * 
         * @return The subsequent element.
         * 
         * @throws java.util.NoSuchElementException if there is no following element.
         * 
         * @see ListIterator#next()
         */
        public T next() {
            current = realIterator.next();
            return current;
        }

        /**
         * Remove the element currently indicated by this iterator from the model.
         * <p>Raises the appropriate <code>TableModelEvent</code> for the deletion
         * for the parent model.</p>
         * 
         * @see GenericTableModel#removeRow(Object)
         */
        public void remove() {
            int index = -1;
            if (current != null) {
                index = rows.indexOf(current);
            }

            realIterator.remove();
            current = null;

            if (index >= 0) {
                fireTableRowsDeleted(index, index);
            }
        }

        /**
         * Add an object to the model via this iterator. This operation is not supported.
         * 
         * @param e The object to add.
         * 
         * @throws UnsupportedOperationException always, as this is not supported.
         */
        public void add(T e) {
            throw new UnsupportedOperationException(
                    "add(T) is not supported on GenericTableModels. " + "Use addRow(T) on the model itself.");
        }

        /**
         * Check whether there are rows prior to the one currently indicated by this
         * iterator.
         * 
         * @return <code>true</code> if there is a preceding row, <code>false</code> if not.
         * 
         * @see ListIterator#hasPrevious()
         */
        public boolean hasPrevious() {
            return realIterator.hasPrevious();
        }

        /**
         * Get the index of the previous object indicated by this iterator.
         * 
         * @return The index of the previous object.
         * 
         * @see ListIterator#previousIndex()
         */
        public int previousIndex() {
            return realIterator.previousIndex();
        }

        /**
         * Get the element preceding the current element of the iterator.
         * 
         * @return The prior element.
         * 
         * @throws java.util.NoSuchElementException if there is no preceding element.
         * 
         * @see ListIterator#previous()
         */
        public T previous() {
            current = realIterator.previous();
            return current;
        }

        /**
         * Replace the current object with that given. This operation is not supported.
         * 
         * @param e The replacement object.
         * 
         * @throws UnsupportedOperationException always, as this is not supported.
         */
        public void set(T e) {
            throw new UnsupportedOperationException("set(T) is not supported on GenericTableModels.");
        }

    }
}