From 4e34193bb43db695feecdb82c24c25db886b76e5 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Mon, 22 Mar 2021 17:26:07 -0400 Subject: [PATCH] GP-792 - Updated the TableChooserDialog to allow for clients to change the sort behavior; deprecated duplicate 'TableChooeserDialog' and created 'TableSelectionDialog' as a replacement --- .../app/tablechooser/TableChooserDialog.java | 87 +++++++++--- .../tablechooser/TableChooserDialogTest.java | 133 +++++++++++++++--- .../widgets/dialogs/TableChooserDialog.java | 15 +- .../widgets/dialogs/TableSelectionDialog.java | 130 +++++++++++++++++ .../table/AbstractSortedTableModel.java | 91 ++++++------ .../widgets/table/ColumnSortState.java | 3 + 6 files changed, 372 insertions(+), 87 deletions(-) create mode 100644 Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/TableSelectionDialog.java diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/tablechooser/TableChooserDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/tablechooser/TableChooserDialog.java index b9ddf2cacf..46369f05e4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/tablechooser/TableChooserDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/tablechooser/TableChooserDialog.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; import javax.swing.*; import javax.swing.table.TableCellRenderer; @@ -38,7 +39,7 @@ import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; import ghidra.util.HelpLocation; -import ghidra.util.SystemUtilities; +import ghidra.util.Swing; import ghidra.util.datastruct.WeakDataStructureFactory; import ghidra.util.datastruct.WeakSet; import ghidra.util.table.*; @@ -47,13 +48,13 @@ import ghidra.util.task.TaskMonitor; import utility.function.Callback; /** - * Dialog to show a table of items. If the dialog is constructed with a non-null + * Dialog to show a table of items. If the dialog is constructed with a non-null * {@link TableChooserExecutor}, then a button will be placed in the dialog, allowing the user * to perform the action defined by the executor. - * - *

Each button press will use the selected items as the items to be processed. While the - * items are scheduled to be processed, they will still be in the table, painted light gray. - * Attempting to reschedule any of these pending items will have no effect. Each time the + * + *

Each button press will use the selected items as the items to be processed. While the + * items are scheduled to be processed, they will still be in the table, painted light gray. + * Attempting to reschedule any of these pending items will have no effect. Each time the * button is pressed, a new {@link SwingWorker} is created, which will put the processing into * a background thread. Further, by using multiple workers, the work will be performed in * parallel. @@ -113,8 +114,7 @@ public class TableChooserDialog extends DialogComponentProvider table.installNavigation(goToService, navigatable); } table.getSelectionModel() - .addListSelectionListener( - e -> setOkEnabled(table.getSelectedRowCount() > 0)); + .addListSelectionListener(e -> setOkEnabled(table.getSelectedRowCount() > 0)); GhidraTableFilterPanel filterPanel = new GhidraTableFilterPanel<>(table, model); @@ -128,12 +128,12 @@ public class TableChooserDialog extends DialogComponentProvider * @param callback the callback to notify */ public void setClosedListener(Callback callback) { - this.closedCallback = Callback.dummyIfNull(callback); + Swing.runNow(() -> closedCallback = Callback.dummyIfNull(callback)); } /** * Adds the given object to this dialog. This method can be called from any thread. - * + * * @param rowObject the object to add */ public void add(AddressableRowObject rowObject) { @@ -141,9 +141,9 @@ public class TableChooserDialog extends DialogComponentProvider } /** - * Removes the given object from this dialog. Nothing will happen if the given item is not + * Removes the given object from this dialog. Nothing will happen if the given item is not * in this dialog. This method can be called from any thread. - * + * * @param rowObject the object to remove */ public void remove(AddressableRowObject rowObject) { @@ -153,8 +153,7 @@ public class TableChooserDialog extends DialogComponentProvider private void createTableModel() { // note: the task monitor is installed later when this model is added to the threaded panel - SystemUtilities.runSwingNow( - () -> model = new TableChooserTableModel("Test", tool, program, null)); + Swing.runNow(() -> model = new TableChooserTableModel("Test", tool, program, null)); } private void createActions() { @@ -175,8 +174,8 @@ public class TableChooserDialog extends DialogComponentProvider }; DockingAction selectionNavigationAction = new SelectionNavigationAction(owner, table); - selectionNavigationAction.setHelpLocation( - new HelpLocation(HelpTopics.SEARCH, "Selection_Navigation")); + selectionNavigationAction + .setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Selection_Navigation")); addAction(selectAction); addAction(selectionNavigationAction); @@ -296,7 +295,49 @@ public class TableChooserDialog extends DialogComponentProvider } public void addCustomColumn(ColumnDisplay columnDisplay) { - model.addCustomColumn(columnDisplay); + Swing.runNow(() -> model.addCustomColumn(columnDisplay)); + } + + /** + * Sets the default sorted column for this dialog. + * + *

This method should be called after all custom columns have been added via + * {@link #addCustomColumn(ColumnDisplay)}. + * + * @param index the view's 0-based column index + * @see #setSortState(TableSortState) + * @throws IllegalArgumentException if an invalid column is requested for sorting + */ + public void setSortColumn(int index) { + setSortState(TableSortState.createDefaultSortState(index)); + } + + /** + * Sets the column sort state for this dialog. The {@link TableSortState} allows for + * combinations of sorted columns in ascending or descending order. + * + *

This method should be called after all custom columns have been added via + * {@link #addCustomColumn(ColumnDisplay)}. + * + * @param state the sort state + * @see #setSortColumn(int) + * @throws IllegalArgumentException if an invalid column is requested for sorting + */ + public void setSortState(TableSortState state) { + AtomicReference ref = new AtomicReference<>(); + Swing.runNow(() -> { + try { + model.setTableSortState(state); + } + catch (IllegalArgumentException e) { + ref.set(e); + } + }); + IllegalArgumentException exception = ref.get(); + if (exception != null) { + // use a new exception so the stack trace points to this class, not the runnable above + throw new IllegalArgumentException(exception); + } } @Override @@ -313,15 +354,17 @@ public class TableChooserDialog extends DialogComponentProvider } public void clearSelection() { - table.clearSelection(); + Swing.runNow(() -> table.clearSelection()); } public void selectRows(int... rows) { - ListSelectionModel selectionModel = table.getSelectionModel(); - for (int row : rows) { - selectionModel.addSelectionInterval(row, row); - } + Swing.runNow(() -> { + ListSelectionModel selectionModel = table.getSelectionModel(); + for (int row : rows) { + selectionModel.addSelectionInterval(row, row); + } + }); } public int[] getSelectedRows() { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/tablechooser/TableChooserDialogTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/tablechooser/TableChooserDialogTest.java index 304ad880d9..8378c84438 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/tablechooser/TableChooserDialogTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/tablechooser/TableChooserDialogTest.java @@ -33,12 +33,15 @@ import docking.action.*; import docking.actions.KeyEntryDialog; import docking.actions.ToolActions; import docking.tool.util.DockingToolConstants; +import docking.widgets.table.TableSortState; import ghidra.app.nav.Navigatable; import ghidra.framework.options.ToolOptions; import ghidra.framework.plugintool.DummyPluginTool; +import ghidra.program.database.ProgramBuilder; +import ghidra.program.database.ProgramDB; import ghidra.program.model.address.Address; -import ghidra.program.model.address.TestAddress; -import ghidra.program.model.listing.Program; +import ghidra.program.model.data.DataType; +import ghidra.program.model.listing.*; import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.ToyProgramBuilder; import resources.Icons; @@ -75,16 +78,48 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest tool = new DummyPluginTool(); tool.setVisible(true); - Program program = new ToyProgramBuilder("Test", true).getProgram(); + + List

addresses = new ArrayList<>(); + ToyProgramBuilder builder = new ToyProgramBuilder("Test", true); + builder.createMemory(".text", "0x0", 0x110); + Function f = createFunction(builder, 0x00); + addresses.add(f.getEntryPoint()); + f = createFunction(builder, 0x10); + addresses.add(f.getEntryPoint()); + f = createFunction(builder, 0x20); + addresses.add(f.getEntryPoint()); + f = createFunction(builder, 0x30); + addresses.add(f.getEntryPoint()); + f = createFunction(builder, 0x40); + addresses.add(f.getEntryPoint()); + f = createFunction(builder, 0x50); + addresses.add(f.getEntryPoint()); + + Program program = builder.getProgram(); Navigatable navigatable = null; dialog = new TableChooserDialog(tool, executor, program, "Dialog Title", navigatable); testAction = new TestAction(); dialog.addAction(testAction); + dialog.addCustomColumn(new OffsetTestColumn()); + dialog.addCustomColumn(new SpaceTestColumn()); + dialog.show(); waitForDialogComponent(TableChooserDialog.class); - loadData(); + loadData(addresses); + } + + private Function createFunction(ProgramBuilder builder, long addr) throws Exception { + ProgramDB p = builder.getProgram(); + FunctionManager fm = p.getFunctionManager(); + Function f = fm.getFunctionAt(builder.addr(addr)); + if (f != null) { + return f; + } + + String a = Long.toHexString(addr); + return builder.createEmptyFunction("Function_" + a, "0x" + a, 5, DataType.DEFAULT); } private void reCreateDialog(SpyTableChooserExecutor dialogExecutor) throws Exception { @@ -92,9 +127,9 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest createDialog(dialogExecutor); } - private void loadData() { - for (int i = 0; i < 7; i++) { - dialog.add(new TestStubRowObject()); + private void loadData(List
addresses) { + for (Address a : addresses) { + dialog.add(new TestStubRowObject(a)); } waitForDialog(); @@ -151,7 +186,7 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest @Test public void testCalllbackRemovesItems_OtherItemSelected() { /* - Select multiple items. + Select multiple items. Have the first callback remove one of the remaining *unselected* items. The removed item should not itself get a callback. */ @@ -187,7 +222,7 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest public void testCalllbackRemovesItems_OtherItemNotSelected() { /* - Select multiple items. + Select multiple items. Have the first callback remove one of the remaining *selected* items. The removed item should not itself get a callback. */ @@ -234,7 +269,7 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest CountDownLatch continueLatch = new CountDownLatch(1); testDecision = r -> { - // + // // Signal that we have started and wait to continue // startLatch.countDown(); @@ -308,10 +343,40 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest assertTrue(newToolTip.contains("(A)")); } + @Test + public void testSetSortColumn() throws Exception { + assertSortedColumn(0); + dialog.setSortColumn(1); + assertSortedColumn(1); + } + + @Test + public void testSetSortState() throws Exception { + assertSortedColumn(0); + dialog.setSortState(TableSortState.createDefaultSortState(2, false)); + assertSortedColumn(2); + } + + @Test(expected = IllegalArgumentException.class) + public void testSetSortState_Invalid() throws Exception { + assertSortedColumn(0); + dialog.setSortState(TableSortState.createDefaultSortState(100)); + } + //================================================================================================== // Private Methods //================================================================================================== + private void assertSortedColumn(int expectedColumn) { + waitForCondition(() -> expectedColumn == getSortColumn(), + "Incorrect sorted column; expected " + expectedColumn + ", found " + getSortColumn()); + } + + private int getSortColumn() { + TableChooserTableModel model = getModel(); + return runSwing(() -> model.getPrimarySortColumnIndex()); + } + private void setKeyBindingViaF4Dialog(DockingAction action, KeyStroke ks) { // simulate the user mousing over the toolbar button @@ -415,6 +480,7 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest private void waitForDialog() { waitForCondition(() -> !dialog.isBusy()); + waitForSwing(); } private void pressExecuteButton() { @@ -443,7 +509,7 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest //================================================================================================== // Inner Classes -//================================================================================================== +//================================================================================================== private interface TestExecutorDecision { public boolean decide(AddressableRowObject rowObject); @@ -485,16 +551,15 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest private static class TestStubRowObject implements AddressableRowObject { - private static int counter; - private long addr; + private Address addr; - TestStubRowObject() { - addr = ++counter; + TestStubRowObject(Address a) { + this.addr = a; } @Override public Address getAddress() { - return new TestAddress(addr); + return addr; } @Override @@ -503,6 +568,42 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest } } + private static class OffsetTestColumn extends AbstractColumnDisplay { + + @Override + public String getColumnValue(AddressableRowObject rowObject) { + return Long.toString(rowObject.getAddress().getOffset()); + } + + @Override + public String getColumnName() { + return "Offset"; + } + + @Override + public int compare(AddressableRowObject o1, AddressableRowObject o2) { + return o1.getAddress().compareTo(o2.getAddress()); + } + } + + private static class SpaceTestColumn extends AbstractColumnDisplay { + + @Override + public String getColumnValue(AddressableRowObject rowObject) { + return rowObject.getAddress().getAddressSpace().toString(); + } + + @Override + public String getColumnName() { + return "Space"; + } + + @Override + public int compare(AddressableRowObject o1, AddressableRowObject o2) { + return o1.getAddress().compareTo(o2.getAddress()); + } + } + private class TestAction extends DockingAction { private int invoked; diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/TableChooserDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/TableChooserDialog.java index 4640bfd686..1151c00a79 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/TableChooserDialog.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/TableChooserDialog.java @@ -26,11 +26,14 @@ import docking.DialogComponentProvider; import docking.widgets.table.*; /** - * Dialog for displaying table data in a dialog for the purpose of the user selecting one or - * more items from the table. + * @param the type * - * @param The type of row object in the table. + * + * @deprecated This class has been replaced by {@link TableSelectionDialog}. At the time of + * writing, both classes are identical. This version introduced a naming conflict with another + * API. Thus, the new version better matches the existing dialog choosing API. */ +@Deprecated(forRemoval = true, since = "9.3") public class TableChooserDialog extends DialogComponentProvider { private RowObjectTableModel model; @@ -39,12 +42,14 @@ public class TableChooserDialog extends DialogComponentProvider { /** * Create a new Dialog for displaying and choosing table row items - * + * * @param title The title for the dialog * @param model a {@link RowObjectTableModel} that has the tRable data * @param allowMultipleSelection if true, the dialog allows the user to select more * than one row; otherwise, only single selection is allowed + * @deprecated see the class header */ + @Deprecated(forRemoval = true, since = "9.3") public TableChooserDialog(String title, RowObjectTableModel model, boolean allowMultipleSelection) { super(title); @@ -57,7 +62,9 @@ public class TableChooserDialog extends DialogComponentProvider { /** * Returns the list of selected items or null if the dialog was cancelled. * @return the list of selected items or null if the dialog was cancelled. + * @deprecated see the class header */ + @Deprecated(forRemoval = true, since = "9.3") public List getSelectionItems() { return selectedItems; } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/TableSelectionDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/TableSelectionDialog.java new file mode 100644 index 0000000000..b0d8f7d2af --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/TableSelectionDialog.java @@ -0,0 +1,130 @@ +/* ### + * 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.dialogs; + +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.Arrays; +import java.util.List; + +import javax.swing.*; + +import docking.DialogComponentProvider; +import docking.widgets.table.*; + +/** + * Dialog for displaying table data in a dialog for the purpose of the user selecting one or + * more items from the table. + * + * @param The type of row object in the table. + */ +public class TableSelectionDialog extends DialogComponentProvider { + + private RowObjectTableModel model; + private GFilterTable gFilterTable; + private List selectedItems; + + /** + * Create a new Dialog for displaying and choosing table row items + * + * @param title The title for the dialog + * @param model a {@link RowObjectTableModel} that has the tRable data + * @param allowMultipleSelection if true, the dialog allows the user to select more + * than one row; otherwise, only single selection is allowed + */ + public TableSelectionDialog(String title, RowObjectTableModel model, + boolean allowMultipleSelection) { + super(title); + this.model = model; + addWorkPanel(buildTable(allowMultipleSelection)); + addOKButton(); + addCancelButton(); + } + + /** + * Returns the list of selected items or null if the dialog was cancelled. + * @return the list of selected items or null if the dialog was cancelled. + */ + public List getSelectionItems() { + return selectedItems; + } + + private void initializeTable(boolean allowMultipleSelection) { + GTable table = gFilterTable.getTable(); + + table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); + + int selectionMode = allowMultipleSelection ? ListSelectionModel.MULTIPLE_INTERVAL_SELECTION + : ListSelectionModel.SINGLE_SELECTION; + table.getSelectionModel().setSelectionMode(selectionMode); + + } + + protected void processMouseClicked(MouseEvent e) { + + if (e.getClickCount() != 2) { + return; + } + + int rowAtPoint = gFilterTable.getTable().rowAtPoint(e.getPoint()); + if (rowAtPoint < 0) { + return; + } + + T selectedRowObject = gFilterTable.getSelectedRowObject(); + selectedItems = Arrays.asList(selectedRowObject); + close(); + } + + @Override + protected void okCallback() { + selectedItems = gFilterTable.getSelectedRowObjects(); + close(); + gFilterTable.dispose(); + } + + @Override + protected void cancelCallback() { + selectedItems = null; + close(); + gFilterTable.dispose(); + } + + @Override + protected void dialogShown() { + gFilterTable.focusFilter(); + } + + private JComponent buildTable(boolean allowMultipleSelection) { + gFilterTable = new GFilterTable<>(model); + initializeTable(allowMultipleSelection); + gFilterTable.getTable().addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (!e.isShiftDown()) { + processMouseClicked(e); + } + updateOkEnabled(); + } + }); + setOkEnabled(false); + return gFilterTable; + } + + protected void updateOkEnabled() { + setOkEnabled(gFilterTable.getSelectedRowObject() != null); + } +} 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 23e6b68cbb..6981bed897 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 @@ -28,15 +28,15 @@ import ghidra.util.datastruct.WeakSet; /** * Table models should extends this model when they want sorting, potentially across multiple - * columns, but do not want Threading or do not work on Program-related data (Address, + * columns, but do not want Threading or do not work on Program-related data (Address, * ProgramLocations, etc...). *

- * In order to define custom comparators for a column, simply override + * 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 + * + *

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 @@ -92,11 +92,11 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel } /** - * Returns the index of the given row object in this model; a negative value if the model - * does not contain 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 + * Returns the index of the given row object in this model; a negative value if the model + * does not contain 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 @@ -158,14 +158,15 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel } // verify the requested columns are sortable - for (int i = 0; i < columnCount; i++) { - ColumnSortState state = tableSortState.getColumnSortState(i); - if (state == null) { - continue; // no sort state for this column--nothing to validate + for (ColumnSortState state : tableSortState) { + + int index = state.getColumnModelIndex(); + if (!isSortable(index)) { + return false; // the state wants to sort on an unsortable column } - if (!isSortable(i)) { - return false; // the state wants to sort on an unsortable column + if (index >= columnCount) { + return false; // requested a column that is larger than the number of columns } } @@ -174,10 +175,10 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel /** * A convenience method that will take a given sort state and remove from it any columns - * that cannot be sorted. This is useful if the client is restoring a sort state that - * contains columns that have been removed or are no longer sortable (such as during major + * that cannot be sorted. This is useful if the client is restoring a sort state that + * contains columns that have been removed or are no longer sortable (such as during major * table model rewrites). - * + * * @param state the state * @return the updated state */ @@ -220,9 +221,9 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel } /** - * Returns true if there is a pending change to the current sort state + * Returns true if there is a pending change to the current sort state * (this includes a sort state that signals no sort will be applied) - * + * * @return true if there is a pending change to the current sort state */ public boolean isSortPending() { @@ -232,7 +233,7 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel /** * Returns true if this model has been sorted and does not have a new pending sort that will * be applied - * + * * @return true if sorted * @see #isSortPending() */ @@ -245,7 +246,7 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel } /** - * The default implementation of {@link TableModel#getValueAt(int, int)} that calls the + * The default implementation of {@link TableModel#getValueAt(int, int)} that calls the * abstract {@link #getColumnValueForRow(Object, int)}. */ @Override @@ -255,9 +256,9 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel } /** - * This method is an attempt to help models that forget to call fireTableDataChanged(). It - * is expected that tables will fire the notification when they are ready to display data, - * even if they have that data at construction time. We put this call here so that the + * This method is an attempt to help models that forget to call fireTableDataChanged(). It + * is expected that tables will fire the notification when they are ready to display data, + * even if they have that data at construction time. We put this call here so that the * forgetful subclasses will have their data sorted for them the first time that this table * tries to render itself. */ @@ -275,11 +276,11 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel /** * A convenience method for subclasses to quickly/efficiently 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. - * + * fact that the data searched is retrieved from {@link #getModelData()}, which may be + * filtered. + * * @param rowObject The object for which to search. - * @return the index of the item in the data returned by + * @return the index of the item in the data returned by */ @Override protected int getIndexForRowObject(T rowObject) { @@ -287,8 +288,8 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel } /** - * Returns the index for the given object in the given list - * + * Returns the index for the given object in the given list + * * @param rowObject the item * @param data the data * @return the index @@ -312,11 +313,11 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel /** * A default sort method that uses the {@link Collections#sort(List, Comparator)} method for * sorting. Implementors with reasonably sized data sets can rely on this method. For data - * sets that can become large, the ThreadedTableModel is the recommended base class, + * sets that can become large, the ThreadedTableModel is the recommended base class, * as it handles loading/sorting/filtering in a threaded way. - * + * * @param data The data to be sorted - * @param sortingContext The context required to sort (it contains the sorting columns, a + * @param sortingContext The context required to sort (it contains the sorting columns, a * comparator for sorting, etc...). */ protected void sort(List data, TableSortingContext sortingContext) { @@ -344,7 +345,7 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel } /** - * Fires an event to let the listeners (like JTable) know that things have been changed. + * Fires an event to let the listeners (like JTable) know that things have been changed. * This method exists so that subclasses have a way to call the various tableChanged() * methods without triggering this class's overridden version. * @param dataChanged True signals that the actual data has changed; false signals that the @@ -366,10 +367,10 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel /** * An extension point for subclasses to insert their own comparator objects for their data. - * Subclasses can create comparators for a single or multiple columns, as desired. - * + * Subclasses can create comparators for a single or multiple columns, as desired. + * * @param columnIndex the column index - * @return the comparator + * @return the comparator */ protected Comparator createSortComparator(int columnIndex) { return new RowBasedColumnComparator<>(this, columnIndex, new DefaultColumnComparator(), @@ -385,7 +386,7 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel // one column sorted and that column is 'reversed', then to provide consistent sorting, // and later searching, we need to reverse the tie-breaker comparator. Without this, // the tie-breaker always produces the same results, regardless of which direction the - // search is going. That will break any clients that call Collections.reverse() in + // search is going. That will break any clients that call Collections.reverse() in // order to quickly invert an existing sort. // if (parentChain.primaryComparator instanceof AbstractSortedTableModel.ReverseComparator) { @@ -419,8 +420,8 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel } /** - * Builds a comparator for the given column sort state while allowing for subclasses to - * provider their own comparators. This method also handles directionality of sorting, so + * Builds a comparator for the given column sort state while allowing for subclasses to + * provider their own comparators. This method also handles directionality of sorting, so * that the comparators used can be simple and generic. */ private Comparator getComparator(ColumnSortState columnSortState) { @@ -487,8 +488,8 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel public int compare(T t1, T t2) { // at this point we compare the rows, since all of the sorting column values are equal - // (Warning: due to comparable being specific to the class upon which it is defined, - // we have to make sure the class is the same to prevent class cast + // (Warning: due to comparable being specific to the class upon which it is defined, + // we have to make sure the class is the same to prevent class cast // exceptions when the table has mixed implementations of 'T') if (t1 instanceof Comparable && t1.getClass().equals(t2.getClass())) { return ((Comparable) t1).compareTo(t2); 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 cda0ca238a..a4962f49d9 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 @@ -55,6 +55,9 @@ public class ColumnSortState { private int sortOrder_OneBased = -1; ColumnSortState(int columnModelIndex, SortDirection sortDirection, int sortOrder) { + if (columnModelIndex < 0) { + throw new IllegalArgumentException("Column index cannot be negative"); + } this.columnModelIndex = columnModelIndex; this.sortDirection = sortDirection; this.sortOrder_OneBased = sortOrder;