Staging
v0.4.2
https://repo1.maven.org/maven2/org/prefuse/prefuse
Raw File
Table.java
package prefuse.data;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;

import javax.swing.event.TableModelEvent;

import prefuse.data.column.Column;
import prefuse.data.column.ColumnFactory;
import prefuse.data.column.ColumnMetadata;
import prefuse.data.event.ColumnListener;
import prefuse.data.event.EventConstants;
import prefuse.data.event.TableListener;
import prefuse.data.expression.Expression;
import prefuse.data.expression.Predicate;
import prefuse.data.expression.parser.ExpressionParser;
import prefuse.data.tuple.AbstractTupleSet;
import prefuse.data.tuple.TableTuple;
import prefuse.data.tuple.TupleManager;
import prefuse.data.util.FilterIteratorFactory;
import prefuse.data.util.Index;
import prefuse.data.util.RowManager;
import prefuse.data.util.TableIterator;
import prefuse.data.util.TreeIndex;
import prefuse.util.TypeLib;
import prefuse.util.collections.CopyOnWriteArrayList;
import prefuse.util.collections.IncompatibleComparatorException;
import prefuse.util.collections.IntIterator;


/**
 * <p>A Table organizes a collection of data into rows and columns, each row 
 * containing a data record, and each column containing data values for a
 * named data field with a specific data type. Table data can be accessed
 * directly using the row number and column name, or rows can be treated
 * in an object-oriented fashion using {@link prefuse.data.Tuple}
 * instances that represent a single row of data in the table. As such,
 * tables implement the {@link prefuse.data.tuple.TupleSet} interface.</p>
 * 
 * <p>Table rows can be inserted or deleted. In any case, none of the other
 * existing table rows are effected by an insertion or deletion. A deleted
 * row simply becomes invalid--any subsequent attempts to access the row
 * either directly or through a pre-existing Tuple instance will result
 * in an exception. However, if news rows are later added to the table,
 * the row number for previously deleted nodes can be reused. In fact, the
 * lower row number currently unused is assigned to the new row. This results
 * in an efficient reuse of the table rows, but carries an important side
 * effect -- rows do not necesarily maintain the order in which they were
 * added once deletions have occurred on the table. If not deletions
 * occur, the ordering of table rows will reflect the order in which
 * rows were added to the table.</p>
 * 
 * <p>Collections of table rows can be accessed using both iterators over
 * the actual row numbers and iterators over the Tuple instances that
 * encapsulate access to that row. Both types of iteration can also be
 * filtered by providing a {@link prefuse.data.expression.Predicate},
 * allowing tables to be queried for specific values.</p>
 * 
 * <p>Columns (alternativele referred to as data fields) can be added to
 * the Table using {@link #addColumn(String, Class)} and a host of
 * similar methods. This method will automatically determine the right
 * kind of backing column instance to use. Furthermore, Table columns
 * can be specified using a {@link Schema} instance, which describes
 * the column names, data types, and default values. The Table class
 * also maintains its own internal Schema, which be accessed (in a
 * read-only way) using the {@link #getSchema()} method.</p>
 * 
 * <p>Tables also support additional structures. The {@link ColumnMetadata}
 * class returned by the {@link #getMetadata(String)} method supports
 * calculation of different statistics for a column, including minimum
 * and maximum values, and the number of unique data values in the column.
 * {@link prefuse.data.util.Index} instances can be created and retrieved
 * using the {@link #index(String)} method and retrieved without triggering
 * creation using {@link #getIndex(String)} method. An index keeps a
 * sorted collection of all data values in a column, accelerating the creation
 * of filtered iterators by optimizing query calculations and also providing
 * faster computation of many of the {@link ColumnMetadata} methods. If
 * you will be issuing a number of queries (i.e., requesting filtered
 * iterators) dependent on the values of a given column, indexing that column
 * may result in a significant performance increase, though at the cost
 * of storing and maintaining the backing index structure.</p>  
 * 
 * @author <a href="http://jheer.org">jeffrey heer</a>
 */
public class Table extends AbstractTupleSet implements ColumnListener {
    
    /** Listeners for changes to this table */
    protected CopyOnWriteArrayList m_listeners;
    
    /** Locally stored data columns */
    protected ArrayList m_columns;
    /** Column names for locally store data columns */
    protected ArrayList m_names;
    
    /** Mapping between column names and column entries
     * containing column, metadata, and index references */ 
    protected HashMap m_entries;
    
    /** Manager for valid row indices */
    protected RowManager m_rows;
    
    /** manager for tuples, which are object representations for rows */
    protected TupleManager m_tuples;
    
    /** Tracks the number of edits of this table */
    protected int m_modCount = 0;
    
    /** Memoize the index of the last column operated on,
     * used to expedite handling of column updates. */
    protected int m_lastCol = -1;
    
    /** A cached schema instance, loaded lazily */
    protected Schema m_schema;
    
    // ------------------------------------------------------------------------
    // Constructors
    
    /**
     * Create a new, empty Table. Rows can be added to the table using
     * the {@link #addRow()} method.
     */
    public Table() {
        this(0, 0);
    }
    
    /**
     * Create a new Table with a given number of rows, and the starting
     * capacity for a given number of columns.
     * @param nrows the starting number of table rows
     * @param ncols the starting capacity for columns 
     */
    public Table(int nrows, int ncols) {
        this(nrows, ncols, TableTuple.class);
    }
    
    /**
     * Create a new Table.
     * @param nrows the starting number of table rows
     * @param ncols the starting capacity for columns 
     * @param tupleType the class of the Tuple instances to use
     */
    protected Table(int nrows, int ncols, Class tupleType) {
        m_listeners = new CopyOnWriteArrayList();
        m_columns = new ArrayList(ncols);
        m_names = new ArrayList(ncols);
        m_rows = new RowManager(this);
        m_entries = new HashMap(ncols+5);        
        m_tuples = new TupleManager(this, null, tupleType);

        if ( nrows > 0 )
            addRows(nrows);
    }
    
    // ------------------------------------------------------------------------
    // Table Metadata
    
    /**
     * Get the number of columns / data fields in this table.
     * @return the number of columns 
     */
    public int getColumnCount() {
        return m_columns.size();
    }

    /**
     * Get the data type of the column at the given column index.
     * @param col the column index
     * @return the data type (as a Java Class) of the column
     */
    public Class getColumnType(int col) {
        return getColumn(col).getColumnClass();
    }

    /**
     * Get the data type of the column with the given data field name.
     * @param field the column / data field name
     * @return the data type (as a Java Class) of the column
     */
    public Class getColumnType(String field) {
        Column c = getColumn(field); 
        return (c==null ? null : c.getColumnClass());
    }

