/*
* Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
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 Vector
s 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 null
s 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: ** * @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* 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 *
*
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
* newIdentifier
s 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 newIdentifier
s 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
* newIdentifier
s 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 newIdentifier
s 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