com.fs.commons.desktop.swing.comp.model.FSDefaultTableModel Maven / Gradle / Ivy
/*
* Copyright 2002-2016 Jalal Kiswani.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fs.commons.desktop.swing.comp.model;
import java.io.Serializable;
import java.util.Vector;
import javax.swing.event.TableModelEvent;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
/**
* 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}.
*
* @version 1.43 04/07/06
* @author Philip Milne
*
* @see TableModel
* @see #getDataVector
*/
public class FSDefaultTableModel extends AbstractTableModel implements Serializable {
//
// Instance Variables
//
/**
*
*/
private static final long serialVersionUID = -8125197035369796500L;
/**
* 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(final Object[] anArray) {
if (anArray == null) {
return null;
}
final Vector v = new Vector(anArray.length);
for (final Object element : anArray) {
v.addElement(element);
}
return v;
}
/**
* Returns a vector of vectors that contains the same objects as the array.
*
* @param anArray
* the double array to be converted
* @return the new vector of vectors; if anArray
is
* null
, returns null
*/
protected static Vector convertToVector(final Object[][] anArray) {
if (anArray == null) {
return null;
}
final Vector v = new Vector(anArray.length);
for (final Object[] element : anArray) {
v.addElement(convertToVector(element));
}
return v;
}
//
// Constructors
//
private static int gcd(final int i, final int j) {
return j == 0 ? i : gcd(j, i % j);
}
private static Vector newVector(final int size) {
final Vector v = new Vector(size);
v.setSize(size);
return v;
}
private static Vector nonNullVector(final Vector v) {
return v != null ? v : new Vector();
}
private static void rotate(final Vector v, final int a, final int b, final int shift) {
final int size = b - a;
final int r = size - shift;
final int g = gcd(size, r);
for (int i = 0; i < g; i++) {
int to = i;
final 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);
}
}
/**
* The Vector
of Vectors
of Object
* values.
*/
protected Vector dataVector;
/** The Vector
of column identifiers. */
protected Vector columnIdentifiers;
/**
* Constructs a default DefaultTableModel
which is a table of
* zero columns and zero rows.
*/
public FSDefaultTableModel() {
this(0, 0);
}
/**
* 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 FSDefaultTableModel(final int rowCount, final 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
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 FSDefaultTableModel(final Object[] columnNames, final int rowCount) {
this(convertToVector(columnNames), rowCount);
}
/**
* 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 FSDefaultTableModel(final Object[][] data, final Object[] columnNames) {
setDataVector(data, 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
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 FSDefaultTableModel(final Vector columnNames, final int rowCount) {
setDataVector(newVector(rowCount), columnNames);
}
/**
* 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 FSDefaultTableModel(final Vector data, final Vector columnNames) {
setDataVector(data, columnNames);
}
//
// Manipulating rows
//
/**
* 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(final Object columnName) {
addColumn(columnName, (Vector) null);
}
/**
* 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(final Object columnName, final Object[] columnData) {
addColumn(columnName, convertToVector(columnData));
}
/**
* 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(final Object columnName, final Vector columnData) {
this.columnIdentifiers.addElement(columnName);
if (columnData != null) {
final int columnSize = columnData.size();
if (columnSize > getRowCount()) {
this.dataVector.setSize(columnSize);
}
justifyRows(0, getRowCount());
final int newColumn = getColumnCount() - 1;
for (int i = 0; i < columnSize; i++) {
final Vector row = (Vector) this.dataVector.elementAt(i);
row.setElementAt(columnData.elementAt(i), newColumn);
}
} else {
justifyRows(0, getRowCount());
}
fireTableStructureChanged();
}
/**
* 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(final Object[] rowData) {
addRow(convertToVector(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(final Vector rowData) {
insertRow(getRowCount(), rowData);
}
/**
* Returns the number of columns in this data table.
*
* @return the number of columns in the model
*/
@Override
public int getColumnCount() {
return this.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.
*/
@Override
public String getColumnName(final int column) {
Object id = null;
// This test is to cover the case when
// getColumnCount has been subclassed by mistake ...
if (column < this.columnIdentifiers.size() && column >= 0) {
id = this.columnIdentifiers.elementAt(column);
}
return id == null ? super.getColumnName(column) : id.toString();
}
/**
* 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 this.dataVector;
}
/**
* Returns the number of rows in this data table.
*
* @return the number of rows in the model
*/
@Override
public int getRowCount() {
return this.dataVector.size();
}
/**
* 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
*/
@Override
public Object getValueAt(final int row, final int column) {
final Vector rowVector = (Vector) this.dataVector.elementAt(row);
return rowVector.elementAt(column);
}
/**
* 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(final int row, final Object[] rowData) {
insertRow(row, 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(final int row, final Vector rowData) {
this.dataVector.insertElementAt(rowData, row);
justifyRows(row, row + 1);
fireTableRowsInserted(row, row);
}
/**
* 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
*/
@Override
public boolean isCellEditable(final int row, final int column) {
return true;
}
//
// Manipulating columns
//
private void justifyRows(final int from, final 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.
this.dataVector.setSize(getRowCount());
for (int i = from; i < to; i++) {
if (this.dataVector.elementAt(i) == null) {
this.dataVector.setElementAt(new Vector(), i);
}
// ((Vector)dataVector.elementAt(i)).setSize(getColumnCount());
}
}
/**
* 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(final int start, final int end, final int to) {
final int shift = to - start;
int first, last;
if (shift < 0) {
first = to;
last = end;
} else {
first = start;
last = to + end - start;
}
rotate(this.dataVector, first, last + 1, shift);
fireTableRowsUpdated(first, last);
}
/**
* Equivalent to fireTableChanged
.
*
* @param event
* the change event
*
*/
public void newDataAvailable(final TableModelEvent event) {
fireTableChanged(event);
}
/**
* 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(final TableModelEvent e) {
justifyRows(e.getFirstRow(), e.getLastRow() + 1);
fireTableChanged(e);
}
/**
* 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
* @return
* @exception ArrayIndexOutOfBoundsException
* if the row was invalid
*/
public Object removeRow(final int row) {
final Object removed = this.dataVector.remove(row);
fireTableRowsDeleted(row, row);
return removed;
}
/**
* Equivalent to fireTableChanged
.
*
* @param event
* the change event
*
*/
public void rowsRemoved(final TableModelEvent event) {
fireTableChanged(event);
}
//
// Implementing the TableModel interface
//
/**
* 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(final int columnCount) {
this.columnIdentifiers.setSize(columnCount);
justifyRows(0, getRowCount());
fireTableStructureChanged();
}
/**
* 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(final Object[] newIdentifiers) {
setColumnIdentifiers(convertToVector(newIdentifiers));
}
/**
* 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(final Vector columnIdentifiers) {
setDataVector(this.dataVector, columnIdentifiers);
}
/**
* 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(final Object[][] dataVector, final Object[] columnIdentifiers) {
setDataVector(convertToVector(dataVector), convertToVector(columnIdentifiers));
}
/**
* 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(final Vector dataVector, final Vector columnIdentifiers) {
this.dataVector = nonNullVector(dataVector);
this.columnIdentifiers = nonNullVector(columnIdentifiers);
justifyRows(0, getRowCount());
fireTableStructureChanged();
}
/**
* 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(final int rowCount) {
final int old = getRowCount();
if (old == rowCount) {
return;
}
this.dataVector.setSize(rowCount);
if (rowCount <= old) {
fireTableRowsDeleted(rowCount, old - 1);
} else {
justifyRows(old, rowCount);
fireTableRowsInserted(old, rowCount - 1);
}
}
//
// Protected Methods
//
/**
* 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(final int rowCount) {
setNumRows(rowCount);
}
/**
* 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
*/
@Override
public void setValueAt(final Object aValue, final int row, final int column) {
final Vector rowVector = (Vector) this.dataVector.elementAt(row);
rowVector.setElementAt(aValue, column);
fireTableCellUpdated(row, column);
}
} // End of class DefaultTableModel