package com.limegroup.gnutella.gui.tables;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.table.AbstractTableModel;
import com.limegroup.gnutella.Assert;
/**
* Handles common tasks associated with storing the DataLine's of a table.
* Previously, this class used to be split between a DataLineList and a
* AbstractTableModel. However, because the function of the DataLineList was
* really to handle all interactions with the data, it essentially was a model.
* Now, because the two classes are combined, the model can fire its own events.
* @author Sam Berlin
*/
public class BasicDataLineModel extends AbstractTableModel
implements DataLineModel {
/**
* Internally used list object storing the DataLines.
*/
protected List _list = new ArrayList();
private static final int ASCENDING = 1;
private static final int DESCENDING = -1;
/**
* Variable for whether or not the current sorting scheme
* is ascending (value 1) or descending (value -1).
*/
protected int _ascending = ASCENDING;
/**
* Variable for which column is currently being sorted.
*/
protected int _activeColumn = -1;
/**
* Variable to determine which DataLine class
* to create instances of
*/
private final Class _dataLineClass;
/**
* Variable for the instance of the DataLine that'll be used
* to determine column length/names/classes.
*/
private final DataLine _internalDataLine;
/**
* Variable for whether or not this list has been sorted
* at least once.
*/
protected boolean _isSorted = false;
/*
* Constructor -- creates the model, tying it to
* a specific DataLine class.
*/
public BasicDataLineModel(Class dataLineClass) {
_dataLineClass = dataLineClass;
_internalDataLine = createDataLine();
}
//Implements DataLineModel interface
public String[] getToolTipArray(int row, int col) {
return ((DataLine)_list.get(row)).getToolTipArray(col);
}
public boolean isTooltipRequired(int row, int col) {
return ((DataLine)_list.get(row)).isTooltipRequired(col);
}
//Implements DataLineModel interface.
public boolean isSortAscending() {
return _ascending == ASCENDING;
}
//Implements DataLineModel interface.
public int getSortColumn() {
return _activeColumn;
}
//Implements DataLineModel interface.
public boolean isSorted() {
return _isSorted;
}
//Implements DataLineModel interface.
public void sort(int col) {
if (col == _activeColumn) {
if(_ascending == DESCENDING) {
unsort();
return;
} else {
_ascending = DESCENDING;
}
} else {
_ascending = ASCENDING;
_activeColumn = col;
}
_isSorted = true;
resort();
}
// Re-sort the list to provide real-time sorting
public void resort() {
if (_isSorted) {
doResort();
fireTableDataChanged();
}
}
/**
* Stops sorting.
*/
public void unsort() {
_isSorted = false;
_activeColumn = -1;
}
/**
* Implementation of resorting.
*/
protected void doResort() {
Collections.sort(_list, this);
}
/*
* Determines whether or not the active column is dynamic
* and needs resorting.
*/
public boolean needsResort() {
return _isSorted &&
_internalDataLine.isDynamic(_activeColumn);
}
//Implements DataLineModel interface
public void clear() {
cleanup();
_list.clear();
fireTableDataChanged();
}
//Cleans up all the datalines.
protected void cleanup() {
int end = _list.size();
for(int i = 0; i < end; i++) {
((DataLine)_list.get(i)).cleanup();
}
}
/**
* Basic linear update.
* Extending classes may wish to override this function to provide
* a fine-tuned refresh, possibly recieving feedback from each
* row after it is updated. The return value can be used to notify
* the Mediator of information related to the refresh.
* @return null
*/
public Object refresh() {
int end = _list.size();
for (int i = 0; i < end; i++) {
((DataLine)_list.get(i)).update();
}
fireTableRowsUpdated(0, end);
return null;
}
/**
* Update a specific DataLine
* The DataLine updated is the one that was initialized by Object o
*/
public int update(Object o) {
int row = getRow(o);
((DataLine)_list.get(row)).update();
fireTableRowsUpdated(row, row);
return row;
}
/**
* Instantiates a DataLine.
*
* This uses reflection to create an instance of the DataLine class.
* The dataLineClass Class is used to determine which class should
* be created.
* Failure to create results in AssertFailures.
*
* Extending classes should override this to change the way
* DataLine's are instantiated.
*/
public DataLine createDataLine() {
try {
DataLine dl = (DataLine)_dataLineClass.newInstance();
return dl;
} catch (IllegalAccessException e) {
Assert.that(false, e.getMessage());
} catch (InstantiationException e) {
Assert.that(false, e.getMessage());
} catch (ClassCastException e) {
Assert.that(false, e.getMessage());
}
return null;
}
/**
* Returns an initialized new dataline.
*/
public DataLine getNewDataLine(Object o) {
DataLine dl = createDataLine();
dl.initialize(o);
return dl;
}
/**
* Determines where the DataLine should be inserted.
* Runs in log(n) time ArrayLists and linear time for LinkedLists.
*
* Extending classes should override this to change the method
* used to determine where to insert a new DataLine.
*
* The current methodology is to use Collections.binarySearch
* with _list as the list, the DataLine as the key, and this
* as the Comparator.
*/
public int getSortedPosition(DataLine dl) {
// Collections.binarySearch return notes:
// index of the search key, if it is contained in the list;
// otherwise, (-(insertion point) - 1). The insertion point
// is defined as the point at which the key would be inserted
// into the list: the index of the first element greater than
// the key, or list.size(), if all elements in the list are
// less than the specified key. Note that this guarantees that
// the return value will be >= 0 if & only if the key is found.
// So....
// Remember we're comparing columns, not entire DataLines, so
// it is entirely likely that two columns will be the same.
// If the returned row is < 0, we want to convert it to
// the insertion point.
int row = Collections.binarySearch(_list, dl, this);
if (row < 0) row = -(row + 1);
return row;
}
/**
* Helper function.
*
* Adds a DataLine initialized by the object to row 0.
*
* This should be overriden only if you want the default,
* non-sorting add to go someplace other than row 0.
*
* Delegates to add(Object, int).
*
* Extending classes should maintain the delegation to add(Object, int).
*/
public int add(Object o) {
return add(o, 0);
}
/**
* Helper function.
*
* Uses getNewDataLine(Object) and add(DataLine, int).
*
* Extending classes can override this, but it is recommended
* that they override the two above methods instead.
*/
public int add(Object o, int row) {
DataLine dl = getNewDataLine(o);
return dl == null ? -1 : add(dl, row);
}
/**
* Adds a DataLine to row 0.
* Currently unused.
*/
public int add(DataLine dl) {
return add(dl, 0);
}
/**
* Adds a DataLine to the list at a row.
*
* All forms of add(..) eventually end up here.
*
* Extending classes should override this if they want
* to maintain a HashMap of any type for speedier access.
*/
public int add(DataLine dl, int row) {
_list.add(row, dl);
fireTableRowsInserted(row, row);
return row;
}
/**
* Helper function.
*
* Uses getNewDataLine(Object), getSortedPosition(DataLine),
* and add(DataLine, int)
*
* Extending classes can override this, but it is recommended
* they override the above mentioned methods instead.
*/
public int addSorted(Object o) {
DataLine dl = getNewDataLine(o);
return dl == null ? -1 : add(dl, getSortedPosition(dl));
}
/**
* Helper function.
*
* Uses getSortedPosition(DataLine) and add(DataLine, int).
*
* Extending classes can override this, but it is recommended
* they override the above mentioned methods instead.
*/
public int addSorted(DataLine dl) {
return add(dl, getSortedPosition(dl));
}
//Implements the DataLineModel interface.
public DataLine get(int row) {
return (DataLine)_list.get(row);
}
/**
* Implements DataLineModel interface.
* Delegates the row find to getRow(Object o).
* Returns the first DataLine initialized by object o.
* If no object matches, null is returned.
*/
public DataLine get(Object o) {
int row = getRow(o);
if (row != -1)
return (DataLine)_list.get(row);
else
return null;
}
/**
* Implements DataLineModel interface.
* Delegates the row find to getRow(Object o, int col).
* Returns the first DataLine that contains object o in column col.
* If no object matches, null is returned.
*/
public DataLine get(Object o, int col) {
int row = getRow(o, col);
if (row != -1)
return (DataLine)_list.get(row);
else
return null;
}
/**
* Calls cleanup on the DataLine and then removes it from the list.
*/
public void remove(int row) {
((DataLine)_list.get(row)).cleanup();
_list.remove(row);
fireTableRowsDeleted(row, row);
}
/**
* Helper-function that resolves to remove(int).
* Removes the line associated with the DataLine line.
* If no matching DataLine exists, nothing happens.
*/
public void remove(DataLine line) {
int idx = _list.indexOf(line);
if (idx != -1)
remove(idx);
}
/**
* Helper function that resolves to remove(int).
* Removes the DataLine that was initialized by the Object o.
* Uses a linear search through the list to find a match.
* Extending objects that have large lists and call remove(Object)
* often may wish to override this, add(Object, int) and sort using
* a HashMap for more timely access.
*/
public void remove(Object o) {
int end = _list.size();
for (int i = 0; i < end; i++) {
if (((DataLine)_list.get(i)).getInitializeObject().equals(o)) {
remove(i);
break;
}
}
}
//Implements the TableModel method
public Object getValueAt(int row, int col) {
return ((DataLine)_list.get(row)).getValueAt(col);
}
//Implements the TableModel method
// Ignores the update if the row doesn't exist.
public void setValueAt(Object o, int row, int col) {
if(row >= 0 && row < _list.size()) {
((DataLine)_list.get(row)).setValueAt(o, col);
fireTableRowsUpdated(row, row);
}
}
/**
* @return true if List contains the Object o in column col.
* @notes Extending classes may wish to override this function
* if a particular column is searched frequently, using a HashMap.
* The add(Object, int) & sort function should be overriden to initialize
* the HashMap.
*/
public boolean contains(Object o, int col) {
int end = _list.size();
for (int i = 0; i < end; i++) {
if (((DataLine)_list.get(i)).getValueAt(col).equals(o))
return true;
}
return false;
}
/**
* @return true if the List contains a DataLine that was initialized
* by Object o.
* @notes Extending classes may wish to override this function
* if searching is done frequently, using a HashMap.
* The add(Object, int) & sort function should be overriden to intialize
* the HashMap.
*/
public boolean contains(Object o) {
int end = _list.size();
for (int i = 0; i < end; i++) {
if (((DataLine)_list.get(i)).getInitializeObject().equals(o))
return true;
}
return false;
}
/**
* @return the index of the matching DataLine.
*/
public int getRow(DataLine dl) {
return _list.indexOf(dl);
}
/**
* @return the index of the first DataLine that contains Object o
* in column col.
* @notes Extending classes may wish to override this function
* if a particular column is searched frequently, using a HashMap.
* The add(Object, int) & sort function should be overriden to initialize
* the HashMap.
*/
public int getRow(Object o, int col) {
int end = _list.size();
for (int i = 0; i < end; i++) {
if (((DataLine)_list.get(i)).getValueAt(col).equals(o))
return i;
}
return -1;
}
/**
* @return the index of the first DataLine that was initialized by Object o.
* @notes Extending classes may wish to override this function
* if searching is done frequently, using a HashMap.
* The add(Object, int) & sort function should be overriden to initialize
* the HashMap.
*/
public int getRow(Object o) {
int end = _list.size();
for (int i = 0; i < end; i++) {
if (((DataLine)_list.get(i)).getInitializeObject().equals(o))
return i;
}
return -1;
}
/**
* A generic compare function.
*/
public int compare(Object a, Object b) {
Object o1 = ((DataLine)a).getValueAt(_activeColumn);
Object o2 = ((DataLine)b).getValueAt(_activeColumn);
return AbstractTableMediator.compare(o1, o2) * _ascending;
}
/**
* Returns the LimeTableColumn at the specific column this data line.
*/
public LimeTableColumn getTableColumn(int col) {
if( _internalDataLine == null)
return null;
else
return _internalDataLine.getColumn(col);
}
/**
* Returns the size of _list.
*/
public int getRowCount() {
return _list.size();
}
/**
* Returns the number of columns as speicifed by the data line.
*/
public int getColumnCount() {
if (_internalDataLine == null)
return 0;
else
return _internalDataLine.getColumnCount();
}
/**
* Returns whether or not the specified column is clippable.
*/
public boolean isClippable(int col) {
if (_internalDataLine == null)
return false;
else
return _internalDataLine.isClippable(col);
}
public int getTypeAheadColumn() {
if (_internalDataLine == null)
return -1;
else
return _internalDataLine.getTypeAheadColumn();
}
/**
* Returns the name of the TableColumn as specified by the data line.
*/
public String getColumnName(int col) {
return getTableColumn(col).getName();
}
/**
* Returns the Id of the TableColumn as specified by the data line.
*/
public Object getColumnId(int col) {
return getTableColumn(col).getIdentifier();
}
/**
* Returns the class of the TableColumn as specified by the data line.
*/
public Class getColumnClass(int col) {
return getTableColumn(col).getColumnClass();
}
}