/**
* Copyright 2005-2012 Akiban Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.persistit.ui;
import java.rmi.RemoteException;
import javax.swing.SwingUtilities;
import com.persistit.Key;
import com.persistit.KeyState;
import com.persistit.Management;
import com.persistit.Management.LogicalRecordCount;
/**
* @version 1.0
*/
class ManagementSlidingTableModel extends ManagementTableModel {
public final static int DEFAULT_ROW_CACHE_SIZE = 2048;
public final static int DEFAULT_INITIAL_SIZE_ESTIMATE = 10000;
public final static int SCROLL_BAR_ESTIMATE_MULTIPLIER = 3;
public final static int MAXIMUM_GROWTH_ESTIMATE = 100000;
public final static int DEFAULT_MAXIMUM_VALUE_SIZE = 400;
public final static int MINIMUM_REQUESTED_ROW_COUNT = 256;
/**
* Offset from beginning of logical keyspace to the first row in the row
* cache.
*/
private int _offset;
private boolean _deletingRows;
/**
* Size of _infoArray row cache
*/
private final int _rowCacheSize = DEFAULT_ROW_CACHE_SIZE;
private final int _maxValueSize = DEFAULT_MAXIMUM_VALUE_SIZE;
/**
* Current estimate of total row count
*/
private int _currentRowCount;
/**
* Indicates whether the total size is definite, meaning that we have
* traversed to the end of the logical key space and discovered the actual
* count.
*/
private boolean _definite;
/**
* Count of valid rows in the row cache.
*/
private int _valid = 0;
/**
* The volume name
*/
private String _volumeName;
/**
* The tree name
*/
private String _treeName;
/**
* The KeyFilter, represented as a string. If null, then all keys in the
* tree are selected.
*/
private String _keyFilterString;
/**
* Indicates that the row cache is unstable because a fetch operation is
* currently pending.
*/
private boolean _waiting;
/**
* Construct a TableModel to display one of the management info array
* structures such as BufferInfo[]. Each column is described by a property
* defined in the <code>ResourceBundle</code> associated with this class.
* The property name is constructed from the last "." piece of the name of
* the supplied class and the column index as follows: <code><pre>
* property Name ::=
* <i>classNameTail</i>.column.<i>columnIndex</i>
* </pre></code> For example, the specification for the first column of the
* {@link com.persistit.Management.BufferInfo} class is <code><pre>
* BufferInfo.column.0
* </pre></code> The value associated with this property has the following
* structure: <code><pre>
* property Value ::=
* <i>accessorMethodName</i>:<i>width</i>:<i>flags</i>:<i>header</i>
* </code></pre> For example <code><pre>
* getRightSiblingAddress:10:A:Right Pointer
* </code></pre> where the <i>accessorMethodName</code> is simply a method
* name in the supplied class (the method must take no arguments), the
* <i>width</i> is a percentage of the total table width, and
* <i>justification</i> is L, C or R.
*
* @param clazz
*/
public ManagementSlidingTableModel(final Class clazz, final String className, final AdminUI ui)
throws NoSuchMethodException {
super(clazz, className, ui);
}
void reset() {
_volumeName = null;
_treeName = null;
_keyFilterString = null;
_valid = 0;
_offset = 0;
_currentRowCount = DEFAULT_INITIAL_SIZE_ESTIMATE;
_definite = false;
}
void set(final String volumeName, final String treeName, final String keyFilterString) {
reset();
_volumeName = volumeName;
_treeName = treeName;
_keyFilterString = keyFilterString;
fireTableDataChanged();
}
boolean isDeletingRows() {
return _deletingRows;
}
@Override
public int getRowCount() {
return _currentRowCount;
}
@Override
public Object getValueAt(final int row, final int col) {
// if ((row % 1000) == 0 && col == 0) System.out.println("Getting row "
// + row);
if (row < 0 || (_definite && row >= _currentRowCount)) {
return null;
} else if (col >= 0 && (row < _offset || row >= _offset + _valid)) {
return fetch(row);
} else {
return super.getValueAt(row - _offset, col);
}
}
private synchronized Object fetch(final int row) {
if (_waiting) {
return _adminUI.getWaitingMessage();
}
_waiting = true;
int fromOffset;
int requestedCount;
int skipCount = 0;
boolean forward;
if (_valid == 0) {
forward = true;
fromOffset = -1;
requestedCount = row + 1;
} else if (row >= _offset + _valid) {
forward = true;
fromOffset = _offset + _valid - 1;
requestedCount = row - (_offset + _valid) + 1;
} else if (row < _offset && row > _offset - row) {
forward = false;
requestedCount = _offset - row;
fromOffset = _offset;
} else {
forward = true;
fromOffset = -1;
requestedCount = row + 1;
}
if (requestedCount < MINIMUM_REQUESTED_ROW_COUNT) {
requestedCount = MINIMUM_REQUESTED_ROW_COUNT;
} else {
if (requestedCount > _rowCacheSize) {
skipCount = requestedCount - (_rowCacheSize / 2);
requestedCount = _rowCacheSize;
}
}
// System.out.println(
// "new Fetcher(forward=" + forward +
// ",fromOffset=" + fromOffset + ",requestedCount=" + requestedCount +
// ",skipCount=" + skipCount + "): _offset=" + _offset +
// " and _valid=" + _valid);
final Fetcher fetcher = new Fetcher(forward, fromOffset, requestedCount, skipCount);
new Thread(fetcher).start();
return _adminUI.getWaitingMessage();
}
private synchronized void receiveData(final Fetcher fetcher) {
_waiting = false;
if (fetcher._exception != null) {
_adminUI.postException(fetcher._exception);
}
if (fetcher._resultRows == null)
return;
if (_infoArray == null || _infoArray.length != _rowCacheSize) {
_infoArray = new Object[_rowCacheSize];
_offset = 0;
_valid = 0;
}
final int count = fetcher._resultRows.length;
int newValid = _valid + count;
// System.out.println(
// "receivedData(_offset=" + _offset +
// ",_valid=" + _valid +
// ",count=" + count +
// ",fetcher._from=" + fetcher._from +
// ",fetcher._forward=" + fetcher._forward +
// ",fetcher._skipCount=" + fetcher._skipCount +
// ",fetcher._requestedCount=" + fetcher._requestedCount
// );
final int oldOffset = _offset;
int newOffset = oldOffset;
final int oldRowCount = _currentRowCount;
int firstUpdatedRow;
int lastUpdatedRow;
int lost = 0; // rows lost from row cache
int kept = _valid; // rows kept from row cache
int cut = 0; // rows cut from fetcher._resultRows
if (fetcher._forward) {
if (fetcher._from != _offset + _valid - 1) {
_valid = 0;
kept = 0;
newValid = count;
newOffset = fetcher._from + 1;
}
if (newValid > _rowCacheSize) {
lost = newValid - _rowCacheSize;
newOffset += lost;
if (lost < _valid) {
kept = _valid - lost;
System.arraycopy(_infoArray, lost, _infoArray, 0, kept);
} else {
cut = lost - _valid;
kept = 0;
}
newValid = _rowCacheSize;
}
System.arraycopy(fetcher._resultRows, cut, _infoArray, kept, count - cut);
firstUpdatedRow = newOffset + kept;
lastUpdatedRow = firstUpdatedRow + count - cut - 1;
if (count < fetcher._requestedCount) {
changeRowCount(newOffset + newValid, true);
}
} else {
if (fetcher._from != _offset) {
_valid = 0;
kept = 0;
newValid = count;
newOffset = fetcher._from - count;
} else {
newOffset = oldOffset - count;
}
if (newValid > _rowCacheSize) {
lost = newValid - _rowCacheSize;
if (lost < _valid) {
kept = _valid - lost;
System.arraycopy(_infoArray, 0, _infoArray, _valid - kept, kept);
} else {
cut = lost - _valid;
kept = 0;
}
newValid = _rowCacheSize;
}
System.arraycopy(fetcher._resultRows, 0, _infoArray, 0, count - cut);
firstUpdatedRow = newOffset;
lastUpdatedRow = firstUpdatedRow + count - cut - 1;
if (count < fetcher._requestedCount) {
_definite = false;
_currentRowCount = DEFAULT_INITIAL_SIZE_ESTIMATE;
_offset = 0;
}
}
_offset = newOffset;
_valid = newValid;
if (!_definite) {
final int receivedRowCount = newOffset + newValid;
int estimatedRowCount = (newOffset + _valid) * SCROLL_BAR_ESTIMATE_MULTIPLIER;
if (estimatedRowCount - receivedRowCount > MAXIMUM_GROWTH_ESTIMATE) {
estimatedRowCount = receivedRowCount + MAXIMUM_GROWTH_ESTIMATE;
}
if (_currentRowCount < estimatedRowCount && _offset + _valid > _currentRowCount) {
changeRowCount(estimatedRowCount, false);
}
}
fireTableRowsUpdated(0, _currentRowCount - 1);
}
private void changeRowCount(final int newRowCount, final boolean definite) {
// System.out.println(
// "Changing row count from " + _currentRowCount +
// " to " + newRowCount +
// " definite=" + definite);
_deletingRows = true;
final int oldRowCount = _currentRowCount;
_definite = definite;
_currentRowCount = newRowCount;
if (oldRowCount < newRowCount) {
// System.out.println("fireTableRowsInserted(" + oldRowCount + "," +
// (newRowCount - 1) + ")");
fireTableRowsInserted(oldRowCount, newRowCount - 1);
} else if (oldRowCount > newRowCount) {
// System.out.println("fireTableRowsDeleted(" + newRowCount + "," +
// (oldRowCount - 1) + ")");
fireTableRowsDeleted(newRowCount, oldRowCount - 1);
}
_deletingRows = false;
}
private class Fetcher implements Runnable {
boolean _forward;
int _from;
int _requestedCount;
int _skipCount;
Object[] _resultRows;
Exception _exception;
Fetcher(final boolean forward, final int from, final int requestedCount, final int skipCount) {
_forward = forward;
_from = from;
_requestedCount = requestedCount;
_skipCount = skipCount;
}
@Override
public void run() {
Management.LogicalRecord rec = null;
if (_from != -1) {
rec = (Management.LogicalRecord) _infoArray[_from - _offset];
}
KeyState ks = rec == null ? KeyState.LEFT_GUARD_KEYSTATE : rec.getKeyState();
final Management management = _adminUI.getManagement();
_resultRows = new Management.LogicalRecord[0];
if (management != null) {
try {
if (_skipCount > 0) {
final LogicalRecordCount lrc = management.getLogicalRecordCount(_volumeName, _treeName,
_keyFilterString, ks, _forward ? Key.GT : Key.LT, _skipCount);
final int skipped = lrc.getCount();
if (skipped > 0) {
ks = lrc.getKeyState();
_from = _forward ? _from + skipped : _from - skipped;
}
}
_resultRows = management.getLogicalRecordArray(_volumeName, _treeName, _keyFilterString, ks,
_forward ? Key.GT : Key.LT, _requestedCount, _maxValueSize, true);
} catch (final RemoteException remoteException) {
// TODO
_exception = remoteException;
}
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
receiveData(Fetcher.this);
}
});
}
}
}