    /**
     * Get the number of rows in the table.
     * @return the number of rows
     */
    public int getRowCount() {
        return m_rows.getRowCount();
    }
    
    /**
     * Indicates if the value of the given table cell can be changed. 
     * @param row the row number
     * @param col the column number
     * @return true if the value can be edited/changed, false otherwise
     */
    public boolean isCellEditable(int row, int col) {
        if ( !m_rows.isValidRow(row) ) {
            return false;
        } else {
            return getColumn(col).isCellEditable(row);
        }
    }
    
    /**
     * Get the number of times this Table has been modified. Adding rows,
     * deleting rows, and updating table cell values all contribute to
     * this count.
     * @return the number of modifications to this table
     */
    public int getModificationCount() {
        return m_modCount;
    }
    
    /**
     * Sets the TupleManager used by this Table. Use this method
     * carefully, as it will cause all existing Tuples retrieved
     * from this Table to be invalidated.
     * @param tm the TupleManager to use
     */
    public void setTupleManager(TupleManager tm) {
        m_tuples.invalidateAll();
        m_tuples = tm;
    }
    
    /**
     * Returns this Table's schema. The returned schema will be
     * locked, which means that any attempts to edit the returned schema
     * by adding additional columns will result in a runtime exception.
     * 
     * If this Table subsequently has columns added or removed, this will not
     * be reflected in the returned schema. Instead, this method will need to
     * be called again to get a current schema. Accordingly, it is not
     * recommended that Schema instances returned by this method be stored
     * or reused across scopes unless that exact schema snapshot is
     * desired.
     * 
     * @return a copy of this Table's schema
     */
    public Schema getSchema() {
        if ( m_schema == null ) {
            Schema s = new Schema();
            for ( int i=0; i<getColumnCount(); ++i ) {
                s.addColumn(getColumnName(i), getColumnType(i), 
                            getColumn(i).getDefaultValue());
            }
            s.lockSchema();
            m_schema = s;
        }
        return m_schema;
    }
    
    /**
     * Invalidates this table's cached schema. This method should be called
     * whenever columns are added or removed from this table.
     */
    protected void invalidateSchema() {
        m_schema = null;
    }
    
    // ------------------------------------------------------------------------
    // Row Operations
    
    /**
     * Get the row value for accessing an underlying Column instance,
     * corresponding to the given table cell. For basic tables this just
     * returns the input row value. However, for tables that inherit
     * data columns from a parent table and present a filtered view on
     * this data, a mapping between the row numbers of the table and
     * the row numbers of the backing data column is needed. In those cases,
     * this method returns the result of that mapping. The method
     * {@link #getTableRow(int, int)} accesses this map in the reverse
     * direction.
     * @param row the table row to lookup
     * @param col the table column to lookup
     * @return the column row number for accessing the desired table cell
     */
    public int getColumnRow(int row, int col) {
        return m_rows.getColumnRow(row, col);
    }
    
    /**
     * Get the row number for this table given a row number for a backing
     * data column and the column number for the data column. For basic
     * tables this just returns the column row value. However, for tables that
     * inherit data columns from a parent table and present a filtered view on
     * this data, a mapping between the row numbers of the table and
     * the row numbers of the backing data column is needed. In those cases,
     * this method returns the result of this mapping, in the direction of
     * the backing column rows to the table rows of the cascaded table. The
     * method {@link #getColumnRow(int, int)} accesses this map in the reverse
     * direction.
     * @param colrow the row of the backing data column
     * @param col the table column to lookup.
     * @return the table row number for accessing the desired table cell
     */
    public int getTableRow(int colrow, int col) {
        return m_rows.getTableRow(colrow, col);
    }
    
    /**
     * Add a row to this table. All data columns will be notified and will
     * take on the appropriate default values for the added row.
     * @return the row number of the newly added row
     */
    public int addRow() {
        int r = m_rows.addRow();
        updateRowCount();
        
        fireTableEvent(r, r, TableModelEvent.ALL_COLUMNS,
                       TableModelEvent.INSERT);        
        return r;
    }
    
    /**
     * Add a given number of rows to this table. All data columns will be
     * notified and will take on the appropriate default values for the
     * added rows.
     * @param nrows the number of rows to add.
     */
    public void addRows(int nrows) {
        for ( int i=0; i<nrows; ++i ) {
            addRow();
        }
    }
    
    /**
     * Internal method that updates the row counts for local data columns.
     */
    protected void updateRowCount() {
        int maxrow = m_rows.getMaximumRow() + 1;
        
        // update tuple manager
        m_tuples.setMaximumRow(maxrow);
        
        // update columns
        Iterator cols = getColumns();
        while ( cols.hasNext() ) {
            Column c = (Column)cols.next();
            c.setMaximumRow(maxrow);
        }
    }
    
    /**
     * Removes a row from this table.
     * @param row the row to delete
     * @return true if the row was successfully deleted, false if the
     * row was already invalid
     */
    public boolean removeRow(int row) {
        if ( m_rows.isValidRow(row) ) {
            // the order of operations here is extremely important
            // otherwise listeners may end up with corrupted state
            // fire update *BEFORE* clearing values
            // allow listeners (e.g., indices) to perform clean-up
            fireTableEvent(row, row, TableModelEvent.ALL_COLUMNS, 
                           TableModelEvent.DELETE);
            // invalidate the tuple
            m_tuples.invalidate(row);
            // release row with row manager
            // do this before clearing column values, so that any
            // listeners can determine that the row is invalid
            m_rows.releaseRow(row);
            // now clear column values
            for ( Iterator cols = getColumns(); cols.hasNext(); ) {
                Column c = (Column)cols.next();
                c.revertToDefault(row);
            }
            return true;
        }
        return false;
    }
    
    /**
     * Clear this table, removing all rows.
     * @see prefuse.data.tuple.TupleSet#clear()
     */
    public void clear() {
        IntIterator rows = rows(true);
        while ( rows.hasNext() ) {
            removeRow(rows.nextInt());
        }
    }
    
    /**
     * Indicates if the given row number corresponds to a valid table row.
     * @param row the row number to check for validity
     * @return true if the row is valid, false if it is not
     */
    public boolean isValidRow(int row) {
        return m_rows.isValidRow(row);
    }
    
    // ------------------------------------------------------------------------
    // Column Operations
    
    /**
     * Internal method indicating if the given data field is included as a
     * data column.
     */
    protected boolean hasColumn(String name) {
        return getColumnNumber(name) != -1;
    }
    
    /**
     * Get the data field name of the column at the given column number.
     * @param col the column number
     * @return the data field name of the column
     */
    public String getColumnName(int col) {
        return (String)m_names.get(col);
    }

