/*
* Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package javax.swing.table;
import java.io.Serializable;
import java.util.Vector;
import java.util.Enumeration;
import javax.swing.event.TableModelEvent;
/**
* This is an implementation of TableModel that
* uses a Vector of Vectors to store the
* cell value objects.
*
* Warning: DefaultTableModel returns a
* column class of Object. When
* DefaultTableModel is used with a
* TableRowSorter this will result in extensive use of
* toString, which for non-String data types
* is expensive. If you use DefaultTableModel with a
* TableRowSorter you are strongly encouraged to override
* getColumnClass to return the appropriate type.
*
* Warning:
* Serialized objects of this class will not be compatible with
* future Swing releases. The current serialization support is
* appropriate for short term storage or RMI between applications running
* the same version of Swing. As of 1.4, support for long term storage
* of all JavaBeansTM
* has been added to the java.beans package.
* Please see {@link java.beans.XMLEncoder}.
*
* @author Philip Milne
*
* @see TableModel
* @see #getDataVector
*/
public class DefaultTableModel extends AbstractTableModel implements Serializable {
//
// Instance Variables
//
/**
* The Vector of Vectors of
* Object values.
*/
protected Vector dataVector;
/** The Vector of column identifiers. */
protected Vector columnIdentifiers;
//
// Constructors
//
/**
* Constructs a default DefaultTableModel
* which is a table of zero columns and zero rows.
*/
public DefaultTableModel() {
this(0, 0);
}
private static Vector newVector(int size) {
Vector v = new Vector(size);
v.setSize(size);
return v;
}
/**
* Constructs a DefaultTableModel with
* rowCount and columnCount of
* null object values.
*
* @param rowCount the number of rows the table holds
* @param columnCount the number of columns the table holds
*
* @see #setValueAt
*/
public DefaultTableModel(int rowCount, int columnCount) {
this(newVector(columnCount), rowCount);
}
/**
* Constructs a DefaultTableModel with as many columns
* as there are elements in columnNames
* and rowCount of null
* object values. Each column's name will be taken from
* the columnNames vector.
*
* @param columnNames vector containing the names
* of the new columns; if this is
* null then the model has no columns
* @param rowCount the number of rows the table holds
* @see #setDataVector
* @see #setValueAt
*/
public DefaultTableModel(Vector columnNames, int rowCount) {
setDataVector(newVector(rowCount), columnNames);
}
/**
* Constructs a DefaultTableModel with as many
* columns as there are elements in columnNames
* and rowCount of null
* object values. Each column's name will be taken from
* the columnNames array.
*
* @param columnNames array containing the names
* of the new columns; if this is
* null then the model has no columns
* @param rowCount the number of rows the table holds
* @see #setDataVector
* @see #setValueAt
*/
public DefaultTableModel(Object[] columnNames, int rowCount) {
this(convertToVector(columnNames), rowCount);
}
/**
* Constructs a DefaultTableModel and initializes the table
* by passing data and columnNames
* to the setDataVector method.
*
* @param data the data of the table, a Vector
* of Vectors of Object
* values
* @param columnNames vector containing the names
* of the new columns
* @see #getDataVector
* @see #setDataVector
*/
public DefaultTableModel(Vector data, Vector columnNames) {
setDataVector(data, columnNames);
}
/**
* Constructs a DefaultTableModel and initializes the table
* by passing data and columnNames
* to the setDataVector
* method. The first index in the Object[][] array is
* the row index and the second is the column index.
*
* @param data the data of the table
* @param columnNames the names of the columns
* @see #getDataVector
* @see #setDataVector
*/
public DefaultTableModel(Object[][] data, Object[] columnNames) {
setDataVector(data, columnNames);
}
/**
* Returns the Vector of Vectors
* that contains the table's
* data values. The vectors contained in the outer vector are
* each a single row of values. In other words, to get to the cell
* at row 1, column 5:
*
* ((Vector)getDataVector().elementAt(1)).elementAt(5);
*
* @return the vector of vectors containing the tables data values
*
* @see #newDataAvailable
* @see #newRowsAdded
* @see #setDataVector
*/
public Vector getDataVector() {
return dataVector;
}
private static Vector nonNullVector(Vector v) {
return (v != null) ? v : new Vector();
}
/**
* Replaces the current dataVector instance variable
* with the new Vector of rows, dataVector.
* Each row is represented in dataVector as a
* Vector of Object values.
* columnIdentifiers are the names of the new
* columns. The first name in columnIdentifiers is
* mapped to column 0 in dataVector. Each row in
* dataVector is adjusted to match the number of
* columns in columnIdentifiers
* either by truncating the Vector if it is too long,
* or adding null values if it is too short.
*
Note that passing in a null value for
* dataVector results in unspecified behavior,
* an possibly an exception.
*
* @param dataVector the new data vector
* @param columnIdentifiers the names of the columns
* @see #getDataVector
*/
public void setDataVector(Vector dataVector, Vector columnIdentifiers) {
this.dataVector = nonNullVector(dataVector);
this.columnIdentifiers = nonNullVector(columnIdentifiers);
justifyRows(0, getRowCount());
fireTableStructureChanged();
}
/**
* Replaces the value in the dataVector instance
* variable with the values in the array dataVector.
* The first index in the Object[][]
* array is the row index and the second is the column index.
* columnIdentifiers are the names of the new columns.
*
* @param dataVector the new data vector
* @param columnIdentifiers the names of the columns
* @see #setDataVector(Vector, Vector)
*/
public void setDataVector(Object[][] dataVector, Object[] columnIdentifiers) {
setDataVector(convertToVector(dataVector), convertToVector(columnIdentifiers));
}
/**
* Equivalent to fireTableChanged.
*
* @param event the change event
*
*/
public void newDataAvailable(TableModelEvent event) {
fireTableChanged(event);
}
//
// Manipulating rows
//
private void justifyRows(int from, int to) {
// Sometimes the DefaultTableModel is subclassed
// instead of the AbstractTableModel by mistake.
// Set the number of rows for the case when getRowCount
// is overridden.
dataVector.setSize(getRowCount());
for (int i = from; i < to; i++) {
if (dataVector.elementAt(i) == null) {
dataVector.setElementAt(new Vector(), i);
}
((Vector)dataVector.elementAt(i)).setSize(getColumnCount());
}
}
/**
* Ensures that the new rows have the correct number of columns.
* This is accomplished by using the setSize method in
* Vector which truncates vectors
* which are too long, and appends nulls if they
* are too short.
* This method also sends out a tableChanged
* notification message to all the listeners.
*
* @param e this TableModelEvent describes
* where the rows were added.
* If null it assumes
* all the rows were newly added
* @see #getDataVector
*/
public void newRowsAdded(TableModelEvent e) {
justifyRows(e.getFirstRow(), e.getLastRow() + 1);
fireTableChanged(e);
}
/**
* Equivalent to fireTableChanged.
*
* @param event the change event
*
*/
public void rowsRemoved(TableModelEvent event) {
fireTableChanged(event);
}
/**
* Obsolete as of Java 2 platform v1.3. Please use setRowCount instead.
*/
/*
* Sets the number of rows in the model. If the new size is greater
* than the current size, new rows are added to the end of the model
* If the new size is less than the current size, all
* rows at index rowCount and greater are discarded.
*
* @param rowCount the new number of rows
* @see #setRowCount
*/
public void setNumRows(int rowCount) {
int old = getRowCount();
if (old == rowCount) {
return;
}
dataVector.setSize(rowCount);
if (rowCount <= old) {
fireTableRowsDeleted(rowCount, old-1);
}
else {
justifyRows(old, rowCount);
fireTableRowsInserted(old, rowCount-1);
}
}
/**
* Sets the number of rows in the model. If the new size is greater
* than the current size, new rows are added to the end of the model
* If the new size is less than the current size, all
* rows at index rowCount and greater are discarded.
*
* @see #setColumnCount
* @since 1.3
*/
public void setRowCount(int rowCount) {
setNumRows(rowCount);
}
/**
* Adds a row to the end of the model. The new row will contain
* null values unless rowData is specified.
* Notification of the row being added will be generated.
*
* @param rowData optional data of the row being added
*/
public void addRow(Vector rowData) {
insertRow(getRowCount(), rowData);
}
/**
* Adds a row to the end of the model. The new row will contain
* null values unless rowData is specified.
* Notification of the row being added will be generated.
*
* @param rowData optional data of the row being added
*/
public void addRow(Object[] rowData) {
addRow(convertToVector(rowData));
}
/**
* Inserts a row at row in the model. The new row
* will contain null values unless rowData
* is specified. Notification of the row being added will be generated.
*
* @param row the row index of the row to be inserted
* @param rowData optional data of the row being added
* @exception ArrayIndexOutOfBoundsException if the row was invalid
*/
public void insertRow(int row, Vector rowData) {
dataVector.insertElementAt(rowData, row);
justifyRows(row, row+1);
fireTableRowsInserted(row, row);
}
/**
* Inserts a row at row in the model. The new row
* will contain null values unless rowData
* is specified. Notification of the row being added will be generated.
*
* @param row the row index of the row to be inserted
* @param rowData optional data of the row being added
* @exception ArrayIndexOutOfBoundsException if the row was invalid
*/
public void insertRow(int row, Object[] rowData) {
insertRow(row, convertToVector(rowData));
}
private static int gcd(int i, int j) {
return (j == 0) ? i : gcd(j, i%j);
}
private static void rotate(Vector v, int a, int b, int shift) {
int size = b - a;
int r = size - shift;
int g = gcd(size, r);
for(int i = 0; i < g; i++) {
int to = i;
Object tmp = v.elementAt(a + to);
for(int from = (to + r) % size; from != i; from = (to + r) % size) {
v.setElementAt(v.elementAt(a + from), a + to);
to = from;
}
v.setElementAt(tmp, a + to);
}
}
/**
* Moves one or more rows from the inclusive range start to
* end to the to position in the model.
* After the move, the row that was at index start
* will be at index to.
* This method will send a tableChanged notification
* message to all the listeners.
* *
* Examples of moves:
*
* 1. moveRow(1,3,5);
* a|B|C|D|e|f|g|h|i|j|k - before
* a|e|f|g|h|B|C|D|i|j|k - after
*
* 2. moveRow(6,7,1);
* a|b|c|d|e|f|G|H|i|j|k - before
* a|G|H|b|c|d|e|f|i|j|k - after
*
*
*
* @param start the starting row index to be moved
* @param end the ending row index to be moved
* @param to the destination of the rows to be moved
* @exception ArrayIndexOutOfBoundsException if any of the elements
* would be moved out of the table's range
*
*/
public void moveRow(int start, int end, int to) {
int shift = to - start;
int first, last;
if (shift < 0) {
first = to;
last = end;
}
else {
first = start;
last = to + end - start;
}
rotate(dataVector, first, last + 1, shift);
fireTableRowsUpdated(first, last);
}
/**
* Removes the row at row from the model. Notification
* of the row being removed will be sent to all the listeners.
*
* @param row the row index of the row to be removed
* @exception ArrayIndexOutOfBoundsException if the row was invalid
*/
public void removeRow(int row) {
dataVector.removeElementAt(row);
fireTableRowsDeleted(row, row);
}
//
// Manipulating columns
//
/**
* Replaces the column identifiers in the model. If the number of
* newIdentifiers is greater than the current number
* of columns, new columns are added to the end of each row in the model.
* If the number of newIdentifiers is less than the current
* number of columns, all the extra columns at the end of a row are
* discarded.
*
* @param columnIdentifiers vector of column identifiers. If
* null, set the model
* to zero columns
* @see #setNumRows
*/
public void setColumnIdentifiers(Vector columnIdentifiers) {
setDataVector(dataVector, columnIdentifiers);
}
/**
* Replaces the column identifiers in the model. If the number of
* newIdentifiers is greater than the current number
* of columns, new columns are added to the end of each row in the model.
* If the number of newIdentifiers is less than the current
* number of columns, all the extra columns at the end of a row are
* discarded.
*
* @param newIdentifiers array of column identifiers.
* If null, set
* the model to zero columns
* @see #setNumRows
*/
public void setColumnIdentifiers(Object[] newIdentifiers) {
setColumnIdentifiers(convertToVector(newIdentifiers));
}
/**
* Sets the number of columns in the model. If the new size is greater
* than the current size, new columns are added to the end of the model
* with null cell values.
* If the new size is less than the current size, all columns at index
* columnCount and greater are discarded.
*
* @param columnCount the new number of columns in the model
*
* @see #setColumnCount
* @since 1.3
*/
public void setColumnCount(int columnCount) {
columnIdentifiers.setSize(columnCount);
justifyRows(0, getRowCount());
fireTableStructureChanged();
}
/**
* Adds a column to the model. The new column will have the
* identifier columnName, which may be null. This method
* will send a
* tableChanged notification message to all the listeners.
* This method is a cover for addColumn(Object, Vector) which
* uses null as the data vector.
*
* @param columnName the identifier of the column being added
*/
public void addColumn(Object columnName) {
addColumn(columnName, (Vector)null);
}
/**
* Adds a column to the model. The new column will have the
* identifier columnName, which may be null.
* columnData is the
* optional vector of data for the column. If it is null
* the column is filled with null values. Otherwise,
* the new data will be added to model starting with the first
* element going to row 0, etc. This method will send a
* tableChanged notification message to all the listeners.
*
* @param columnName the identifier of the column being added
* @param columnData optional data of the column being added
*/
public void addColumn(Object columnName, Vector columnData) {
columnIdentifiers.addElement(columnName);
if (columnData != null) {
int columnSize = columnData.size();
if (columnSize > getRowCount()) {
dataVector.setSize(columnSize);
}
justifyRows(0, getRowCount());
int newColumn = getColumnCount() - 1;
for(int i = 0; i < columnSize; i++) {
Vector row = (Vector)dataVector.elementAt(i);
row.setElementAt(columnData.elementAt(i), newColumn);
}
}
else {
justifyRows(0, getRowCount());
}
fireTableStructureChanged();
}
/**
* Adds a column to the model. The new column will have the
* identifier columnName. columnData is the
* optional array of data for the column. If it is null
* the column is filled with null values. Otherwise,
* the new data will be added to model starting with the first
* element going to row 0, etc. This method will send a
* tableChanged notification message to all the listeners.
*
* @see #addColumn(Object, Vector)
*/
public void addColumn(Object columnName, Object[] columnData) {
addColumn(columnName, convertToVector(columnData));
}
//
// Implementing the TableModel interface
//
/**
* Returns the number of rows in this data table.
* @return the number of rows in the model
*/
public int getRowCount() {
return dataVector.size();
}
/**
* Returns the number of columns in this data table.
* @return the number of columns in the model
*/
public int getColumnCount() {
return columnIdentifiers.size();
}
/**
* Returns the column name.
*
* @return a name for this column using the string value of the
* appropriate member in columnIdentifiers.
* If columnIdentifiers does not have an entry
* for this index, returns the default
* name provided by the superclass.
*/
public String getColumnName(int column) {
Object id = null;
// This test is to cover the case when
// getColumnCount has been subclassed by mistake ...
if (column < columnIdentifiers.size() && (column >= 0)) {
id = columnIdentifiers.elementAt(column);
}
return (id == null) ? super.getColumnName(column)
: id.toString();
}
/**
* Returns true regardless of parameter values.
*
* @param row the row whose value is to be queried
* @param column the column whose value is to be queried
* @return true
* @see #setValueAt
*/
public boolean isCellEditable(int row, int column) {
return true;
}
/**
* Returns an attribute value for the cell at row
* and column.
*
* @param row the row whose value is to be queried
* @param column the column whose value is to be queried
* @return the value Object at the specified cell
* @exception ArrayIndexOutOfBoundsException if an invalid row or
* column was given
*/
public Object getValueAt(int row, int column) {
Vector rowVector = (Vector)dataVector.elementAt(row);
return rowVector.elementAt(column);
}
/**
* Sets the object value for the cell at column and
* row. aValue is the new value. This method
* will generate a tableChanged notification.
*
* @param aValue the new value; this can be null
* @param row the row whose value is to be changed
* @param column the column whose value is to be changed
* @exception ArrayIndexOutOfBoundsException if an invalid row or
* column was given
*/
public void setValueAt(Object aValue, int row, int column) {
Vector rowVector = (Vector)dataVector.elementAt(row);
rowVector.setElementAt(aValue, column);
fireTableCellUpdated(row, column);
}
//
// Protected Methods
//
/**
* Returns a vector that contains the same objects as the array.
* @param anArray the array to be converted
* @return the new vector; if anArray is null,
* returns null
*/
protected static Vector convertToVector(Object[] anArray) {
if (anArray == null) {
return null;
}
Vector