/* * Copyright (c) 1997, 2015, 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 <code>TableModel</code> that * uses a <code>Vector</code> of <code>Vectors</code> to store the * cell value objects. * <p> * <strong>Warning:</strong> <code>DefaultTableModel</code> returns a * column class of <code>Object</code>. When * <code>DefaultTableModel</code> is used with a * <code>TableRowSorter</code> this will result in extensive use of * <code>toString</code>, which for non-<code>String</code> data types * is expensive. If you use <code>DefaultTableModel</code> with a * <code>TableRowSorter</code> you are strongly encouraged to override * <code>getColumnClass</code> to return the appropriate type. * <p> * <strong>Warning:</strong> * 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 JavaBeans™ * has been added to the <code>java.beans</code> package. * Please see {@link java.beans.XMLEncoder}. * * @author Philip Milne * * @see TableModel * @see #getDataVector */ @SuppressWarnings("serial") // Same-version serialization only public class DefaultTableModel extends AbstractTableModel implements Serializable { // // Instance Variables // /** * The <code>Vector</code> of <code>Vectors</code> of * <code>Object</code> values. */ @SuppressWarnings("rawtypes") protected Vector<Vector> dataVector; /** The <code>Vector</code> of column identifiers. */ @SuppressWarnings("rawtypes") protected Vector columnIdentifiers; // Unfortunately, for greater source compatibility the inner-most // Vector in the two fields above is being left raw. The Vector is // read as well as written so using Vector<?> is not suitable and // using Vector<Object> (without adding copying of input Vectors), // would disallow existing code that used, say, a Vector<String> // as an input parameter. // // Constructors // /** * Constructs a default <code>DefaultTableModel</code> * which is a table of zero columns and zero rows. */ public DefaultTableModel() { this(0, 0); } private static <E> Vector<E> newVector(int size) { Vector<E> v = new Vector<>(size); v.setSize(size); return v; } /** * Constructs a <code>DefaultTableModel</code> with * <code>rowCount</code> and <code>columnCount</code> of * <code>null</code> 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 <code>DefaultTableModel</code> with as many columns * as there are elements in <code>columnNames</code> * and <code>rowCount</code> of <code>null</code> * object values. Each column's name will be taken from * the <code>columnNames</code> vector. * * @param columnNames <code>vector</code> containing the names * of the new columns; if this is * <code>null</code> 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 <code>DefaultTableModel</code> with as many * columns as there are elements in <code>columnNames</code> * and <code>rowCount</code> of <code>null</code> * object values. Each column's name will be taken from * the <code>columnNames</code> array. * * @param columnNames <code>array</code> containing the names * of the new columns; if this is * <code>null</code> 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 <code>DefaultTableModel</code> and initializes the table * by passing <code>data</code> and <code>columnNames</code> * to the <code>setDataVector</code> method. * * @param data the data of the table, a <code>Vector</code> * of <code>Vector</code>s of <code>Object</code> * values * @param columnNames <code>vector</code> containing the names * of the new columns * @see #getDataVector * @see #setDataVector */ @SuppressWarnings("rawtypes") public DefaultTableModel(Vector<? extends Vector> data, Vector<?> columnNames) { setDataVector(data, columnNames); } /** * Constructs a <code>DefaultTableModel</code> and initializes the table * by passing <code>data</code> and <code>columnNames</code> * to the <code>setDataVector</code> * method. The first index in the <code>Object[][]</code> 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 <code>Vector</code> of <code>Vectors</code> * 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: <p> * * <code>((Vector)getDataVector().elementAt(1)).elementAt(5);</code> * * @return the vector of vectors containing the tables data values * * @see #newDataAvailable * @see #newRowsAdded * @see #setDataVector */ @SuppressWarnings("rawtypes") public Vector<Vector> getDataVector() { return dataVector; } private static <E> Vector<E> nonNullVector(Vector<E> v) { return (v != null) ? v : new Vector<>(); } /** * Replaces the current <code>dataVector</code> instance variable * with the new <code>Vector</code> of rows, <code>dataVector</code>. * Each row is represented in <code>dataVector</code> as a * <code>Vector</code> of <code>Object</code> values. * <code>columnIdentifiers</code> are the names of the new * columns. The first name in <code>columnIdentifiers</code> is * mapped to column 0 in <code>dataVector</code>. Each row in * <code>dataVector</code> is adjusted to match the number of * columns in <code>columnIdentifiers</code> * either by truncating the <code>Vector</code> if it is too long, * or adding <code>null</code> values if it is too short. * <p>Note that passing in a <code>null</code> value for * <code>dataVector</code> results in unspecified behavior, * an possibly an exception. * * @param dataVector the new data vector * @param columnIdentifiers the names of the columns * @see #getDataVector */ @SuppressWarnings({"rawtypes", "unchecked"}) public void setDataVector(Vector<? extends Vector> dataVector, Vector<?> columnIdentifiers) { this.dataVector = nonNullVector((Vector<Vector>)dataVector); this.columnIdentifiers = nonNullVector(columnIdentifiers); justifyRows(0, getRowCount()); fireTableStructureChanged(); } /** * Replaces the value in the <code>dataVector</code> instance * variable with the values in the array <code>dataVector</code>. * The first index in the <code>Object[][]</code> * array is the row index and the second is the column index. * <code>columnIdentifiers</code> 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 <code>fireTableChanged</code>. * * @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); } dataVector.elementAt(i).setSize(getColumnCount()); } } /** * Ensures that the new rows have the correct number of columns. * This is accomplished by using the <code>setSize</code> method in * <code>Vector</code> which truncates vectors * which are too long, and appends <code>null</code>s if they * are too short. * This method also sends out a <code>tableChanged</code> * notification message to all the listeners. * * @param e this <code>TableModelEvent</code> describes * where the rows were added. * If <code>null</code> 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 <code>fireTableChanged</code>. * * @param event the change event * */ public void rowsRemoved(TableModelEvent event) { fireTableChanged(event); } /** * Obsolete as of Java 2 platform v1.3. Please use <code>setRowCount</code> instead. * @param rowCount the new number of rows */ 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 <code>rowCount</code> and greater are discarded. * * @see #setColumnCount * @since 1.3 * * @param rowCount number of rows in the model */ public void setRowCount(int rowCount) { setNumRows(rowCount); } /** * Adds a row to the end of the model. The new row will contain * <code>null</code> values unless <code>rowData</code> 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 * <code>null</code> values unless <code>rowData</code> 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 <code>row</code> in the model. The new row * will contain <code>null</code> values unless <code>rowData</code> * 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 <code>row</code> in the model. The new row * will contain <code>null</code> values unless <code>rowData</code> * 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 <E> void rotate(Vector<E> 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; E 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 <code>start</code> to * <code>end</code> to the <code>to</code> position in the model. * After the move, the row that was at index <code>start</code> * will be at index <code>to</code>. * This method will send a <code>tableChanged</code> notification message to all the listeners. * * <pre> * 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 * </pre> * * @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 <code>row</code> 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 * <code>newIdentifier</code>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 <code>newIdentifier</code>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 * <code>null</code>, 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 * <code>newIdentifier</code>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 <code>newIdentifier</code>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 <code>null</code>, 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 <code>null</code> cell values. * If the new size is less than the current size, all columns at index * <code>columnCount</code> 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 <code>columnName</code>, which may be null. This method * will send a * <code>tableChanged</code> notification message to all the listeners. * This method is a cover for <code>addColumn(Object, Vector)</code> which * uses <code>null</code> as the data vector. * * @param columnName the identifier of the column being added */ public void addColumn(Object columnName) { addColumn(columnName, (Vector<Object>)null); } /** * Adds a column to the model. The new column will have the * identifier <code>columnName</code>, which may be null. * <code>columnData</code> is the * optional vector of data for the column. If it is <code>null</code> * the column is filled with <code>null</code> 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 * <code>tableChanged</code> notification message to all the listeners. * * @param columnName the identifier of the column being added * @param columnData optional data of the column being added */ @SuppressWarnings("unchecked") // Adding element to raw columnIdentifiers 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<Object> row = 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 <code>columnName</code>. <code>columnData</code> is the * optional array of data for the column. If it is <code>null</code> * the column is filled with <code>null</code> 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 * <code>tableChanged</code> notification message to all the listeners. * * @param columnName identifier of the newly created column * @param columnData new data to be added to the column * * @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 <code>columnIdentifiers</code>. * If <code>columnIdentifiers</code> 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 <code>row</code> * and <code>column</code>. * * @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) { @SuppressWarnings("unchecked") Vector<Object> rowVector = dataVector.elementAt(row); return rowVector.elementAt(column); } /** * Sets the object value for the cell at <code>column</code> and * <code>row</code>. <code>aValue</code> is the new value. This method * will generate a <code>tableChanged</code> 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) { @SuppressWarnings("unchecked") Vector<Object> rowVector = 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 <code>anArray</code> is <code>null</code>, * returns <code>null</code> */ protected static Vector<Object> convertToVector(Object[] anArray) { if (anArray == null) { return null; } Vector<Object> v = new Vector<>(anArray.length); for (Object o : anArray) { v.addElement(o); } 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 <code>anArray</code> is * <code>null</code>, returns <code>null</code> */ protected static Vector<Vector<Object>> convertToVector(Object[][] anArray) { if (anArray == null) { return null; } Vector<Vector<Object>> v = new Vector<>(anArray.length); for (Object[] o : anArray) { v.addElement(convertToVector(o)); } return v; } } // End of class DefaultTableModel