    /**
     * Get the column number for a given data field name.
     * @param field the name of the column to lookup
     * @return the column number of the column, or -1 if the name is not found
     */
    public int getColumnNumber(String field) {
        ColumnEntry e = (ColumnEntry)m_entries.get(field);
        return ( e==null ? -1 : e.colnum );
    }

    /**
     * Get the column number for the given Column instance.
     * @param col the Column instance to lookup
     * @return the column number of the column, or -1 if the name is not found
     */
    public int getColumnNumber(Column col) {
        return m_columns.indexOf(col);
    }
    
    /**
     * Get the column at the given column number.
     * @param col the column number
     * @return the Column instance
     */
    public Column getColumn(int col) {
        m_lastCol = col;
        return (Column)m_columns.get(col);
    }
    
    /**
     * Get the column with the given data field name
     * @param field the data field name of the column
     * @return the Column instance
     */
    public Column getColumn(String field) {
        ColumnEntry e = (ColumnEntry)m_entries.get(field);
        return ( e != null ? e.column : null );
    }
    
    /**
     * Add a column with the given name and data type to this table.
     * @param name the data field name for the column
     * @param type the data type, as a Java Class, for the column
     * @see prefuse.data.tuple.TupleSet#addColumn(java.lang.String, java.lang.Class)
     */
    public void addColumn(String name, Class type) {
        addColumn(name, type, null);
    }

    /**
     * Add a column with the given name and data type to this table.
     * @param name the data field name for the column
     * @param type the data type, as a Java Class, for the column
     * @param defaultValue the default value for column data values
     * @see prefuse.data.tuple.TupleSet#addColumn(java.lang.String, java.lang.Class, java.lang.Object)
     */
    public void addColumn(String name, Class type, Object defaultValue) {
        Column col = ColumnFactory.getColumn(type, 
                        m_rows.getMaximumRow()+1, defaultValue);
        addColumn(name, col);
    }
    
    /**
     * Add a derived column to this table, using an Expression instance to
     * dynamically calculate the column data values.
     * @param name the data field name for the column
     * @param expr a String expression in the prefuse expression language, to
     * be parsed into an {@link prefuse.data.expression.Expression} instance.
     * The string is parsed by the
     * {@link prefuse.data.expression.parser.ExpressionParser}. If an error
     * occurs during parsing, an exception will be thrown. 
     * @see prefuse.data.tuple.TupleSet#addColumn(java.lang.String, java.lang.String)
     */
    public void addColumn(String name, String expr) {
        Expression ex = ExpressionParser.parse(expr);
        Throwable t = ExpressionParser.getError();
        if ( t != null ) {
            throw new RuntimeException(t);
        } else {
            addColumn(name, ex);
        }
    }
    
    /**
     * Add a derived column to this table, using an Expression instance to
     * dynamically calculate the column data values.
     * @param name the data field name for the column
     * @param expr the Expression that will determine the column values
     * @see prefuse.data.tuple.TupleSet#addColumn(java.lang.String, prefuse.data.expression.Expression)
     */
    public void addColumn(String name, Expression expr) {
        addColumn(name, ColumnFactory.getColumn(this, expr));
    }
    
    /**
     * Add a constant column to this table, which returns one constant value
     * for all column rows.
     * @param name the data field name for the column
     * @param type the data type, as a Java Class, for the column
     * @param dflt the default value for column data values
     */
    public void addConstantColumn(String name, Class type, Object dflt) {
        addColumn(name, ColumnFactory.getConstantColumn(type, dflt));
    }
    
    /**
     * Internal method for adding a column.
     * @param name the name of the column
     * @param col the actual Column instance
     */
    protected void addColumn(String name, Column col) {
        int idx = getColumnNumber(name);
        if ( idx >= 0 && idx < m_columns.size() ) {
            throw new IllegalArgumentException(
                "Table already has column with name \""+name+"\"");
        }
        
        // add the column
        m_columns.add(col);
        m_names.add(name);
        m_lastCol = m_columns.size()-1;
        ColumnEntry entry = new ColumnEntry(m_lastCol, col, 
                new ColumnMetadata(this, name));
        
        // add entry, dispose of an overridden entry if needed
        ColumnEntry oldEntry = (ColumnEntry)m_entries.put(name, entry);
        if ( oldEntry != null ) oldEntry.dispose();
        
        invalidateSchema();
        
        // listen to what the column has to say
        col.addColumnListener(this);
        
        // fire notification
        fireTableEvent(m_rows.getMinimumRow(), m_rows.getMaximumRow(), 
                m_lastCol, TableModelEvent.INSERT);
    }

    /**
     * Internal method for removing a column.
     * @param idx the column number of the column to remove
     * @return the removed Column instance
     */
    protected Column removeColumn(int idx) {
        // make sure index is legal
        if ( idx < 0 || idx >= m_columns.size() ) {
            throw new IllegalArgumentException("Column index is not legal.");
        }
        
        String name = (String)m_names.get(idx);
        ((ColumnEntry)m_entries.get(name)).dispose();
        Column col = (Column)m_columns.remove(idx);
        m_entries.remove(name);
        m_names.remove(idx);
        renumberColumns();
        
        m_lastCol = -1;
        invalidateSchema();
        
        // ignore what the old column has to say
        col.removeColumnListener(this);
        
        // fire notification
        fireTableEvent(m_rows.getMinimumRow(), m_rows.getMaximumRow(), 
                       idx, TableModelEvent.DELETE);
        
        return col;
    }
    
    /**
     * Remove a data field from this table
     * @param field the name of the data field / column to remove
     * @return the removed Column instance
     */
    public Column removeColumn(String field) {
        int idx = m_names.indexOf(field);
        if ( idx < 0 ) {
            throw new IllegalArgumentException("No such column.");
        }
        return removeColumn(idx);
    }
    
    /**
     * Remove a column from this table
     * @param c the column instance to remove
     */
    public void removeColumn(Column c) {
        int idx = m_columns.indexOf(c);
        if ( idx < 0 ) {
            throw new IllegalArgumentException("No such column.");
        }
        removeColumn(idx);
    }
    
    /**
     * Internal method that re-numbers columns upon column removal.
     */
    protected void renumberColumns() {
        Iterator iter = m_names.iterator();
        for ( int idx=0; iter.hasNext(); ++idx ) {
            String name = (String)iter.next();
            ColumnEntry e = (ColumnEntry)m_entries.get(name);
            e.colnum = idx;
        }
    }
    
    /**
     * Internal method that returns an iterator over columns
     * @return an iterator over columns
     */
    protected Iterator getColumns() {
        return m_columns.iterator();
    }

    /**
     * Internal method that returns an iterator over column names
     * @return an iterator over column name
     */
    protected Iterator getColumnNames() {
        return m_names.iterator();
    }
    
