diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/IncomingReferenceEndpoint.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/IncomingReferenceEndpoint.java index e967b85a26..0493b33cf8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/IncomingReferenceEndpoint.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/IncomingReferenceEndpoint.java @@ -26,4 +26,9 @@ public class IncomingReferenceEndpoint extends ReferenceEndpoint { public IncomingReferenceEndpoint(Reference r, boolean isOffcut) { super(r, r.getFromAddress(), r.getReferenceType(), isOffcut, r.getSource()); } + + @Override + public String toString() { + return "Incoming " + getReferenceType().getName(); + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/OutgoingReferenceEndpoint.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/OutgoingReferenceEndpoint.java index baa158b3a9..04c0aca707 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/OutgoingReferenceEndpoint.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/OutgoingReferenceEndpoint.java @@ -38,4 +38,9 @@ public class OutgoingReferenceEndpoint extends ReferenceEndpoint { public OutgoingReferenceEndpoint(Reference r, Address toAddress, boolean isOffcut) { super(r, toAddress, r.getReferenceType(), isOffcut, r.getSource()); } + + @Override + public String toString() { + return "Outgoing " + getReferenceType().getName(); + } } 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 030e31d6c4..61efa594e9 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 @@ -20,7 +20,9 @@ import java.util.*; import javax.swing.event.TableModelEvent; import javax.swing.table.TableModel; -import ghidra.util.SystemUtilities; +import docking.widgets.table.sort.DefaultColumnComparator; +import docking.widgets.table.sort.RowToColumnComparator; +import ghidra.util.Swing; import ghidra.util.datastruct.WeakDataStructureFactory; import ghidra.util.datastruct.WeakSet; @@ -178,8 +180,7 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel isSortPending = true; pendingSortState = newSortState; - SystemUtilities.runSwingLater( - () -> sort(getModelData(), createSortingContext(newSortState))); + Swing.runLater(() -> sort(getModelData(), createSortingContext(newSortState))); } public TableSortState getPendingSortState() { @@ -223,7 +224,7 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel hasEverSorted = true; isSortPending = true; pendingSortState = sortState; - SystemUtilities.runSwingLater(() -> sort(getModelData(), createSortingContext(sortState))); + Swing.runLater(() -> sort(getModelData(), createSortingContext(sortState))); } /** @@ -320,14 +321,14 @@ 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. The - * {@link DefaultColumnComparator} is used as a, well, default comparator. + * Subclasses can create comparators for a single or multiple columns, as desired. * - * @param columnIndex the column index for which a comparator is desired. - * @return a comparator for the given index. + * @param columnIndex the column index + * @return the comparator */ protected Comparator createSortComparator(int columnIndex) { - return new DefaultColumnComparator(columnIndex); + return new RowToColumnComparator<>(this, columnIndex, new DefaultColumnComparator(), + new StringBasedBackupRowToColumnComparator(columnIndex)); } private Comparator createLastResortComparator(ComparatorLink parentChain) { @@ -416,23 +417,6 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel } } - int size() { - int count = 0; - if (primaryComparator != null) { - count++; - } - - if (nextComparator == null) { - return count; - } - - if (nextComparator instanceof AbstractSortedTableModel.ComparatorLink) { - count += ((ComparatorLink) nextComparator).size(); - } - - return count + 1; // +1 for the non-null comparator - } - @Override public int compare(T t1, T t2) { int result = primaryComparator.compare(t1, t2); @@ -451,18 +435,18 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel * when we get to this comparator, then we have to make a decision about reasonable default * comparisons in order to maintain sorting consistency across sorts. */ - @SuppressWarnings("unchecked") - // Comparable cast + @SuppressWarnings("unchecked") // Comparable cast private class EndOfChainComparator implements Comparator { @SuppressWarnings("rawtypes") @Override public int compare(T t1, T t2) { - // at this point we compare the rows, since all of the sorting columns are - // completely equal + // at this point we compare the rows, since all of the sorting column values are equal if (t1 instanceof Comparable) { return ((Comparable) t1).compareTo(t2); } + + // use the identity hash to provide a consistent unique identifier within a JVM session return System.identityHashCode(t1) - System.identityHashCode(t2); } } @@ -480,19 +464,35 @@ public abstract class AbstractSortedTableModel extends AbstractGTableModel } } - private class DefaultColumnComparator implements Comparator { - private final int columnIndex; + private class StringBasedBackupRowToColumnComparator implements Comparator { - public DefaultColumnComparator(int columnIndex) { - this.columnIndex = columnIndex; + private int sortColumn; + + StringBasedBackupRowToColumnComparator(int sortColumn) { + this.sortColumn = sortColumn; } @Override public int compare(T t1, T t2) { - Object value1 = getColumnValueForRow(t1, columnIndex); - Object value2 = getColumnValueForRow(t2, columnIndex); - return DEFAULT_COMPARATOR.compare(value1, value2); + if (t1 == t2) { + return 0; + } + + String s1 = getColumStringValue(t1); + String s2 = getColumStringValue(t2); + + if (s1 == null || s2 == null) { + return TableComparators.compareWithNullValues(s1, s2); + } + + return s1.compareToIgnoreCase(s2); + } + + private String getColumStringValue(T t) { + // just use the toString(), which may or may not produce a good value (this will + // catch the cases where the column value is itself a string) + Object o = getColumnValueForRow(t, sortColumn); + return o == null ? null : o.toString(); } } - } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/DynamicColumnTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/DynamicColumnTableModel.java index 8748d1b68c..5e5c49400f 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/DynamicColumnTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/DynamicColumnTableModel.java @@ -20,7 +20,8 @@ package docking.widgets.table; * * @param the row type of the underlying table model */ -public interface DynamicColumnTableModel extends ConfigurableColumnTableModel { +public interface DynamicColumnTableModel + extends ConfigurableColumnTableModel, RowObjectTableModel { /** * Returns the column for the given model index diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GDynamicColumnTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GDynamicColumnTableModel.java index 90b0f716df..556a16011e 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GDynamicColumnTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GDynamicColumnTableModel.java @@ -22,6 +22,7 @@ import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.table.TableCellRenderer; +import docking.widgets.table.sort.*; import ghidra.docking.settings.*; import ghidra.framework.plugintool.ServiceProvider; import ghidra.util.Msg; @@ -174,11 +175,14 @@ public abstract class GDynamicColumnTableModel @Override protected Comparator createSortComparator(int columnIndex) { - Comparator comparator = createSortComparatorForColumn(columnIndex); - if (comparator != null) { - return new RowToColumnComparator(columnIndex, comparator); + Comparator columnComparator = createSortComparatorForColumn(columnIndex); + if (columnComparator != null) { + // the given column has its own comparator; wrap and us that + return new RowToColumnComparator<>(this, columnIndex, columnComparator); } - return super.createSortComparator(columnIndex); + + return new RowToColumnComparator<>(this, columnIndex, new DefaultColumnComparator(), + new ColumnRenderedValueBackupRowComparator<>(this, columnIndex)); } /** @@ -187,7 +191,7 @@ public abstract class GDynamicColumnTableModel * column values. * * @param columnIndex the column index - * @return a comparator for the specific column values; may be null + * @return a comparator for the specific column values */ @SuppressWarnings("unchecked") // the column provides the values itself; safe cast protected Comparator createSortComparatorForColumn(int columnIndex) { @@ -545,45 +549,4 @@ public abstract class GDynamicColumnTableModel DynamicTableColumn column = tableColumns.get(index); return column.getMaxLines(columnSettings.get(column)); } - - /** - * A comparator for a specific column that will take in a ROW_TYPE object, extract the value - * for the given column and then call the give comparator. - */ - private class RowToColumnComparator implements Comparator { - - private int columnIndex; - private Comparator columnComparator; - - RowToColumnComparator(int columnIndex, Comparator comparator) { - this.columnIndex = columnIndex; - this.columnComparator = comparator; - } - - @Override - public int compare(ROW_TYPE t1, ROW_TYPE t2) { - Object value1 = getColumnValueForRow(t1, columnIndex); - Object value2 = getColumnValueForRow(t2, columnIndex); - - if (value1 == null || value2 == null) { - return handleNullValues(value1, value2); - } - - return columnComparator.compare(value1, value2); - } - - private int handleNullValues(Object o1, Object o2) { - // If both values are null return 0 - if (o1 == null && o2 == null) { - return 0; - } - - if (o1 == null) { // Define null less than everything. - return -1; - } - - return 1; // o2 is null, so the o1 comes after - } - } - } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/SortedTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/SortedTableModel.java index 43f71f6ef2..582572e2d7 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/SortedTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/SortedTableModel.java @@ -15,17 +15,13 @@ */ package docking.widgets.table; -import java.util.Comparator; - import javax.swing.table.TableModel; /** - * A table model that allows for setting the sorted column and direction. + * A table model that allows for setting the sorted column(s) and direction */ public interface SortedTableModel extends TableModel { - public static final Comparator DEFAULT_COMPARATOR = new DefaultComparator(); - /** * Sort order in ascending order. */ @@ -43,10 +39,23 @@ public interface SortedTableModel extends TableModel { */ public boolean isSortable(int columnIndex); + /** + * Returns the column index that is the primary sorted column + * + * @return the index + */ public int getPrimarySortColumnIndex(); - public void setTableSortState(TableSortState tableSortState); + /** + * Sets the sort state for this table model + * @param state the sort state + */ + public void setTableSortState(TableSortState state); + /** + * Gets the sort state of this sorted model + * @return the current sort state + */ public TableSortState getTableSortState(); /** @@ -58,54 +67,4 @@ public interface SortedTableModel extends TableModel { * @param l the listener */ public void addSortListener(SortListener l); - -//================================================================================================== -// Inner Classes -//================================================================================================== - - public static class DefaultComparator implements Comparator { - @Override - @SuppressWarnings("unchecked") - // we checked cast to be safe - public int compare(Object o1, Object o2) { - - if (o1 == null || o2 == null) { - return handleNullValues(o1, o2); - } - - if (String.class == o1.getClass() && String.class == o2.getClass()) { - return compareAsStrings(o1, o2); - } - - if (Comparable.class.isAssignableFrom(o1.getClass()) && o1.getClass() == o2.getClass()) { - @SuppressWarnings("rawtypes") - Comparable comparable = (Comparable) o1; - int result = comparable.compareTo(o2); - return result; - } - - // give up and use the toString() - return compareAsStrings(o1, o2); - } - - private int handleNullValues(Object o1, Object o2) { - // If both values are null return 0 - if (o1 == null && o2 == null) { - return 0; - } - - if (o1 == null) { // Define null less than everything. - return -1; - } - - return 1; // o2 is null, so the o1 comes after - } - - private int compareAsStrings(Object o1, Object o2) { - String s1 = o1.toString(); - String s2 = o2.toString(); - return s1.compareToIgnoreCase(s2); - } - } - } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableComparators.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableComparators.java new file mode 100644 index 0000000000..7b9dcf8bfd --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableComparators.java @@ -0,0 +1,44 @@ +/* ### + * 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; + +import java.util.Comparator; + +/** + * A utility class for tables to use when sorting + */ +public class TableComparators { + + private static final Comparator NO_SORT_COMPARATOR = (o1, o2) -> 0; + + @SuppressWarnings("unchecked") // we are casting to Object; safe since everything is an Object + public static Comparator getNoSortComparator() { + return (Comparator) NO_SORT_COMPARATOR; + } + + public static int compareWithNullValues(Object o1, Object o2) { + // If both values are null return 0 + if (o1 == null && o2 == null) { + return 0; + } + + if (o1 == null) { // Define null less than everything. + return -1; + } + + return 1; // o2 is null, so the o1 comes after + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/sort/ColumnRenderedValueBackupRowComparator.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/sort/ColumnRenderedValueBackupRowComparator.java new file mode 100644 index 0000000000..4605b05873 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/sort/ColumnRenderedValueBackupRowComparator.java @@ -0,0 +1,78 @@ +/* ### + * 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.sort; + +import java.util.Comparator; + +import docking.widgets.table.*; +import ghidra.docking.settings.Settings; +import ghidra.util.table.column.GColumnRenderer; + +/** + * A special version of the backup comparator that uses the column's rendered value for + * the backup sort, rather the just toString, which is what the default parent + * table model will do. + * + * @param the row type + */ +public class ColumnRenderedValueBackupRowComparator implements Comparator { + + protected int sortColumn; + protected DynamicColumnTableModel model; + + public ColumnRenderedValueBackupRowComparator(DynamicColumnTableModel model, + int sortColumn) { + this.model = model; + this.sortColumn = sortColumn; + } + + @Override + public int compare(T t1, T t2) { + if (t1 == t2) { + return 0; + } + + String s1 = getRenderedColumnStringValue(t1); + String s2 = getRenderedColumnStringValue(t2); + + if (s1 == null || s2 == null) { + return TableComparators.compareWithNullValues(s1, s2); + } + + return s1.compareToIgnoreCase(s2); + } + + // The case to Object is safe, but passing the object to the renderer below is potentially + // unsafe. We happen know that we retrieved the value from the column that we are passing + // it to, so the casting and usage is indeed safe. + @SuppressWarnings("unchecked") + private String getRenderedColumnStringValue(T t) { + + DynamicTableColumn column = model.getColumn(sortColumn); + GColumnRenderer renderer = (GColumnRenderer) column.getColumnRenderer(); + Object o = model.getColumnValueForRow(t, sortColumn); + if (renderer == null) { + return o == null ? null : o.toString(); + } + + Settings settings = model.getColumnSettings(sortColumn); + return renderer.getFilterString(o, settings); + } + + protected Object getColumnValue(T t) { + return model.getColumnValueForRow(t, sortColumn); + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/sort/DefaultColumnComparator.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/sort/DefaultColumnComparator.java new file mode 100644 index 0000000000..ffdb8ff1f0 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/sort/DefaultColumnComparator.java @@ -0,0 +1,60 @@ +/* ### + * 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.sort; + +import java.util.Comparator; + +import docking.widgets.table.TableComparators; + +/** + * A column comparator that is used when columns do not supply their own comparator. This + * comparator will use the natural sorting (i.e., the value implements Comparable), + * defaulting to the String representation for the given value. + */ +public class DefaultColumnComparator implements Comparator { + + @Override + @SuppressWarnings("unchecked") // we checked cast to be safe + public int compare(Object o1, Object o2) { + + if (o1 == null || o2 == null) { + return TableComparators.compareWithNullValues(o1, o2); + } + + Class c1 = o1.getClass(); + Class c2 = o2.getClass(); + if (String.class == c1 && String.class == c2) { + return compareAsStrings(o1, o2); + } + + if (Comparable.class.isAssignableFrom(c1) && c1 == c2) { + @SuppressWarnings("rawtypes") + Comparable comparable = (Comparable) o1; + int result = comparable.compareTo(o2); + return result; + } + + // At this point we do not know how to compare these items well. Return 0, which + // will signal to any further comparators that more comparing is needed. + return 0; + } + + private int compareAsStrings(Object o1, Object o2) { + String s1 = o1.toString(); + String s2 = o2.toString(); + return s1.compareToIgnoreCase(s2); + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/sort/RowToColumnComparator.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/sort/RowToColumnComparator.java new file mode 100644 index 0000000000..648437cfb0 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/sort/RowToColumnComparator.java @@ -0,0 +1,106 @@ +/* ### + * 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.sort; + +import java.util.Comparator; +import java.util.Objects; + +import docking.widgets.table.RowObjectTableModel; +import docking.widgets.table.TableComparators; + +/** + * A comparator for a specific column that will take in a T row object, extract the value + * for the given column and then call the give comparator + * + * @param the row type + */ +public class RowToColumnComparator implements Comparator { + + protected RowObjectTableModel model; + protected int sortColumn; + protected Comparator columnComparator; + protected Comparator backupRowComparator = TableComparators.getNoSortComparator(); + + /** + * Constructs this class with the given column comparator that will get called after the + * given row is converted to the column value for the given sort column + * + * @param model the table model using this comparator + * @param sortColumn the column being sorted + * @param comparator the column comparator to use for sorting + */ + public RowToColumnComparator(RowObjectTableModel model, int sortColumn, + Comparator comparator) { + this.model = model; + this.sortColumn = sortColumn; + this.columnComparator = Objects.requireNonNull(comparator); + } + + /** + * This version of the constructor is used for the default case where the client will + * supply a backup row comparator that will get called if the given column comparator returns + * a '0' value. + * + * @param model the table model using this comparator + * @param sortColumn the column being sorted + * @param comparator the column comparator to use for sorting + * @param backupRowComparator the backup row comparator + */ + public RowToColumnComparator(RowObjectTableModel model, int sortColumn, + Comparator comparator, Comparator backupRowComparator) { + this.model = model; + this.sortColumn = sortColumn; + this.columnComparator = Objects.requireNonNull(comparator); + this.backupRowComparator = Objects.requireNonNull(backupRowComparator); + } + + @Override + public int compare(T t1, T t2) { + if (t1 == t2) { + return 0; + } + + Object value1 = getColumnValue(t1); + Object value2 = getColumnValue(t2); + + if (value1 == null || value2 == null) { + return TableComparators.compareWithNullValues(value1, value2); + } + + int result = columnComparator.compare(value1, value2); + if (result != 0) { + return result; + } + + // + // At this point we have one of two cases: + // 1) the column comparator is a non-default comparator that has returned 0, which means + // the column values should sort the same, or + // 2) the column comparator is a default/non-specific comparator, which means that the + // column values should sort the same, or *that the default comparator could not + // figure out how to sort them. + // + // In case 1, this backup comparator will be just a stub comparator; in case 2, this + // backup comparator is not a stub and will do something reasonable for the sort, + // depending upon how the model created this class. + // + return backupRowComparator.compare(t1, t2); + } + + protected Object getColumnValue(T t) { + return model.getColumnValueForRow(t, sortColumn); + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/TableColumnComparator.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/TableColumnComparator.java deleted file mode 100644 index 2ff6b98365..0000000000 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/TableColumnComparator.java +++ /dev/null @@ -1,92 +0,0 @@ -/* ### - * 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 java.util.Comparator; - -import docking.widgets.table.SortedTableModel; - -public class TableColumnComparator implements Comparator { - private ThreadedTableModel model; - private final int sortColumn; - private Comparator columnComparator; - - public TableColumnComparator(ThreadedTableModel model, - Comparator columnComparator, int sortColumn) { - this.model = model; - this.columnComparator = columnComparator; - this.sortColumn = sortColumn; - } - - @Override - public int compare(T t1, T t2) { - if (t1 == t2) { - return 0; - } - - Object o1 = model.getCachedColumnValueForRow(t1, sortColumn); - Object o2 = model.getCachedColumnValueForRow(t2, sortColumn); - - if (o1 == null || o2 == null) { - return handleNullValues(o1, o2); - } - - if (columnComparator != null) { - return columnComparator.compare(o1, o2); - } - - return SortedTableModel.DEFAULT_COMPARATOR.compare(o1, o2); - } - - private int handleNullValues(Object o1, Object o2) { - // If both values are null return 0 - if (o1 == null && o2 == null) { - return 0; - } - - if (o1 == null) { // Define null less than everything. - return -1; - } - - return 1; // o2 is null, so the o1 comes after - } - - public int getSortColumn() { - return sortColumn; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (obj.getClass() != getClass()) { - return false; - } - - @SuppressWarnings("rawtypes") - TableColumnComparator other = (TableColumnComparator) obj; - return (sortColumn == other.sortColumn); - } - - @Override - public int hashCode() { - return sortColumn; - } -} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedBackupRowComparator.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedBackupRowComparator.java new file mode 100644 index 0000000000..a81703e55f --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedBackupRowComparator.java @@ -0,0 +1,48 @@ +/* ### + * 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 docking.widgets.table.sort.ColumnRenderedValueBackupRowComparator; +import docking.widgets.table.sort.RowToColumnComparator; + +/** + * A version of {@link ColumnRenderedValueBackupRowComparator} that uses the + * {@link ThreadedTableModel}'s cache for column lookups + * + * @param the row type + */ +public class ThreadedBackupRowComparator extends ColumnRenderedValueBackupRowComparator { + + private ThreadedTableModel threadedModel; + + /** + * Constructs this class with the given column comparator that will get called after the + * given row is converted to the column value for the given sort column + * + * @param model the table model using this comparator + * @param sortColumn the column being sorted + * @see RowToColumnComparator + */ + public ThreadedBackupRowComparator(ThreadedTableModel model, int sortColumn) { + super(model, sortColumn); + this.threadedModel = model; + } + + @Override + protected Object getColumnValue(T t) { + return threadedModel.getCachedColumnValueForRow(t, sortColumn); + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableColumnComparator.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableColumnComparator.java new file mode 100644 index 0000000000..c37d058e8c --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableColumnComparator.java @@ -0,0 +1,67 @@ +/* ### + * 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 java.util.Comparator; + +import docking.widgets.table.sort.RowToColumnComparator; + +/** + * A comparator for comparing table column values for threaded table models. This comparator + * uses the column cache of the {@link ThreadedTableModel}. + * + * @param the row type + */ +public class ThreadedTableColumnComparator extends RowToColumnComparator { + private ThreadedTableModel threadedModel; + + /** + * Constructs this class with the given column comparator that will get called after the + * given row is converted to the column value for the given sort column + * + * @param model the table model using this comparator + * @param sortColumn the column being sorted + * @param comparator the column comparator to use for sorting + * @see RowToColumnComparator + */ + public ThreadedTableColumnComparator(ThreadedTableModel model, int sortColumn, + Comparator comparator) { + super(model, sortColumn, comparator); + this.threadedModel = model; + } + + /** + * This version of the constructor is used for the default case where the client will + * supply a backup row comparator that will get called if the given column comparator returns + * a '0' value. + * + * @param model the table model using this comparator + * @param sortColumn the column being sorted + * @param comparator the column comparator to use for sorting + * @param backupRowComparator the backup row comparator + * @see RowToColumnComparator + */ + public ThreadedTableColumnComparator(ThreadedTableModel model, int sortColumn, + Comparator comparator, Comparator backupRowComparator) { + super(model, sortColumn, comparator, backupRowComparator); + this.threadedModel = model; + } + + @Override + protected Object getColumnValue(T t) { + return threadedModel.getCachedColumnValueForRow(t, sortColumn); + } +} 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 12f451ff6c..0ba1569521 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 @@ -21,6 +21,7 @@ import javax.swing.SwingUtilities; import javax.swing.event.TableModelEvent; import docking.widgets.table.*; +import docking.widgets.table.sort.DefaultColumnComparator; import generic.concurrent.ConcurrentListenerSet; import ghidra.framework.plugintool.ServiceProvider; import ghidra.util.SystemUtilities; @@ -290,8 +291,15 @@ public abstract class ThreadedTableModel @Override protected Comparator createSortComparator(int columnIndex) { + Comparator columnComparator = createSortComparatorForColumn(columnIndex); - return new TableColumnComparator<>(this, columnComparator, columnIndex); + if (columnComparator != null) { + // the given column has its own comparator; wrap and us that + return new ThreadedTableColumnComparator<>(this, columnIndex, columnComparator); + } + + return new ThreadedTableColumnComparator<>(this, columnIndex, new DefaultColumnComparator(), + new ThreadedBackupRowComparator<>(this, columnIndex)); } @Override