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(); } }