    // ------------------------------------------------------------------------
    // Column Metadata
    
    /**
     * Return a metadata instance providing summary information about a column.
     * @param field the data field name of the column
     * @return the columns' associated ColumnMetadata instance
     */
    public ColumnMetadata getMetadata(String field) {
        ColumnEntry e = (ColumnEntry)m_entries.get(field);
        if ( e == null ) {
            throw new IllegalArgumentException("Unknown column name: "+field);
        }
        return e.metadata;
    }
    
    // ------------------------------------------------------------------------
    // Index Methods
    
    /**
     * Create (if necessary) and return an index over the given data field.
     * The first call to this method with a given field name will cause the
     * index to be created and stored. Subsequent calls will simply return
     * the stored index. To attempt to retrieve an index without triggering
     * creation of a new index, use the {@link #getIndex(String)} method.
     * @param field the data field name of the column to index
     * @return the index over the specified data column
     */
    public Index index(String field) {
        ColumnEntry e = (ColumnEntry)m_entries.get(field);
        if ( e == null ) {
            throw new IllegalArgumentException("Unknown column name: "+field);
        } else if ( e.index != null ) {
            return e.index; // already indexed
        }
        
        Column col = e.column;
        try {
            e.index = new TreeIndex(this, m_rows, col, null);
        } catch ( IncompatibleComparatorException ice ) { /* can't happen */ }
        
        return e.index;
    }
    
    /**
     * Retrieve, without creating, an index for the given data field.
     * @param field the data field name of the column
     * @return the stored index for the column, or null if no index has
     * been created
     */
    public Index getIndex(String field) {
        ColumnEntry e = (ColumnEntry)m_entries.get(field);
        if ( e == null ) {
            throw new IllegalArgumentException("Unknown column name: "+field);
        }
        return e.index;
    }
    
    /**
     * Internal method for index creation and retrieval.
     * @param field the data field name of the column
     * @param expType the expected data type of the index
     * @param create indicates whether or not a new index should be created
     * if none currently exists for the given data field
     * @return the Index for the given data field
     */
    protected Index getIndex(String field, Class expType, boolean create) {
        if ( !expType.equals(getColumnType(field)) ) {
            // TODO: need more nuanced type checking here?
            throw new IllegalArgumentException("Column type does not match.");
        }
        if ( getIndex(field)==null && create) {
            index(field);
        }
        return getIndex(field);
    }
    
    /**
     * Remove the Index associated with the given data field / column name.
     * @param field the name of the column for which to remove the index
     * @return true if an index was successfully removed, false if no
     * such index was found
     */
    public boolean removeIndex(String field) {
        ColumnEntry e = (ColumnEntry)m_entries.get(field);
        if ( e == null ) {
            throw new IllegalArgumentException("Unknown column name: "+field);
        }
        if ( e.index == null ) {
            return false;
        } else {
            e.index.dispose();
            e.index = null;
            return true;
        }
    }
    
    // ------------------------------------------------------------------------
    // Tuple Methods
    
    /**
     * Get the Tuple instance providing object-oriented access to the given
     * table row.
     * @param row the table row
     * @return the Tuple for the given table row
     */
    public Tuple getTuple(int row) {
        return m_tuples.getTuple(row);
    }
    
    /**
     * Add a Tuple to this table. If the Tuple is already a member of this
     * table, nothing is done and null is returned. If the Tuple is not
     * a member of this Table but has a compatible data schema, as
     * determined by {@link Schema#isAssignableFrom(Schema)}, a new row
     * is created, the Tuple's values are copied, and the new Tuple that
     * is a member of this Table is returned. If the data schemas are not
     * compatible, nothing is done and null is returned.
     * @param t the Tuple to "add" to this table
     * @return the actual Tuple instance added to this table, or null if
     * no new Tuple has been added
     * @see prefuse.data.tuple.TupleSet#addTuple(prefuse.data.Tuple)
     */
    public Tuple addTuple(Tuple t) {
        if ( t.getTable() == this ) {
            return null;
        } else {
            Schema s = t.getSchema();
            if ( getSchema().isAssignableFrom(s) ) {
                int r = addRow();
                for ( int i=0; i<s.getColumnCount(); ++i ) {
                    String field = s.getColumnName(i);
                    this.set(r, field, t.getValueAt(i));
                }
                return getTuple(r);
            } else {
                return null;
            }
        }
    }
    
    /**
     * Clears the contents of this table and then attempts to add the given
     * Tuple instance.
     * @param t the Tuple to make the sole tuple in thie table
     * @return the actual Tuple instance added to this table, or null if
     * no new Tuple has been added
     * @see prefuse.data.tuple.TupleSet#setTuple(prefuse.data.Tuple)
     */
    public Tuple setTuple(Tuple t) {
        clear();
        return addTuple(t);
    }
    
    /**
     * Remove a tuple from this table. If the Tuple is a member of this table,
     * its row is deleted from the table. Otherwise, nothing is done.
     * @param t the Tuple to remove from the table
     * @return true if the Tuple row was successfully deleted, false if the
     * Tuple is invalid or not a member of this table
     * @see prefuse.data.tuple.TupleSet#removeTuple(prefuse.data.Tuple)
     */
    public boolean removeTuple(Tuple t) {
        if ( containsTuple(t) ) {
            removeRow(t.getRow());
            return true;
        } else {
            return false;
        }
    }
    
    /**
     * Indicates if this table contains the given Tuple instance.
     * @param t the Tuple to check for containment
     * @return true if the Tuple represents a row of this table, false if
     * it does not
     * @see prefuse.data.tuple.TupleSet#containsTuple(prefuse.data.Tuple)
     */
    public boolean containsTuple(Tuple t) {
        return (t.getTable()==this && isValidRow(t.getRow())); 
    }
    
    /**
     * Get the number of tuples in this table. This is the same as the
     * value returned by {@link #getRowCount()}.
     * @return the number of tuples, which is the same as the number of rows
     * @see prefuse.data.tuple.TupleSet#getTupleCount()
     */
    public int getTupleCount() {
        return getRowCount();
    }
    
    /**
     * Returns true, as this table supports the addition of new data fields.
     * @see prefuse.data.tuple.TupleSet#isAddColumnSupported()
     */
    public boolean isAddColumnSupported() {
        return true;
    }
    
    // ------------------------------------------------------------------------
    // Data Access Methods
    
