www.pudn.com > code_source_compiere_erp_crm_logiciel_java.zip > MTable.java
/******************************************************************************
* The contents of this file are subject to the Compiere License Version 1.1
* ("License"); You may not use this file except in compliance with the License
* You may obtain a copy of the License at http://www.compiere.org/license.html
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
* the specific language governing rights and limitations under the License.
* The Original Code is Compiere ERP & CRM Business Solution
* The Initial Developer of the Original Code is Jorg Janke and ComPiere, Inc.
* Portions created by Jorg Janke are Copyright (C) 1999-2001 Jorg Janke, parts
* created by ComPiere are Copyright (C) ComPiere, Inc.; All Rights Reserved.
* Contributor(s): ______________________________________.
*****************************************************************************/
package org.compiere.model;
import javax.swing.table.*;
import javax.swing.event.*;
import java.sql.*;
import java.util.*;
import java.math.*;
import java.beans.*;
import java.io.Serializable;
import org.compiere.util.*;
/**
* Grid Table Model for JDBC access including buffering.
*
* The following data types are handeled
* Integer for all IDs
* BigDecimal for all Numbers
* Timestamp for all Dates
* String for all others
* The data is read via r/o resultset and cached in m_buffer. Writes/updates
* are via dynamically constructed SQL INSERT/UPDATE statements. The record
* is re-read via the resultset to get results of triggers.
*
*
* The model maintains and fires the requires TableModelEvent changes,
* the DataChanged events (loading, changed, etc.)
* as well as Vetoable Change event "RowChange"
* (for row changes initiated by moving the row in the table grid).
*
* @author Jorg Janke
* @version $Id: MTable.java,v 1.36 2003/05/04 06:40:55 jjanke Exp $
*/
public final class MTable
implements TableModel, Serializable
{
/**
* JDBC Based Buffered Table
*
* @param ctx Properties
* @param TableName table name
* @param WindowNo window no
* @param TabNo tab no
* @param withAccessControl if true adds AD_Client/Org restrictuins
*/
public MTable(Properties ctx, String TableName, int WindowNo, int TabNo,
boolean withAccessControl)
{
super();
Log.trace(Log.l4_Data, "MTable - " + TableName);
m_ctx = ctx;
setTableName(TableName);
m_WindowNo = WindowNo;
m_TabNo = TabNo;
m_withAccessControl = withAccessControl;
} // MTable
private Properties m_ctx;
private String m_tableName = "";
private int m_WindowNo;
private int m_TabNo;
private boolean m_withAccessControl;
private boolean m_readOnly = true;
private boolean m_deleteable = true;
/** Rowcount */
private int m_rowCount = 0;
/** Has Data changed? */
private boolean m_changed = false;
/** Index of changed row via SetValueAt */
private int m_rowChanged = -1;
/** Insert mode active */
private boolean m_inserting = false;
/** Inserted Row number */
private int m_newRow = -1;
/** Is the Resultset open? */
private boolean m_open = false;
// The buffer for all data
private volatile ArrayList m_buffer = new ArrayList(100);
private volatile ArrayList m_sort = new ArrayList(100);
/** Original row data */
private Object[] m_rowData = null;
/** Original data [row,col,data] */
private Object[] m_oldValue = null;
//
private Loader m_loader = null;
/** Columns */
private ArrayList m_fields = new ArrayList(30);
private ArrayList m_parameterSELECT = new ArrayList(5);
private ArrayList m_parameterWHERE = new ArrayList(5);
/** Complete SQL statement */
private String m_SQL;
/** SQL Statement for Row Count */
private String m_SQL_Count;
/** The SELECT clause with FROM */
private String m_SQL_Select;
/** The static where clause */
private String m_whereClause = "";
/** Show only Processed='N' and last 24h records */
private boolean m_onlyCurrentRows = false;
/** Show only Not processed and x days */
private int m_onlyCurrentDays = 1;
/** Static ORDER BY clause */
private String m_orderClause = "";
/** Index of Key Column */
private int m_keyColumnIndex = -1;
/** Index of ID */
private int m_IDColumnIndex = -1;
/** Index of Color determinating column */
private int m_colorColumnIndex = -1;
/** Index of RowID column */
private int m_rowIdColumnIndex = -1;
/** Index of Processed Column */
private int m_processedColumnIndex = -1;
/** Index of Active column */
private int m_activeColumnIndex = -1;
/** List of DataStatus Listeners */
private Vector m_dataStatusListeners;
/** List of TableModel Listeners */
private EventListenerList m_tableModelListeners = new EventListenerList();
/** Vetoable Change Bean support */
private VetoableChangeSupport m_vetoableChangeSupport = new VetoableChangeSupport(this);
/** Property of Vetoable Bean support "RowChange" */
public static final String PROPERTY = "MTable-RowSave";
/**
* Set Table Name
* @param newTableName table name
*/
public void setTableName(String newTableName)
{
if (m_open)
{
Log.error("MTable.setTableName - Table already open - ignored");
return;
}
if (newTableName == null || newTableName.length() == 0)
return;
m_tableName = newTableName;
} // setTableName
/**
* Get Table Name
* @return table name
*/
public String getTableName()
{
return m_tableName;
} // getTableName
/**
* Set Where Clause (w/o the WHERE and w/o History).
* @param newWhereClause sql where clause
* @param onlyCurrentRows only current rows
* @param onlyCurrentDays how many days back for current
* @return true if where clase set
*/
public boolean setWhereClause(String newWhereClause, boolean onlyCurrentRows, int onlyCurrentDays)
{
if (m_open)
{
Log.error("MTable.setWhereClause - Table already open - ignored");
return false;
}
//
m_whereClause = newWhereClause;
m_onlyCurrentRows = onlyCurrentRows;
m_onlyCurrentDays = onlyCurrentDays;
if (m_whereClause == null)
m_whereClause = "";
return true;
} // setWhereClause
/**
* Get Where Clause (w/o the WHERE and w/o History)
* @return where clause
*/
public String getWhereClause()
{
return m_whereClause;
} // getWhereClause
/**
* Is History displayed
* @return true if history displayed
*/
public boolean isOnlyCurrentRowsDisplayed()
{
return !m_onlyCurrentRows;
} // isHistoryDisplayed
/**
* Set Order Clause (w/o the ORDER BY)
* @param newOrderClause sql order by clause
*/
public void setOrderClause(String newOrderClause)
{
m_orderClause = newOrderClause;
if (m_orderClause == null)
m_orderClause = "";
} // setOrderClause
/**
* Get Order Clause (w/o the ORDER BY)
* @return order by clause
*/
public String getOrderClause()
{
return m_orderClause;
} // getOrderClause
/**
* Assemble & store
* m_SQL and m_countSQL
* @return m_SQL
*/
private String createSQL ()
{
Log.trace(Log.l4_Data, "MTable.getSQL");
//
if (m_fields.size() == 0 || m_tableName == null || m_tableName.equals(""))
return "";
// Create SELECT Part
StringBuffer select = new StringBuffer("SELECT ");
for (int i = 0; i < m_fields.size(); i++)
{
if (i > 0)
select.append(",");
MField field = (MField)m_fields.get(i);
select.append(field.getColumnName());
}
//
select.append(" FROM ").append(m_tableName);
m_SQL_Select = select.toString();
m_SQL_Count = "SELECT COUNT(*) FROM " + m_tableName;
//
StringBuffer where = new StringBuffer("");
// WHERE
if (m_whereClause.length() > 0)
{
where.append(" WHERE ");
if (m_whereClause.indexOf("@") == -1)
where.append(m_whereClause);
else // replace variables
where.append(Env.parseContext(m_ctx, m_WindowNo, m_whereClause, false));
}
if (m_onlyCurrentRows)
{
if (where.toString().indexOf(" WHERE ") == -1)
where.append(" WHERE ");
else
where.append(" AND ");
// Show only unprocessed or the one updated within x days
where.append("(Processed='N' OR Updated>SysDate-").append(m_onlyCurrentDays).append(")");
}
// RO/RW Access
m_SQL = m_SQL_Select + where.toString();
m_SQL_Count += where.toString();
if (m_withAccessControl)
{
if (m_readOnly)
{
m_SQL = Access.addROAccessSQL(m_ctx, m_SQL, m_tableName, true);
m_SQL_Count = Access.addROAccessSQL(m_ctx, m_SQL_Count, m_tableName, true);
}
else
{
m_SQL = Access.addRWAccessSQL(m_ctx, m_SQL, m_tableName, true);
m_SQL_Count = Access.addRWAccessSQL(m_ctx, m_SQL_Count, m_tableName, true);
}
}
// ORDER BY
if (!m_orderClause.equals(""))
m_SQL += " ORDER BY " + m_orderClause;
//
Env.setContext(m_ctx, m_WindowNo, m_TabNo, "SQL", m_SQL);
return m_SQL;
} // getSQL
/**
* Add Field to Table
* @param field field
*/
public void addField (MField field)
{
Log.trace(Log.l4_Data, "MTable.addField (" + m_tableName + ") - " + field.getColumnName());
if (m_open)
{
Log.error("MTable.addField - Table already open - ignored: " + field.getColumnName());
return;
}
// Set Index for RowID column
if (field.getDisplayType() == DisplayType.RowID)
m_rowIdColumnIndex = m_fields.size();
// Set Index for Key column
if (field.isKey())
m_keyColumnIndex = m_fields.size();
else if (field.getColumnName().equals("IsActive"))
m_activeColumnIndex = m_fields.size();
else if (field.getColumnName().equals("Processed"))
m_processedColumnIndex = m_fields.size();
else if (field.getDisplayType() == DisplayType.RowID)
m_IDColumnIndex = m_fields.size();
//
m_fields.add(field);
} // addColumn
/**
* Returns database column name
*
* @param index the column being queried
* @return column name
*/
public String getColumnName (int index)
{
if (index < 0 || index > m_fields.size())
{
Log.error("MTable.getColumnName - invalid index=" + index);
return "";
}
//
MField field = (MField)m_fields.get(index);
return field.getColumnName();
} // getColumnName
/**
* Returns a column given its name.
*
* @param columnName string containing name of column to be located
* @return the column index with columnName, or -1 if not found
*/
public int findColumn (String columnName)
{
for (int i = 0; i < m_fields.size(); i++)
{
MField field = (MField)m_fields.get(i);
if (columnName.equals(field.getColumnName()))
return i;
}
return -1;
} // findColumn
/**
* Returns Class of database column/field
*
* @param index the column being queried
* @return the class
*/
public Class getColumnClass (int index)
{
if (index < 0 || index >= m_fields.size())
{
Log.error("MTable.getColumnClass - invalid index=" + index);
return null;
}
MField field = (MField)m_fields.get(index);
return DisplayType.getClass(field.getDisplayType(), false);
} // getColumnClass
/**
* Set Select Clause Parameter.
* Assumes that you set parameters starting from index zero
* @param index index
* @param parameter parameter
*/
public void setParameterSELECT (int index, Object parameter)
{
if (index >= m_parameterSELECT.size())
m_parameterSELECT.add(parameter);
else
m_parameterSELECT.set(index, parameter);
} // setParameterSELECT
/**
* Set Where Clause Parameter.
* Assumes that you set parameters starting from index zero
* @param index index
* @param parameter parameter
*/
public void setParameterWHERE (int index, Object parameter)
{
if (index >= m_parameterWHERE.size())
m_parameterWHERE.add(parameter);
else
m_parameterWHERE.set(index, parameter);
} // setParameterWHERE
/**
* Get Column at index
* @param index index
* @return MField
*/
protected MField getField (int index)
{
if (index < 0 || index >= m_fields.size())
return null;
return (MField)m_fields.get(index);
} // getColumn
/**
* Return Columns with Indentifier (ColumnName)
* @param identifier column name
* @return MField
*/
protected MField getField (String identifier)
{
if (identifier == null || identifier.length() == 0)
return null;
int cols = m_fields.size();
for (int i = 0; i < cols; i++)
{
MField field = (MField)m_fields.get(i);
if (identifier.equalsIgnoreCase(field.getColumnName()))
return field;
}
// Log.error ("MTable.getField - not found: '" + identifier + "'");
return null;
} // getField
/*************************************************************************/
/**
* Open Database.
* if already opened, data is refreshed
*
* @return true if success
*/
public boolean open ()
{
Log.trace(Log.l4_Data, "MTable.open");
if (m_open)
{
Log.trace(Log.l5_DData, "already open");
dataRefreshAll();
return true;
}
// create m_SQL and m_countSQL
createSQL();
if (m_SQL.equals(""))
{
Log.error("MTable.open - No SQL");
return false;
}
// Start Loading
m_loader = new Loader();
m_rowCount = m_loader.open();
m_buffer = new ArrayList(m_rowCount+10);
m_sort = new ArrayList(m_rowCount+10);
if (m_rowCount > 0)
m_loader.start();
else
m_loader.close();
m_open = true;
//
m_changed = false;
m_rowChanged = -1;
return true;
} // open
/**
* Wait until async loader of Table and Lookup Fields is complete
* Used for performance tests
*/
public void loadComplete()
{
// Wait for loader
if (m_loader != null)
{
if (m_loader.isAlive())
{
try
{
m_loader.join();
}
catch (InterruptedException ie)
{
Log.error("MTable.loadComplete - join interrupted", ie);
}
}
}
// wait for field lookup loaders
for (int i = 0; i < m_fields.size(); i++)
{
MField field = (MField)m_fields.get(i);
field.lookupLoadComplete();
}
} // loadComplete
/**
* Is Loading
* @return true if loading
*/
public boolean isLoading()
{
if (m_loader != null && m_loader.isAlive())
return true;
return false;
} // isLoading
/**
* Is it open?
* @return true if opened
*/
public boolean isOpen()
{
return m_open;
} // isOpen
/**
* Close Resultset
* @param finalCall final call
*/
public void close (boolean finalCall)
{
if (!m_open)
return;
Log.trace(Log.l4_Data, "MTable.close - final=" + finalCall);
// remove listeners
if (finalCall)
{
m_dataStatusListeners.clear();
EventListener evl[] = m_tableModelListeners.getListeners(TableModelListener.class);
for (int i = 0; i < evl.length; i++)
m_tableModelListeners.remove(TableModelListener.class, evl[i]);
VetoableChangeListener vcl[] = m_vetoableChangeSupport.getVetoableChangeListeners();
for (int i = 0; i < vcl.length; i++)
m_vetoableChangeSupport.removeVetoableChangeListener(vcl[i]);
}
// Stop loader
while (m_loader != null && m_loader.isAlive())
{
Log.trace(Log.l5_DData, "Interrupting Loader");
m_loader.interrupt();
try
{
Thread.currentThread().sleep(200); // .2 second
}
catch (InterruptedException ie)
{}
}
if (!m_inserting)
dataSave(true);
if (m_buffer != null)
m_buffer.clear();
m_buffer = null;
if (m_sort != null)
m_sort.clear();
m_sort = null;
if (finalCall)
dispose();
// Fields are disposed from MTab
Log.trace(Log.l4_Data, "MTable.close - complete");
m_open = false;
} // close
/**
* Dispose MTable.
* Called by close-final
*/
private void dispose()
{
// MFields
for (int i = 0; i < m_fields.size(); i++)
((MField)m_fields.get(i)).dispose();
m_fields.clear();
m_fields = null;
//
m_dataStatusListeners = null;
m_tableModelListeners = null;
m_vetoableChangeSupport = null;
//
m_parameterSELECT.clear();
m_parameterSELECT = null;
m_parameterWHERE.clear();
m_parameterWHERE = null;
// clear data arrays
m_buffer = null;
m_sort = null;
m_rowData = null;
m_oldValue = null;
m_loader = null;
} // dispose
/**
* Get total database column count (displayed and not displayed)
* @return column count
*/
public int getColumnCount()
{
return m_fields.size();
} // getColumnCount
/**
* Get (displayed) field count
* @return field count
*/
public int getFieldCount()
{
return m_fields.size();
} // getFieldCount
/**
* Return number of rows
* @return Number of rows or 0 if not opened
*/
public int getRowCount()
{
return m_rowCount;
} // getRowCount
/**
* Set the Column to determine the color of the row
* @param columnName column name
*/
public void setColorColumn (String columnName)
{
m_colorColumnIndex = findColumn(columnName);
} // setColorColumn
/**
* Get ColorCode for Row.
*
* If numerical value in compare column is
* negative = -1,
* positive = 1,
* otherwise = 0
*
* @see #setColorColumn
* @param row row
* @return color code
*/
public int getColorCode (int row)
{
if (m_colorColumnIndex == -1)
return 0;
Object data = getValueAt(row, m_colorColumnIndex);
// We need to have a Number
if (data == null || !(data instanceof BigDecimal))
return 0;
int cmp = Env.ZERO.compareTo(data);
if (cmp > 0)
return -1;
if (cmp < 0)
return 1;
return 0;
} // getColorCode
/**
* Sort Entries by Column.
*
* actually the rows are not sorted, just the access pointer ArrayList
* with the same size as m_buffer with MSort entities
* @param col col
* @param ascending ascending
*/
public void sort (int col, boolean ascending)
{
Log.trace(Log.l4_Data, "MTable.sort #" + col + " " + ascending);
if (getRowCount() == 0)
return;
MField field = getField (col);
// RowIDs are not sorted
if (field.getDisplayType() == DisplayType.RowID)
return;
boolean isLookup = DisplayType.isLookup(field.getDisplayType());
// fill MSort entities with data entity
for (int i = 0; i < m_sort.size(); i++)
{
MSort sort = (MSort)m_sort.get(i);
Object[] rowData = (Object[])m_buffer.get(sort.index);
if (isLookup)
sort.data = field.getLookup().getDisplay(rowData[col]); // lookup
else
sort.data = rowData[col]; // data
}
// sort it
MSort sort = new MSort(0, null);
sort.setSortAsc(ascending);
Collections.sort(m_sort, sort);
// update UI
fireTableDataChanged();
// Info detected by MTab.dataStatusChanged and current row set to 0
fireDataStatusIEvent("Sorted");
} // sort
/**
* Get Key ID or -1 of none
* @param row row
* @return ID or -1
*/
public int getKeyID (int row)
{
// Log.info("MTable.getKeyID - row=" + row + ", keyColIdx=" + m_keyColumnIndex);
if (m_keyColumnIndex != -1)
{
try
{
Integer ii = (Integer)getValueAt(row, m_keyColumnIndex);
if (ii == null)
return -1;
return ii.intValue();
}
catch (Exception e) // Alpha Key
{
return -1;
}
}
return -1;
} // getKeyID
/**
* Get Key ColumnName
* @return key column name
*/
public String getKeyColumnName()
{
if (m_keyColumnIndex != -1)
return getColumnName(m_keyColumnIndex);
return "";
} // getKeyColumnName
/**
* Get Selected ROWID or null, if no RowID exists
* @param row row
* @return ROWID
*/
public Object getRowID (int row)
{
Object[] rid = getRID(row);
if (rid == null)
return null;
return rid[0];
} // getSelectedRowID
/**
* Get RowID Structure [0]=RowID, [1]=Selected, [2]=ID.
*
* Either RowID or ID is populated (views don't have RowID)
* @param row row
* @return RowID
*/
public Object[] getRID (int row)
{
if (m_rowIdColumnIndex == -1 || row < 0 || row >= getRowCount())
return null;
return (Object[])getValueAt(row, m_rowIdColumnIndex);
} // getRID
/**
* Find Row with RowID
* @param RowID row id or oid
* @return number of row or 0 if not found
*/
public int getRow (Object RowID)
{
if (RowID == null)
return 0;
// the value to find
String find = RowID.toString();
// Wait a bit to load rows
if (m_loader != null && m_loader.isAlive())
{
try
{
Thread.currentThread().sleep(250); // 1/4 second
}
catch (InterruptedException ie)
{}
}
// Build search vector
int size = m_sort.size(); // may still change
ArrayList search = new ArrayList(size);
for (int i = 0; i < size; i++)
{
Object[] r = (Object[])getValueAt(i, 0);
String s = r[0].toString();
MSort so = new MSort(i, s);
search.add(so);
}
// Sort it
MSort sort = new MSort(0, null);
Collections.sort(search, sort);
// Find it
int index = Collections.binarySearch(search, find, sort);
if (index < 0) // not found
{
search.clear();
return 0;
}
// Get Info
MSort result = (MSort)search.get(index);
// Clean up
search.clear();
return result.index;
} // getRow
/*************************************************************************/
/**
* Get Value in Resultset
* @param row row
* @param col col
* @return Object of that row/column
*/
public Object getValueAt (int row, int col)
{
// Log.trace(Log.l4_Data, "MTable.getValueAt r=" + row + " c=" + col);
if (!m_open || row < 0 || col < 0 || row >= m_rowCount)
{
// Log.trace(Log.l5_DData, "Out of bounds - Open=" + m_open + ", RowCount=" + m_rowCount);
return null;
}
// need to wait for data read into buffer
int loops = 0;
while (row >= m_buffer.size() && m_loader.isAlive() && loops < 15)
{
Log.trace(Log.l5_DData, "MTable.getValueAt - waiting for loader row=" + row + ", size=" + m_buffer.size());
try
{
Thread.currentThread().sleep(500); // 1/2 second
}
catch (InterruptedException ie)
{}
loops++;
}
// empty buffer
if (row >= m_buffer.size())
{
// Log.trace(Log.l5_DData, "Empty buffer");
return null;
}
// return Data item
MSort sort = (MSort)m_sort.get(row);
Object[] rowData = (Object[])m_buffer.get(sort.index);
// out of bounds
if (rowData == null || col > rowData.length)
{
// Log.trace(Log.l5_DData, "No data or Column out of bounds");
return null;
}
return rowData[col];
} // getValueAt
/**
* Indicate that there will be a change
* @param changed changed
*/
public void setChanged (boolean changed)
{
// Can we edit?
if (!m_open || m_readOnly)
return;
// Indicate Change
m_changed = changed;
if (!changed)
m_rowChanged = -1;
fireDataStatusIEvent("");
} // setChanged
/**
* Set Value in data and update MField.
* (called directly or from JTable.editingStopped())
*
* @param value value to assign to cell
* @param row row index of cell
* @param col column index of cell
*/
public final void setValueAt (Object value, int row, int col)
{
setValueAt (value, row, col, false);
} // setValueAt
/**
* Set Value in data and update MField.
* (called directly or from JTable.editingStopped())
*
* @param value value to assign to cell
* @param row row index of cell
* @param col column index of cell
* @param force force setting new value
*/
public final void setValueAt (Object value, int row, int col, boolean force)
{
// Can we edit?
if (!m_open || m_readOnly // not accessible
|| row < 0 || col < 0 // invalid index
|| col == 0 // cannot change ID
|| m_rowCount == 0) // no rows
return;
dataSave(row, false);
// Has anything changed?
Object oldValue = getValueAt(row, col);
if (!force && (
(oldValue == null && value == null)
|| (oldValue != null && oldValue.equals(value))
|| (oldValue != null && value != null && oldValue.toString().equals(value.toString()))
))
return;
Log.trace(Log.l4_Data, "MTable.setValueAt r=" + row + " c=" + col
+ " = " + oldValue + " => " + value);
// Save old value
m_oldValue = new Object[3];
m_oldValue[0] = new Integer(row);
m_oldValue[1] = new Integer(col);
m_oldValue[2] = oldValue;
// Set Data item
MSort sort = (MSort)m_sort.get(row);
Object[] rowData = (Object[])m_buffer.get(sort.index);
m_rowChanged = row;
// Selection
if (col == 0)
{
rowData[col] = value;
m_buffer.set(sort.index, rowData);
return;
}
// save original value - deep copy
if (m_rowData == null)
{
int size = m_fields.size();
m_rowData = new Object[size];
for (int i = 0; i < size; i++)
m_rowData[i] = rowData[i];
}
// save & update
rowData[col] = value;
m_buffer.set(sort.index, rowData);
// update Table
fireTableCellUpdated(row, col);
// update MField
getField(col).setValue(value, m_inserting);
// inform
DataStatusEvent evt = createDSE();
evt.setChangedColumn(col);
fireDataStatusChanged(evt);
} // setValueAt
/**
* Get Old Value
* @param row row
* @param col col
* @return old value
*/
public Object getOldValue (int row, int col)
{
if (m_oldValue == null)
return null;
if (((Integer)m_oldValue[0]).intValue() == row
&& ((Integer)m_oldValue[1]).intValue() == col)
return m_oldValue[2];
return null;
} // getOldValue
/**
* Check if the current row needs to be saved.
* @param onlyRealChange if true the value of a field was actually changed
* (e.g. for new records, which have not been changed) - default false
* @return true it needs to be saved
*/
public boolean needSave(boolean onlyRealChange)
{
return needSave(m_rowChanged, onlyRealChange);
} // needSave
/**
* Check if the row needs to be saved.
* - only if nothing was changed
* @return true it needs to be saved
*/
public boolean needSave()
{
return needSave(m_rowChanged, false);
} // needSave
/**
* Check if the row needs to be saved.
* - only when row changed
* - only if nothing was changed
* @param newRow to check
* @return true it needs to be saved
*/
public boolean needSave(int newRow)
{
return needSave(newRow, false);
} // needSave
/**
* Check if the row needs to be saved.
* - only when row changed
* - only if nothing was changed
* @param newRow to check
* @param onlyRealChange if true the value of a field was actually changed
* (e.g. for new records, which have not been changed) - default false
* @return true it needs to be saved
*/
public boolean needSave(int newRow, boolean onlyRealChange)
{
Log.trace(Log.l4_Data, "MTable.needSave - Row=" + newRow,
"Changed=" + m_rowChanged + " " + m_changed); // m_rowChanged set in setValueAt
// nothing done
if (!m_changed && m_rowChanged == -1)
return false;
// E.g. New unchanged records
if (m_changed && m_rowChanged == -1 && onlyRealChange)
return false;
// same row
if (newRow == m_rowChanged)
return false;
return true;
} // dataSave
/*************************************************************************/
public static final char SAVE_OK = 'O'; // the only OK condition
public static final char SAVE_ERROR = 'E';
public static final char SAVE_ACCESS = 'A';
public static final char SAVE_MANDATORY = 'M';
public static final char SAVE_ABORT = 'U';
/**
* Check if it needs to be saved and save it.
* @param newRow row
* @param manualCmd manual command to save
* @return true if not needed to be saved or successful saved
*/
public boolean dataSave (int newRow, boolean manualCmd)
{
Log.trace(Log.l4_Data, "MTable.dataSave Row=" + newRow,
"Changed=" + m_rowChanged + " " + m_changed); // m_rowChanged set in setValueAt
// nothing done
if (!m_changed && m_rowChanged == -1)
return true;
// same row, don't save yet
if (newRow == m_rowChanged)
return true;
return (dataSave(manualCmd) == SAVE_OK);
} // dataSave
/**
* Save unconditional.
* @param manualCmd if true, no vetoable PropertyChange will be fired for save confirmation
* @return OK Or Error condition
* Error info (Access*, FillMandatory, SaveErrorNotUnique,
* SaveErrorRowNotFound, SaveErrorDataChanged) is saved in the log
*/
public char dataSave (boolean manualCmd)
{
// cannot save
if (!m_open)
{
Log.trace(Log.l4_Data, "MTable.dataSave Error", "Open=" + m_open);
return SAVE_ERROR;
}
// no need - not changed - row not positioned - no Value changed
if (m_rowChanged == -1)
{
Log.trace(Log.l4_Data, "MTable.dataSave NoNeed", "Changed=" + m_changed + ", Row=" + m_rowChanged);
// return SAVE_ERROR;
if (!manualCmd)
return SAVE_OK;
}
// Value not changed
if (m_rowData == null)
{
Log.trace(Log.l4_Data, "MTable.dataSave Error", "DataNull=" + (m_rowData == null));
return SAVE_ERROR;
}
Log.trace(Log.l4_Data, "MTable.dataSave", "Saving row " + m_rowChanged);
if (m_readOnly)
/** @todo save changes when processed */
// If Processed - not editable (Find always editable) -> ok for changing payment terms, etc.
{
Log.trace(Log.l4_Data, "IsReadOnly - ignored");
dataIgnore();
return SAVE_ACCESS;
}
// Can we change?
if (!Access.canUpdate(m_ctx, m_WindowNo))
{
fireDataStatusEEvent(Log.retrieveError());
dataIgnore();
return SAVE_ACCESS;
}
// row not positioned - no Value changed
if (m_rowChanged == -1)
{
if (m_newRow != -1) // new row and nothing changed - might be OK
m_rowChanged = m_newRow;
else
{
fireDataStatusEEvent("SaveErrorNoChange", "");
return SAVE_ERROR;
}
}
// inform about data save action, if not manually initiated
try
{
if (!manualCmd)
m_vetoableChangeSupport.fireVetoableChange(PROPERTY, 0, m_rowChanged);
}
catch (PropertyVetoException pve)
{
Log.trace(Log.l5_DData, pve.getMessage());
dataIgnore();
return SAVE_ABORT;
}
// get updated row data
MSort sort = (MSort)m_sort.get(m_rowChanged);
Object[] rowData = (Object[])m_buffer.get(sort.index);
// Check Mandatory
String missingColumns = getMandatory(rowData);
if (missingColumns.length() != 0)
{
fireDataStatusEEvent("FillMandatory", missingColumns);
return SAVE_MANDATORY;
}
/**
* Update row
*/
boolean error = false;
//
String is;
final String ERROR = "ERROR: ";
final String INFO = "Info: ";
// SQL with specific where clause
String SQL = m_SQL_Select;
StringBuffer refreshSQL = new StringBuffer(SQL).append(" WHERE "); // to be completed when key known
StringBuffer multiRowWHERE = new StringBuffer();
// Create SQL & RowID
Object rowID = null;
if (m_inserting)
SQL += " WHERE 1=2";
else
{
// FOR UPDATE causes - ORA-01002 fetch out of sequence
SQL += " WHERE ROWID=?";
rowID = getRowID (m_rowChanged);
}
try
{
PreparedStatement pstmt = DB.prepareStatement (SQL,
ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
if (!m_inserting)
DB.getDatabase().setRowID(pstmt, 1, rowID);
ResultSet rs = pstmt.executeQuery();
// only one row
if (!(m_inserting || rs.next()))
{
rs.close();
pstmt.close();
fireDataStatusEEvent("SaveErrorRowNotFound", "");
dataRefresh(m_rowChanged);
return SAVE_ERROR;
}
Object[] rowDataDB = null;
// Prepare
if (m_inserting)
{
Log.trace(Log.l4_Data, "start inserting ...");
rs.moveToInsertRow();
}
else
// get current Data in DB
rowDataDB = readData(rs);
/** Data:
* m_rowData = original Data
* rowData = updated Data
* rowDataDB = current Data in DB
* 1) Difference between original & updated Data? N:next
* 2) Difference between original & current Data? Y:don't update
* 3) Update current Data
* 4) Refresh to get last Data (changed by trigger, ...)
*/
// Constants for Created/Updated(By)
Timestamp now = new Timestamp(System.currentTimeMillis());
int user = Env.getContextAsInt(m_ctx, "#AD_User_ID");
/**
* for every column
*/
int size = m_fields.size();
for (int col = 0; col < size; col++)
{
MField field = (MField)m_fields.get(col);
// Log.trace(Log.l6_Database, field.getColumnName() + "= " + m_rowData[col] + " <> DB: " + rowDataDB[col] + " -> " + rowData[col]);
String columnName = field.getColumnName();
// RowID
if (field.getDisplayType() == DisplayType.RowID)
; // ignore
// New Key
else if (m_inserting && field.isKey())
{
if (columnName.endsWith("_ID"))
{
int insertID = DB.getKeyNextNo(m_ctx, m_WindowNo, m_tableName);
rs.updateInt(col+1, insertID); // ***
refreshSQL.append(columnName).append("=").append(insertID);
//
is = INFO + columnName + " -> " + insertID;
}
else // Key with String value
{
rs.updateString(col+1, rowData[col].toString()); // ***
refreshSQL.append(columnName).append("='").append(rowData[col]).append("'");
//
is = INFO + columnName + " -> " + rowData[col].toString();
}
Log.trace(Log.l5_DData, is);
} // New Key
// New DocumentNo
else if (columnName.equals("DocumentNo"))
{
boolean newDocNo = false;
String docNo = (String)rowData[col];
// we need to have a doc number
if (docNo == null || docNo.length() == 0)
newDocNo = true;
// Preliminary ID from CalloutSystem
else if (docNo.startsWith("<") && docNo.endsWith(">"))
newDocNo = true;
if (newDocNo || m_inserting)
{
String insertDoc = null;
// always overwrite if insering with mandatory DocType DocNo
if (m_inserting)
insertDoc = DB.getDocumentNo(m_ctx, m_WindowNo, m_tableName, true);
Log.trace(Log.l6_Database, "MTable.dataSave", "DocumentNo entered=" + docNo
+ ", DocTypeInsert=" + insertDoc + ", newDocNo=" + newDocNo);
// can we use entered DocNo?
if (insertDoc == null || insertDoc.length() == 0)
{
if (!newDocNo && docNo != null && docNo.length() > 0)
insertDoc = docNo;
else // get a number from DocType or Table
insertDoc = DB.getDocumentNo(m_ctx, m_WindowNo, m_tableName, false);
}
// There might not be an automatic document no for this document
if (insertDoc == null || insertDoc.length() == 0)
{
// in case DB function did not return a value
if (docNo != null && docNo.length() != 0)
insertDoc = (String)rowData[col];
else
{
error = true;
is = ERROR + field.getColumnName() + "= " + rowData[col]
+ " NO DocumentNo";
Log.trace(Log.l5_DData, is);
break;
}
}
//
rs.updateString(col+1, insertDoc); // ***
//
is = INFO + columnName + " -> " + insertDoc;
Log.trace(Log.l5_DData, is);
}
} // New DocumentNo
// New Value
else if (columnName.equals("Value") && m_inserting)
{
String value = (String)rowData[col];
// Get from Sequence, if not entered
if (value == null || value.length() == 0)
{
value = DB.getDocumentNo(m_ctx, m_WindowNo, m_tableName, false);
// No DocumentNo
if (value == null || value.length() == 0)
{
error = true;
is = ERROR + field.getColumnName() + "= " + rowData[col]
+ " No DocumentNo";
Log.trace(Log.l5_DData, is);
break;
}
}
rs.updateString(col+1, value); // ***
//
is = INFO + columnName + " -> " + value;
Log.trace(Log.l5_DData, is);
}
// Updated - check database
else if (columnName.equals("Updated"))
{
if (!m_inserting && !m_rowData[col].equals(rowDataDB[col])) // changed
{
error = true;
is = ERROR + field.getColumnName() + "= " + m_rowData[col]
+ " != DB: " + rowDataDB[col];
Log.trace(Log.l5_DData, is);
break;
}
rs.updateTimestamp(col+1, now); // ***
//
is = INFO + "Updated/By -> " + now + " " + user;
Log.trace(Log.l5_DData, is);
} // Updated
// UpdatedBy - update
else if (columnName.equals("UpdatedBy"))
{
rs.updateInt(col+1, user); // ***
} // UpdatedBy
// Created
else if (m_inserting && columnName.equals("Created"))
{
rs.updateTimestamp(col+1, now); // ***
} // Created
// CreatedBy
else if (m_inserting && columnName.equals("CreatedBy"))
{
rs.updateInt(col+1, user); // ***
} // CreatedBy
// Nothing changed & null
else if (m_rowData[col] == null && rowData[col] == null)
{
if (m_inserting)
{
rs.updateNull(col+1); // ***
is = INFO + columnName + "= NULL";
Log.trace(Log.l5_DData, is);
}
}
// Data changed
else if (m_inserting
|| (m_rowData[col] == null && rowData[col] != null)
|| (m_rowData[col] != null && rowData[col] == null)
|| !m_rowData[col].equals(rowData[col])) // changed
{
// Original == DB
if (m_inserting
|| (m_rowData[col] == null && rowDataDB[col] == null)
|| (m_rowData[col] != null && m_rowData[col].equals(rowDataDB[col])) )
{
String type = "String";
if (rowData[col] == null)
rs.updateNull(col+1); // ***
else
{
if (DisplayType.isNumeric(field.getDisplayType()))
{
rs.updateBigDecimal(col+1, (BigDecimal)rowData[col]); // ***
type = "Number";
}
else if (DisplayType.isID(field.getDisplayType()))
{
int number = 0;
try
{
number = Integer.parseInt(rowData[col].toString());
rs.updateInt(col+1, number);
}
catch (Exception e) // could also be a String (AD_Language, AD_Message)
{
rs.updateString(col+1, rowData[col].toString());
}
type = "ID";
}
else if (DisplayType.isDate(field.getDisplayType()))
{
rs.updateTimestamp(col+1, (Timestamp)rowData[col]); // ***
type = "Date";
}
else
rs.updateString(col+1, rowData[col].toString()); // ***
}
//
is = INFO + columnName + "= " + m_rowData[col]
+ " -> " + rowData[col] + " - " + type;
Log.trace(Log.l5_DData, is);
}
// Original != DB
else
{
error = true;
is = ERROR + field.getColumnName() + "= " + m_rowData[col]
+ " != DB: " + rowDataDB[col] + " -> " + rowData[col];
Log.trace(Log.l5_DData, is);
}
}
// MultiKey Inserting - retrieval sql
if (m_inserting && field.isParent())
{
if (multiRowWHERE.length() != 0)
multiRowWHERE.append(" AND ");
multiRowWHERE.append(columnName).append("=").append(rowData[col]);
if (rowData[col] == null)
Log.error("MTable.dataSave - inserting MultiKey Parent " + columnName + " is NULL");
}
} // for every column
if (error)
{
rs.cancelRowUpdates();
rs.close();
pstmt.close();
fireDataStatusEEvent("SaveErrorDataChanged", "");
dataRefresh(m_rowChanged);
return SAVE_ERROR;
}
/**
* Save to Database
*/
if (m_inserting)
{
Log.trace(Log.l5_DData, "inserting ...");
rs.insertRow();
}
else
{
Log.trace(Log.l5_DData, "updating ...");
rs.updateRow();
}
Log.trace(Log.l5_DData, "committing ...");
DB.commit(true);
// data may be updated by trigger after update
if (m_inserting)
{
// for rows with multiple keys, there is no single keyColumn=value
if (refreshSQL.indexOf("=") == -1)
{
refreshSQL.append(multiRowWHERE);
Log.trace(Log.l5_DData, "reading ... " + multiRowWHERE.toString());
}
else
Log.trace(Log.l5_DData, "reading ...");
// need to re-read row to get ROWID, Key, DocumentNo
rs.close();
rs = pstmt.executeQuery(refreshSQL.toString());
if (rs.next())
{
rowDataDB = readData(rs);
// update buffer
m_buffer.set(sort.index, rowDataDB);
fireTableRowsUpdated(m_rowChanged, m_rowChanged);
}
else
Log.error("MTable.dataSave - inserted row not found");
}
else
{
Log.trace(Log.l5_DData, "refreshing ...");
rs.refreshRow();
rowDataDB = readData(rs);
// update buffer
m_buffer.set(sort.index, rowDataDB);
fireTableRowsUpdated(m_rowChanged, m_rowChanged);
}
//
rs.close();
pstmt.close();
}
catch (SQLException e)
{
String msg = "SaveError";
if (e.getErrorCode() == 1) // Unique Constraint
{
Log.error ("MTable.dataSave - Key Not Unique", e);
msg = "SaveErrorNotUnique";
}
else
Log.error ("MTable.dataSave\nSQL= " + SQL, e);
fireDataStatusEEvent(msg, e.getLocalizedMessage());
return SAVE_ERROR;
}
// everything ok
m_rowData = null;
m_changed = false;
m_rowChanged = -1;
m_newRow = -1;
m_inserting = false;
fireDataStatusIEvent("Saved");
//
Log.trace(Log.l4_Data, "MTable.dataSave - fini");
return SAVE_OK;
} // dataSave
/**
* Get Mandatory empty columns
* @param rowData row data
* @return String with missing column headers/labels
*/
private String getMandatory(Object[] rowData)
{
// see also => ProcessParameter.saveParameter
StringBuffer sb = new StringBuffer();
// Check all columns
int size = m_fields.size();
for (int i = 0; i < size; i++)
{
MField field = (MField)m_fields.get(i);
if (field.isMandatory(true)) // check context
{
if (rowData[i] == null || rowData[i].toString().length() == 0)
{
field.setInserting (true); // set editable otherwise deadlock
field.setError(true);
if (sb.length() > 0)
sb.append(", ");
sb.append(field.getHeader());
}
else
field.setError(false);
}
}
if (sb.length() == 0)
return "";
return sb.toString();
} // getMandatory
/*************************************************************************/
/**
* New Record after current Row
* @param currentRow row
* @param copyCurrent copy
* @return true if success -
* Error info (Access*, AccessCannotInsert) is saved in the log
*/
public boolean dataNew (int currentRow, boolean copyCurrent)
{
Log.trace(Log.l4_Data, "MTable.dataNew - Current=" + currentRow + ", Copy=" + copyCurrent);
// see if we need to save
dataSave(-2, false);
// Read only
if (m_readOnly)
{
fireDataStatusEEvent("AccessCannotInsert", "");
return false;
}
/** @todo No TableLevel */
// || !Access.canViewInsert(m_ctx, m_WindowNo, tableLevel, true, true))
// fireDataStatusEvent(Log.retrieveError());
m_inserting = true;
// Create default data
int size = m_fields.size();
m_rowData = new Object[size]; // "original" data
Object[] rowData = new Object[size];
// fill data
if (copyCurrent)
{
MSort sort = (MSort) m_sort.get(currentRow);
Object[] origData = (Object[])m_buffer.get(sort.index);
for (int i = 0; i < size; i++)
rowData[i] = origData[i];
}
else // new
{
for (int i = 0; i < size; i++)
{
MField field = (MField)m_fields.get(i);
rowData[i] = field.getDefault();
field.setValue(rowData[i], m_inserting);
}
}
m_changed = true;
m_rowChanged = -1; // only changed in setValueAt
m_newRow = currentRow + 1;
// if there is no record, the current row could be 0 (and not -1)
if (m_buffer.size() < m_newRow)
m_newRow = m_buffer.size();
// add Data at end of buffer
MSort sort = new MSort(m_buffer.size(), null); // index
m_buffer.add(rowData);
// add Sort pointer
m_sort.add(m_newRow, sort);
m_rowCount++;
// inform
fireTableRowsInserted(m_newRow, m_newRow);
fireDataStatusIEvent(copyCurrent ? "UpdateCopied" : "Inserted");
Log.trace(Log.l4_Data, "MTable.dataNew - Current=" + currentRow + ", New=" + m_newRow + " - complete");
return true;
} // dataNew
/*************************************************************************/
/**
* Delete Data
* @param row row
* @return true if success -
* Error info (Access*, AccessNotDeleteable, DeleteErrorDependent,
* DeleteError) is saved in the log
*/
public boolean dataDelete (int row)
{
Log.trace(Log.l4_Data, "MTable.dataDelete - " + row);
if (row < 0)
return false;
Object rowID = getRowID(row);
if (rowID == null)
return false;
// Tab R/O
if (m_readOnly)
{
fireDataStatusEEvent("AccessCannotDelete", "");
return false;
}
// Is this record deletable?
if (!m_deleteable
|| (m_processedColumnIndex > 0 // processed
&& "Y".equals(getValueAt(row, m_processedColumnIndex)) // = Y
&& !m_tableName.startsWith("I_") )) // ignore Import tables
{
fireDataStatusEEvent("AccessNotDeleteable", "");
return false;
}
/** @todo check Access */
// fireDataStatusEvent(Log.retrieveError());
//
StringBuffer SQL = new StringBuffer("DELETE ");
SQL.append(m_tableName).append(" WHERE ROWID=?");
int no = 0;
try
{
PreparedStatement pstmt = DB.prepareStatement(SQL.toString());
DB.getDatabase().setRowID(pstmt, 1, rowID);
no = pstmt.executeUpdate();
pstmt.close();
}
catch (SQLException e)
{
Log.error ("MTable.dataDelete", e);
String msg = "DeleteError";
if (e.getErrorCode() == 2292) // Child Record Found
msg = "DeleteErrorDependent";
fireDataStatusEEvent(msg, e.getLocalizedMessage());
return false;
}
// Check Result
if (no != 1)
{
Log.error("MTable.dataDelete - Number of deleted rows = " + no);
return false;
}
// Get Sort
MSort sort = (MSort)m_sort.get(row);
int bufferRow = sort.index;
// Delete row in Buffer and shifts all below up
m_buffer.remove(bufferRow);
m_rowCount--;
// Delete row in Sort
m_sort.remove(row);
// Correct pointer in Sort
for (int i = 0; i < m_sort.size(); i++)
{
MSort ptr = (MSort)m_sort.get(i);
if (ptr.index > bufferRow)
ptr.index--; // move up
}
// inform
m_changed = false;
m_rowChanged = -1;
fireTableRowsDeleted(row, row);
fireDataStatusIEvent("Deleted");
Log.trace(Log.l4_Data, "MTable.dataDelete - " + row + " complete");
return true;
} // dataDelete
/*************************************************************************/
/**
* Ignore changes
*/
public void dataIgnore()
{
Log.trace(Log.l4_Data, "MTable.dataIgnore", "Inserting=" + m_inserting);
if (!m_inserting && !m_changed && m_rowChanged < 0)
{
Log.trace(Log.l5_DData, "MTable.dataIgnore - Nothing to ignore");
return;
}
// Inserting - delete new row
if (m_inserting)
{
// Get Sort
MSort sort = (MSort)m_sort.get(m_newRow);
int bufferRow = sort.index;
// Delete row in Buffer and shifts all below up
m_buffer.remove(bufferRow);
m_rowCount--;
// Delete row in Sort
m_sort.remove(m_newRow); // pintint to the last column, so no adjustment
//
m_changed = false;
m_rowData = null;
m_rowChanged = -1;
m_inserting = false;
// inform
fireTableRowsDeleted(m_newRow, m_newRow);
}
else
{
// update buffer
if (m_rowData != null)
{
MSort sort = (MSort)m_sort.get(m_rowChanged);
m_buffer.set(sort.index, m_rowData);
}
m_changed = false;
m_rowData = null;
m_rowChanged = -1;
m_inserting = false;
// inform
// fireTableRowsUpdated(m_rowChanged, m_rowChanged); >> messes up display?? (clearSelection)
}
m_newRow = -1;
fireDataStatusIEvent("Ignored");
} // dataIgnore
/**
* Refresh Row - ignore changes
* @param row row
*/
public void dataRefresh (int row)
{
Log.trace(Log.l4_Data, "MTable.dataRefresh " + row);
if (row < 0)
return;
Object rowID = getRowID(row);
if (rowID == null)
return;
// ignore
dataIgnore();
// Create SQL
String SQL = m_SQL_Select + " WHERE ROWID=?";
MSort sort = (MSort)m_sort.get(row);
Object[] rowDataDB = null;
try
{
PreparedStatement pstmt = DB.prepareStatement(SQL);
DB.getDatabase().setRowID(pstmt, 1, rowID);
ResultSet rs = pstmt.executeQuery();
// only one row
if (rs.next())
rowDataDB = readData(rs);
rs.close();
pstmt.close();
}
catch (SQLException e)
{
Log.error ("MTable.dataRefresh\nSQL=" + SQL, e);
fireTableRowsUpdated(row, row);
fireDataStatusEEvent("RefreshError", "");
return;
}
// update buffer
m_buffer.set(sort.index, rowDataDB);
// info
m_rowData = null;
m_changed = false;
m_rowChanged = -1;
m_inserting = false;
fireTableRowsUpdated(row, row);
fireDataStatusIEvent("Refreshed");
} // dataRefresh
/**
* Refresh all Rows - ignore changes
*/
public void dataRefreshAll()
{
Log.trace(Log.l4_Data, "MTable.dataRefreshAll");
dataIgnore();
close(false);
open();
// Info
m_rowData = null;
m_changed = false;
m_rowChanged = -1;
m_inserting = false;
fireTableDataChanged();
fireDataStatusIEvent("Refreshed");
} // dataRefreshAll
/**
* Requery with new whereClause
* @param whereClause sql where clause
* @param onlyCurrentRows only current rows
* @param onlyCurrentDays how many days back
* @return true if success
*/
public boolean dataRequery (String whereClause, boolean onlyCurrentRows, int onlyCurrentDays)
{
Log.trace(Log.l4_Data, "MTable.dataRequery - " + whereClause + "; OnlyCurrent=" + onlyCurrentRows);
close(false);
m_onlyCurrentDays = onlyCurrentDays;
setWhereClause(whereClause, onlyCurrentRows, m_onlyCurrentDays);
open();
// Info
m_rowData = null;
m_changed = false;
m_rowChanged = -1;
m_inserting = false;
fireTableDataChanged();
fireDataStatusIEvent("Refreshed");
return true;
} // dataRequery
/*************************************************************************/
/**
* Is Cell Editable
*
* @param row the row index being queried
* @param col the column index being queried
* @return true, if editable
*/
public boolean isCellEditable (int row, int col)
{
// Log.trace(Log.l6_Database, "MTable.isCellEditable");
// Make Rows selectable
if (col == 0)
return true;
// Entire Table not editable
if (m_readOnly)
return false;
// Key & ID not editable
if (col == m_IDColumnIndex || col == m_keyColumnIndex)
return false;
/** @todo check link columns */
// Check column range
if (col < 0 && col >= m_fields.size())
return false;
// IsActive Column always editable if no processed exists
if (col == m_activeColumnIndex && m_processedColumnIndex == -1)
return true;
// Row
if (!isRowEditable(row))
return false;
// Column
return ((MField)m_fields.get(col)).isEditable(false);
} // IsCellEditable
/**
* Is Current Row Editable
* @param row row
* @return true if editable
*/
public boolean isRowEditable (int row)
{
// Log.trace(Log.l6_Database, "MTable.isRowEditable");
// Entire Table not editable or no row
if (m_readOnly || row < 0)
return false;
// If not Active - not editable
if (m_activeColumnIndex > 0 && "N".equals(getValueAt(row, m_activeColumnIndex))) // && m_TabNo != Find.s_TabNo)
return false;
// If Processed - not editable (Find always editable)
if (m_processedColumnIndex > 0 && "Y".equals(getValueAt(row, m_processedColumnIndex))) // && m_TabNo != Find.s_TabNo)
return false;
return true;
} // isRowEditable
/**
* Set entire table as read only
* @param value new read only value
*/
public void setReadOnly (boolean value)
{
Log.trace(Log.l4_Data, "MTable.setReadOnly " + value);
m_readOnly = value;
} // setReadOnly
/**
* Is Read/Only
* @return true if read only
*/
public boolean isReadOnly()
{
return m_readOnly;
} // isReadOnly
/**
* Is inserting
* @return true if inserting
*/
public boolean isInserting()
{
return m_inserting;
} // isInserting
/**
* Can Table rows be deleted
* @param value new deleteable value
*/
public void setDeleteable (boolean value)
{
Log.trace(Log.l4_Data, "MTable.setDeleteable " + value);
m_deleteable = value;
} // setDeleteable
/*************************************************************************/
/**
* Read Data from Recordset
* @param rs result set
* @return Data Array
*/
private Object[] readData (ResultSet rs)
{
int size = m_fields.size();
Object[] rowData = new Object[size];
String columnName = null;
int displayType = 0;
try
{
// get row data
for (int j = 0; j < size; j++)
{
// Column Info
MField field = (MField)m_fields.get(j);
columnName = field.getColumnName();
displayType = field.getDisplayType();
// Number
if (DisplayType.isNumeric(displayType))
rowData[j] = rs.getBigDecimal(j+1); // BigDecimal
// ID, Lookup (UpdatedBy is a numeric column)
else if (DisplayType.isID(displayType) && (columnName.endsWith("_ID")) || columnName.endsWith("edBy"))
{
rowData[j] = new Integer(rs.getInt(j+1)); // Integer
if (rs.wasNull())
rowData[j] = null;
}
// Date
else if (DisplayType.isDate(displayType))
rowData[j] = rs.getTimestamp(j+1); // Timestamp
// RowID or Key (and Selection)
else if (displayType == DisplayType.RowID)
{
Object[] rid = new Object[3];
if (columnName.equals("ROWID"))
rid[0] = DB.getDatabase().getRowID(rs, j+1);
else
rid[2] = new Integer (rs.getInt(j+1));
rid[1] = new Boolean(false);
rowData[j] = rid;
}
// String
else
rowData[j] = rs.getString(j+1); // String
}
}
catch (SQLException e)
{
Log.error("MTable.readData - " + columnName + ", DT=" + displayType, e);
}
return rowData;
} // readData
/*************************************************************************/
/**
* Remove Data Status Listener
* @param l listener
*/
public synchronized void removeDataStatusListener(DataStatusListener l)
{
if (m_dataStatusListeners != null && m_dataStatusListeners.contains(l))
{
Vector v = (Vector) m_dataStatusListeners.clone();
v.removeElement(l);
m_dataStatusListeners = v;
}
} // removeDataStatusListener
/**
* Add Data Status Listener
* @param l listener
*/
public synchronized void addDataStatusListener(DataStatusListener l)
{
Vector v = m_dataStatusListeners == null ? new Vector(2) : (Vector) m_dataStatusListeners.clone();
if (!v.contains(l))
{
v.addElement(l);
m_dataStatusListeners = v;
}
} // addDataStatusListener
/**
* Inform Listeners
* @param e event
*/
private void fireDataStatusChanged (DataStatusEvent e)
{
if (m_dataStatusListeners != null)
{
Vector listeners = m_dataStatusListeners;
int count = listeners.size();
for (int i = 0; i < count; i++)
((DataStatusListener) listeners.elementAt(i)).dataStatusChanged(e);
}
} // fireDataStatusChanged
/**
* Create Data Status Event
* @return data status event
*/
private DataStatusEvent createDSE()
{
boolean changed = m_changed;
if (m_rowChanged != -1)
changed = true;
DataStatusEvent dse = new DataStatusEvent(this, m_rowCount, changed,
Env.isAutoCommit(m_ctx, m_WindowNo), m_inserting);
return dse;
} // createDSE
/**
* Create and fire Data Status Info Event
* @param AD_Message message
*/
protected void fireDataStatusIEvent (String AD_Message)
{
DataStatusEvent e = createDSE();
e.setInfo(AD_Message, "", false);
fireDataStatusChanged (e);
} // fireDataStatusEvent
/**
* Create and fire Data Status Error Event
* @param AD_Message message
* @param info info
*/
protected void fireDataStatusEEvent (String AD_Message, String info)
{
DataStatusEvent e = createDSE();
e.setInfo(AD_Message, info, true);
Log.saveError(AD_Message, info);
fireDataStatusChanged (e);
} // fireDataStatusEvent
/**
* Create and fire Data Status Event (from Error Log)
* @param errorLog error log info
*/
protected void fireDataStatusEEvent (ValueNamePair errorLog)
{
if (errorLog != null)
fireDataStatusEEvent (errorLog.getValue(), errorLog.getName());
} // fireDataStatusEvent
/*************************************************************************/
/**
* Adds a listener to the list that's notified each time a change
* to the data model occurs.
* @param l the TableModelListener
*/
public void addTableModelListener (TableModelListener l)
{
m_tableModelListeners.add (TableModelListener.class, l);
} // addTableModelListener
/**
* Removes a listener from the list that's notified each time a
* change to the data model occurs.
* @param l the TableModelListener
*/
public void removeTableModelListener (TableModelListener l)
{
m_tableModelListeners.remove (TableModelListener.class, l);
} // removeTableModelListener
/**
* Notifies all listeners that all cell values in the table's
* rows may have changed. The number of rows may also have changed
* and the JTable should redraw the
* table from scratch. The structure of the table (as in the order of the
* columns) is assumed to be the same.
*
* @see TableModelEvent
* @see EventListenerList
*/
public void fireTableDataChanged()
{
fireTableChanged (new TableModelEvent(this));
} // fireTableDataChanged
/**
* Notifies all listeners that the table's structure has changed.
* The number of columns in the table, and the names and types of
* the new columns may be different from the previous state.
* If the JTable receives this event and its
* autoCreateColumnsFromModel
* flag is set it discards any table columns that it had and reallocates
* default columns in the order they appear in the model. This is the
* same as calling setModel(TableModel) on the
* JTable.
*
* @see TableModelEvent
* @see EventListenerList
*/
public void fireTableStructureChanged()
{
fireTableChanged (new TableModelEvent(this, TableModelEvent.HEADER_ROW));
} // fireTableStructureChanged
/**
* Notifies all listeners that rows in the range
* [firstRow, lastRow], inclusive, have been inserted.
*
* @param firstRow the first row
* @param lastRow the last row
*
* @see TableModelEvent
* @see EventListenerList
*/
public void fireTableRowsInserted (int firstRow, int lastRow)
{
fireTableChanged (new TableModelEvent(this, firstRow, lastRow,
TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT));
} // fireTableRowsInserted
/**
* Notifies all listeners that rows in the range
* [firstRow, lastRow], inclusive, have been updated.
*
* @param firstRow the first row
* @param lastRow the last row
*
* @see TableModelEvent
* @see EventListenerList
*/
public void fireTableRowsUpdated(int firstRow, int lastRow)
{
fireTableChanged(new TableModelEvent(this, firstRow, lastRow,
TableModelEvent.ALL_COLUMNS, TableModelEvent.UPDATE));
} // fireTableRowsUpdated
/**
* Notifies all listeners that rows in the range
* [firstRow, lastRow], inclusive, have been deleted.
*
* @param firstRow the first row
* @param lastRow the last row
*
* @see TableModelEvent
* @see EventListenerList
*/
public void fireTableRowsDeleted(int firstRow, int lastRow)
{
fireTableChanged(new TableModelEvent(this, firstRow, lastRow,
TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE));
} // fireTableRowsDeleted
/**
* Notifies all listeners that the value of the cell at
* [row, column] has been updated.
*
* @param row row of cell which has been updated
* @param column column of cell which has been updated
* @see TableModelEvent
* @see EventListenerList
*/
public void fireTableCellUpdated(int row, int column)
{
fireTableChanged(new TableModelEvent(this, row, row, column));
} // fireTableCellUpdated
/**
* Forwards the given notification event to all
* TableModelListeners that registered
* themselves as listeners for this table model.
*
* @param e the event to be forwarded
*
* @see #addTableModelListener
* @see TableModelEvent
* @see EventListenerList
*/
public void fireTableChanged(TableModelEvent e)
{
// Guaranteed to return a non-null array
Object[] listeners = m_tableModelListeners.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length-2; i>=0; i-=2)
{
if (listeners[i]==TableModelListener.class)
{
((TableModelListener)listeners[i+1]).tableChanged(e);
}
}
} // fireTableChanged
/**
* Returns an array of all the listeners of the given type that
* were added to this model.
*
* @returns all of the objects receiving listenerType
* notifications from this model
* @since 1.3
*
public EventListener[] getListeners(Class listenerType)
{
return m_tableModelListeners.getListeners(listenerType);
} // getListeners
/*************************************************************************/
/**
* Remove Vetoable change listener for row changes
* @param l listener
*/
public synchronized void removeVetoableChangeListener(VetoableChangeListener l)
{
m_vetoableChangeSupport.removeVetoableChangeListener(l);
} // removeVetoableChangeListener
/**
* Add Vetoable change listener for row changes
* @param l listener
*/
public synchronized void addVetoableChangeListener(VetoableChangeListener l)
{
m_vetoableChangeSupport.addVetoableChangeListener(l);
} // addVetoableChangeListener
/**
* Fire Vetoable change listener for row changes
* @param e event
* @throws PropertyVetoException
*/
protected void fireVetoableChange(PropertyChangeEvent e) throws java.beans.PropertyVetoException
{
m_vetoableChangeSupport.fireVetoableChange(e);
} // fireVetoableChange
/**
* toString
* @return String representation
*/
public String toString()
{
return new StringBuffer("MTable[").append(m_tableName)
.append(",WindowNo=").append(m_WindowNo)
.append(",Tab=").append(m_TabNo).append("]").toString();
} // toString
/*************************************************************************/
/**
* ASync Loader
*/
class Loader extends Thread implements Serializable
{
/**
* Construct Loader
*/
public Loader()
{
super("TLoader");
} // Loader
private PreparedStatement m_pstmt = null;
private ResultSet m_rs = null;
/**
* Open ResultSet
* @return number of records
*/
private int open()
{
// Log.trace(Log.l4_Data, "MTable Loader.open");
// Get Number of Rows
int rows = 0;
try
{
PreparedStatement pstmt = DB.prepareStatement(m_SQL_Count);
setParameter (pstmt, true);
ResultSet rs = pstmt.executeQuery();
if (rs.next())
rows = rs.getInt(1);
rs.close();
pstmt.close();
}
catch (SQLException e0)
{
Log.error("MTable.Loader.open\nCount SQL=" + m_SQL_Count, e0);
return 0;
}
// open Statement (closed by Loader.close)
try
{
m_pstmt = DB.prepareStatement(m_SQL);
// m_pstmt.setFetchSize(20);
setParameter (m_pstmt, false);
m_rs = m_pstmt.executeQuery();
}
catch (SQLException e)
{
Log.error ("MTable.Loader.open\nFull SQL=" + m_SQL, e);
return 0;
}
StringBuffer info = new StringBuffer("Rows=");
info.append(rows);
if (rows == 0)
info.append(" - ").append(m_SQL_Count);
Log.trace(Log.l5_DData, "MTable.Loader.open", info.toString());
return rows;
} // open
/**
* Close RS and Statement
*/
private void close()
{
// Log.trace(Log.l4_Data, "MTable Loader.close");
try
{
if (m_rs != null)
m_rs.close();
if (m_pstmt != null)
m_pstmt.close();
}
catch (SQLException e)
{
Log.error ("Loader.closeRS", e);
}
m_rs = null;
m_pstmt = null;
} // close
/**
* Fill Buffer to include Row
*/
public void run()
{
Log.trace(Log.l4_Data, "MTable Loader.run");
if (m_rs == null)
return;
try
{
while(m_rs.next())
{
if (this.isInterrupted())
{
Log.trace(Log.l4_Data, "MTable Loader interrupted");
close();
return;
}
// Get Data
Object[] rowData = readData(m_rs);
// add Data
MSort sort = new MSort(m_buffer.size(), null); // index
m_buffer.add(rowData);
m_sort.add(sort);
// Statement all 250 rows & sleep
if (m_buffer.size() % 250 == 0)
{
// give the other processes a chance
try
{
yield();
sleep(10); // .01 second
}
catch (InterruptedException ie)
{
Log.trace(Log.l4_Data, "MTable Loader interrupted while sleeping");
close();
return;
}
DataStatusEvent evt = createDSE();
evt.setLoading(m_buffer.size());
fireDataStatusChanged(evt);
}
} // while(rs.next())
}
catch (SQLException e)
{
Log.error ("MTable Loader.run", e);
}
close();
fireDataStatusIEvent("");
} // run
/**
* Set Parameter for Query.
* elements must be Integer, BigDecimal, String (default)
* @param pstmt prepared statement
* @param countSQL count
*/
private void setParameter (PreparedStatement pstmt, boolean countSQL)
{
if (m_parameterSELECT.size() == 0 && m_parameterWHERE.size() == 0)
return;
try
{
int pos = 1; // position in Statement
// Select Clause Parameters
for (int i = 0; !countSQL && i < m_parameterSELECT.size(); i++)
{
Object para = m_parameterSELECT.get(i);
if (para != null)
Log.trace(Log.l6_Database, "Select Parameter " + i + "=" + para);
//
if (para == null)
;
else if (para instanceof Integer)
{
Integer ii = (Integer)para;
pstmt.setInt (pos++, ii.intValue());
}
else if (para instanceof BigDecimal)
pstmt.setBigDecimal (pos++, (BigDecimal)para);
else
pstmt.setString(pos++, para.toString());
}
// Where Clause Parameters
for (int i = 0; i < m_parameterWHERE.size(); i++)
{
Object para = m_parameterWHERE.get(i);
if (para != null)
Log.trace(Log.l6_Database, "Where Parameter " + i + "=" + para);
//
if (para == null)
;
else if (para instanceof Integer)
{
Integer ii = (Integer)para;
pstmt.setInt (pos++, ii.intValue());
}
else if (para instanceof BigDecimal)
pstmt.setBigDecimal (pos++, (BigDecimal)para);
else
pstmt.setString(pos++, para.toString());
}
}
catch (SQLException e)
{
Log.error ("MTable Loader.setParameter", e);
}
} // setParameter
} // Loader
} // MTable