diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/commentwindow/CommentWindowProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/commentwindow/CommentWindowProvider.java index fa66d48523..e427c082ac 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/commentwindow/CommentWindowProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/commentwindow/CommentWindowProvider.java @@ -112,7 +112,7 @@ class CommentWindowProvider extends ComponentProviderAdapter { commentModel.addTableModelListener(e -> { int rowCount = commentModel.getRowCount(); - int unfilteredCount = commentModel.getUnfilteredCount(); + int unfilteredCount = commentModel.getUnfilteredRowCount(); StringBuilder buffy = new StringBuilder(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datawindow/DataWindowProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datawindow/DataWindowProvider.java index fc969cd2dd..2569ea574d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datawindow/DataWindowProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datawindow/DataWindowProvider.java @@ -118,7 +118,7 @@ class DataWindowProvider extends ComponentProviderAdapter { dataModel.addTableModelListener(e -> { int rowCount = dataModel.getRowCount(); - int unfilteredCount = dataModel.getUnfilteredCount(); + int unfilteredCount = dataModel.getUnfilteredRowCount(); StringBuilder buffy = new StringBuilder(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowProvider.java index f0e958e3a6..ea1b1cf89a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowProvider.java @@ -133,7 +133,7 @@ public class FunctionWindowProvider extends ComponentProviderAdapter { functionModel.addTableModelListener(e -> { int rowCount = functionModel.getRowCount(); - int unfilteredCount = functionModel.getUnfilteredCount(); + int unfilteredCount = functionModel.getUnfilteredRowCount(); StringBuilder buffy = new StringBuilder(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/StringTableProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/StringTableProvider.java index e2ba844ced..46b0241584 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/StringTableProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/StringTableProvider.java @@ -134,7 +134,7 @@ public class StringTableProvider extends ComponentProviderAdapter implements Dom StringBuilder builder = new StringBuilder(); int rowCount = stringModel.getRowCount(); - int unfilteredCount = stringModel.getUnfilteredCount(); + int unfilteredCount = stringModel.getUnfilteredRowCount(); builder.append(rowCount); builder.append(" items"); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsProvider.java index 40c120a420..2fa97d67a0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsProvider.java @@ -124,7 +124,7 @@ public class ViewStringsProvider extends ComponentProviderAdapter { stringModel.addTableModelListener(e -> { int rowCount = stringModel.getRowCount(); - int unfilteredCount = stringModel.getUnfilteredCount(); + int unfilteredCount = stringModel.getUnfilteredRowCount(); setSubTitle("" + rowCount + " items" + (rowCount != unfilteredCount ? " (of " + unfilteredCount + ")" : "")); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolProvider.java index cafa4cd818..fc6a30e273 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolProvider.java @@ -152,7 +152,7 @@ class SymbolProvider extends ComponentProviderAdapter { private String generateSubTitle() { SymbolFilter filter = symbolKeyModel.getFilter(); int rowCount = symbolKeyModel.getRowCount(); - int unfilteredCount = symbolKeyModel.getUnfilteredCount(); + int unfilteredCount = symbolKeyModel.getUnfilteredRowCount(); if (rowCount != unfilteredCount) { return " (Text filter matched " + rowCount + " of " + unfilteredCount + " symbols)"; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/AddressBasedTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/AddressBasedTableModel.java index 5c35672059..2c77d601e2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/table/AddressBasedTableModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/AddressBasedTableModel.java @@ -65,18 +65,15 @@ public abstract class AddressBasedTableModel extends GhidraProgramTabl ROW_TYPE rowObject = filteredData.get(row); DynamicTableColumn tableColumn = getColumn(column); - if (tableColumn != null) { - if (tableColumn instanceof ProgramLocationTableColumn) { + if (tableColumn instanceof ProgramLocationTableColumn) { - @SuppressWarnings("unchecked") - // we checked - ProgramLocationTableColumn programField = - (ProgramLocationTableColumn) tableColumn; - ProgramLocation loc = programField.getProgramLocation(rowObject, - getColumnSettings(column), getProgram(), serviceProvider); - if (loc != null) { - return loc; - } + @SuppressWarnings("unchecked") // we checked + ProgramLocationTableColumn programField = + (ProgramLocationTableColumn) tableColumn; + ProgramLocation loc = programField.getProgramLocation(rowObject, + getColumnSettings(column), getProgram(), serviceProvider); + if (loc != null) { + return loc; } } diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/functionassociation/VTFunctionAssociationProvider.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/functionassociation/VTFunctionAssociationProvider.java index d3ac427273..9ef1cb2d6b 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/functionassociation/VTFunctionAssociationProvider.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/functionassociation/VTFunctionAssociationProvider.java @@ -795,7 +795,7 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter private void getTableFilterString(String tableName, ThreadedTableModel model, StringBuffer buffy) { int filteredCount = model.getRowCount(); - int unfilteredCount = model.getUnfilteredCount(); + int unfilteredCount = model.getUnfilteredRowCount(); buffy.append(tableName).append(" - ").append(filteredCount).append(" functions"); if (filteredCount != unfilteredCount) { diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/impliedmatches/VTImpliedMatchesTableProvider.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/impliedmatches/VTImpliedMatchesTableProvider.java index f353944cea..a2c0625018 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/impliedmatches/VTImpliedMatchesTableProvider.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/impliedmatches/VTImpliedMatchesTableProvider.java @@ -263,7 +263,7 @@ public class VTImpliedMatchesTableProvider extends ComponentProviderAdapter impliedMatchTableModel.addTableModelListener(e -> { int filteredCount = impliedMatchTableModel.getRowCount(); - int unfilteredCount = impliedMatchTableModel.getUnfilteredCount(); + int unfilteredCount = impliedMatchTableModel.getUnfilteredRowCount(); String sessionName = controller.getVersionTrackingSessionName(); StringBuffer buffy = new StringBuffer(); diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/VTMarkupItemsTableProvider.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/VTMarkupItemsTableProvider.java index f623d85271..78f7d6a7d5 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/VTMarkupItemsTableProvider.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/VTMarkupItemsTableProvider.java @@ -321,14 +321,14 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter markupItemsTableModel = new VTMarkupItemsTableModel(controller); markupItemsTableModel.addTableModelListener(e -> { int filteredCount = markupItemsTableModel.getRowCount(); - int unfilteredCount = markupItemsTableModel.getUnfilteredCount(); + int unfilteredCount = markupItemsTableModel.getUnfilteredRowCount(); String sessionName = controller.getVersionTrackingSessionName(); StringBuffer buffy = new StringBuffer(); buffy.append("[Session: ").append(sessionName).append("] "); buffy.append('-').append(markupItemsTableModel.getRowCount()).append(" markup items"); if (filteredCount != unfilteredCount) { - buffy.append(" (of ").append(markupItemsTableModel.getUnfilteredCount()).append( + buffy.append(" (of ").append(markupItemsTableModel.getUnfilteredRowCount()).append( ')'); } @@ -848,7 +848,7 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter if (filtered) { int filteredCount = markupItemsTableModel.getRowCount(); - int unfilteredCount = markupItemsTableModel.getUnfilteredCount(); + int unfilteredCount = markupItemsTableModel.getUnfilteredRowCount(); int filteredOutCount = unfilteredCount - filteredCount; ancillaryFilterButton.setToolTipText( "More Filters - " + filteredOutCount + " item(s) hidden"); diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchTableProvider.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchTableProvider.java index b2e5028145..fd37aa2a8d 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchTableProvider.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchTableProvider.java @@ -181,7 +181,7 @@ public class VTMatchTableProvider extends ComponentProviderAdapter if (filtered) { int filteredCount = matchesTableModel.getRowCount(); - int unfilteredCount = matchesTableModel.getUnfilteredCount(); + int unfilteredCount = matchesTableModel.getUnfilteredRowCount(); int filteredOutCount = unfilteredCount - filteredCount; ancillaryFilterButton.setToolTipText( "More Filters - " + filteredOutCount + " item(s) hidden"); @@ -231,7 +231,7 @@ public class VTMatchTableProvider extends ComponentProviderAdapter matchesTableModel = new VTMatchTableModel(controller); matchesTableModel.addTableModelListener(e -> { int filteredCount = matchesTableModel.getRowCount(); - int unfilteredCount = matchesTableModel.getUnfilteredCount(); + int unfilteredCount = matchesTableModel.getUnfilteredRowCount(); String sessionName = controller.getVersionTrackingSessionName(); StringBuffer buffy = new StringBuffer(); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AbstractGTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AbstractGTableModel.java index 8c88d90b2f..b4bdd0643f 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AbstractGTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AbstractGTableModel.java @@ -93,13 +93,13 @@ public abstract class AbstractGTableModel extends AbstractTableModel } /** - * A convenience method for subclasses to quickly/efficiently search for the index of a given + * A convenience method to search for the index of a given * row object that is visible in the GUI. The visible limitation is due to the * fact that the data searched is retrieved from {@link #getModelData()}, which may be * filtered. - *

- * If a need for access to all of the data is required in the future, then an overloaded - * version of this method should be created that takes the data to be searched. + * + *

Note: this operation is O(n). For quick lookups, consider using the sorted version + * of this class. * * @param rowObject The object for which to search. * @return the index of the item in the data returned by @@ -108,6 +108,14 @@ public abstract class AbstractGTableModel extends AbstractTableModel return getIndexForRowObject(rowObject, getModelData()); } + /** + * Returns the index for the given object in the given list; -1 when the item is not in + * the list. + * + * @param rowObject the item + * @param data the data + * @return the index + */ protected int getIndexForRowObject(T rowObject, List data) { return data.indexOf(rowObject); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AbstractSortedTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AbstractSortedTableModel.java index 3f344bffe6..de66c700b3 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AbstractSortedTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AbstractSortedTableModel.java @@ -17,10 +17,10 @@ package docking.widgets.table; import java.util.*; -import javax.swing.SwingUtilities; import javax.swing.event.TableModelEvent; import javax.swing.table.TableModel; +import ghidra.util.SystemUtilities; import ghidra.util.datastruct.WeakDataStructureFactory; import ghidra.util.datastruct.WeakSet; @@ -31,15 +31,21 @@ import ghidra.util.datastruct.WeakSet; *

* In order to define custom comparators for a column, simply override * {@link #createSortComparator(int)}. Otherwise, a default comparator will be created for you. + * + *

Note on sorting: it is possible that the user can disable sorting by de-selecting all + * sorted columns. This can also be achieved programmatically by calling + * {@link #setTableSortState(TableSortState)} with a value of + * {@link TableSortState#createUnsortedSortState()}. * * @param The row type upon which the table is based */ public abstract class AbstractSortedTableModel extends AbstractGTableModel implements SortedTableModel { - private static final long serialVersionUID = 1L; + + private final Comparator NO_SORT_COMPARATOR = (o1, o2) -> 0; private TableSortState pendingSortState; - private TableSortState sortState; + private TableSortState sortState = TableSortState.createUnsortedSortState(); private boolean isSortPending; protected boolean hasEverSorted; @@ -58,6 +64,10 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel protected void setDefaultTableSortState(TableSortState defaultSortState) { sortState = defaultSortState; + if (sortState == null) { + sortState = TableSortState.createUnsortedSortState(); + } + } @Override @@ -81,14 +91,17 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel /** * Returns the index of the given row object in this model; -1 if the model does not contain - * the given object. + * the given object. + * + *

Warning: if the this model has no sort applied, then performance will be O(n). If + * sorted, then performance is O(log n). You can call {@link #isSorted()} to know when + * this will happen. */ @Override public int getRowIndex(T rowObject) { if (rowObject == null) { return -1; } - return getIndexForRowObject(rowObject); } @@ -121,7 +134,7 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel } @Override - public void setTableSortState(final TableSortState newSortState) { + public void setTableSortState(TableSortState newSortState) { if (!isValidSortState(newSortState)) { // if the user calls this method with an invalid value, then let them know! @@ -165,7 +178,8 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel isSortPending = true; pendingSortState = newSortState; - SwingUtilities.invokeLater(() -> sort(getModelData(), createSortingContext(newSortState))); + SystemUtilities.runSwingLater( + () -> sort(getModelData(), createSortingContext(newSortState))); } public TableSortState getPendingSortState() { @@ -176,6 +190,10 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel return isSortPending; } + public boolean isSorted() { + return !isSortPending && !sortState.isUnsorted(); + } + protected TableSortingContext createSortingContext(TableSortState newSortState) { return new TableSortingContext<>(newSortState, getComparatorChain(newSortState)); } @@ -205,7 +223,7 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel hasEverSorted = true; isSortPending = true; pendingSortState = sortState; - SwingUtilities.invokeLater(() -> sort(getModelData(), createSortingContext(sortState))); + SystemUtilities.runSwingLater(() -> sort(getModelData(), createSortingContext(sortState))); } /** @@ -213,9 +231,9 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel * row object that is visible in the GUI. The visible limitation is due to the * fact that the data searched is retrieved from {@link #getModelData()}, which may be * filtered. - *

- * If a need for access to all of the data is required in the future, then an overloaded - * version of this method should be created that takes the data to be searched. + * + *

Warning: if the this model has no sort applied, then -1 will be returned. You can call + * {@link #isSorted()} to know when this will happen. * * @param rowObject The object for which to search. * @return the index of the item in the data returned by @@ -225,10 +243,27 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel return getIndexForRowObject(rowObject, getModelData()); } + /** + * Returns the index for the given object in the given list + * + * @param rowObject the item + * @param data the data + * @return the index + */ @Override protected int getIndexForRowObject(T rowObject, List data) { - Comparator comparator = getComparatorChain(sortState); - return Collections.binarySearch(data, rowObject, comparator); + if (isSorted()) { + Comparator comparator = getComparatorChain(sortState); + return Collections.binarySearch(data, rowObject, comparator); + } + + for (int i = 0; i < data.size(); i++) { + T t = data.get(i); + if (rowObject.equals(t)) { + return i; + } + } + return -1; } /** @@ -242,6 +277,14 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel * comparator for sorting, etc...). */ protected void sort(List data, TableSortingContext sortingContext) { + + if (sortingContext.isUnsorted()) { + // this is the 'no sort' state + sortCompleted(sortingContext); + notifyModelSorted(false); + return; + } + hasEverSorted = true; // signal that we have sorted at least one time Collections.sort(data, sortingContext.getComparator()); sortCompleted(sortingContext); @@ -315,6 +358,11 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel * data is sorted). */ private Comparator getComparatorChain(TableSortState newSortState) { + + if (newSortState.isUnsorted()) { + return NO_SORT_COMPARATOR; + } + ComparatorLink comparatorLink = new ComparatorLink(); for (ColumnSortState columnSortState : newSortState) { Comparator nextComparator = getComparator(columnSortState); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/ColumnSortState.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/ColumnSortState.java index 81d26c7cb9..cda0ca238a 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/ColumnSortState.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/ColumnSortState.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +28,7 @@ public class ColumnSortState { private static final String XML_SORT_ORDER = "SORT_ORDER"; public enum SortDirection { - ASCENDING, DESCENDING; + ASCENDING, DESCENDING, UNSORTED; public boolean isAscending() { return this == ASCENDING; @@ -82,9 +81,8 @@ public class ColumnSortState { } public ColumnSortState createFlipState() { - ColumnSortState newSortState = - new ColumnSortState(columnModelIndex, isAscending() ? SortDirection.DESCENDING - : SortDirection.ASCENDING, sortOrder_OneBased); + ColumnSortState newSortState = new ColumnSortState(columnModelIndex, + isAscending() ? SortDirection.DESCENDING : SortDirection.ASCENDING, sortOrder_OneBased); return newSortState; } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/RowObjectFilterModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/RowObjectFilterModel.java index 192f98ea0b..178f01c20c 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/RowObjectFilterModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/RowObjectFilterModel.java @@ -56,7 +56,35 @@ public interface RowObjectFilterModel extends RowObjectTableModelThis operation will be O(n) unless the implementation is sorted, in which case the + * operation is O(log n), as it uses a binary search. + * + *

Note: if a sorted implementation is moved to an unsorted state, then -1 will be returned + * from this method. + * + * @param t the item + * @return the view index + */ public int getViewIndex(ROW_OBJECT t); + /** + * Returns the model index of the given item. When filtered, this is the index is the larger, + * set of data; when unfiltered, this index is the same as that returned by + * {@link #getModelIndex(Object)}. + * + *

This operation will be O(n) unless the implementation is sorted, in which case the + * operation is O(log n), as it uses a binary search. + * + *

Note: if a sorted implementation is moved to an unsorted state, then -1 will be returned + * from this method. + * + * @param t the item + * @return the model index + */ public int getModelIndex(ROW_OBJECT t); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableSortState.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableSortState.java index b1e49b0008..8b0ffdeba8 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableSortState.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableSortState.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +31,15 @@ public class TableSortState implements Iterable { private static final String XML_TABLE_SORT_STATE = "TABLE_SORT_STATE"; - private List columnSortStates = new ArrayList(); + private List columnSortStates = new ArrayList<>(); + + /** + * Creates a sort state that represents being unsorted + * @return a sort state that represents being unsorted + */ + public static TableSortState createUnsortedSortState() { + return new TableSortState(); + } /** * Creates a sort state with the given column as the sorted column (sorted ascending). @@ -64,8 +71,8 @@ public class TableSortState implements Iterable { } public TableSortState(List sortStates) { - List sortOrders = new ArrayList(); - List columnIndices = new ArrayList(); + List sortOrders = new ArrayList<>(); + List columnIndices = new ArrayList<>(); for (ColumnSortState state : sortStates) { int sortOrder = state.getSortOrder(); if (sortOrders.contains(sortOrder)) { @@ -84,7 +91,7 @@ public class TableSortState implements Iterable { columnIndices.add(columnModelIndex); } - this.columnSortStates = new ArrayList(sortStates); + this.columnSortStates = new ArrayList<>(sortStates); } public TableSortState(ColumnSortState columnSortState) { @@ -100,6 +107,10 @@ public class TableSortState implements Iterable { return columnSortStates.size(); } + public boolean isUnsorted() { + return columnSortStates.isEmpty(); + } + public ColumnSortState getColumnSortState(int columnIndex) { for (ColumnSortState sortState : columnSortStates) { if (sortState.getColumnModelIndex() == columnIndex) { @@ -110,7 +121,7 @@ public class TableSortState implements Iterable { } public List getAllSortStates() { - return new ArrayList(columnSortStates); + return new ArrayList<>(columnSortStates); } @Override @@ -140,7 +151,7 @@ public class TableSortState implements Iterable { Element sortStateElement = (Element) tableSortStateElementList.get(0); List children = sortStateElement.getChildren(ColumnSortState.XML_COLUMN_SORT_STATE); - List columnStates = new ArrayList(children.size()); + List columnStates = new ArrayList<>(children.size()); for (Object object : children) { columnStates.add(ColumnSortState.restoreFromXML((Element) object)); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableSortingContext.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableSortingContext.java index cc6c15dd7e..2c29f3b9ca 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableSortingContext.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableSortingContext.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +15,7 @@ */ package docking.widgets.table; -import java.util.Comparator; -import java.util.Iterator; +import java.util.*; public class TableSortingContext { @@ -25,8 +23,8 @@ public class TableSortingContext { private Comparator comparator; public TableSortingContext(TableSortState sortState, Comparator comparator) { - this.sortState = sortState; - this.comparator = comparator; + this.sortState = Objects.requireNonNull(sortState); + this.comparator = Objects.requireNonNull(comparator); } public Comparator getComparator() { @@ -37,6 +35,15 @@ public class TableSortingContext { return sortState; } + /** + * Returns true if there are no columns marked as sorted, which represents a 'no sort' state + * + * @return true if there are no columns sorted + */ + public boolean isUnsorted() { + return sortState.isUnsorted(); + } + public boolean isReverseOf(TableSortingContext otherContext) { int sortedColumnCount = sortState.getSortedColumnCount(); if (sortedColumnCount != 1) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableUtils.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableUtils.java index 3fcfdf7781..b76943aed7 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableUtils.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableUtils.java @@ -15,8 +15,6 @@ */ package docking.widgets.table; -import java.awt.Toolkit; - import javax.swing.JTable; import javax.swing.table.*; @@ -113,11 +111,17 @@ public class TableUtils { TableSortStateEditor editor = new TableSortStateEditor(columnSortStates); if (editor.isColumnSorted(modelColumnIndex)) { - // remove it. If there is only one, don't remove the last one - if (editor.getSortedColumnCount() == 1) { - Toolkit.getDefaultToolkit().beep(); - return; - } + + /* + Note: this code allows us to disable the 'unsorting' of a table via the UI + + // remove it. If there is only one, don't remove the last one + if (editor.getSortedColumnCount() == 1) { + Toolkit.getDefaultToolkit().beep(); + return; + } + */ + editor.removeSortedColumn(modelColumnIndex); } else { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/TableData.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/TableData.java index 3e4f65a3dd..11f19e548f 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/TableData.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/TableData.java @@ -104,7 +104,7 @@ public class TableData implements Iterable { } boolean isSorted() { - return sortContext != null; + return sortContext != null && !sortContext.isUnsorted(); } void setSortContext(TableSortingContext sortContext) { @@ -128,12 +128,24 @@ public class TableData implements Iterable { } /** - * Uses the current sort to perform a fast lookup of the given item in the given list. + * Uses the current sort to perform a fast lookup of the given item in the given list + * @param t the item + * @return the index */ int indexOf(ROW_OBJECT t) { - Comparator comparator = sortContext.getComparator(); - int index = Collections.binarySearch(data, t, comparator); - return index; + if (!sortContext.isUnsorted()) { + Comparator comparator = sortContext.getComparator(); + return Collections.binarySearch(data, t, comparator); + } + + // brute force + for (int i = 0; i < data.size(); i++) { + ROW_OBJECT item = data.get(i); + if (t.equals(item)) { + return i; + } + } + return -1; } boolean remove(ROW_OBJECT o) { @@ -145,9 +157,9 @@ public class TableData implements Iterable { } /** - * Adds the new value to the data at the appropriate location based on the sort. - * @param value the row Object to insert. - * @param comparator the comparator to use to find the appropriate location to insert the new data. + * Adds the new value to the data at the appropriate location based on the sort + * + * @param value the row Object to insert */ void insert(ROW_OBJECT value) { @@ -160,10 +172,8 @@ public class TableData implements Iterable { return; // this item is filtered out of this data } - if (sortContext == null) { - // - // Not yet sorted; just add the item anywhere and it will get sorted later - // + if (!isSorted()) { + // Not yet sorted or intentionally unsorted; just add the item, it will get sorted later data.add(value); return; } @@ -180,7 +190,7 @@ public class TableData implements Iterable { // The search thinks the item is in the list because a compareTo() result of 0 was // found. If the two objects are not equal(), then add the new value. ROW_OBJECT existingValue = data.get(index); - if (!SystemUtilities.isEqual(value, existingValue)) { + if (!Objects.equals(value, existingValue)) { data.add(index, value); } } @@ -310,4 +320,10 @@ public class TableData implements Iterable { // of 'data', as that could be expensive. return super.equals(obj); } + + @Override + final public int hashCode() { + // Made final to match equals() + return super.hashCode(); + } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/TableUpdateJob.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/TableUpdateJob.java index 58ce325c5d..13016de9ef 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/TableUpdateJob.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/TableUpdateJob.java @@ -498,6 +498,11 @@ public class TableUpdateJob { } private void doSortData(List data) { + + if (newSortContext.isUnsorted()) { + return; + } + int size = data.size(); monitor.setMessage("Sorting " + model.getName() + " (" + size + " rows)" + "..."); monitor.initialize(size); @@ -512,7 +517,7 @@ public class TableUpdateJob { // // Usually the source data is sorted before any filter is applied. However, this is not // the case when a load of new data is followed directly by a filter action. We rely on - // the source data being filtered in order to perform fast translations from the table's + // the source data being sorted in order to perform fast translations from the table's // view to the table's model when it is filtered. Thus, make sure that any time we are // sorting the filtered data, that the source data too is sorted. // diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java index 690b877843..af78fb31e5 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java @@ -27,7 +27,6 @@ import ghidra.util.SystemUtilities; import ghidra.util.datastruct.*; import ghidra.util.exception.*; import ghidra.util.task.TaskMonitor; -import ghidra.util.task.TaskMonitorAdapter; import ghidra.util.worker.Worker; /** @@ -36,14 +35,14 @@ import ghidra.util.worker.Worker; * You can optionally set this model to load data incrementally by passing the correct * constructor argument. Note, if you make this model incremental, then you need to set an * incremental task monitor in order to get feedback about loading - * (see {@link #setIncrementalTaskMonitor(TaskMonitor)). Alternatively, you can use + * (see {@link #setIncrementalTaskMonitor(TaskMonitor)}. Alternatively, you can use * a {@link GThreadedTablePanel}, which will install the proper monitor for you. * - * @param ROW_OBJECT the row object class for this table model. - * @param DATA_SOURCE the type of data that will be returned from {@link #getDataSource()}. This + * @param the row object class for this table model. + * @param the type of data that will be returned from {@link #getDataSource()}. This * object will be given to the {@link DynamicTableColumn} objects used by this * table model when - * {@link DynamicTableColumn#getValue(Object, generic.settings.Settings, Object, ServiceProvider)} + * {@link DynamicTableColumn#getValue(Object, ghidra.docking.settings.Settings, Object, ServiceProvider)} * is called. */ public abstract class ThreadedTableModel @@ -52,7 +51,7 @@ public abstract class ThreadedTableModel private ThreadedTableModelUpdateMgr updateManager; private boolean loadIncrementally; - private TaskMonitor incrementalMonitor = TaskMonitorAdapter.DUMMY_MONITOR; + private TaskMonitor incrementalMonitor = TaskMonitor.DUMMY; private ConcurrentListenerSet listeners = new ConcurrentListenerSet<>(); @@ -157,8 +156,12 @@ public abstract class ThreadedTableModel /** * A package-level method. Subclasses should not call this. - *

- * This exists to handle whether this model should load incrementally. + * + *

This exists to handle whether this model should load incrementally. + * + * @param monitor the monitor + * @return the loaded data + * @throws CancelledException */ final List load(TaskMonitor monitor) throws CancelledException { if (loadIncrementally) { @@ -261,7 +264,7 @@ public abstract class ThreadedTableModel * * @param rowObject The object for which to search * @return The index for the given object; a negative value if the object is not in the list - * @see #getIndexForRowObject(Object); + * @see #getIndexForRowObject(Object) */ protected int getUnfilteredIndexForRowObject(ROW_OBJECT rowObject) { return getIndexForRowObject(rowObject, getUnfilteredData()); @@ -327,6 +330,7 @@ public abstract class ThreadedTableModel /** * Returns the current sorting context, which is the next one to be applied, if a sort is * pending; otherwise the current sorting context. + * @return the sort context */ TableSortingContext getSortingContext() { if (pendingSortContext != null) { @@ -365,8 +369,7 @@ public abstract class ThreadedTableModel /** * Override this to change how filtering is performed. This implementation will do nothing * if a TableFilter has not been set via a call to {@link #setTableFilter(TableFilter)}. - * Also, no filtering will happen if there is no filter text set via a call to - * {@link #setFilterText(String)}. + * * * @param data The list of data to be filtered. * @@ -629,10 +632,6 @@ public abstract class ThreadedTableModel return filteredData.size(); } - public int getUnfilteredCount() { - return allData.size(); - } - /** * Given a row index for the raw (unfiltered) model, return the corresponding index in the * view (filtered) model. @@ -643,11 +642,12 @@ public abstract class ThreadedTableModel */ @Override public int getViewRow(int modelRow) { - if (getRowCount() == getUnfilteredCount()) { + int unfilteredCount = getUnfilteredRowCount(); + if (getRowCount() == unfilteredCount) { return modelRow; // same list; no need to translate values } - if (modelRow >= allData.size()) { + if (modelRow >= unfilteredCount) { return -1; // out-of-bounds request } @@ -665,7 +665,7 @@ public abstract class ThreadedTableModel */ @Override public int getModelRow(int viewRow) { - if (getRowCount() == getUnfilteredCount()) { + if (getRowCount() == getUnfilteredRowCount()) { return viewRow; // same list; no need to translate values } @@ -715,8 +715,10 @@ public abstract class ThreadedTableModel /** * Sets the update delay, which is how long the model should wait before updating, after - * a change has been made the data. + * a change has been made the data + * * @param updateDelayMillis the new update delay + * @param maxUpdateDelayMillis the new max update delay; updates will not wait past this time */ void setUpdateDelay(int updateDelayMillis, int maxUpdateDelayMillis) { this.minUpdateDelayMillis = updateDelayMillis; @@ -724,16 +726,11 @@ public abstract class ThreadedTableModel updateManager.setUpdateDelay(updateDelayMillis, maxUpdateDelayMillis); } - /** - * @see #setMinDelay(int) - */ + // see setUpdateDelay long getMinDelay() { return minUpdateDelayMillis; } - /** - * @see #setMaxDelay(int) - */ long getMaxDelay() { return maxUpdateDelayMillis; } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModelUpdateMgr.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModelUpdateMgr.java index c17cdf7621..b150d89438 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModelUpdateMgr.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModelUpdateMgr.java @@ -297,7 +297,8 @@ class ThreadedTableModelUpdateMgr { /** * Sets the delay for the swing update manager. - * @param updateDelayMillis the new delay for the swing update manager. + * @param updateDelayMillis the new delay for the swing update manager + * @param maxUpdateDelayMillis the new max update delay; updates will not wait past this time */ void setUpdateDelay(int updateDelayMillis, int maxUpdateDelayMillis) { updateManager.dispose(); diff --git a/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/table/threaded/ThreadedTableFilterTest.java b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/table/threaded/DefaultThreadedTableFilterTest.java similarity index 97% rename from Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/table/threaded/ThreadedTableFilterTest.java rename to Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/table/threaded/DefaultThreadedTableFilterTest.java index ab5f01c193..9058fb2598 100644 --- a/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/table/threaded/ThreadedTableFilterTest.java +++ b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/table/threaded/DefaultThreadedTableFilterTest.java @@ -32,7 +32,7 @@ import ghidra.docking.spy.SpyEventRecorder; import ghidra.framework.plugintool.ServiceProvider; import ghidra.util.task.TaskMonitor; -public class ThreadedTableFilterTest extends AbstractThreadedTableTest { +public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest { private SpyEventRecorder recorder = new SpyEventRecorder(getClass().getSimpleName()); private SpyTaskMonitor monitor = new SpyTaskMonitor(); @@ -98,7 +98,7 @@ public class ThreadedTableFilterTest extends AbstractThreadedTableTest { @Test public void testRefilterHappensAfterAddItem_ItemAddedPassesFilter() throws Exception { - int newRowIndex = model.getRowCount() + 1; + int newRowIndex = getRowCount() + 1; filterOnRawColumnValue(newRowIndex); resetSpies(); @@ -114,7 +114,7 @@ public class ThreadedTableFilterTest extends AbstractThreadedTableTest { @Test public void testRefilterHappensAfterRemoveAddItem_ItemAddedPassesFilter() throws Exception { - int newRowIndex = model.getRowCount() + 1; + int newRowIndex = getRowCount() + 1; filterOnRawColumnValue(newRowIndex); resetSpies(); @@ -137,7 +137,7 @@ public class ThreadedTableFilterTest extends AbstractThreadedTableTest { @Test public void testRefilterHappensAfterAdd_ItemAddedFailsFilter() throws Exception { - int newRowIndex = model.getRowCount() + 1; + int newRowIndex = getRowCount() + 1; long nonMatchingFilter = 1; filterOnRawColumnValue(nonMatchingFilter); @@ -380,18 +380,18 @@ public class ThreadedTableFilterTest extends AbstractThreadedTableTest { } private void assertFilteredEntireModel() { - int allCount = model.getUnfilteredCount(); + int allCount = getUnfilteredRowCount(); assertNumberOfItemsPassedThroughFilter(allCount); } private void assertTableContainsValue(long expected) { - List modelValues = model.getModelData(); + List modelValues = getModelData(); assertTrue("Value not in the model--filtered out? - Expected " + expected + "; found " + modelValues, modelValues.contains(expected)); } private void assertTableDoesNotContainValue(long expected) { - List modelValues = model.getModelData(); + List modelValues = getModelData(); assertFalse("Value in the model--should not be there - Value " + expected + "; found " + modelValues, modelValues.contains(expected)); } @@ -464,9 +464,11 @@ public class ThreadedTableFilterTest extends AbstractThreadedTableTest { spyFilter = new SpyTextFilter<>(textFilter, transformer, recorder); CombinedTableFilter combinedFilter = - new CombinedTableFilter(spyFilter, secondFilter, null); + new CombinedTableFilter<>(spyFilter, secondFilter, null); + recorder.record("Before setting the new filter"); runSwing(() -> model.setTableFilter(combinedFilter)); + recorder.record("\tafter setting filter"); waitForNotBusy(); waitForTableModel(model); diff --git a/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/table/threaded/NonSortedThreadedTableFilterTest.java b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/table/threaded/NonSortedThreadedTableFilterTest.java new file mode 100644 index 0000000000..6f17dc6ed4 --- /dev/null +++ b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/table/threaded/NonSortedThreadedTableFilterTest.java @@ -0,0 +1,34 @@ +/* ### + * IP: GHIDRA + * + * 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 docking.widgets.table.threaded; + +import org.junit.Before; + +import docking.widgets.table.TableSortState; + +public class NonSortedThreadedTableFilterTest extends DefaultThreadedTableFilterTest { + + @Override + @Before + public void setUp() throws Exception { + + super.setUp(); + + TableSortState sortState = TableSortState.createUnsortedSortState(); + runSwing(() -> model.setTableSortState(sortState)); + waitForTableModel(model); + } +} diff --git a/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/table/threaded/NonSortedThreadedTableTest.java b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/table/threaded/NonSortedThreadedTableTest.java new file mode 100644 index 0000000000..eccd02cfd3 --- /dev/null +++ b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/table/threaded/NonSortedThreadedTableTest.java @@ -0,0 +1,35 @@ +/* ### + * IP: GHIDRA + * + * 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 docking.widgets.table.threaded; + +import org.junit.Before; + +import docking.widgets.table.TableSortState; + +public class NonSortedThreadedTableTest extends ThreadedTableTest { + + @Override + @Before + public void setUp() throws Exception { + + super.setUp(); + + TableSortState sortState = TableSortState.createUnsortedSortState(); + runSwing(() -> model.setTableSortState(sortState)); + waitForTableModel(model); + } + +} diff --git a/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/table/threaded/SpyTextFilter.java b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/table/threaded/SpyTextFilter.java index 724bbf5ac1..4e3b4002ae 100644 --- a/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/table/threaded/SpyTextFilter.java +++ b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/table/threaded/SpyTextFilter.java @@ -29,8 +29,7 @@ public class SpyTextFilter extends TableTextFilter { private SpyEventRecorder recorder; SpyTextFilter(TextFilter textFilter, RowFilterTransformer transformer) { - super(textFilter, transformer); - this.recorder = new SpyEventRecorder("Stub"); + this(textFilter, transformer, new SpyEventRecorder("Stub")); } SpyTextFilter(TextFilter textFilter, RowFilterTransformer transformer, diff --git a/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/table/threaded/ThreadedTableTest.java b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/table/threaded/ThreadedTableTest.java index 9a68e99aa2..40e01949b8 100644 --- a/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/table/threaded/ThreadedTableTest.java +++ b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/table/threaded/ThreadedTableTest.java @@ -45,7 +45,7 @@ public class ThreadedTableTest extends AbstractThreadedTableTest { private final Pattern SORT_SIZE_PATTERN = Pattern.compile(".*\\((\\d+) rows\\).*"); - private SpyEventRecorder recorder = new SpyEventRecorder(getClass().getSimpleName()); + private SpyEventRecorder recorder = new SpyEventRecorder(testName.getMethodName()); private SpyTaskMonitor spyMonitor = new SpyTaskMonitor(recorder); private SpyTextFilter spyFilter; private SortListener spySortListener = @@ -102,6 +102,12 @@ public class ThreadedTableTest extends AbstractThreadedTableTest { public void setIncrementalTaskMonitor(TaskMonitor monitor) { // no! some of our tests use a spy monitor } + + @Override + public void setTableSortState(TableSortState newSortState) { + record("model.setTableSortState() - " + newSortState); + super.setTableSortState(newSortState); + } }); return box[0]; } @@ -111,7 +117,6 @@ public class ThreadedTableTest extends AbstractThreadedTableTest { // // make sure the sort is on the filtered data and not *all* data // - waitForNotBusy(); assertSortSize(12); filter_ten(); @@ -512,7 +517,6 @@ public class ThreadedTableTest extends AbstractThreadedTableTest { // // we need to use a model that loads slowly enough to trigger the pending panel to show // - waitForNotBusy();// if we are working while adding, we will not get the pending notification model.setDelayTimeBetweenAddingDataItemsWhileLoading(60000); model.setUpdateDelay(100000000, 100000001);// make sure we don't update after repeated requests arrive @@ -534,15 +538,15 @@ public class ThreadedTableTest extends AbstractThreadedTableTest { @Override protected void sortByClick(int columnToClick, int modifiers) throws Exception { - recorder.record( - "Test." + testName + " - clicking column=" + columnToClick + "; modifiers=" + 0); + recorder.record("Test." + testName.getMethodName() + " - clicking column=" + columnToClick + + "; modifiers=" + 0); super.sortByClick(columnToClick, modifiers); } @Override protected void removeSortByClicking(int columnToClick) throws Exception { - recorder.record( - "Test." + testName + " - clicking column to remove sort - column=" + columnToClick); + recorder.record("Test." + testName.getMethodName() + + " - clicking column to remove sort - column=" + columnToClick); super.removeSortByClicking(columnToClick); } @@ -607,7 +611,7 @@ public class ThreadedTableTest extends AbstractThreadedTableTest { private void toggleStringColumnSort() throws Exception { Rectangle rect = header.getHeaderRect(TestDataKeyModel.STRING_COL); - testTableModelListener.reset(model); + resetBusyListener(); clickMouse(header, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0); waitForNotBusy(); waitForSwing(); @@ -753,6 +757,8 @@ public class ThreadedTableTest extends AbstractThreadedTableTest { } private void clearFilter() throws Exception { + resetSpies(); + resetBusyListener(); runSwing(() -> model.setTableFilter(null)); waitForNotBusy(); @@ -782,6 +788,7 @@ public class ThreadedTableTest extends AbstractThreadedTableTest { @Override protected void doTestSorting(int columnIndex) throws Exception { + sortByNormalClicking(columnIndex); SortedTableModel sortedModel = (SortedTableModel) table.getModel(); @@ -829,7 +836,7 @@ public class ThreadedTableTest extends AbstractThreadedTableTest { TextFilterFactory textFactory = options.getTextFilterFactory(); TextFilter textFilter = textFactory.getTextFilter(text); - testTableModelListener.reset(model); + resetBusyListener(); spyFilter = new SpyTextFilter<>(textFilter, transformer, recorder); @@ -859,6 +866,11 @@ public class ThreadedTableTest extends AbstractThreadedTableTest { assertTrue("Table did not filter; requested filter on '" + text + "'", hasFiltered); } + @Override + protected void record(String message) { + recorder.record("Test - " + message); + } + private void resetSpies() { spyFilter.reset(); spyMonitor.clearMessages(); diff --git a/Ghidra/Framework/Docking/src/test/java/docking/widgets/table/threaded/AbstractThreadedTableTest.java b/Ghidra/Framework/Docking/src/test/java/docking/widgets/table/threaded/AbstractThreadedTableTest.java index b9fa08f6fe..a72c59f4a3 100644 --- a/Ghidra/Framework/Docking/src/test/java/docking/widgets/table/threaded/AbstractThreadedTableTest.java +++ b/Ghidra/Framework/Docking/src/test/java/docking/widgets/table/threaded/AbstractThreadedTableTest.java @@ -90,12 +90,12 @@ public abstract class AbstractThreadedTableTest extends AbstractDockingTest { } protected void addItemToModel(long value) { - model.addObject(new Long(value)); + model.addObject(Long.valueOf(value)); waitForTableModel(model); } protected void removeItemFromModel(int value) { - model.removeObject(new Long(value)); + model.removeObject(Long.valueOf(value)); waitForTableModel(model); } @@ -161,10 +161,20 @@ public abstract class AbstractThreadedTableTest extends AbstractDockingTest { SortedTableModel sortedModel = (SortedTableModel) table.getModel(); TableSortState sortState = getSortState(sortedModel); - ColumnSortState originalColumnSortState = sortState.iterator().next(); - int currentSortedIndex = originalColumnSortState.getColumnModelIndex(); - boolean checkSortDirection = (columnToClick == currentSortedIndex); - boolean isAscending = originalColumnSortState.isAscending(); + record("sortByClick() - initial sort state: " + sortState); + + int currentSortColunn = -1; + boolean isAscending = true; + boolean checkSortDirection = false; + if (!sortState.isUnsorted()) { + + // check to see if the tests is clicking the same column twice (to change the + // sort direction) + ColumnSortState originalColumnSortState = sortState.iterator().next(); + currentSortColunn = originalColumnSortState.getColumnModelIndex(); + checkSortDirection = (columnToClick == currentSortColunn); + isAscending = originalColumnSortState.isAscending(); + } testTableModelListener.reset(model); Rectangle rect = header.getHeaderRect(columnToClick); @@ -172,10 +182,14 @@ public abstract class AbstractThreadedTableTest extends AbstractDockingTest { waitForPostedSwingRunnables(); } + record("Clicking table at column " + columnToClick); clickMouse(header, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, modifiers); waitForNotBusy(); + record("\tafter click; table not busy"); sortState = getSortState(sortedModel); + record("Updated sort state: " + sortState); + ColumnSortState columnSortState = sortState.iterator().next(); int sortedIndex = columnSortState.getColumnModelIndex(); verifyColumnSorted(sortedIndex, sortState); @@ -188,10 +202,8 @@ public abstract class AbstractThreadedTableTest extends AbstractDockingTest { } } - protected TableSortState getSortState(final SortedTableModel sortedModel) { - final TableSortState[] box = new TableSortState[1]; - runSwing(() -> box[0] = sortedModel.getTableSortState()); - return box[0]; + protected TableSortState getSortState(SortedTableModel sortedModel) { + return runSwing(() -> sortedModel.getTableSortState()); } protected void removeSortByClicking(int columnToClick) throws Exception { @@ -212,20 +224,35 @@ public abstract class AbstractThreadedTableTest extends AbstractDockingTest { assertNotNull(columnSortState); } + protected void resetBusyListener() { + testTableModelListener.reset(model); + } + protected void waitForNotBusy() { sleep(50); - int nWaits = 0; - int maxWaits = 500; - while (!testTableModelListener.doneWork() && nWaits++ < maxWaits) { - sleep(50); - } - - assertTrue("Timed-out waiting for table model to update.", nWaits < maxWaits); + waitForCondition(() -> testTableModelListener.doneWork(), + "Timed-out waiting for table model to update."); waitForSwing(); } protected void addLong(final long value) { - runSwing(() -> model.addObject(new Long(value))); + runSwing(() -> model.addObject(Long.valueOf(value))); + } + + protected int getRowCount() { + return runSwing(() -> model.getRowCount()); + } + + protected int getUnfilteredRowCount() { + return runSwing(() -> model.getUnfilteredRowCount()); + } + + protected List getModelData() { + return runSwing(() -> model.getModelData()); + } + + protected void record(String message) { + // no-op for base class; subclasses know how to record debug } protected void assertRowCount(int expectedCount) { diff --git a/Ghidra/Framework/Docking/src/test/java/ghidra/docking/spy/SpyEventRecorder.java b/Ghidra/Framework/Docking/src/test/java/ghidra/docking/spy/SpyEventRecorder.java index 33df552641..9ac24b422b 100644 --- a/Ghidra/Framework/Docking/src/test/java/ghidra/docking/spy/SpyEventRecorder.java +++ b/Ghidra/Framework/Docking/src/test/java/ghidra/docking/spy/SpyEventRecorder.java @@ -43,18 +43,27 @@ public class SpyEventRecorder { events.add(event); } - // synchronized because we spy on multiple threads (like Test and Swing) - public synchronized void dumpEvents() { + private synchronized String eventsToString() { StringBuilder buffy = new StringBuilder("Recorded Events - " + recorderName + '\n'); for (SpyEvent event : events) { buffy.append(event.toString()).append('\n'); } - Msg.debug(this, buffy.toString()); + return buffy.toString(); + } + + // synchronized because we spy on multiple threads (like Test and Swing) + public void dumpEvents() { + Msg.debug(this, eventsToString()); + } + + @Override + public String toString() { + return eventsToString(); } private class SpyEvent { - private FastDateFormat dateFormat = FastDateFormat.getInstance("'T'HH:mm:ssZZ"); + private FastDateFormat dateFormat = FastDateFormat.getInstance("'T'HH:mm:ss:SSS"); private int id; private String message;