Package swingtree.api.model
Class TableData
java.lang.Object
swingtree.api.model.TableData
An immutable value describing the entire contents of a table: its cells, the
name and class of every column, the
UI.CellOrder tying the two together,
and whether the user may edit any of it. Hold one of these in a Var
property, bind that property to a table, and you have a complete, thread safe,
reactive table:
Var<TableData> data = Var.of(
TableData.of(UI.CellOrder.ROW_MAJOR, "Name", "Age")
.addRow("Alice", 30)
.addRow("Bob", 42)
);
UI.table(data);
Changing the table is then simply a matter of changing the value in the property,
from wherever your business logic happens to live:
data.update( it -> it.addRow("Carol", 55) );
It is a value
Every method on this class which sounds like it changes something really returns a newTableData, leaving the one you called it on untouched. Two
tables with the same cell order, editability, columns and cells are
equals(Object) to each
other and have the same hashCode(), which is what lets a table tell
whether anything actually changed.
Do not let the copying scare you: the cells live in immutable Tuples,
which share their structure between versions. Adding a row to a thousand row
table does not copy a thousand rows.
It is thread safe
Because this is a deeply immutable value, the application thread and the UI thread (the AWT Event Dispatch Thread) can hand it to each other without any locking. UnderEventProcessor.DECOUPLED, the value
in your property is what the UI thread reads, so every read within a
single UI thread task sees the same consistent table, even while your
application thread is busy building the next version of it.
It is cheap to update
ATuple remembers how it was derived from its predecessor, and a
UI.CellOrder.ROW_MAJOR table passes that knowledge straight to the
JTable: adding, removing or changing a handful of rows only
repaints those rows, rather than rebuilding the whole table. This is why a row
major cell order is the right default for large, frequently changing tables.
Rows and columns, whatever the cell order
ThecellOrder() decides how the cells() are stored: in a
UI.CellOrder.ROW_MAJOR table the outer Tuple holds the rows and
each inner Tuple holds the cells of one row, whereas in a
UI.CellOrder.COLUMN_MAJOR table the outer Tuple holds the columns.
That is a storage detail: every method on this class speaks in (row, column)
terms no matter which cell order you picked, so addRow(Object...) adds a row
to a column major table just as happily (it is merely a little more work for it).-
Method Summary
Modifier and TypeMethodDescriptionaddColumn(@Nullable String columnName, Class<?> columnClass, sprouts.Tuple<@Nullable Object> values) Appends a column to the right of this table, with a name, a class and its cells:addColumnAt(int columnIndex, @Nullable String columnName, Class<?> columnClass, sprouts.Tuple<@Nullable Object> values) Inserts a column at the given index, with a name, a class and its cells.Appends a row to the end of this table, built from plain values:Appends a row to the end of this table.Inserts a row at the given index, built from plain values.Inserts a row at the given index.Appends several rows at once, which is the efficient way to grow a table by more than one row: a row major table hands this to aJTableas a single insertion of a range of rows, rather than one event per row.Inserts several rows at once, starting at the given index.Permits the user to edit the cells of this table, shorthand forwithEditability(UI.Editability.EDITABLE).Forbids the user to edit the cells of this table, shorthand forwithEditability(UI.Editability.READ_ONLY).The cell order deciding whether thecells()of this table are stored row by row or column by column.sprouts.Tuple<sprouts.Tuple<@Nullable Object>> cells()The raw cell values of this table, in the order described by itscellOrder().sprouts.Tuple<Class<?>> The column classes of this table, which aJTableconsults to pick a renderer and editor for each column.sprouts.Tuple<@Nullable String> The column names of this table, where anullentry means that the table falls back to the default (spreadsheet style) column name.Tells whether the user is allowed to edit the cells of this table.static TableDataempty()The shared empty table: no columns, no rows, not editable.booleansprouts.Tuple<@Nullable Object> getColumn(int columnIndex) Reads an entire column, padded withnullto the height of the table, so that the returned tuple always has exactlygetRowCount()entries.Class<?> getColumnClass(int columnIndex) Reads the class of a column, which aJTableconsults to pick a renderer and an editor for it.intReturns the number of columns in this table.@Nullable StringgetColumnName(int columnIndex) Reads the name of a column.sprouts.Tuple<@Nullable Object> getRow(int rowIndex) Reads an entire row, padded withnullto the width of the table, so that the returned tuple always has exactlygetColumnCount()entries.intReturns the number of rows in this table.@Nullable ObjectgetValueAt(int rowIndex, int columnIndex) Reads the value of a single cell, always in(row, column)terms, irrespective of thecellOrder()of this table.inthashCode()intindexOfColumn(@Nullable String columnName) Looks up a column by name, so that your business logic may address columns by what they mean rather than by where they happen to sit:booleanTells whether the user is allowed to edit the cells of this table.booleanisEmpty()Tells whether this table has nothing to show, meaning it has no rows, no columns, or neither.static TableDataof(UI.CellOrder cellOrder, String... columnNames) Creates a table which has columns but no rows yet, which is how most tables start out in life:static TableDataof(UI.CellOrder cellOrder, sprouts.Tuple<sprouts.Tuple<@Nullable Object>> cells) Creates a table from nothing but its cells, whose dimensions are derived from them: the major axis of thecellOrderis as long as the outerTuple, and the minor axis is as long as the longest innerTuple(so a ragged matrix reads as though it were padded withnullcells).static TableDataof(UI.CellOrder cellOrder, UI.Editability editability, sprouts.Tuple<@Nullable String> columnNames, sprouts.Tuple<Class<?>> columnClasses, sprouts.Tuple<sprouts.Tuple<@Nullable Object>> cells) Creates a fully described table, whose columns carry both a name and a class.Removes every row, while keeping the columns of this table exactly as they are.removeColumnAt(int columnIndex) Removes the column at the given index, along with its name and class.removeColumnsAt(int columnIndex, int count) Removes a whole range of columns at once, along with their names and classes.removeRowAt(int rowIndex) Removes the row at the given index.removeRowsAt(int rowIndex, int count) Removes a whole range of rows at once, which a row major table hands to aJTableas a single deletion of a range of rows.static sprouts.Tuple<@Nullable Object> Builds a single row (or column) out of plain values, which is handy when you want to hand a prepared row toaddRow(Tuple)orsetRowsAt(int, Tuple), or a prepared column toaddColumn(String, Class, Tuple).Produces a new table in which a single cell has a new value, reusing the immutable structure of this table for everything else.Replaces all of the cells of this table at once, keeping its cell order, editability and columns.setColumnAt(int columnIndex, sprouts.Tuple<@Nullable Object> values) Replaces the cells of the column at the given index, leaving its name and class alone.setColumnClassAt(int columnIndex, Class<?> columnClass) setColumnClasses(sprouts.Tuple<Class<?>> columnClasses) Changes the class of every column at once.setColumnNameAt(int columnIndex, @Nullable String columnName) Renames a single column, which a table picks up as a change of its header.setColumnNames(String... columnNames) Renames every column at once.setColumnNames(sprouts.Tuple<@Nullable String> columnNames) Renames every column at once.Replaces the row at the given index, built from plain values.Replaces the row at the given index.Replaces a whole range of rows at once, starting at the given index, which a row major table hands to aJTableas a single update of a range of rows.toString()withCellOrder(UI.CellOrder cellOrder) Changes the cell order of this table, which is how you tell it to read the very samecells()along the other axis:withEditability(UI.Editability editability) Changes whether the user may edit this table, without touching a single cell and without any risk of disturbing how the cells are read:
-
Method Details
-
empty
The shared empty table: no columns, no rows, not editable. A good starting point for a table you are about to build up.- Returns:
- An empty
TableData.
-
of
Creates a table which has columns but no rows yet, which is how most tables start out in life:
The columns are all of typeTableData.of(UI.CellOrder.ROW_MAJOR, "Name", "Age", "City")Objectuntil you say otherwise throughsetColumnClassAt(int, Class), and the table is read only until you say otherwise throughasEditable().- Parameters:
cellOrder- TheUI.CellOrderdeciding how the cells of the table are stored, seecellOrder().columnNames- The names of the columns, one per column.- Returns:
- A new
TableDatawith the given columns and no rows.
-
of
public static TableData of(UI.CellOrder cellOrder, sprouts.Tuple<sprouts.Tuple<@Nullable Object>> cells) Creates a table from nothing but its cells, whose dimensions are derived from them: the major axis of thecellOrderis as long as the outerTuple, and the minor axis is as long as the longest innerTuple(so a ragged matrix reads as though it were padded withnullcells). The columns have no names, which makes a table fall back to the default (spreadsheet style) names, and they are all of typeObject. The table is read only until you say otherwise throughasEditable().- Parameters:
cellOrder- TheUI.CellOrderdescribing howcellsis to be interpreted.cells- The cell values, an immutable tuple of rows (or columns, seecellOrder), each of which is an immutable tuple of cell values.- Returns:
- A new
TableData.
-
of
public static TableData of(UI.CellOrder cellOrder, UI.Editability editability, sprouts.Tuple<@Nullable String> columnNames, sprouts.Tuple<Class<?>> columnClasses, sprouts.Tuple<sprouts.Tuple<@Nullable Object>> cells) Creates a fully described table, whose columns carry both a name and a class.Note that the dimensions of the table are derived from what you pass here, and that the column metadata takes part in that: a table with three column names but rows only two cells wide still has three columns, the last of which reads as
null. This is what lets a table have columns but no rows.- Parameters:
cellOrder- TheUI.CellOrderdescribing howcellsis to be interpreted.editability- TheUI.Editabilitydeciding whether the user may edit the cells.columnNames- The column names, one per column, wherenullmeans "fall back to the default (spreadsheet style) name".columnClasses- The column classes, one per column, which aJTableconsults to pick a renderer and an editor.cells- The cell values, an immutable tuple of rows (or columns, seecellOrder), each of which is an immutable tuple of cell values.- Returns:
- A new
TableData.
-
row
Builds a single row (or column) out of plain values, which is handy when you want to hand a prepared row toaddRow(Tuple)orsetRowsAt(int, Tuple), or a prepared column toaddColumn(String, Class, Tuple).- Parameters:
values- The cell values of the row.- Returns:
- An immutable tuple of the given values.
-
cellOrder
The cell order deciding whether thecells()of this table are stored row by row or column by column.- Returns:
- The
UI.CellOrderof this table.
-
editability
Tells whether the user is allowed to edit the cells of this table.- Returns:
- The
UI.Editabilityof this table.
-
isEditable
public boolean isEditable()Tells whether the user is allowed to edit the cells of this table. Note that a table also needs to live in a mutableVarbefore an edit has anywhere to go.- Returns:
- True if the
editability()of this table permits cell editing.
-
getRowCount
public int getRowCount()Returns the number of rows in this table. @return The row count. -
getColumnCount
public int getColumnCount()Returns the number of columns in this table. @return The column count. -
isEmpty
public boolean isEmpty()Tells whether this table has nothing to show, meaning it has no rows, no columns, or neither.- Returns:
- True if this table has no cells to display.
-
getValueAt
Reads the value of a single cell, always in(row, column)terms, irrespective of thecellOrder()of this table.- Parameters:
rowIndex- The row index of the cell.columnIndex- The column index of the cell.- Returns:
- The value of the cell, or
nullif the indices are out of bounds.
-
getRow
Reads an entire row, padded withnullto the width of the table, so that the returned tuple always has exactlygetColumnCount()entries.- Parameters:
rowIndex- The index of the row.- Returns:
- The cells of the row, or an empty tuple if the index is out of bounds.
-
getColumn
Reads an entire column, padded withnullto the height of the table, so that the returned tuple always has exactlygetRowCount()entries.- Parameters:
columnIndex- The index of the column.- Returns:
- The cells of the column, or an empty tuple if the index is out of bounds.
-
getColumnName
Reads the name of a column.- Parameters:
columnIndex- The column index.- Returns:
- The column name, or
nullif the column has no name of its own (in which case a table falls back to the default, spreadsheet style name).
-
getColumnClass
Reads the class of a column, which aJTableconsults to pick a renderer and an editor for it.- Parameters:
columnIndex- The column index.- Returns:
- The column class, or
Objectif the column has no class of its own.
-
indexOfColumn
Looks up a column by name, so that your business logic may address columns by what they mean rather than by where they happen to sit:data.setCellAt(0, data.indexOfColumn("Age"), 31);- Parameters:
columnName- The name of the column to look for.- Returns:
- The index of the first column with the given name, or
-1if there is none.
-
cells
The raw cell values of this table, in the order described by itscellOrder(). This is exposed so that a table model can reuse the very same immutable structure without copying it, and so that you may reach for the fullTupleAPI when this class does not have the operation you need.- Returns:
- The immutable tuple of rows (or columns, see
cellOrder()).
-
columnNames
The column names of this table, where anullentry means that the table falls back to the default (spreadsheet style) column name.- Returns:
- The immutable tuple of column names.
-
columnClasses
The column classes of this table, which aJTableconsults to pick a renderer and editor for each column.- Returns:
- The immutable tuple of column classes.
-
setCellAt
Produces a new table in which a single cell has a new value, reusing the immutable structure of this table for everything else.Note that this is a targeted change: a row major table hands it to a
JTableas a single row update, so only that row repaints.- Parameters:
rowIndex- The row index of the cell to change.columnIndex- The column index of the cell to change.value- The new value of the cell.- Returns:
- A new
TableDatawith the changed cell, or this table unchanged if the indices are out of bounds or the cell already holds that value.
-
addRow
Appends a row to the end of this table, built from plain values:data.addRow("Alice", 30, "Rome")- Parameters:
values- The cell values of the new row, one per column.- Returns:
- A new
TableDatawith the additional row.
-
addRow
Appends a row to the end of this table.- Parameters:
row- The cells of the new row, one per column.- Returns:
- A new
TableDatawith the additional row.
-
addRowAt
Inserts a row at the given index, built from plain values.- Parameters:
rowIndex- The index the new row will have.values- The cell values of the new row, one per column.- Returns:
- A new
TableDatawith the additional row.
-
addRowAt
Inserts a row at the given index.- Parameters:
rowIndex- The index the new row will have.row- The cells of the new row, one per column.- Returns:
- A new
TableDatawith the additional row, or this table unchanged if the index is out of bounds.
-
addRows
Appends several rows at once, which is the efficient way to grow a table by more than one row: a row major table hands this to aJTableas a single insertion of a range of rows, rather than one event per row.- Parameters:
rows- The new rows, each holding one cell value per column.- Returns:
- A new
TableDatawith the additional rows.
-
addRowsAt
Inserts several rows at once, starting at the given index. SeeaddRows(Tuple)for why you should prefer this over adding the rows one at a time.- Parameters:
rowIndex- The index the first of the new rows will have.rows- The new rows, each holding one cell value per column.- Returns:
- A new
TableDatawith the additional rows, or this table unchanged if the index is out of bounds or there is nothing to add.
-
setRowAt
Replaces the row at the given index, built from plain values.- Parameters:
rowIndex- The index of the row to replace.values- The new cell values of the row, one per column.- Returns:
- A new
TableDatawith the replaced row.
-
setRowAt
Replaces the row at the given index.- Parameters:
rowIndex- The index of the row to replace.row- The new cells of the row, one per column.- Returns:
- A new
TableDatawith the replaced row, or this table unchanged if the index is out of bounds.
-
setRowsAt
Replaces a whole range of rows at once, starting at the given index, which a row major table hands to aJTableas a single update of a range of rows. This is the operation you want when a batch of your data changed and you would rather not rebuild the whole table for it.- Parameters:
rowIndex- The index of the first row to replace.rows- The new rows, each holding one cell value per column.- Returns:
- A new
TableDatawith the replaced rows, or this table unchanged if the range is out of bounds or there is nothing to replace.
-
removeRowAt
Removes the row at the given index.- Parameters:
rowIndex- The index of the row to remove.- Returns:
- A new
TableDatawithout that row, or this table unchanged if the index is out of bounds.
-
removeRowsAt
Removes a whole range of rows at once, which a row major table hands to aJTableas a single deletion of a range of rows.- Parameters:
rowIndex- The index of the first row to remove.count- The number of rows to remove.- Returns:
- A new
TableDatawithout those rows, or this table unchanged if the range is out of bounds or there is nothing to remove.
-
removeAllRows
Removes every row, while keeping the columns of this table exactly as they are. Reach forempty()instead if you want to be rid of the columns too.- Returns:
- A new
TableDatawith the same columns but no rows.
-
addColumn
public TableData addColumn(@Nullable String columnName, Class<?> columnClass, sprouts.Tuple<@Nullable Object> values) Appends a column to the right of this table, with a name, a class and its cells:data.addColumn("City", String.class, TableData.row("Rome", "Oslo"))- Parameters:
columnName- The name of the new column.columnClass- The class of the new column.values- The cells of the new column, one per row.- Returns:
- A new
TableDatawith the additional column.
-
addColumnAt
public TableData addColumnAt(int columnIndex, @Nullable String columnName, Class<?> columnClass, sprouts.Tuple<@Nullable Object> values) Inserts a column at the given index, with a name, a class and its cells.- Parameters:
columnIndex- The index the new column will have.columnName- The name of the new column.columnClass- The class of the new column.values- The cells of the new column, one per row.- Returns:
- A new
TableDatawith the additional column, or this table unchanged if the index is out of bounds.
-
setColumnAt
Replaces the cells of the column at the given index, leaving its name and class alone.- Parameters:
columnIndex- The index of the column to replace.values- The new cells of the column, one per row.- Returns:
- A new
TableDatawith the replaced column, or this table unchanged if the index is out of bounds.
-
removeColumnAt
Removes the column at the given index, along with its name and class.- Parameters:
columnIndex- The index of the column to remove.- Returns:
- A new
TableDatawithout that column, or this table unchanged if the index is out of bounds.
-
removeColumnsAt
Removes a whole range of columns at once, along with their names and classes.- Parameters:
columnIndex- The index of the first column to remove.count- The number of columns to remove.- Returns:
- A new
TableDatawithout those columns, or this table unchanged if the range is out of bounds or there is nothing to remove.
-
setColumnNameAt
Renames a single column, which a table picks up as a change of its header.- Parameters:
columnIndex- The index of the column to rename.columnName- The new name of the column, wherenullmeans "fall back to the default (spreadsheet style) name".- Returns:
- A new
TableDatawith the renamed column, or this table unchanged if the index is out of bounds or the name did not change.
-
setColumnNames
Renames every column at once.- Parameters:
columnNames- The new column names, one per column.- Returns:
- A new
TableDatawith the new column names.
-
setColumnNames
Renames every column at once. Note that the number of names takes part in deciding how many columns this table has, so handing over more names than the cells fill widens the table.- Parameters:
columnNames- The new column names, one per column.- Returns:
- A new
TableDatawith the new column names.
-
setColumnClassAt
Changes the class of a single column, which is how you tell aJTableto render and edit it differently (aBooleancolumn becomes a column of check boxes, say).- Parameters:
columnIndex- The index of the column.columnClass- The new class of the column.- Returns:
- A new
TableDatawith the new column class, or this table unchanged if the index is out of bounds or the class did not change.
-
setColumnClasses
Changes the class of every column at once.- Parameters:
columnClasses- The new column classes, one per column.- Returns:
- A new
TableDatawith the new column classes.
-
setCells
Replaces all of the cells of this table at once, keeping its cell order, editability and columns.- Parameters:
cells- The new cell values, an immutable tuple of rows (or columns, seecellOrder()).- Returns:
- A new
TableDatawith the new cells.
-
withCellOrder
Changes the cell order of this table, which is how you tell it to read the very samecells()along the other axis:
Careful: this reinterprets the cells rather than rearranging them, so a table whose cells stay put comes out transposed. Use it when you know the cells were the other way around all along, not to flip a table on screen.data.withCellOrder(UI.CellOrder.COLUMN_MAJOR)- Parameters:
cellOrder- The newUI.CellOrderof this table.- Returns:
- A new
TableDatawith the given cell order, or this table unchanged if it already has it.
-
withEditability
Changes whether the user may edit this table, without touching a single cell and without any risk of disturbing how the cells are read:
Note that an editable table also has to live in a mutabledata.withEditability(UI.Editability.EDITABLE)Varbefore an edit has anywhere to go.- Parameters:
editability- The newUI.Editabilityof this table.- Returns:
- A new
TableDatawith the given editability, or this table unchanged if it already has it.
-
asEditable
Permits the user to edit the cells of this table, shorthand forwithEditability(UI.Editability.EDITABLE).- Returns:
- A new editable
TableData, or this table unchanged if it already is.
-
asReadOnly
Forbids the user to edit the cells of this table, shorthand forwithEditability(UI.Editability.READ_ONLY).- Returns:
- A new read only
TableData, or this table unchanged if it already is.
-
equals
-
hashCode
public int hashCode() -
toString
-