    /**
     * Check if the <code>get</code> method for the given data field returns
     * values that are compatible with a given target type.
     * @param field the data field to check
     * @param type a Class instance to check for compatibility with the
     * data field values.
     * @return true if the data field is compatible with provided type,
     * false otherwise. If the value is true, objects returned by
     * the {@link #get(int, String)} can be cast to the given type.
     * @see #get(int, String)
     */
    public boolean canGet(String field, Class type) {
        Column c = getColumn(field);
        return ( c==null ? false : c.canGet(type) );
    }
    
    /**
     * Check if the <code>set</code> method for the given data field can
     * accept values of a given target type.
     * @param field the data field to check
     * @param type a Class instance to check for compatibility with the
     * data field values.
     * @return true if the data field is compatible with provided type,
     * false otherwise. If the value is true, objects of the given type
     * can be used as parameters of the {@link #set(int, String, Object)}
     * method.
     * @see #set(int, String, Object)
     */
    public boolean canSet(String field, Class type) {
        Column c = getColumn(field);
        return ( c==null ? false : c.canSet(type) );
    }
    
    /**
     * Get the data value at the given row and field as an Object.
     * @param row the table row to get
     * @param field the data field to retrieve
     * @return the data value as an Object. The concrete type of this
     * Object is dependent on the underlying data column used.
     * @see #canGet(String, Class)
     * @see #getColumnType(String)
     */
    public Object get(int row, String field) {
        int col = getColumnNumber(field);
        row = getColumnRow(row, col);
        return getColumn(col).get(row);
    }
    
    /**
     * Set the value of a given row and data field.
     * @param row the table row to set
     * @param field the data field to set
     * @param val the value for the field. If the concrete type of this
     * Object is not compatible with the underlying data model, an
     * Exception will be thrown. Use the {@link #canSet(String, Class)}
     * method to check the type-safety ahead of time.
     * @see #canSet(String, Class)
     * @see #getColumnType(String)
     */
    public void set(int row, String field, Object val) {
        int col = getColumnNumber(field);
        row = getColumnRow(row, col);
        getColumn(col).set(val, row);
        
        // we don't fire a notification here, as we catch the
        // notification from the column itself and then dispatch
    }
    
    /**
     * Get the data value at the given row and column numbers as an Object.
     * @param row the row number
     * @param col the column number
     * @return the data value as an Object. The concrete type of this
     * Object is dependent on the underlying data column used.
     * @see #canGet(String, Class)
     * @see #getColumnType(int)
     */
    public Object getValueAt(int row, int col) {
        row = getColumnRow(row, col);
        return getColumn(col).get(row);
    }

    /**
     * Set the value of at the given row and column numbers.
     * @param row the row number
     * @param col the column number
     * @param val the value for the field. If the concrete type of this
     * Object is not compatible with the underlying data model, an
     * Exception will be thrown. Use the {@link #canSet(String, Class)}
     * method to check the type-safety ahead of time.
     * @see #canSet(String, Class)
     * @see #getColumnType(String)
     */
    public void setValueAt(int row, int col, Object val) {
        row = getColumnRow(row, col);
        getColumn(col).set(val, row);
        
        // we don't fire a notification here, as we catch the
        // notification from the column itself and then dispatch
    }
    
    /**
     * Get the default value for the given data field.
     * @param field the data field
     * @return the default value, as an Object, used to populate rows
     * of the data field.
     */
    public Object getDefault(String field) {
        int col = getColumnNumber(field);
        return getColumn(col).getDefaultValue();
    }
    
    /**
     * Revert this tuple's value for the given field to the default value
     * for the field.
     * @param field the data field
     * @see #getDefault(String)
     */
    public void revertToDefault(int row, String field) {
        int col = getColumnNumber(field);
        row = getColumnRow(row, col);
        getColumn(col).revertToDefault(row);
    }

    // ------------------------------------------------------------------------
    // Convenience Data Access Methods
    
    /**
     * Check if the given data field can return primitive <code>int</code>
     * values.
     * @param field the data field to check
     * @return true if the data field can return primitive <code>int</code>
     * values, false otherwise. If true, the {@link #getInt(int, String)}
     * method can be used safely.
     */
    public final boolean canGetInt(String field) {
        Column col = getColumn(field);
        return ( col==null ? false : col.canGetInt() );
    }
    
    /**
     * Check if the <code>setInt</code> method can safely be used for the
     * given data field.
     * @param field the data field to check
     * @return true if the {@link #setInt(int, String, int)} method can safely
     * be used for the given field, false otherwise.
     */
    public final boolean canSetInt(String field) {
        Column col = getColumn(field);
        return ( col==null ? false : col.canSetInt() );
    }
    
    /**
     * Get the data value at the given row and field as an
     * <code>int</code>.
     * @param row the table row to retrieve
     * @param field the data field to retrieve
     * @see #canGetInt(String)
     */
    public final int getInt(int row, String field) {
        int col = getColumnNumber(field);
        row = getColumnRow(row, col);
        return getColumn(col).getInt(row);
    }
    
    /**
     * Set the data value of the given row and field as an
     * <code>int</code>.
     * @param row the table row to set
     * @param field the data field to set
     * @param val the value to set
     * @see #canSetInt(String)
     */
    public final void setInt(int row, String field, int val) {
        int col = getColumnNumber(field);
        row = getColumnRow(row, col);
        getColumn(col).setInt(val, row);
    }
    
    // --------------------------------------------------------------
    
    /**
     * Check if the given data field can return primitive <code>long</code>
     * values.
     * @param field the data field to check
     * @return true if the data field can return primitive <code>long</code>
     * values, false otherwise. If true, the {@link #getLong(int, String)}
     * method can be used safely.
     */
    public final boolean canGetLong(String field) {
        Column col = getColumn(field);
        return ( col==null ? false : col.canGetLong() );
    }
    
    /**
     * Check if the <code>setLong</code> method can safely be used for the
     * given data field.
     * @param field the data field to check
     * @return true if the {@link #setLong(int, String, long)} method can
     * safely be used for the given field, false otherwise.
     */
    public final boolean canSetLong(String field) {
        Column col = getColumn(field);
        return ( col==null ? false : col.canSetLong() );
    }
    
    /**
     * Get the data value at the given row and field as a
     * <code>long</code>.
     * @param row the table row to retrieve
     * @param field the data field to retrieve
     * @see #canGetLong(String)
     */
    public final long getLong(int row, String field) {
        int col = getColumnNumber(field);
        row = getColumnRow(row, col);
        return getColumn(col).getLong(row);
    }
    
    /**
     * Set the data value of the given row and field as a
     * <code>long</code>.
     * @param row the table row to set
     * @param field the data field to set
     * @param val the value to set
     * @see #canSetLong(String)
     */
    public final void setLong(int row, String field, long val) {
        int col = getColumnNumber(field);
        row = getColumnRow(row, col);
        getColumn(col).setLong(val, row);
    }

