package org.appwork.utils.swing.table; import java.util.ArrayList; import java.util.Collections; import java.util.regex.Pattern; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableCellEditor; import org.appwork.storage.JSonStorage; import org.appwork.utils.logging.Log; import org.appwork.utils.swing.EDTHelper; public abstract class ExtTableModel<E> extends AbstractTableModel { /** * */ private static final long serialVersionUID = 939549808899567618L; /** * complete table structure has changed */ protected static final int UPDATE_STRUCTURE = 1; /** * Column instances */ protected ArrayList<ExtColumn<E>> columns = new ArrayList<ExtColumn<E>>(); /** * Modelid to have an seperate key for database savong */ private final String modelID; /** * the table that uses this model */ private ExtTable<E> table = null; /** * a list of objects. Each object represents one table row */ protected ArrayList<E> tableData = new ArrayList<E>(); protected ExtColumn<E> sortColumn; protected boolean sortOrderToggle = true; private final ArrayList<ExtComponentRowHighlighter<E>> extComponentRowHighlighters; /** * Create a new ExtTableModel. * * @param database * databaseinterface instance to store internal tabledata across * sessions * @param id * storageID. */ public ExtTableModel(final String id) { super(); this.extComponentRowHighlighters = new ArrayList<ExtComponentRowHighlighter<E>>(); this.modelID = id; this.initColumns(); final String columnId = JSonStorage.getStorage("ExtTableModel_" + this.modelID).get("SORTCOLUMN", this.columns.get(0).getID()); for (final ExtColumn<E> col : this.columns) { if (col.getID().equals(columnId)) { this.sortColumn = col; break; } } this.sortOrderToggle = JSonStorage.getStorage("ExtTableModel_" + this.modelID).get("SORTORDER", false); this.refreshSort(); } /** * @param files */ public void addAllElements(final E... files) { final E[] tmp = files.clone(); new EDTHelper<Object>() { @Override public Object edtRun() { final ArrayList<E> selection = ExtTableModel.this.getSelectedObjects(); for (final E e : tmp) { ExtTableModel.this.tableData.add(e); } ExtTableModel.this.refreshSort(); ExtTableModel.this.fireTableStructureChanged(); ExtTableModel.this.setSelectedObjects(selection); return null; } }.start(); } /** * Add a new Column to the model * * @param e * @see #columns */ public void addColumn(final ExtColumn<E> e) { e.setModel(this); this.columns.add(e); } /** * Adds a new column at the given index * * @param e * @param index * @see #addColumn(ExtColumn) */ public void addColumn(final ExtColumn<E> e, final int index) { e.setModel(this); this.columns.add(index, e); } /** * @param at */ public void addElement(final E at) { new EDTHelper<Object>() { @Override public Object edtRun() { final ArrayList<E> selection = ExtTableModel.this.getSelectedObjects(); ExtTableModel.this.tableData.add(at); ExtTableModel.this.refreshSort(); ExtTableModel.this.fireTableStructureChanged(); ExtTableModel.this.setSelectedObjects(selection); return null; } }.start(); } public synchronized void addExtComponentRowHighlighter(final ExtComponentRowHighlighter<E> h) { this.extComponentRowHighlighters.add(h); } /** * */ public void clear() { new EDTHelper<Object>() { @Override public Object edtRun() { ExtTableModel.this.tableData.clear(); ExtTableModel.this.fireTableStructureChanged(); return null; } }.start(); } /** * clears all selection models */ public void clearSelection() { if (this.table == null) { return; } this.table.getSelectionModel().clearSelection(); this.table.getColumnModel().getSelectionModel().clearSelection(); } /** * @param at * @return */ public boolean contains(final E at) { return this.tableData.contains(at); } /** * Returns the Celleditor for the given column * * @param convertColumnIndexToModel * @return */ public TableCellEditor getCelleditorByColumn(final int columnIndex) { /* * Math.max(0, columnIndex) * * WORKAROUND for -1 column access,Index out of Bound,Unknown why it * happens but this workaround seems to do its job */ return this.columns.get(Math.max(0, columnIndex)); } /** * Returns the Cellrenderer for this column * * @param columnIndex * @return */ public ExtColumn<E> getCellrendererByColumn(final int columnIndex) { /* * Math.max(0, columnIndex) * * WORKAROUND for -1 column access,Index out of Bound,Unknown why it * happens but this workaround seems to do its job */ return this.columns.get(Math.max(0, columnIndex)); } @SuppressWarnings("unchecked") public <T extends ExtColumn<E>> T getColumnByClass(final Class<T> clazz) { try { for (final ExtColumn<?> column : this.columns) { if (column.getClass().equals(clazz)) { return (T) column; } } } catch (final Exception e) { Log.exception(e); } return null; } @Override public Class<?> getColumnClass(final int columnIndex) { return Object.class; } /** * @return Returns the number of columns defined (incl. invisible columns) */ public int getColumnCount() { return this.columns.size(); } /** * @return the columns headername * @see ExtColumn#getName() */ @Override public String getColumnName(final int column) { /* * Math.max(0, columnIndex) * * WORKAROUND for -1 column access,Index out of Bound,Unknown why it * happens but this workaround seems to do its job */ return this.columns.get(Math.max(0, column)).getName(); } /** * @param i * @return */ public E getElementAt(final int i) { return this.tableData.get(i); } /** * @return */ public ArrayList<E> getElements() { return new ArrayList<E>(this.tableData); } /** * Returns the Columninstance * * @param columnIndex * @return */ public ExtColumn<E> getExtColumn(final int columnIndex) { return this.columns.get(Math.max(0, columnIndex)); } /** * @return a list of all ExtComponentRowHighlighters */ public ArrayList<ExtComponentRowHighlighter<E>> getExtComponentRowHighlighters() { // TODO Auto-generated method stub return this.extComponentRowHighlighters; } /** * @return the modelID */ public String getModelID() { return this.modelID; } /** * Returns the object that represents the row * * @param index * @return */ public E getObjectbyRow(final int index) { synchronized (this.tableData) { if (index >= 0 && index < this.tableData.size()) { return this.tableData.get(index); } return null; } } /** * Returns how many rows the model contains * * @see #tableData */ public int getRowCount() { return this.tableData.size(); } /** * Returns the row index for a given Object * * @param o * @return */ public int getRowforObject(final E o) { synchronized (this.tableData) { return this.tableData.indexOf(o); } } /** * Returns all selected Objects * * @return */ public ArrayList<E> getSelectedObjects() { final ArrayList<E> ret = new ArrayList<E>(); if (this.table == null) { return ret; } final int[] rows = this.table.getSelectedRows(); for (final int row : rows) { final E elem = this.getValueAt(row, 0); if (elem != null) { ret.add(elem); } } return ret; } /** * Returns the currently row sort column * * @return the {@link ExtTableModel#sortColumn} * @see ExtTableModel#sortColumn */ public ExtColumn<E> getSortColumn() { return this.sortColumn; } /** * @return the {@link ExtTableModel#table} * @see ExtTableModel#table */ public ExtTable<E> getTable() { return this.table; } /** * @return the {@link ExtTableModel#tableData} * @see ExtTableModel#tableData */ public ArrayList<E> getTableData() { return this.tableData; } /** * returns a copy of current objects in tablemodel * * @return */ public ArrayList<E> getTableObjects() { final ArrayList<E> ret = new ArrayList<E>(); ret.addAll(this.tableData); return ret; } /** * Returns the object for row 'rowIndex'. IN ExtTableModel, each row is * represented By one single object. the ExtColums renderer just renders * each object in its way */ public E getValueAt(final int rowIndex, final int columnIndex) { try { return this.tableData.get(rowIndex); } catch (final IndexOutOfBoundsException e) { return null; } } /** * Initialize the colums. * * e.g.: this.addColumn(new NameColumn("Name", this));<br> * this.addColumn(new UploadDateColumn("Upload date", this));<br> * this.addColumn(new FilesizeColumn("Size", this));<br> * */ protected abstract void initColumns(); /** * @return if the cell is editable. This information is stored in the * ExtColumn instance. * @see ExtColumn#isCellEditable(int, int) */ @Override public boolean isCellEditable(final int rowIndex, final int columnIndex) { return this.columns.get(columnIndex).isCellEditable(rowIndex, columnIndex); } /** * checks if this column is allowed to be hidden * * @param column * @return */ public boolean isHidable(final int column) { final ExtColumn<E> col = this.getExtColumn(column); try { return col.isHidable(); } catch (final Exception e) { Log.exception(e); return true; } } /** * Returns the current sortOrderToggle * * @return the {@link ExtTableModel#sortOrderToggle} * @see ExtTableModel#sortOrderToggle */ public boolean isSortOrderToggle() { return this.sortOrderToggle; } /** * Retrieves visible information form database interface to determine if the * column is visible or not * * @param column * @return */ public boolean isVisible(final int column) { final ExtColumn<E> col = this.getExtColumn(column); try { return JSonStorage.getStorage("ExtTableModel_" + this.modelID).get("VISABLE_COL_" + col.getName(), col.isDefaultVisible()); } catch (final Exception e) { Log.exception(e); return true; } } /** * Restores the sort order according to {@link #getSortColumn()} and * {@link #isSortOrderToggle()} * * */ public void refreshSort() { this.sort(this.sortColumn == null ? this.getExtColumn(0) : this.sortColumn, this.sortOrderToggle); } /** * @param selectedObjects */ public void removeAll(final ArrayList<E> selectedObjects) { final ArrayList<E> tmp = new ArrayList<E>(this.tableData); tmp.removeAll(selectedObjects); new EDTHelper<Object>() { @Override public Object edtRun() { final ArrayList<E> selection = ExtTableModel.this.getSelectedObjects(); ExtTableModel.this.tableData = tmp; ExtTableModel.this.refreshSort(); ExtTableModel.this.fireTableStructureChanged(); ExtTableModel.this.setSelectedObjects(selection); return null; } }.start(); } /** * @param startRow * @param ret * @param caseSensitive * @param regex * @return */ public E searchNextObject(final int startRow, final String ret, final boolean caseSensitive, final boolean regex) { Pattern p; if (!regex) { final String[] pats = ret.split("\\*"); final StringBuilder pattern = new StringBuilder(); for (final String pp : pats) { if (pattern.length() > 0) { pattern.append(".*?"); } pattern.append(Pattern.quote(pp)); } p = Pattern.compile(".*?" + pattern.toString() + ".*?", caseSensitive ? Pattern.CASE_INSENSITIVE : 0 | Pattern.DOTALL); } else { p = Pattern.compile(".*?" + ret + ".*?", caseSensitive ? Pattern.CASE_INSENSITIVE : 0 | Pattern.DOTALL); } for (int i = startRow; i < this.tableData.size(); i++) { for (int c = 0; c < this.columns.size(); c++) { if (this.columns.get(c).matchSearch(this.tableData.get(i), p)) { return this.tableData.get(i); } } } for (int i = 0; i < startRow; i++) { for (int c = 0; c < this.columns.size(); c++) { if (this.columns.get(c).matchSearch(this.tableData.get(i), p)) { return this.tableData.get(i); } } } return null; } /** * @param latest */ public void setSelectedObject(final E latest) { if (this.table == null) { return; } new EDTHelper<Object>() { @Override public Object edtRun() { if (ExtTableModel.this.table == null) { return null; } if (latest == null) { ExtTableModel.this.clearSelection(); return null; } ExtTableModel.this.clearSelection(); final int row = ExtTableModel.this.getRowforObject(latest); ExtTableModel.this.table.addRowSelectionInterval(row, row); return null; } }.start(); } /** * Sets the current selection to the given objects * * @param selections */ public void setSelectedObjects(final ArrayList<E> selections) { if (this.table == null) { return; } new EDTHelper<Object>() { @Override public Object edtRun() { if (ExtTableModel.this.table == null) { return null; } if (selections == null) { ExtTableModel.this.clearSelection(); return null; } if (selections.size() == 0) { return null; } // Transform to rowindex list final ArrayList<Integer> selectedRows = new ArrayList<Integer>(); int rowIndex = -1; for (final E obj : selections) { rowIndex = ExtTableModel.this.getRowforObject(obj); if (rowIndex >= 0) { selectedRows.add(rowIndex); } } Collections.sort(selectedRows); for (final Integer row : selectedRows) { ExtTableModel.this.table.addRowSelectionInterval(row, row); } return null; } }.start(); } /** * Sets the table in which the model is used. This method should only be * used internally. * * @param table */ protected void setTable(final ExtTable<E> table) { this.table = table; } // TODO docu @Override public void setValueAt(final Object value, final int rowIndex, final int columnIndex) { this.columns.get(columnIndex).setValueAt(value, rowIndex, columnIndex); } /** * Sets the column visible or invisible. This information is stored in * database interface for cross session use. * * * @param column * @param visible */ public void setVisible(final int column, final boolean visible) { final ExtColumn<E> col = this.getExtColumn(column); try { JSonStorage.getStorage("ExtTableModel_" + this.modelID).put("VISABLE_COL_" + col.getName(), visible); } catch (final Exception e) { Log.exception(e); } } /** * @return */ public int size() { return this.tableData.size(); } /** * Sorts the model with the column's rowsorter * * @param column * @param sortOrderToggle */ public void sort(final ExtColumn<E> column, final boolean sortOrderToggle) { this.sortColumn = column; this.sortOrderToggle = sortOrderToggle; try { JSonStorage.getStorage("ExtTableModel_" + this.modelID).put("SORTCOLUMN", column.getID()); JSonStorage.getStorage("ExtTableModel_" + this.modelID).put("SORTORDER", sortOrderToggle); } catch (final Exception e) { Log.exception(e); } Collections.sort(this.getTableData(), column.getRowSorter(sortOrderToggle)); } }