The previous implementation using 2 parallel list doesn't work well in our field testing, reimplemented using a hashmap cache and working fine in production environment now.

Link to SF Tracker: http://sourceforge.net/support/tracker.php?aid=2913975
This commit is contained in:
Heng Sin Low 2010-03-31 02:58:36 +00:00
parent c123ddfdfa
commit 4b2c766803
1 changed files with 144 additions and 105 deletions

View File

@ -30,6 +30,7 @@ import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@ -87,9 +88,9 @@ public class GridTable extends AbstractTableModel
implements Serializable
{
/**
*
* generated
*/
private static final long serialVersionUID = 9013625748218987868L;
private static final long serialVersionUID = -4397161719594270579L;
/**
* JDBC Based Buffered Table
@ -129,7 +130,7 @@ public class GridTable extends AbstractTableModel
m_WindowNo = WindowNo;
m_TabNo = TabNo;
m_withAccessControl = withAccessControl;
m_virtual = virtual && MTable.get(ctx, AD_Table_ID).isHighVolume();
m_virtual = virtual;
} // MTable
private static CLogger log = CLogger.getCLogger(GridTable.class.getName());
@ -144,8 +145,6 @@ public class GridTable extends AbstractTableModel
private boolean m_deleteable = true;
//virtual table state variables
private boolean m_virtual;
private int m_cacheStart;
private int m_cacheEnd;
public static final String CTX_KeyColumnName = "KeyColumnName";
//
@ -168,6 +167,7 @@ public class GridTable extends AbstractTableModel
// The buffer for all data
private volatile ArrayList<Object[]> m_buffer = new ArrayList<Object[]>(100);
private volatile ArrayList<MSort> m_sort = new ArrayList<MSort>(100);
private volatile Map<Integer, Object[]> m_virtualBuffer = new HashMap<Integer, Object[]>(100);
/** Original row data */
private Object[] m_rowData = null;
/** Original data [row,col,data] */
@ -212,9 +212,13 @@ public class GridTable extends AbstractTableModel
/** Vetoable Change Bean support */
private VetoableChangeSupport m_vetoableChangeSupport = new VetoableChangeSupport(this);
private Thread m_loaderThread;
/** Property of Vetoable Bean support "RowChange" */
public static final String PROPERTY = "MTable-RowSave";
private final static Integer NEW_ROW_ID = Integer.valueOf(-1);
private static final int DEFAULT_FETCH_SIZE = 200;
/**
* Set Table Name
* @param newTableName table name
@ -591,17 +595,24 @@ public class GridTable extends AbstractTableModel
m_loader = new Loader();
m_rowCount = m_loader.open(maxRows);
if (m_virtual)
m_buffer = new ArrayList<Object[]>(210);
{
m_buffer = null;
m_virtualBuffer = new HashMap<Integer, Object[]>(210);
}
else
{
m_buffer = new ArrayList<Object[]>(m_rowCount+10);
}
m_sort = new ArrayList<MSort>(m_rowCount+10);
m_cacheStart = m_cacheEnd = -1;
if (m_rowCount > 0)
{
if (m_rowCount < 1000)
m_loader.run();
else
m_loader.start();
{
m_loaderThread = new Thread(m_loader, "TLoader");
m_loaderThread.start();
}
}
else
m_loader.close();
@ -638,13 +649,13 @@ public class GridTable extends AbstractTableModel
public void loadComplete()
{
// Wait for loader
if (m_loader != null)
if (m_loaderThread != null)
{
if (m_loader.isAlive())
if (m_loaderThread.isAlive())
{
try
{
m_loader.join();
m_loaderThread.join();
}
catch (InterruptedException ie)
{
@ -666,7 +677,7 @@ public class GridTable extends AbstractTableModel
*/
public boolean isLoading()
{
if (m_loader != null && m_loader.isAlive())
if (m_loaderThread != null && m_loaderThread.isAlive())
return true;
return false;
} // isLoading
@ -705,10 +716,10 @@ public class GridTable extends AbstractTableModel
}
// Stop loader
while (m_loader != null && m_loader.isAlive())
while (m_loaderThread != null && m_loaderThread.isAlive())
{
log.fine("Interrupting Loader ...");
m_loader.interrupt();
m_loaderThread.interrupt();
try
{
Thread.sleep(200); // .2 second
@ -721,13 +732,20 @@ public class GridTable extends AbstractTableModel
dataSave(false); // not manual
if (m_buffer != null)
{
m_buffer.clear();
m_buffer = null;
m_buffer = null;
}
if (m_sort != null)
{
m_sort.clear();
m_sort = null;
m_cacheStart = m_cacheEnd = -1;
m_sort = null;
}
if (m_virtualBuffer != null)
{
m_virtualBuffer.clear();
m_virtualBuffer = null;
}
if (finalCall)
dispose();
@ -757,10 +775,12 @@ public class GridTable extends AbstractTableModel
m_parameterWHERE = null;
// clear data arrays
m_buffer = null;
m_virtualBuffer = null;
m_sort = null;
m_rowData = null;
m_oldValue = null;
m_loader = null;
m_loaderThread = null;
} // dispose
/**
@ -837,6 +857,10 @@ public class GridTable extends AbstractTableModel
log.info("#" + col + " " + ascending);
if (getRowCount() == 0)
return;
//cache changed row
Object[] changedRow = m_rowChanged >= 0 ? getDataAtRow(m_rowChanged) : null;
GridField field = getField (col);
// RowIDs are not sorted
if (field.getDisplayType() == DisplayType.RowID)
@ -864,8 +888,26 @@ public class GridTable extends AbstractTableModel
Collections.sort(m_sort, sort);
if (m_virtual)
{
m_buffer.clear();
m_cacheStart = m_cacheEnd = -1;
Object[] newRow = m_virtualBuffer.get(NEW_ROW_ID);
m_virtualBuffer.clear();
if (newRow != null && newRow.length > 0)
m_virtualBuffer.put(NEW_ROW_ID, newRow);
if (changedRow != null && changedRow.length > 0)
{
if (changedRow[m_indexKeyColumn] != null && (Integer)changedRow[m_indexKeyColumn] > 0)
{
m_virtualBuffer.put((Integer)changedRow[m_indexKeyColumn], changedRow);
for(int i = 0; i < m_sort.size(); i++)
{
if (m_sort.get(i).index == (Integer)changedRow[m_indexKeyColumn])
{
m_rowChanged = i;
break;
}
}
}
}
//release sort memory
for (int i = 0; i < m_sort.size(); i++)
@ -933,7 +975,7 @@ public class GridTable extends AbstractTableModel
// need to wait for data read into buffer
int loops = 0;
while (row >= m_sort.size() && m_loader.isAlive() && loops < 15)
while (row >= m_sort.size() && m_loaderThread != null && m_loaderThread.isAlive() && loops < 15)
{
log.fine("Waiting for loader row=" + row + ", size=" + m_sort.size());
try
@ -963,20 +1005,22 @@ public class GridTable extends AbstractTableModel
return rowData[col];
} // getValueAt
private Object[] getDataAtRow(int row)
private Object[] getDataAtRow(int row)
{
return getDataAtRow(row, true);
}
private Object[] getDataAtRow(int row, boolean fetchIfNotFound)
{
MSort sort = (MSort)m_sort.get(row);
Object[] rowData = null;
if (m_virtual)
{
int bufferrow = -1;
if (sort.index != -1 && (row < m_cacheStart || row > m_cacheEnd))
if (sort.index != NEW_ROW_ID && !(m_virtualBuffer.containsKey(sort.index)) && fetchIfNotFound)
{
fillBuffer(row);
fillBuffer(row, DEFAULT_FETCH_SIZE);
}
bufferrow = row - m_cacheStart;
rowData = (Object[])m_buffer.get(bufferrow);
rowData = (Object[])m_virtualBuffer.get(sort.index);
}
else
{
@ -989,13 +1033,11 @@ private Object[] getDataAtRow(int row)
MSort sort = m_sort.get(row);
if (m_virtual)
{
int bufferrow = -1;
if (sort.index != -1 && (row < m_cacheStart || row > m_cacheEnd))
if (sort.index != NEW_ROW_ID && !(m_virtualBuffer.containsKey(sort.index)))
{
fillBuffer(row);
fillBuffer(row, DEFAULT_FETCH_SIZE);
}
bufferrow = row - m_cacheStart;
m_buffer.set(bufferrow, rowData);
m_virtualBuffer.put(sort.index, rowData);
}
else
{
@ -1004,14 +1046,14 @@ private Object[] getDataAtRow(int row)
}
private void fillBuffer(int start)
private void fillBuffer(int start, int fetchSize)
{
//adjust start if needed
if (start > 0)
{
if (start + 200 >= m_sort.size())
if (start + fetchSize >= m_sort.size())
{
start = start - (200 - ( m_sort.size() - start ));
start = start - (fetchSize - ( m_sort.size() - start ));
if (start < 0)
start = 0;
}
@ -1021,10 +1063,8 @@ private Object[] getDataAtRow(int row)
.append(" WHERE ")
.append(getKeyColumnName())
.append(" IN (");
m_cacheStart = start;
m_cacheEnd = m_cacheStart - 1;
Map<Integer, Integer>rowmap = new LinkedHashMap<Integer, Integer>(200);
for(int i = start; i < start+200 && i < m_sort.size(); i++)
Map<Integer, Integer>rowmap = new LinkedHashMap<Integer, Integer>(DEFAULT_FETCH_SIZE);
for(int i = start; i < start+fetchSize && i < m_sort.size(); i++)
{
if(i > start)
sql.append(",");
@ -1032,9 +1072,21 @@ private Object[] getDataAtRow(int row)
rowmap.put(m_sort.get(i).index, i);
}
sql.append(")");
m_buffer = new ArrayList<Object[]>(210);
for(int i = 0; i < 200; i++)
m_buffer.add(null);
Object[] newRow = m_virtualBuffer.get(NEW_ROW_ID);
//cache changed row
Object[] changedRow = m_rowChanged >= 0 ? getDataAtRow(m_rowChanged, false) : null;
m_virtualBuffer = new HashMap<Integer, Object[]>(210);
if (newRow != null && newRow.length > 0)
m_virtualBuffer.put(NEW_ROW_ID, newRow);
if (changedRow != null && changedRow.length > 0)
{
if (changedRow[m_indexKeyColumn] != null && (Integer)changedRow[m_indexKeyColumn] > 0)
{
m_virtualBuffer.put((Integer)changedRow[m_indexKeyColumn], changedRow);
}
}
PreparedStatement stmt = null;
ResultSet rs = null;
try
@ -1044,9 +1096,8 @@ private Object[] getDataAtRow(int row)
while(rs.next())
{
Object[] data = readData(rs);
int row = rowmap.remove(data[m_indexKeyColumn]);
m_buffer.set(row - m_cacheStart, data);
m_cacheEnd++;
rowmap.remove(data[m_indexKeyColumn]);
m_virtualBuffer.put((Integer)data[m_indexKeyColumn], data);
}
if (!rowmap.isEmpty())
{
@ -1059,7 +1110,6 @@ private Object[] getDataAtRow(int row)
for(Integer row : toremove)
{
m_sort.remove(row);
m_buffer.remove(row - m_cacheStart);
}
}
}
@ -1929,7 +1979,14 @@ private Object[] getDataAtRow(int row)
if (m_virtual)
{
MSort sort = m_sort.get(m_rowChanged);
sort.index = getKeyID(m_rowChanged);
int oldId = sort.index;
int newId = getKeyID(m_rowChanged);
if (newId != oldId)
{
sort.index = newId;
Object[] data = m_virtualBuffer.remove(oldId);
m_virtualBuffer.put(newId, data);
}
}
fireTableRowsUpdated(m_rowChanged, m_rowChanged);
}
@ -2073,6 +2130,19 @@ private Object[] getDataAtRow(int row)
fireDataStatusEEvent(msg, info, true);
return SAVE_ERROR;
}
else if (m_virtual && po.get_ID() > 0)
{
//update ID
MSort sort = m_sort.get(m_rowChanged);
int oldid = sort.index;
if (oldid != po.get_ID())
{
sort.index = po.get_ID();
Object[] data = m_virtualBuffer.remove(oldid);
data[m_indexKeyColumn] = sort.index;
m_virtualBuffer.put(sort.index, data);
}
}
// Refresh - update buffer
String whereClause = po.get_WhereClause(true);
@ -2090,11 +2160,6 @@ private Object[] getDataAtRow(int row)
Object[] rowDataDB = readData(rs);
// update buffer
setDataAtRow(m_rowChanged, rowDataDB);
if (m_virtual)
{
MSort sort = m_sort.get(m_rowChanged);
sort.index = getKeyID(m_rowChanged);
}
fireTableRowsUpdated(m_rowChanged, m_rowChanged);
}
}
@ -2392,19 +2457,11 @@ private Object[] getDataAtRow(int row)
// add Data at end of buffer
MSort newSort = m_virtual
? new MSort(-1, null)
? new MSort(NEW_ROW_ID, null)
: new MSort(m_sort.size(), null); // index
if (m_virtual)
{
m_buffer.add(m_newRow, rowData);
if (m_cacheStart == -1)
{
m_cacheStart = m_cacheEnd = m_newRow;
}
else if (m_cacheEnd < m_newRow)
{
m_cacheEnd = m_newRow;
}
m_virtualBuffer.put(NEW_ROW_ID, rowData);
}
else
{
@ -2583,11 +2640,15 @@ private Object[] getDataAtRow(int row)
}
// Get Sort
int bufferRow = m_virtual
? row - m_cacheStart
: sort.index;
// Delete row in Buffer and shifts all below up
m_buffer.remove(bufferRow);
if (m_virtual)
{
m_virtualBuffer.remove(sort.index);
}
else
{
// Delete row in Buffer and shifts all below up
m_buffer.remove(sort.index);
}
m_rowCount--;
// Delete row in Sort
@ -2598,26 +2659,10 @@ private Object[] getDataAtRow(int row)
for (int i = 0; i < m_sort.size(); i++)
{
MSort ptr = (MSort)m_sort.get(i);
if (ptr.index > bufferRow)
if (ptr.index > sort.index)
ptr.index--; // move up
}
}
else
{
if (m_cacheStart == row)
{
if (m_cacheStart < m_cacheEnd)
m_cacheStart++;
else
m_cacheStart = m_cacheEnd = -1;
}
else
{
m_cacheEnd--;
if (m_cacheStart > m_cacheEnd)
m_cacheStart = m_cacheEnd;
}
}
// inform
m_changed = false;
@ -2646,23 +2691,18 @@ private Object[] getDataAtRow(int row)
{
// Get Sort
MSort sort = (MSort)m_sort.get(m_newRow);
int bufferRow = m_virtual
? m_buffer.size() - 1
: sort.index;
// Delete row in Buffer and shifts all below up
m_buffer.remove(bufferRow);
if (m_virtual)
{
m_virtualBuffer.remove(NEW_ROW_ID);
}
else
{
// Delete row in Buffer and shifts all below up
m_buffer.remove(sort.index);
}
m_rowCount--;
// Delete row in Sort
m_sort.remove(m_newRow); // pintint to the last column, so no adjustment
if (m_virtual)
{
if (m_cacheEnd == m_newRow)
{
m_cacheEnd--;
if (m_cacheStart > m_cacheEnd)
m_cacheStart = m_cacheEnd;
}
}
//
m_changed = false;
m_rowData = null;
@ -3205,7 +3245,7 @@ private Object[] getDataAtRow(int row)
/**************************************************************************
* ASync Loader
*/
class Loader extends Thread implements Serializable
class Loader implements Serializable, Runnable
{
/**
*
@ -3217,7 +3257,6 @@ private Object[] getDataAtRow(int row)
*/
public Loader()
{
super("TLoader");
} // Loader
private PreparedStatement m_pstmt = null;
@ -3316,7 +3355,7 @@ private Object[] getDataAtRow(int row)
{
while (m_rs.next())
{
if (this.isInterrupted())
if (Thread.interrupted())
{
log.fine("Interrupted");
close();
@ -3345,8 +3384,8 @@ private Object[] getDataAtRow(int row)
// give the other processes a chance
try
{
yield();
sleep(10); // .01 second
Thread.yield();
Thread.sleep(10); // .01 second
}
catch (InterruptedException ie)
{