/*
 * Copyright (c) 2002-2006 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.3 $
 * 
 * @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()
     */
    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);
        }

    }

}