    // --------------------------------------------------------------
    
    /**
     * Check if the given data field can return primitive <code>float</code>
     * values.
     * @param field the data field to check
     * @return true if the data field can return primitive <code>float</code>
     * values, false otherwise. If true, the {@link #getFloat(int, String)}
     * method can be used safely.
     */
    public final boolean canGetFloat(String field) {
        Column col = getColumn(field);
        return ( col==null ? false : col.canGetFloat() );
    }
    
    /**
     * Check if the <code>setFloat</code> method can safely be used for the
     * given data field.
     * @param field the data field to check
     * @return true if the {@link #setFloat(int, String, float)} method can
     * safely be used for the given field, false otherwise.
     */
    public final boolean canSetFloat(String field) {
        Column col = getColumn(field);
        return ( col==null ? false : col.canSetFloat() );
    }
    
    /**
     * Get the data value at the given row and field as a
     * <code>float</code>.
     * @param row the table row to retrieve
     * @param field the data field to retrieve
     * @see #canGetFloat(String)
     */
    public final float getFloat(int row, String field) {
        int col = getColumnNumber(field);
        row = getColumnRow(row, col);
        return getColumn(col).getFloat(row);
    }
    
    /**
     * Set the data value of the given row and field as a
     * <code>float</code>.
     * @param row the table row to set
     * @param field the data field to set
     * @param val the value to set
     * @see #canSetFloat(String)
     */
    public final void setFloat(int row, String field, float val) {
        int col = getColumnNumber(field);
        row = getColumnRow(row, col);
        getColumn(col).setFloat(val, row);   
    }
    
    // --------------------------------------------------------------
    
    /**
     * Check if the given data field can return primitive <code>double</code>
     * values.
     * @param field the data field to check
     * @return true if the data field can return primitive <code>double</code>
     * values, false otherwise. If true, the {@link #getDouble(int, String)}
     * method can be used safely.
     */
    public final boolean canGetDouble(String field) {
        Column col = getColumn(field);
        return ( col==null ? false : col.canGetDouble() );
    }
    
    /**
     * Check if the <code>setDouble</code> method can safely be used for the
     * given data field.
     * @param field the data field to check
     * @return true if the {@link #setDouble(int, String, double)} method can
     * safely be used for the given field, false otherwise.
     */
    public final boolean canSetDouble(String field) {
        Column col = getColumn(field);
        return ( col==null ? false : col.canSetDouble() );
    }
    
    /**
     * Get the data value at the given row and field as a
     * <code>double</code>.
     * @param row the table row to retrieve
     * @param field the data field to retrieve
     * @see #canGetDouble(String)
     */
    public final double getDouble(int row, String field) {
        int col = getColumnNumber(field);
        row = getColumnRow(row, col);
        return getColumn(col).getDouble(row);
    }
    
    /**
     * Set the data value of the given row and field as a
     * <code>double</code>.
     * @param row the table row to set
     * @param field the data field to set
     * @param val the value to set
     * @see #canSetDouble(String)
     */
    public final void setDouble(int row, String field, double val) {
        int col = getColumnNumber(field);
        row = getColumnRow(row, col);
        getColumn(col).setDouble(val, row);
    }
    
    // --------------------------------------------------------------
    
    /**
     * Check if the given data field can return primitive <code>boolean</code>
     * values.
     * @param field the data field to check
     * @return true if the data field can return primitive <code>boolean</code>
     * values, false otherwise. If true, the {@link #getBoolean(int, String)}
     * method can be used safely.
     */
    public final boolean canGetBoolean(String field) {
        Column col = getColumn(field);
        return ( col==null ? false : col.canGetBoolean() );
    }
    
    /**
     * Check if the <code>setBoolean</code> method can safely be used for the
     * given data field.
     * @param field the data field to check
     * @return true if the {@link #setBoolean(int, String, boolean)} method can
     * safely be used for the given field, false otherwise.
     */
    public final boolean canSetBoolean(String field) {
        Column col = getColumn(field);
        return ( col==null ? false : col.canSetBoolean() );
    }
    
    /**
     * Get the data value at the given row and field as a
     * <code>boolean</code>.
     * @param row the table row to retrieve
     * @param field the data field to retrieve
     * @see #canGetBoolean(String)
     */
    public final boolean getBoolean(int row, String field) {
        int col = getColumnNumber(field);
        row = getColumnRow(row, col);
        return getColumn(col).getBoolean(row);
    }
    
    /**
     * Set the data value of the given row and field as a
     * <code>boolean</code>.
     * @param row the table row to set
     * @param field the data field to set
     * @param val the value to set
     * @see #canSetBoolean(String)
     */
    public final void setBoolean(int row, String field, boolean val) {
        int col = getColumnNumber(field);
        row = getColumnRow(row, col);
        getColumn(col).setBoolean(val, row);
    }
    
    // --------------------------------------------------------------
    
    /**
     * Check if the given data field can return primitive <code>String</code>
     * values.
     * @param field the data field to check
     * @return true if the data field can return primitive <code>String</code>
     * values, false otherwise. If true, the {@link #getString(int, String)}
     * method can be used safely.
     */
    public final boolean canGetString(String field) {
        Column col = getColumn(field);
        return ( col==null ? false : col.canGetString() );
    }
    
    /**
     * Check if the <code>setString</code> method can safely be used for the
     * given data field.
     * @param field the data field to check
     * @return true if the {@link #setString(int, String, String)} method can
     * safely be used for the given field, false otherwise.
     */
    public final boolean canSetString(String field) {
        Column col = getColumn(field);
        return ( col==null ? false : col.canSetString() );
    }
    
    /**
     * Get the data value at the given row and field as a
     * <code>String</code>.
     * @param row the table row to retrieve
     * @param field the data field to retrieve
     * @see #canGetString(String)
     */
    public final String getString(int row, String field) {
        int col = getColumnNumber(field);
        row = getColumnRow(row, col);
        return getColumn(col).getString(row);
    }
    
    /**
     * Set the data value of the given row and field as a
     * <code>String</code>.
     * @param row the table row to set
     * @param field the data field to set
     * @param val the value to set
     * @see #canSetString(String)
     */
    public final void setString(int row, String field, String val) {
        int col = getColumnNumber(field);
        row = getColumnRow(row, col);
        getColumn(col).setString(val, row);
    }
    
    // --------------------------------------------------------------
    
    /**
     * Check if the given data field can return primitive <code>Date</code>
     * values.
     * @param field the data field to check
     * @return true if the data field can return primitive <code>Date</code>
     * values, false otherwise. If true, the {@link #getDate(int, String)}
     * method can be used safely.
     */
    public final boolean canGetDate(String field) {
        Column col = getColumn(field);
        return ( col==null ? false : col.canGetDate() );
    }
    
