/* * Copyright (c) 2002-2007 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.adapter; import javax.swing.ListModel; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import javax.swing.table.AbstractTableModel; /** * An abstract implementation of the {@link javax.swing.table.TableModel} * interface that converts a {@link javax.swing.ListModel} of row elements.<p> * * This class provides default implementations for the <code>TableModel</code> * methods <code>#getColumnCount()</code> and <code>#getColumnName(int)</code>. * To use these methods you must use the constructor that accepts an * array of column names and this array must not be <code>null</code>. * If a subclass constructs itself with the column names set to <code>null</code> * it must override the methods <code>#getColumnCount()</code> and * <code>#getColumnName(int)</code>.<p> * * <strong>Example:</strong> API users subclass <code>AbstractTableAdapter</code> * and just implement the method <code>TableModel#getValueAt(int, int)</code>.<p> * * The following example implementation is based on a list of customer rows * and exposes the first and last name as well as the customer ages:<pre> * public class CustomerTableModel extends AbstractTableAdapter { * * private static final String[] COLUMN_NAMES = * { "Last Name", "First Name", "Age" }; * * public CustomerTableModel(ListModel listModel) { * super(listModel, COLUMN_NAMES); * } * * public Object getValueAt(int rowIndex, int columnIndex) { * Customer customer = (Customer) getRow(rowIndex); * switch (columnIndex) { * case 0 : return customer.getLastName(); * case 1 : return customer.getFirstName(); * case 2 : return customer.getAge(); * default: return null; * } * } * * } * </pre> * * @author Karsten Lentzsch * @version $Revision: 1.4 $ * * @see javax.swing.ListModel * @see javax.swing.JTable */ public abstract class AbstractTableAdapter extends AbstractTableModel { /** * Refers to the <code>ListModel</code> that holds the table row elements * and reports changes in the structure and content. The elements of * the list model can be requested using <code>#getRow(int)</code>. * A typical subclass will use the elements to implement the * <code>TableModel</code> method <code>#getValueAt(int, int)</code>. * * @see #getRow(int) * @see #getRowCount() * @see javax.swing.table.TableModel#getValueAt(int, int) */ private final ListModel listModel; /** * Holds an optional array of column names that is used by the * default implementation of the <code>TableModel</code> methods * <code>#getColumnCount()</code> and <code>#getColumnName(int)</code>. * * @see #getColumnCount() * @see #getColumnName(int) */ private final String[] columnNames; // Instance Creation ****************************************************** /** * Constructs an AbstractTableAdapter on the given ListModel. * Subclasses that use this constructor must override the methods * <code>#getColumnCount()</code> and <code>#getColumnName(int)</code>. * * @param listModel the ListModel that holds the row elements * @throws NullPointerException if the list model is <code>null</code> */ public AbstractTableAdapter(ListModel listModel) { this(listModel, null); } /** * Constructs an AbstractTableAdapter on the given ListModel using * the specified table column names. If the column names array is * non-<code>null</code>, it is copied to avoid external mutation.<p> * * Subclasses that invoke this constructor with a <code>null</code> column * name array must override the methods <code>#getColumnCount()</code> and * <code>#getColumnName(int)</code>. * * @param listModel the ListModel that holds the row elements * @param columnNames an optional array of column names * @throws NullPointerException if the list model is <code>null</code> */ public AbstractTableAdapter(ListModel listModel, String[] columnNames) { this.listModel = listModel; if (listModel == null) throw new NullPointerException("The list model must not be null."); if (columnNames == null) { this.columnNames = null; } else { this.columnNames = new String[columnNames.length]; System.arraycopy(columnNames, 0, this.columnNames, 0, columnNames.length); } listModel.addListDataListener(createChangeHandler()); } // TableModel Implementation ********************************************** /** * Returns the number of columns in the model. A JTable uses * this method to determine how many columns it should create and * display by default.<p> * * Subclasses must override this method if they don't provide an * array of column names in the constructor. * * @return the number of columns in the model * @throws NullPointerException if the optional column names array * has not been set in the constructor. In this case API users * must override this method. * * @see #getColumnName(int) * @see #getRowCount() */ public int getColumnCount() { return columnNames.length; } /** * Returns the name of the column at the given column index. * This is used to initialize the table's column header name. * Note: this name does not need to be unique; two columns in a table * can have the same name.<p> * * Subclasses must override this method if they don't provide an * array of column names in the constructor. * * @param columnIndex the index of the column * @return the name of the column * @throws NullPointerException if the optional column names array * has not been set in the constructor. In this case API users * must override this method. * * @see #getColumnCount() * @see #getRowCount() */ @Override public String getColumnName(int columnIndex) { return columnNames[columnIndex]; } /** * Returns the number of rows in the model. A * <code>JTable</code> uses this method to determine how many rows it * should display. This method should be quick, as it * is called frequently during rendering. * * @return the number of rows in the model * * @see #getRow(int) */ public final int getRowCount() { return listModel.getSize(); } // Misc ******************************************************************* /** * Returns the row at the specified row index. * * @param index row index in the underlying list model * @return the row at the specified row index. */ protected final Object getRow(int index) { return listModel.getElementAt(index); } // Event Handling ********************************************************* /** * Creates and returns a listener that handles changes * in the underlying list model. * * @return the listener that handles changes in the underlying ListModel */ protected ListDataListener createChangeHandler() { return new ListDataChangeHandler(); } /** * Listens to subject changes and fires a contents change event. */ private final 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) { fireTableRowsInserted(evt.getIndex0(), evt.getIndex1()); } /** * 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) { fireTableRowsDeleted(evt.getIndex0(), evt.getIndex1()); } /** * 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) { int firstRow = evt.getIndex0(); int lastRow = evt.getIndex1(); fireTableRowsUpdated(firstRow, lastRow); } } }