package org.openswing.swing.table.model.client;
import java.math.*;
import java.util.*;
import javax.swing.event.*;
import javax.swing.table.*;
import org.openswing.swing.logger.client.*;
import org.openswing.swing.message.receive.java.*;
import org.openswing.swing.table.columns.client.*;
import org.openswing.swing.util.java.*;
/**
* <p>Title: OpenSwing Framework</p>
* <p>Description: TableModel based on a ValueObject list.</p>
* <p>Copyright: Copyright (C) 2006 Mauro Carniel</p>
*
* <p> This file is part of OpenSwing Framework.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the (LGPL) Lesser General Public
* License as published by the Free Software Foundation;
*
* GNU LESSER GENERAL PUBLIC LICENSE
* Version 2.1, February 1999
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* The author may be contacted at:
* maurocarniel@tin.it</p>
*
* @author Mauro Carniel
* @version 1.0
*/
public class VOListTableModel extends AbstractTableModel {
/** adapter which maps v.o. attribute names to TableModel columns */
private VOListAdapter fieldAdapter;
/** ValueObject list */
private Vector data;
/** flag which indicates if the TableModel is changed (add/update/delete row) */
private boolean modified = false;
/** current grid mode; default value: READONLY */
private int mode = Consts.READONLY;
/** TableModel events listener */
private VOListTableModelListener modelListener = null;
/** indexes of TableModel changed rows (Integer objects) */
private ArrayList changedRows = new ArrayList();
/** collection of value objects that have been changed; content: row index, original v.o. (before changes) */
private Hashtable changedVOs = new Hashtable();
/**
* @param fieldAdapter adapter che collega modello dati e value object
*/
public VOListTableModel(VOListAdapter fieldAdapter,VOListTableModelListener modelListener) {
this.fieldAdapter = fieldAdapter;
data = new Vector();
this.modelListener = modelListener;
addTableModelListener(new TableModelListener() {
public void tableChanged(TableModelEvent e) {
if (mode==Consts.EDIT) {
Integer selRow = new Integer(e.getFirstRow());
if (!changedRows.contains(selRow)) {
changedRows.add(selRow);
}
}
}
});
}
/**
* @param column index of the column
* @return name of the attribute that corresponds to the given column
*/
public final int findColumn(String attrName) {
return fieldAdapter.getFieldIndex(attrName);
}
/**
* @param column index of the column
* @return name of the attribute that corresponds to the given column
*/
public final String getColumnName(int column) {
return(fieldAdapter.getFieldName(column));
}
/**
* @param column index of the column
* @return column type
*/
public final int getColumnType(int column) {
return(fieldAdapter.getFieldType(column));
}
/**
* Add a ValueObject to TableModel.
* @param object <code>ValueObject</code> to add
*/
public final void addObject(ValueObject object) {
int index = data.size();
data.addElement(object);
modified = true;
fireTableRowsInserted(index, index);
}
/**
* Insert a ValueObject at the specified position in the data vector.
* A new row will be inserted at the corresponding position in the table model.
* @param object <code>ValueObject</code> to add
* @param row row at which to insert the object
*/
public final void insertObjectAt(ValueObject object, int row) {
data.insertElementAt(object, row);
modified = true;
fireTableRowsInserted(row, row);
}
/**
* Notify the model that the ValueObject at the given row has been updated.
* @param row row of the updated object
*/
public final void updateObjectAt(int row) {
if((row < 0) || (row >= data.size()))
throw new IllegalArgumentException("Row out of bounds");
fireTableRowsUpdated(row, row);
}
/**
* Remove all ValueObjects from the data vector.
* All rows will be removed from the TableModel.
*/
public final void clear() {
data.removeAllElements();
modified = true;
}
/**
* Remove the ValueObject at the specified position from the data vector.
* The row at the corresponding position in the table model will be removed.
* @param row row of the object to remove
*/
public final void removeObjectAt(int row) {
data.removeElementAt(row);
modified = true;
fireTableRowsDeleted(row, row);
}
/**
* @return number of objects in the data vector (e.g., the number of rows in the TableModel)
*/
public final int getRowCount() {
return(data.size());
}
/**
* @return number of columns in this table model (e.g., the number of fields as reported by the attribute adapter)
*/
public final int getColumnCount() {
return(fieldAdapter.getFieldCount());
}
/**
* @param row row index
* @param column column index
* @return Object at the specified coordinates
*/
public final Object getValueAt(int row, int column) {
if (data.size()-1<row)
return null;
ValueObject object = (ValueObject)data.elementAt(row);
return(fieldAdapter.getField(object, column));
}
/**
* @param row row index
* @return <code>ValueObject</code> represented in the given row
* @see #getRowForObject
*/
public final ValueObject getObjectForRow(int row) {
if (row<data.size() && row>=0)
return((ValueObject)data.elementAt(row));
else
return null;
}
/**
* @param object <code>ValueObject</code> to locate
* @return row in the TableModel that represents the given object
* @see #getObjectForRow
*/
public final int getRowForObject(ValueObject object) {
return(data.indexOf(object));
}
/**
* @return <code>true</code> if modifications have been made, and <code>false</code> otherwise
* @see #clearModified
*/
public final boolean isModified() {
return(modified);
}
/**
* Reset the modification flag for this model.
* @see #isModified
*/
public final void clearModified() {
modified = false;
}
/**
* @param column column index
* @return <code>Class</code> object for the specified column
*/
public final Class getColumnClass(int column) {
return(fieldAdapter.getFieldClass(column));
}
/**
* Update TableModel with the ValueObject.
* @param object new ValueObject
* @param row row index
*/
public final void updateObjectAt(ValueObject object, int row) {
try {
data.setElementAt(object, row);
}
catch(Exception ex) {
Logger.error(this.getClass().getName(),"updateObjectAt","Error on updating ValueObject",ex);
}
}
/**
* @return ValueObject list
*/
public final Vector getDataVector() {
return(this.data);
}
/**
* @param row row index
* @param column column index
* @return <code>true</code> if the cell at the given row and column is editable and
* <code>false</code> otherwise. The editable state is based on FieldAdapter and on current grid edit mode.
*/
public final boolean isCellEditable(int row, int column) {
try {
if (mode==Consts.READONLY) {
Column col = fieldAdapter.getFieldColumn(column);
// if (col.getColumnType()==col.TYPE_FILE)
// return fieldAdapter.getTableContainer().isCellEditable(fieldAdapter.getGrids().getGridControl(),row,fieldAdapter.getFieldName(column));
// else
if (col.getColumnType()==col.TYPE_BUTTON && ((ButtonColumn)col).isEnableInReadOnlyMode())
// return true;
return fieldAdapter.getTableContainer().isCellEditable(fieldAdapter.getGrids().getGridControl(),row,fieldAdapter.getFieldName(column));
else if (col.getColumnType()==col.TYPE_LINK)
// return true;
return fieldAdapter.getTableContainer().isCellEditable(fieldAdapter.getGrids().getGridControl(),row,fieldAdapter.getFieldName(column));
else if (col.getColumnType()==col.TYPE_CHECK && ((CheckBoxColumn)col).isEnableInReadOnlyMode())
// return true;
return fieldAdapter.getTableContainer().isCellEditable(fieldAdapter.getGrids().getGridControl(),row,fieldAdapter.getFieldName(column));
return false;
}
else if (mode==Consts.INSERT) {
if (fieldAdapter.getGrids().isInsertRowsOnTop()) {
if (row<changedRows.size())
return fieldAdapter.getTableContainer().isCellEditable(fieldAdapter.getGrids().getGridControl(),row,fieldAdapter.getFieldName(column));
else
return false;
}
else {
if (row>=getRowCount()-changedRows.size())
return fieldAdapter.getTableContainer().isCellEditable(fieldAdapter.getGrids().getGridControl(),row,fieldAdapter.getFieldName(column));
else
return false;
}
} else {
// edit mode...
if (fieldAdapter.getGrids()!=null &&
fieldAdapter.getGrids().isEditOnSingleRow() &&
row!=fieldAdapter.getGrids().getCurrentEditingRow())
return false;
return fieldAdapter.getTableContainer().isCellEditable(fieldAdapter.getGrids().getGridControl(),row,fieldAdapter.getFieldName(column));
}
}
catch (Exception ex) {
Logger.error(this.getClass().getName(),"isCellEditable","Error defining cell editability",ex);
return false;
}
}
/**
* Set the current edit grid mode; default value: READONLY.
* @param mode current edit grid mode
*/
public final void setMode(int mode) {
if (mode!=Consts.READONLY && mode!=Consts.EDIT && mode!=Consts.INSERT)
throw new UnsupportedOperationException("Mode not supported: "+mode);
this.mode = mode;
changedRows.clear();
changedVOs.clear();
if (mode==Consts.INSERT) {
try {
if (fieldAdapter.getGrids().isInsertRowsOnTop()) {
this.insertObjectAt((ValueObject) fieldAdapter.getValueObjectType().getConstructor(new Class[0]).newInstance(new Object[0]),0);
changedRows.add(new Integer(0));
}
else {
this.insertObjectAt((ValueObject) fieldAdapter.getValueObjectType().getConstructor(new Class[0]).newInstance(new Object[0]),getRowCount());
changedRows.add(new Integer(getRowCount()-1));
}
}
catch (Exception ex) {
ex.printStackTrace();
}
catch (Error er) {
er.printStackTrace();
}
}
modelListener.modeChanged(mode);
}
/**
* @return current edit grid mode
*/
public final int getMode() {
return mode;
}
/**
* Set the value at the specified row and column in the TableModel.
* @param row row index
* @param column column index
* @param value new object for the specified coordinates
*/
public final void setValueAt(Object value, int row, int column) {
setValue(value, row, column);
}
/**
* Get value within a value object, for the specified attribute name.
* @param row row index
* @param attribute name
* @return object value related to the specified attribute name and row
*/
public final Object getField(int row, String attributeName) {
int colIndex = fieldAdapter.getFieldIndex(attributeName);
if (colIndex!=-1)
return fieldAdapter.getField(getObjectForRow(row),colIndex);
else
return null;
}
/**
* Set value within a value object, for the specified attribute name.
* Note: no validation task is performed.
* @param row row index
* @param attribute name
* @param value new Object to set onto ValueObject
*/
public final void setField(int row, String attributeName, Object value) {
// store the original v.o. if grid mode is EDIT...
try {
if (mode == Consts.EDIT && !changedVOs.containsKey(new Integer(row))) {
if (row<getRowCount()-fieldAdapter.getGrids().getCurrentNumberOfNewRows())
changedVOs.put(new Integer(row), getObjectForRow(row).clone());
}
if ( isValueChanged(value,row,fieldAdapter.getFieldIndex(attributeName)) &&
!changedRows.contains(new Integer(row)))
changedRows.add(new Integer(row));
}
catch (CloneNotSupportedException ex) {
Logger.error(this.getClass().getName(),"setField","Error while duplicating the edited v.o.",ex);
}
fieldAdapter.setField(getObjectForRow(row),attributeName,value);
}
/**
* @param value object to convert
* @param column model column index
* @return converted value, according to attribute type binded to column (e.g. attribute type = Float and value = new BigDecimal(1.2) => returned value = new Float(1.2)
*/
private Object convertObject(Object value,Object oldValue,int column) {
if (value==null)
return null;
if (value != null && value instanceof BigDecimal && oldValue != null &&
this.fieldAdapter.getFieldClass(column).equals(BigDecimal.class)) {
if (this.fieldAdapter.getFieldColumn(column) instanceof DecimalColumn &&
( (DecimalColumn)this.fieldAdapter.getFieldColumn(column)).getDecimals() > 0)
value = new BigDecimal(value.toString()).setScale( ( (DecimalColumn)this.
fieldAdapter.getFieldColumn(column)).getDecimals(),
BigDecimal.ROUND_HALF_UP);
else
value = new BigDecimal(value.toString()).setScale( ( (BigDecimal)oldValue).scale(), BigDecimal.ROUND_HALF_UP);
}
else if (value != null && value instanceof BigDecimal &&
(this.fieldAdapter.getFieldClass(column).equals(Integer.class) ||
this.fieldAdapter.getFieldClass(column).equals(Integer.TYPE))) {
value = new Integer(value.toString());
}
else if (value != null && value instanceof BigDecimal &&
(this.fieldAdapter.getFieldClass(column).equals(Double.class) ||
this.fieldAdapter.getFieldClass(column).equals(Double.TYPE))) {
value = new Double(value.toString());
}
else if (value != null && value instanceof BigDecimal &&
(this.fieldAdapter.getFieldClass(column).equals(Long.class) ||
this.fieldAdapter.getFieldClass(column).equals(Long.TYPE))) {
value = new Long(value.toString());
}
else if (value != null && value instanceof BigDecimal &&
(this.fieldAdapter.getFieldClass(column).equals(Short.class) ||
this.fieldAdapter.getFieldClass(column).equals(Short.TYPE))) {
value = new Short(value.toString());
}
else if (value != null && value instanceof BigDecimal &&
(this.fieldAdapter.getFieldClass(column).equals(Float.class) ||
this.fieldAdapter.getFieldClass(column).equals(Float.TYPE))) {
value = new Float(value.toString());
}
return value;
}
/**
* @param value balue just edited
* @param row row index
* @param column column index
* @return <code>true</code> if value being setted is changed, <code>false</code> otherwise
*/
private boolean isValueChanged(Object value, int row, int column) {
// retrieve previous cell value ...
Object oldValue = getValueAt(row,column);
if (isCellEditable(row,column)) {
if (oldValue != null && oldValue instanceof Number &&
value instanceof Double) {
oldValue = new Double(oldValue.toString());
}
// maybe changed value should be converted...
value = convertObject(value,oldValue,column);
if (oldValue == null && value != null ||
oldValue != null && value == null ||
oldValue != null && !oldValue.equals(value)) {
return true;
}
}
return false;
}
/**
* Set the value at the specified row and column in the TableModel, only if:
* - the grid is editable and the value is valid (i.e. GridController.validateCell method returns <code>true</code>)
* - the grid is in read only mode
* @param row row index
* @param column column index
* @param value new object for the specified coordinates
* @return <code>true</code> if value being setted is valid, <code>false</code> otherwise
*/
public final boolean setValue(Object value, int row, int column) {
// store the original v.o. if grid mode is EDIT...
try {
if (mode == Consts.EDIT && !changedVOs.containsKey(new Integer(row))) {
if (row<getRowCount()-fieldAdapter.getGrids().getCurrentNumberOfNewRows())
changedVOs.put(new Integer(row), getObjectForRow(row).clone());
}
}
catch (CloneNotSupportedException ex) {
Logger.error(this.getClass().getName(),"setValueAt","Error while duplicating the edited v.o.",ex);
}
// retrieve previous cell value ...
Object oldValue = getValueAt(row,column);
// maybe changed value should be converted...
value = convertObject(value,oldValue,column);
if (isCellEditable(row,column)) {
if (isValueChanged(value,row,column)) {
// validate new value...
super.setValueAt(value, row, column);
boolean isOk = fieldAdapter.getTableContainer().validateCell(
row,
fieldAdapter.getFieldName(column),
oldValue,
value);
if (isOk) {
ValueObject object = (ValueObject)data.elementAt(row);
fieldAdapter.setField(object, column, value);
modified = true;
fireTableCellUpdated(row,column);
return true;
}
else {
return false;
}
} else {
// ValueObject object = (ValueObject)data.elementAt(row);
// fieldAdapter.setField(object, column, value);
// modified = false;
// fireTableCellUpdated(row,column);
return true;
}
}
else {
super.setValueAt(value, row, column);
ValueObject object = (ValueObject)data.elementAt(row);
fieldAdapter.setField(object, column, value);
modified = false;
fireTableCellUpdated(row,column);
return true;
}
// super.setValueAt(value, row, column);
}
/**
* @return list of ValueObjects which have been changed (used when the grid is in INSERT/EDIT mode)
*/
public final ArrayList getChangedRows() {
ArrayList voList = new ArrayList();
for(int i=0;i<changedRows.size();i++)
voList.add( this.getObjectForRow( ((Integer)changedRows.get(i)).intValue() ) );
return voList;
}
/**
* @return list of indexes of TableModel changed rows (Integer objects)
*/
public final ArrayList getChangedRowIndexes() {
return changedRows;
}
/**
* @return int[] row indexes related to ValueObjects which have been changed (used when the grid is in INSERT/EDIT mode)
*/
public final int[] getChangedRowNumbers() {
int[] rowNumbers = new int[this.changedRows.size()];
for (int i=0; i<rowNumbers.length; i++)
rowNumbers[i] = ((Integer) this.changedRows.get(i)).intValue();
return rowNumbers;
}
/**
* Update ValueObjects into TableModel for the specified rows.
* @param rowNumbers row indexes in TableModel
* @param objects ValueObject to update into TableModel
*/
public final void updateValueObjectsAt(int[] rowNumbers, ValueObject[] objects) {
if (rowNumbers.length!=objects.length) {
Logger.error(this.getClass().getName(),"updateValueObjectsAt","VaLueObjects length is not equals to rowNumbers length.",null);
return;
}
for (int i=0; i<rowNumbers.length; i++)
updateObjectAt(objects[i],rowNumbers[i]);
}
/**
* @return ValueObject type
*/
public final Class getValueObjectType() {
return fieldAdapter.getValueObjectType();
}
/**
* @return list of value objects that have been changed; content: list of original v.o. (before changes)
*/
public final ArrayList getOldVOsChanged() {
ArrayList voList = new ArrayList();
for(int i=0;i<changedRows.size();i++)
voList.add( changedVOs.get(changedRows.get(i)) );
return voList;
}
}