    /**
     * Check if the <code>setDate</code> method can safely be used for the
     * given data field.
     * @param field the data field to check
     * @return true if the {@link #setDate(int, String, Date)} method can
     * safely be used for the given field, false otherwise.
     */
    public final boolean canSetDate(String field) {
        Column col = getColumn(field);
        return ( col==null ? false : col.canSetDate() );
    }
    
    /**
     * Get the data value at the given row and field as a
     * <code>Date</code>.
     * @param row the table row to retrieve
     * @param field the data field to retrieve
     * @see #canGetDate(String)
     */
    public final Date getDate(int row, String field) {
        int col = getColumnNumber(field);
        row = getColumnRow(row, col);
        return getColumn(col).getDate(row);
    }
    
    /**
     * Set the data value of the given row and field as a
     * <code>Date</code>.
     * @param row the table row to set
     * @param field the data field to set
     * @param val the value to set
     * @see #canSetDate(String)
     */
    public final void setDate(int row, String field, Date val) {
        int col = getColumnNumber(field);
        row = getColumnRow(row, col);
        getColumn(col).setDate(val, row);
    }

    // ------------------------------------------------------------------------
    // Iterators
    
    /**
     * Return a TableIterator over the rows of this table.
     * @return a TableIterator over this table
     */
    public TableIterator iterator() {
        return iterator(rows());
    }

    /**
     * Return a TableIterator over the given rows of this table.
     * @param rows an iterator over the table rows to visit
     * @return a TableIterator over this table
     */
    public TableIterator iterator(IntIterator rows) {
        return new TableIterator(this, rows);
    }
    
    /**
     * Get an iterator over the tuples in this table.
     * @return an iterator over the table tuples
     * @see prefuse.data.tuple.TupleSet#tuples()
     */
    public Iterator tuples() {
        return m_tuples.iterator(rows());
    }
    
    /**
     * Get an iterator over the tuples in this table in reverse order.
     * @return an iterator over the table tuples in reverse order
     */
    public Iterator tuplesReversed() {
        return m_tuples.iterator(rows(true));
    }
    
    /**
     * Get an iterator over the tuples for the given rows in this table.
     * @param rows an iterator over the table rows to visit
     * @return an iterator over the selected table tuples
     */
    public Iterator tuples(IntIterator rows) {
        return m_tuples.iterator(rows);
    }
    
    /**
     * Get an interator over the row numbers of this table.
     * @return an iterator over the rows of this table
     */
    public IntIterator rows() {
        return m_rows.rows();
    }
    
    /**
     * Get a filtered iterator over the row numbers of this table, returning
     * only the rows whose tuples match the given filter predicate.
     * @param filter the filter predicate to apply
     * @return a filtered iterator over the rows of this table
     */
    public IntIterator rows(Predicate filter) {
        return FilterIteratorFactory.rows(this, filter);
    }
    
    /**
     * Get an interator over the row numbers of this table.
     * @param reverse true to iterate in rever order, false for normal order
     * @return an iterator over the rows of this table
     */
    public IntIterator rows(boolean reverse) {
        return m_rows.rows(reverse);
    }
    
    /**
     * Get an iterator over the rows of this table, sorted by the given data
     * field. This method will create an index over the field if one does
     * not yet exist.
     * @param field the data field to sort by
     * @param ascend true if the iteration should proceed in an ascending
     * (lowest to highest) sort order, false for a descending order
     * @return the sorted iterator over rows of this table
     */
    public IntIterator rowsSortedBy(String field, boolean ascend) {
        Class type = getColumnType(field);
        Index index = getIndex(field, type, true);
        int t = ascend ? Index.TYPE_ASCENDING : Index.TYPE_DESCENDING;
        return index.allRows(t);
    }
    
    /**
     * Return an iterator over a range of rwos in this table, determined
     * by a bounded range for a given data field. A new index over the
     * data field will be created if it doesn't already exist.
     * @param field the data field for determining the bounded range
     * @param lo the minimum range value
     * @param hi the maximum range value
     * @param indexType indicate the sort order and inclusivity/exclusivity
     * of the range bounds, using the constants of the
     * {@link prefuse.data.util.Index} class.
     * @return an iterator over a range of table rows, determined by a 
     * sorted bounded range of a data field
     */
    public IntIterator rangeSortedBy(String field, int lo, int hi, int indexType) {
        Index index = getIndex(field, int.class, true);
        return index.rows(lo, hi, indexType);
    }
    
    /**
     * Return an iterator over a range of rwos in this table, determined
     * by a bounded range for a given data field. A new index over the
     * data field will be created if it doesn't already exist. 
     * @param field the data field for determining the bounded range
     * @param lo the minimum range value
     * @param hi the maximum range value
     * @param indexType indicate the sort order and inclusivity/exclusivity
     * of the range bounds, using the constants of the
     * {@link prefuse.data.util.Index} class.
     * @return an iterator over a range of table rows, determined by a 
     * sorted bounded range of a data field
     */
    public IntIterator rangeSortedBy(String field, long lo, long hi, int indexType) {
        Index index = getIndex(field, long.class, true);
        return index.rows(lo, hi, indexType);
    }
    
    /**
     * Return an iterator over a range of rwos in this table, determined
     * by a bounded range for a given data field. A new index over the
     * data field will be created if it doesn't already exist.
     * @param field the data field for determining the bounded range
     * @param lo the minimum range value
     * @param hi the maximum range value
     * @param indexType indicate the sort order and inclusivity/exclusivity
     * of the range bounds, using the constants of the
     * {@link prefuse.data.util.Index} class.
     * @return an iterator over a range of table rows, determined by a 
     * sorted bounded range of a data field
     */
    public IntIterator rangeSortedBy(String field, float lo, float hi, int indexType) {
        Index index = getIndex(field, float.class, true);
        return index.rows(lo, hi, indexType);
    }
    
    /**
     * Return an iterator over a range of rwos in this table, determined
     * by a bounded range for a given data field. A new index over the
     * data field will be created if it doesn't already exist.
     * @param field the data field for determining the bounded range
     * @param lo the minimum range value
     * @param hi the maximum range value
     * @param indexType indicate the sort order and inclusivity/exclusivity
     * of the range bounds, using the constants of the
     * {@link prefuse.data.util.Index} class.
     * @return an iterator over a range of table rows, determined by a 
     * sorted bounded range of a data field
     */
    public IntIterator rangeSortedBy(String field, double lo, double hi, int indexType) {
        Index index = getIndex(field, double.class, true);
        return index.rows(lo, hi, indexType);
    }
    
