BasicTableModel.java
package swingtree.api.model;
import sprouts.Observable;
import sprouts.Event;
import swingtree.UI;
import swingtree.api.Buildable;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import java.util.Objects;
/**
* This interface defines a basic table model which can be used to create a table model using lambda expressions.
* Implementations of this are typically created declarative like so: <br>
* <pre>{@code
* UI.table().withModel(
* UI.tableModel()
* .colNames("A", "B")
* .colCount(()->2)
* .rowCount(()->3)
* .getsEntryAt((int row, int col)->
* vm.getData(row, col)
* )
* )
* }</pre>
*/
public interface BasicTableModel extends TableModel
{
/** {@inheritDoc} */
@Override int getRowCount();
/** {@inheritDoc} */
@Override int getColumnCount();
/** {@inheritDoc} */
@Override Object getValueAt(int rowIndex, int columnIndex);
/** {@inheritDoc} */
@Override void setValueAt(Object aValue, int rowIndex, int columnIndex);
/** {@inheritDoc} */
@Override default Class<?> getColumnClass(int columnIndex) { return Object.class; }
/** {@inheritDoc} */
@Override default String getColumnName(int columnIndex) { return null; }
/** {@inheritDoc} */
@Override default boolean isCellEditable(int rowIndex, int columnIndex) { return false; }
/** {@inheritDoc} */
@Override default void addTableModelListener(TableModelListener l) {throw new IllegalStateException("Not implemented");}
/** {@inheritDoc} */
@Override default void removeTableModelListener(TableModelListener l) {throw new IllegalStateException("Not implemented");}
/**
* Implementations of this functional interface translate to the {@link TableModel#getRowCount()} method.
*/
@FunctionalInterface interface RowCount { int get(); }
/**
* Implementations of this functional interface translate to the {@link TableModel#getColumnCount()} method.
*/
@FunctionalInterface interface ColumnCount { int get(); }
/**
* Implementations of this functional interface translate to the {@link TableModel#getValueAt(int, int)} method.
*/
@FunctionalInterface interface EntryGetter<E> { E get(int rowIndex, int colIndex); }
/**
* Implementations of this functional interface translate to the {@link TableModel#setValueAt(Object, int, int)} method.
*/
@FunctionalInterface interface EntrySetter<E> { void set(int rowIndex, int colIndex, E aValue); }
/**
* Implementations of this functional interface translate to the {@link TableModel#getColumnClass(int)} method.
*/
@FunctionalInterface interface ColumnClass<E> { Class<? extends E> get(int colIndex); }
/**
* Implementations of this functional interface translate to the {@link TableModel#isCellEditable(int, int)} method.
*/
@FunctionalInterface interface CellEditable { boolean is(int rowIndex, int colIndex); }
/**
* Implementations of this functional interface translate to the {@link TableModel#getColumnName(int)} method.
*/
@FunctionalInterface interface ColumnName { String get(int colIndex); }
/**
* The class below is a functional builder for creating a lambda based implementation of the {@link BasicTableModel}.
* This allows fo a boilerplate free functional API.
*/
class Builder<E> implements Buildable<BasicTableModel>
{
private final Class<E> commonEntryType;
private RowCount rowCount;
private ColumnCount colCount;
private EntryGetter<E> entryGetter;
private EntrySetter<E> entrySetter;
private ColumnClass<E> columnClass;
private CellEditable cellEditable;
private ColumnName columnName;
private Observable observableEvent;
public Builder( Class<E> commonEntryType ) {
this.commonEntryType = Objects.requireNonNull(commonEntryType);
}
/**
* Use this to define the lambda which dynamically determines the row count of the table model.
* @param rowCount The lambda which will be used to determine the row count of the table model.
* @return This builder instance.
*/
public Builder<E> rowCount( RowCount rowCount ) {
if ( rowCount == null ) throw new IllegalArgumentException("rowCount cannot be null");
if ( this.rowCount != null ) throw new IllegalStateException(RowCount.class.getSimpleName()+" already set");
this.rowCount = rowCount;
return this;
}
/**
* Use this to define the lambda which dynamically determines the column count of the table model.
* @param columnCount The lambda which will be used to determine the column count of the table model.
* @return This builder instance.
*/
public Builder<E> colCount( ColumnCount columnCount ) {
if ( columnCount == null ) throw new IllegalArgumentException("columnCount cannot be null");
if ( this.colCount != null ) throw new IllegalStateException(ColumnCount.class.getSimpleName()+" already set");
this.colCount = columnCount;
return this;
}
/**
* Accepts a lambda allowing the {@link javax.swing.JTable} to dynamically determines the value at a given row and column.
* @param entryGetter The lambda which will be used to determine the value at a given row and column.
* @return This builder instance.
*/
public Builder<E> getsEntryAt(EntryGetter<E> entryGetter) {
if ( entryGetter == null ) throw new IllegalArgumentException("valueAt cannot be null");
if ( this.entryGetter != null ) throw new IllegalStateException(EntryGetter.class.getSimpleName()+" already set");
this.entryGetter = entryGetter;
return this;
}
/**
* Accepts a lambda allowing lambda which allows the user of the {@link javax.swing.JTable} to set the value at a given row and column.
* @param entrySetter The lambda which will be used to set the value at a given row and column.
* @return This builder instance.
*/
public Builder<E> setsEntryAt(EntrySetter<E> entrySetter) {
if ( entrySetter == null ) throw new IllegalArgumentException("setValueAt cannot be null");
if ( this.entrySetter != null ) throw new IllegalStateException(EntrySetter.class.getSimpleName()+" already set");
this.entrySetter = entrySetter;
return this;
}
/**
* Accepts a lambda which allows the {@link javax.swing.JTable} to determine the class of the column at a given index.
* @param columnClass The lambda which will be used to determine the class of the column at a given index.
* @return This builder instance.
*/
public Builder<E> colClass( ColumnClass<E> columnClass ) {
if ( columnClass == null ) throw new IllegalArgumentException("columnClass cannot be null");
if ( this.columnClass != null ) throw new IllegalStateException(ColumnClass.class.getSimpleName()+" already set");
this.columnClass = columnClass;
return this;
}
/**
* Use this to define a fixed array of column classes.
* @param classes An array of column classes.
* @return This builder instance.
*/
public Builder<E> colClasses( Class<? extends E>... classes ) {
if ( classes == null ) throw new IllegalArgumentException("classes cannot be null");
return colClass((colIndex) -> colIndex >= classes.length ? commonEntryType : classes[colIndex]);
}
/**
* Accepts a lambda allowing the {@link javax.swing.JTable} to determine if the cell at a given row and column is editable.
* @param cellEditable The lambda which will be used to determine if the cell at a given row and column is editable.
* @return This builder instance.
*/
public Builder<E> isEditableIf( CellEditable cellEditable ) {
if ( cellEditable == null ) throw new IllegalArgumentException("cellEditable cannot be null");
if ( this.cellEditable != null ) throw new IllegalStateException(CellEditable.class.getSimpleName()+" already set");
this.cellEditable = cellEditable;
return this;
}
/**
* Use this to define the lambda which allows the {@link javax.swing.JTable} to determine the name of the column at a given index.
* @param columnName The lambda which will be used to determine the name of the column at a given index.
* @return This builder instance.
*/
public Builder<E> colName( ColumnName columnName ) {
if ( columnName == null ) throw new IllegalArgumentException("columnName cannot be null");
if (this.columnName != null)
throw new IllegalStateException(ColumnName.class.getSimpleName() + " already set");
this.columnName = columnName;
return this;
}
/**
* Use this to define a fixed array of column names.
* @param names An array of column names.
* @return This builder instance.
*/
public Builder<E> colNames( String... names ) {
if ( names == null ) throw new IllegalArgumentException("names cannot be null");
return colName((colIndex) -> names[colIndex]);
}
/**
* Use this to define the event which will be fired when the table model is updated.
* @param updateEvent The event which will be fired when the table model is updated.
* @return This builder instance.
*/
public Builder<E> updateOn( Observable updateEvent ) {
if ( updateEvent == null ) throw new IllegalArgumentException("updateEvent cannot be null");
if ( this.observableEvent != null ) throw new IllegalStateException(Event.class.getSimpleName()+" already set");
this.observableEvent = updateEvent;
return this;
}
/**
* Use this to build the {@link BasicTableModel} instance.
* @return The {@link BasicTableModel} instance.
*/
@Override public BasicTableModel build() {
FunTableModel tm = new FunTableModel();
if ( observableEvent != null )
observableEvent.subscribe(()-> UI.run(()->{
// We want the table model update to be as thorough as possible, so we
// will fire a table structure changed event, followed by a table data
// changed event.
tm.fireTableStructureChanged();
tm.fireTableDataChanged();
}));
return tm;
}
private class FunTableModel extends AbstractTableModel implements BasicTableModel {
@Override public int getRowCount() { return rowCount == null ? 0 : rowCount.get(); }
@Override public int getColumnCount() { return colCount == null ? 0 : colCount.get(); }
@Override public Object getValueAt(int rowIndex, int colIndex) { return entryGetter == null ? null : entryGetter.get(rowIndex, colIndex); }
@Override public void setValueAt(Object value, int rowIndex, int colIndex) { if ( entrySetter != null ) entrySetter.set(rowIndex, colIndex, (E) value); }
@Override public Class<?> getColumnClass(int colIndex) { return columnClass == null ? super.getColumnClass(colIndex) : columnClass.get(colIndex); }
@Override public boolean isCellEditable(int rowIndex, int colIndex) { return cellEditable != null && cellEditable.is(rowIndex, colIndex); }
@Override public String getColumnName(int colIndex) { return columnName == null ? super.getColumnName(colIndex) : columnName.get(colIndex); }
}
}
}