    /**
     * Return an iterator over a range of rwos in this table, determined
     * by a bounded range for a given data field. A new index over the
     * data field will be created if it doesn't already exist.
     * @param field the data field for determining the bounded range
     * @param lo the minimum range value
     * @param hi the maximum range value
     * @param indexType indicate the sort order and inclusivity/exclusivity
     * of the range bounds, using the constants of the
     * {@link prefuse.data.util.Index} class.
     * @return an iterator over a range of table rows, determined by a 
     * sorted bounded range of a data field
     */
    public IntIterator rangeSortedBy(String field, Object lo, Object hi, int indexType) {
        Class type = TypeLib.getSharedType(lo, hi);
        // TODO: check this for correctness
        if ( type == null )
            throw new IllegalArgumentException("Incompatible arguments");
        Index index = getIndex(field, type, true);
        return index.rows(lo, hi, indexType);
    }
    
    // ------------------------------------------------------------------------
    // Listener Methods
    
    // -- ColumnListeners -----------------------------------------------------
    
    /**
     * @see prefuse.data.event.ColumnListener#columnChanged(prefuse.data.column.Column, int, boolean)
     */
    public void columnChanged(Column src, int idx, boolean prev) {
        handleColumnChanged(src, idx, idx);
    }

    /**
     * @see prefuse.data.event.ColumnListener#columnChanged(prefuse.data.column.Column, int, double)
     */
    public void columnChanged(Column src, int idx, double prev) {
        handleColumnChanged(src, idx, idx);
    }

    /**
     * @see prefuse.data.event.ColumnListener#columnChanged(prefuse.data.column.Column, int, float)
     */
    public void columnChanged(Column src, int idx, float prev) {
        handleColumnChanged(src, idx, idx);
    }

    /**
     * @see prefuse.data.event.ColumnListener#columnChanged(prefuse.data.column.Column, int, int)
     */
    public void columnChanged(Column src, int idx, int prev) {
        handleColumnChanged(src, idx, idx);
    }

    /**
     * @see prefuse.data.event.ColumnListener#columnChanged(prefuse.data.column.Column, int, long)
     */
    public void columnChanged(Column src, int idx, long prev) {
        handleColumnChanged(src, idx, idx);
    }

    /**
     * @see prefuse.data.event.ColumnListener#columnChanged(prefuse.data.column.Column, int, java.lang.Object)
     */
    public void columnChanged(Column src, int idx, Object prev) {
        handleColumnChanged(src, idx, idx);
    }

    /**
     * @see prefuse.data.event.ColumnListener#columnChanged(prefuse.data.column.Column, int, int, int)
     */
    public void columnChanged(Column src, int type, int start, int end) {
        handleColumnChanged(src, start, end);
    }
    
    /**
     * Handle a column change event.
     * @param c the modified column
     * @param start the starting row of the modified range
     * @param end the ending row (inclusive) of the modified range
     */
    protected void handleColumnChanged(Column c, int start, int end) {
        for ( ; !isValidRow(start) && start <= end; ++start );
        if ( start > end ) return; // bail if no valid rows
        
        // determine the index of the updated column
        int idx;
        if ( m_lastCol != -1 && c == getColumn(m_lastCol) ) {
            // constant time
            idx = m_lastCol;
        } else {
            // linear time
            idx = getColumnNumber(c);
        }
        
        // if we have a valid index, fire a notification
        if ( idx >= 0 ) {
            fireTableEvent(start, end, idx, TableModelEvent.UPDATE);
        }
    }
    
    // -- TableListeners ------------------------------------------------------
    
    /**
     * Add a table listener to this table.
     * @param listnr the listener to add
     */
    public void addTableListener(TableListener listnr) {
        if ( !m_listeners.contains(listnr) )
            m_listeners.add(listnr);
    }

    /**
     * Remove a table listener from this table.
     * @param listnr the listener to remove
     */
    public void removeTableListener(TableListener listnr) {
        m_listeners.remove(listnr);
    }
    
    /**
     * Fire a table event to notify listeners.
     * @param row0 the starting row of the modified range
     * @param row1 the ending row (inclusive) of the modified range
     * @param col the number of the column modified, or
     * {@link prefuse.data.event.EventConstants#ALL_COLUMNS} for operations
     * effecting all columns.
     * @param type the table modification type, one of
     * {@link prefuse.data.event.EventConstants#INSERT},
     * {@link prefuse.data.event.EventConstants#DELETE}, or
     * {@link prefuse.data.event.EventConstants#UPDATE}.
     */
    protected void fireTableEvent(int row0, int row1, int col, int type) {
        // increment the modification count
        ++m_modCount;
        
        if ( type != EventConstants.UPDATE && 
             col == EventConstants.ALL_COLUMNS )
        {
            // fire event to all tuple set listeners
            fireTupleEvent(this, row0, row1, type);
        }
        
        if ( !m_listeners.isEmpty() ) {
            // fire event to all table listeners
            Object[] lstnrs = m_listeners.getArray();
            for ( int i=0; i<lstnrs.length; ++i ) {
                ((TableListener)lstnrs[i]).tableChanged(
                        this, row0, row1, col, type);
            }
        }
    }
    
    // ------------------------------------------------------------------------
    // String Methods
    
    /**
     * @see java.lang.Object#toString()
     */
    public String toString() {
        StringBuffer sbuf = new StringBuffer();
        sbuf.append("Table[");
        sbuf.append("rows=").append(getRowCount());
        sbuf.append(", cols=").append(getColumnCount());
        sbuf.append(", maxrow=").append(m_rows.getMaximumRow());
        sbuf.append("]");
        return sbuf.toString();
    }
    
    // ------------------------------------------------------------------------
    // ColumnEntry helper
    
    /**
     * Helper class that encapsulates a map entry for a column, including the
     * column itself and its metadata and index.
     * 
     * @author <a href="http://jheer.org">jeffrey heer</a>
     */
    protected static class ColumnEntry {

        /** The column number. */
        public int            colnum;
        /** The Column instance. */
        public Column         column;
        /** The column metadata instance. */
        public ColumnMetadata metadata;
        /** The column Index instance. */
        public Index          index;
        
        /**
         * Create a new ColumnEntry.
         * @param col the column number
         * @param column the Column instance
         * @param metadata the ColumnMetadata instance
         */
        public ColumnEntry(int col, Column column, ColumnMetadata metadata) {
            this.colnum = col;
            this.column = column;
            this.metadata = metadata;
            this.index = null;
        }
        
        /**
         * Dispose of this column entry, disposing of any allocated
         * metadata or index instances.
         */
        public void dispose() {
            if ( metadata != null )
                metadata.dispose();
            if ( index != null )
                index.dispose();
        }

    } // end of inner class ColumnEntry
    
} // end of class Table
back to top